001    /*
002     * CDDL HEADER START
003     *
004     * The contents of this file are subject to the terms of the
005     * Common Development and Distribution License, Version 1.0 only
006     * (the "License").  You may not use this file except in compliance
007     * with the License.
008     *
009     * You can obtain a copy of the license at
010     * trunk/opends/resource/legal-notices/OpenDS.LICENSE
011     * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
012     * See the License for the specific language governing permissions
013     * and limitations under the License.
014     *
015     * When distributing Covered Code, include this CDDL HEADER in each
016     * file and include the License file at
017     * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
018     * add the following below this CDDL HEADER, with the fields enclosed
019     * by brackets "[]" replaced with your own identifying information:
020     *      Portions Copyright [yyyy] [name of copyright owner]
021     *
022     * CDDL HEADER END
023     *
024     *
025     *      Copyright 2008 Sun Microsystems, Inc.
026     */
027    package org.opends.server.core;
028    
029    
030    
031    import java.io.File;
032    import java.io.FileInputStream;
033    import java.io.FileOutputStream;
034    import java.util.ArrayList;
035    import java.util.LinkedHashMap;
036    import java.util.LinkedHashSet;
037    import java.util.Map;
038    import java.util.concurrent.ConcurrentHashMap;
039    import java.util.concurrent.atomic.AtomicInteger;
040    
041    import org.opends.messages.Message;
042    import org.opends.server.api.CompressedSchema;
043    import org.opends.server.loggers.debug.DebugTracer;
044    import org.opends.server.protocols.asn1.ASN1Element;
045    import org.opends.server.protocols.asn1.ASN1Integer;
046    import org.opends.server.protocols.asn1.ASN1OctetString;
047    import org.opends.server.protocols.asn1.ASN1Reader;
048    import org.opends.server.protocols.asn1.ASN1Sequence;
049    import org.opends.server.protocols.asn1.ASN1Writer;
050    import org.opends.server.types.Attribute;
051    import org.opends.server.types.AttributeType;
052    import org.opends.server.types.AttributeValue;
053    import org.opends.server.types.ByteArray;
054    import org.opends.server.types.DebugLogLevel;
055    import org.opends.server.types.DirectoryException;
056    import org.opends.server.types.ObjectClass;
057    
058    import static org.opends.server.config.ConfigConstants.*;
059    import static org.opends.server.loggers.debug.DebugLogger.*;
060    import static org.opends.messages.CoreMessages.*;
061    import static org.opends.server.util.StaticUtils.*;
062    
063    
064    
065    /**
066     * This class provides a default implementation of a compressed schema manager
067     * that will store the schema definitions in a binary file
068     * (config/schematokens.dat).
069     */
070    public final class DefaultCompressedSchema
071           extends CompressedSchema
072    {
073      /**
074       * The tracer object for the debug logger.
075       */
076      private static final DebugTracer TRACER = getTracer();
077    
078    
079    
080      // The counter used for attribute descriptions.
081      private AtomicInteger adCounter;
082    
083      // The counter used for object class sets.
084      private AtomicInteger ocCounter;
085    
086      // The map between encoded representations and attribute types.
087      private ConcurrentHashMap<ByteArray,AttributeType> atDecodeMap;
088    
089      // The map between encoded representations and attribute options.
090      private ConcurrentHashMap<ByteArray,LinkedHashSet<String>> aoDecodeMap;
091    
092      // The map between encoded representations and object class sets.
093      private ConcurrentHashMap<ByteArray,Map<ObjectClass,String>> ocDecodeMap;
094    
095      // The map between attribute descriptions and their encoded
096      // representations.
097      private ConcurrentHashMap<AttributeType,
098                   ConcurrentHashMap<LinkedHashSet<String>,ByteArray>> adEncodeMap;
099    
100      // The map between object class sets and encoded representations.
101      private ConcurrentHashMap<Map<ObjectClass,String>,ByteArray> ocEncodeMap;
102    
103    
104    
105      /**
106       * Creates a new instance of this compressed schema manager.
107       */
108      public DefaultCompressedSchema()
109      {
110        atDecodeMap = new ConcurrentHashMap<ByteArray,AttributeType>();
111        aoDecodeMap = new ConcurrentHashMap<ByteArray,LinkedHashSet<String>>();
112        ocDecodeMap = new ConcurrentHashMap<ByteArray,Map<ObjectClass,String>>();
113        adEncodeMap =
114             new ConcurrentHashMap<AttributeType,
115                      ConcurrentHashMap<LinkedHashSet<String>,ByteArray>>();
116        ocEncodeMap = new ConcurrentHashMap<Map<ObjectClass,String>,ByteArray>();
117    
118        adCounter = new AtomicInteger(1);
119        ocCounter = new AtomicInteger(1);
120    
121        load();
122      }
123    
124    
125    
126      /**
127       * Loads the compressed schema information from disk.
128       */
129      private void load()
130      {
131        ASN1Reader reader = null;
132    
133        try
134        {
135          // Determine the location of the compressed schema data file.  It should
136          // be in the config directory with a name of "schematokens.dat".  If that
137          // file doesn't exist, then don't do anything.
138          String path = DirectoryServer.getServerRoot() + File.separator +
139                        CONFIG_DIR_NAME + File.separator +
140                        COMPRESSED_SCHEMA_FILE_NAME;
141          if (! new File(path).exists())
142          {
143            return;
144          }
145          FileInputStream inputStream = new FileInputStream(path);
146          reader = new ASN1Reader(inputStream);
147    
148    
149          // The first element in the file should be a sequence of object class
150          // sets.  Each object class set will itself be a sequence of octet
151          // strings, where the first one is the token and the remaining elements
152          // are the names of the associated object classes.
153          ASN1Sequence ocSequence = reader.readElement().decodeAsSequence();
154          for (ASN1Element element : ocSequence.elements())
155          {
156            ArrayList<ASN1Element> elements = element.decodeAsSequence().elements();
157            ASN1OctetString os = elements.get(0).decodeAsOctetString();
158            ByteArray token = new ByteArray(os.value());
159    
160            LinkedHashMap<ObjectClass,String> ocMap =
161                 new LinkedHashMap<ObjectClass,String>(elements.size()-1);
162            for (int i=1; i < elements.size(); i++)
163            {
164              os = elements.get(i).decodeAsOctetString();
165              String ocName = os.stringValue();
166              String lowerName = toLowerCase(ocName);
167              ObjectClass oc = DirectoryServer.getObjectClass(lowerName, true);
168              ocMap.put(oc, ocName);
169            }
170    
171            ocEncodeMap.put(ocMap, token);
172            ocDecodeMap.put(token, ocMap);
173          }
174    
175    
176          // The second element in the file should be an integer element that holds
177          // the value to use to initialize the object class counter.
178          ASN1Element counterElement = reader.readElement();
179          ocCounter.set(counterElement.decodeAsInteger().intValue());
180    
181    
182          // The third element in the file should be a sequence of attribute
183          // description components.  Each attribute description component will
184          // itself be a sequence of octet strings, where the first one is the
185          // token, the second is the attribute name, and all remaining elements are
186          // the attribute options.
187          ASN1Sequence adSequence = reader.readElement().decodeAsSequence();
188          for (ASN1Element element : adSequence.elements())
189          {
190            ArrayList<ASN1Element> elements = element.decodeAsSequence().elements();
191            ASN1OctetString os = elements. get(0).decodeAsOctetString();
192            ByteArray token = new ByteArray(os.value());
193    
194            os = elements.get(1).decodeAsOctetString();
195            String attrName = os.stringValue();
196            String lowerName = toLowerCase(attrName);
197            AttributeType attrType =
198                 DirectoryServer.getAttributeType(lowerName, true);
199    
200            LinkedHashSet<String> options =
201                 new LinkedHashSet<String>(elements.size()-2);
202            for (int i=2; i < elements.size(); i++)
203            {
204              os = elements.get(i).decodeAsOctetString();
205              options.add(os.stringValue());
206            }
207    
208            atDecodeMap.put(token, attrType);
209            aoDecodeMap.put(token, options);
210    
211            ConcurrentHashMap<LinkedHashSet<String>,ByteArray> map =
212                 adEncodeMap.get(attrType);
213            if (map == null)
214            {
215              map = new ConcurrentHashMap<LinkedHashSet<String>,ByteArray>(1);
216              map.put(options, token);
217              adEncodeMap.put(attrType, map);
218            }
219            else
220            {
221              map.put(options, token);
222            }
223          }
224    
225    
226          // The fourth element in the file should be an integer element that holds
227          // the value to use to initialize the attribute description counter.
228          counterElement = reader.readElement();
229          adCounter.set(counterElement.decodeAsInteger().intValue());
230        }
231        catch (Exception e)
232        {
233          if (debugEnabled())
234          {
235            TRACER.debugCaught(DebugLogLevel.ERROR, e);
236          }
237    
238          // FIXME -- Should we do something else here?
239          throw new RuntimeException(e);
240        }
241        finally
242        {
243          try
244          {
245            if (reader != null)
246            {
247              reader.close();
248            }
249          }
250          catch (Exception e)
251          {
252            if (debugEnabled())
253            {
254              TRACER.debugCaught(DebugLogLevel.ERROR, e);
255            }
256          }
257        }
258      }
259    
260    
261    
262      /**
263       * Writes the compressed schema information to disk.
264       *
265       * @throws  DirectoryException  If a problem occurs while writing the updated
266       *                              information.
267       */
268      private void save()
269              throws DirectoryException
270      {
271        ASN1Writer writer = null;
272        try
273        {
274          // Determine the location of the "live" compressed schema data file, and
275          // then append ".tmp" to get the name of the temporary file that we will
276          // use.
277          String path = DirectoryServer.getServerRoot() + File.separator +
278                        CONFIG_DIR_NAME + File.separator +
279                        COMPRESSED_SCHEMA_FILE_NAME;
280          String tempPath = path + ".tmp";
281    
282          FileOutputStream outputStream = new FileOutputStream(tempPath);
283          writer = new ASN1Writer(outputStream);
284    
285    
286          // The first element in the file should be a sequence of object class
287          // sets.  Each object class set will itself be a sequence of octet
288          // strings, where the first one is the token and the remaining elements
289          // are the names of the associated object classes.
290          ArrayList<ASN1Element> ocElements =
291               new ArrayList<ASN1Element>(ocDecodeMap.size());
292          for (Map.Entry<ByteArray,Map<ObjectClass,String>> mapEntry :
293               ocDecodeMap.entrySet())
294          {
295            ByteArray token = mapEntry.getKey();
296            Map<ObjectClass,String> ocMap = mapEntry.getValue();
297    
298            ArrayList<ASN1Element> elements =
299                 new ArrayList<ASN1Element>(ocMap.size()+1);
300            elements.add(new ASN1OctetString(token.array()));
301    
302            for (String ocName : ocMap.values())
303            {
304              elements.add(new ASN1OctetString(ocName));
305            }
306    
307            ocElements.add(new ASN1Sequence(elements));
308          }
309          writer.writeElement(new ASN1Sequence(ocElements));
310    
311    
312          // The second element in the file should be an integer element that holds
313          // the value to use to initialize the object class counter.
314          writer.writeElement(new ASN1Integer(ocCounter.get()));
315    
316    
317          // The third element in the file should be a sequence of attribute
318          // description components.  Each attribute description component will
319          // itself be a sequence of octet strings, where the first one is the
320          // token, the second is the attribute name, and all remaining elements are
321          // the attribute options.
322          ArrayList<ASN1Element> adElements =
323               new ArrayList<ASN1Element>(atDecodeMap.size());
324          for (ByteArray token : atDecodeMap.keySet())
325          {
326            AttributeType attrType = atDecodeMap.get(token);
327            LinkedHashSet<String> options = aoDecodeMap.get(token);
328    
329            ArrayList<ASN1Element> elements =
330                 new ArrayList<ASN1Element>(options.size()+2);
331            elements.add(new ASN1OctetString(token.array()));
332            elements.add(new ASN1OctetString(attrType.getNameOrOID()));
333            for (String option : options)
334            {
335              elements.add(new ASN1OctetString(option));
336            }
337    
338            adElements.add(new ASN1Sequence(elements));
339          }
340          writer.writeElement(new ASN1Sequence(adElements));
341    
342    
343          // The fourth element in the file should be an integer element that holds
344          // the value to use to initialize the attribute description counter.
345          writer.writeElement(new ASN1Integer(adCounter.get()));
346    
347    
348          // Close the writer and swing the temp file into place.
349          writer.close();
350          File liveFile = new File(path);
351          File tempFile = new File(tempPath);
352    
353          if (liveFile.exists())
354          {
355            File saveFile = new File(liveFile.getAbsolutePath() + ".save");
356            if (saveFile.exists())
357            {
358              saveFile.delete();
359            }
360            liveFile.renameTo(saveFile);
361          }
362          tempFile.renameTo(liveFile);
363        }
364        catch (Exception e)
365        {
366          if (debugEnabled())
367          {
368            TRACER.debugCaught(DebugLogLevel.ERROR, e);
369          }
370    
371          Message message = ERR_COMPRESSEDSCHEMA_CANNOT_WRITE_UPDATED_DATA.get(
372                                 stackTraceToSingleLineString(e));
373          throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
374                                       message, e);
375        }
376        finally
377        {
378          try
379          {
380            if (writer != null)
381            {
382              writer.close();
383            }
384          }
385          catch (Exception e)
386          {
387            if (debugEnabled())
388            {
389              TRACER.debugCaught(DebugLogLevel.ERROR, e);
390            }
391          }
392        }
393      }
394    
395    
396    
397      /**
398       * {@inheritDoc}
399       */
400      @Override()
401      public byte[] encodeObjectClasses(Map<ObjectClass,String> objectClasses)
402             throws DirectoryException
403      {
404        ByteArray encodedClasses = ocEncodeMap.get(objectClasses);
405        if (encodedClasses == null)
406        {
407          synchronized (ocEncodeMap)
408          {
409            int setValue = ocCounter.getAndIncrement();
410            byte[] array = encodeInt(setValue);
411    
412            encodedClasses = new ByteArray(array);
413            ocEncodeMap.put(objectClasses, encodedClasses);
414            ocDecodeMap.put(encodedClasses, objectClasses);
415    
416            save();
417            return array;
418          }
419        }
420        else
421        {
422          return encodedClasses.array();
423        }
424      }
425    
426    
427    
428      /**
429       * {@inheritDoc}
430       */
431      @Override()
432      public Map<ObjectClass,String> decodeObjectClasses(
433                                          byte[] encodedObjectClasses)
434             throws DirectoryException
435      {
436        ByteArray byteArray = new ByteArray(encodedObjectClasses);
437        Map<ObjectClass,String> ocMap = ocDecodeMap.get(byteArray);
438        if (ocMap == null)
439        {
440          Message message = ERR_COMPRESSEDSCHEMA_UNKNOWN_OC_TOKEN.get(
441                                 bytesToHex(encodedObjectClasses));
442          throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
443                                       message);
444        }
445        else
446        {
447          return ocMap;
448        }
449      }
450    
451    
452    
453      /**
454       * {@inheritDoc}
455       */
456      @Override()
457      public byte[] encodeAttribute(Attribute attribute)
458             throws DirectoryException
459      {
460        AttributeType type = attribute.getAttributeType();
461        LinkedHashSet<String> options = attribute.getOptions();
462    
463        ConcurrentHashMap<LinkedHashSet<String>,ByteArray> map =
464             adEncodeMap.get(type);
465        if (map == null)
466        {
467          byte[] array;
468          synchronized (adEncodeMap)
469          {
470            map = new ConcurrentHashMap<LinkedHashSet<String>,ByteArray>(1);
471    
472            int intValue = adCounter.getAndIncrement();
473            array = encodeInt(intValue);
474            ByteArray byteArray = new ByteArray(array);
475            map.put(options,byteArray);
476    
477            adEncodeMap.put(type, map);
478            atDecodeMap.put(byteArray, type);
479            aoDecodeMap.put(byteArray, options);
480            save();
481          }
482    
483          return encodeAttribute(array, attribute);
484        }
485        else
486        {
487          ByteArray byteArray = map.get(options);
488          if (byteArray == null)
489          {
490            byte[] array;
491            synchronized (map)
492            {
493              int intValue = adCounter.getAndIncrement();
494              array = encodeInt(intValue);
495              byteArray = new ByteArray(array);
496              map.put(options,byteArray);
497    
498              atDecodeMap.put(byteArray, type);
499              aoDecodeMap.put(byteArray, options);
500              save();
501            }
502    
503            return encodeAttribute(array, attribute);
504          }
505          else
506          {
507            return encodeAttribute(byteArray.array(), attribute);
508          }
509        }
510      }
511    
512    
513    
514      /**
515       * Encodes the information in the provided attribute to a byte
516       * array.
517       *
518       * @param  adArray    The byte array that is a placeholder for the
519       *                    attribute type and set of options.
520       * @param  attribute  The attribute to be encoded.
521       *
522       * @return  An encoded representation of the provided attribute.
523       */
524      private byte[] encodeAttribute(byte[] adArray, Attribute attribute)
525      {
526        LinkedHashSet<AttributeValue> values = attribute.getValues();
527        int totalValuesLength = 0;
528        byte[][] subArrays = new  byte[values.size()*2][];
529        int pos = 0;
530        for (AttributeValue v : values)
531        {
532          byte[] vBytes = v.getValueBytes();
533          byte[] lBytes = ASN1Element.encodeLength(vBytes.length);
534    
535          subArrays[pos++] = lBytes;
536          subArrays[pos++] = vBytes;
537    
538          totalValuesLength += lBytes.length + vBytes.length;
539        }
540    
541        byte[] adArrayLength = ASN1Element.encodeLength(adArray.length);
542        byte[] countBytes = ASN1Element.encodeLength(values.size());
543        int totalLength = adArrayLength.length + adArray.length +
544                          countBytes.length + totalValuesLength;
545        byte[] array = new byte[totalLength];
546    
547        System.arraycopy(adArrayLength, 0, array, 0,
548                         adArrayLength.length);
549        pos = adArrayLength.length;
550        System.arraycopy(adArray, 0, array, pos, adArray.length);
551        pos += adArray.length;
552        System.arraycopy(countBytes, 0, array, pos, countBytes.length);
553        pos += countBytes.length;
554    
555        for (int i=0; i < subArrays.length; i++)
556        {
557          System.arraycopy(subArrays[i], 0, array, pos,
558                           subArrays[i].length);
559          pos += subArrays[i].length;
560        }
561    
562        return array;
563      }
564    
565    
566    
567      /**
568       * {@inheritDoc}
569       */
570      @Override()
571      public Attribute decodeAttribute(byte[] encodedEntry, int startPos,
572                                       int length)
573             throws DirectoryException
574      {
575        // Figure out how many bytes are in the token that is the placeholder for
576        // the attribute description.
577        int pos = startPos;
578        int adArrayLength = encodedEntry[pos] & 0x7F;
579        if (adArrayLength != encodedEntry[pos++])
580        {
581          int numLengthBytes = adArrayLength;
582          adArrayLength = 0;
583          for (int i=0; i < numLengthBytes; i++, pos++)
584          {
585            adArrayLength = (adArrayLength << 8) | (encodedEntry[pos] & 0xFF);
586          }
587        }
588    
589    
590        // Get the attribute description token and make sure it resolves to an
591        // attribute type and option set.
592        ByteArray adArray = new ByteArray(new byte[adArrayLength]);
593        System.arraycopy(encodedEntry, pos, adArray.array(), 0, adArrayLength);
594        pos += adArrayLength;
595        AttributeType attrType = atDecodeMap.get(adArray);
596        LinkedHashSet<String> options = aoDecodeMap.get(adArray);
597        if ((attrType == null) || (options == null))
598        {
599          Message message = ERR_COMPRESSEDSCHEMA_UNRECOGNIZED_AD_TOKEN.get(
600                                 bytesToHex(adArray.array()));
601          throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
602                                       message);
603        }
604    
605    
606        // Determine the number of values for the attribute.
607        int numValues = encodedEntry[pos] & 0x7F;
608        if (numValues != encodedEntry[pos++])
609        {
610          int numValuesBytes = numValues;
611          numValues = 0;
612          for (int i=0; i < numValuesBytes; i++, pos++)
613          {
614            numValues = (numValues << 8) | (encodedEntry[pos] & 0xFF);
615          }
616        }
617    
618    
619        // Read the appropriate number of values.
620        LinkedHashSet<AttributeValue> values =
621             new LinkedHashSet<AttributeValue>(numValues);
622        for (int i=0; i < numValues; i++)
623        {
624          int valueLength = encodedEntry[pos] & 0x7F;
625          if (valueLength != encodedEntry[pos++])
626          {
627            int valueLengthBytes = valueLength;
628            valueLength = 0;
629            for (int j=0; j < valueLengthBytes; j++, pos++)
630            {
631              valueLength = (valueLength << 8) | (encodedEntry[pos] & 0xFF);
632            }
633          }
634    
635          byte[] valueBytes = new byte[valueLength];
636          System.arraycopy(encodedEntry, pos, valueBytes, 0, valueLength);
637          pos += valueLength;
638          values.add(new AttributeValue(attrType, new ASN1OctetString(valueBytes)));
639        }
640    
641        return new Attribute(attrType, attrType.getPrimaryName(), options, values);
642      }
643    
644    
645    
646      /**
647       * Encodes the provided int value to a byte array.
648       *
649       * @param  intValue  The int value to be encoded.
650       *
651       * @return  The byte array containing the encoded int value.
652       */
653      private byte[] encodeInt(int intValue)
654      {
655        byte[] array;
656        if (intValue <= 0xFF)
657        {
658          array = new byte[1];
659          array[0] = (byte) (intValue & 0xFF);
660        }
661        else if (intValue <= 0xFFFF)
662        {
663          array = new byte[2];
664          array[0] = (byte) ((intValue >> 8)  & 0xFF);
665          array[1] = (byte) (intValue & 0xFF);
666        }
667        else if (intValue <= 0xFFFFFF)
668        {
669          array = new byte[3];
670          array[0] = (byte) ((intValue >> 16) & 0xFF);
671          array[1] = (byte) ((intValue >> 8)  & 0xFF);
672          array[2] = (byte) (intValue & 0xFF);
673        }
674        else
675        {
676          array = new byte[4];
677          array[0] = (byte) ((intValue >> 24) & 0xFF);
678          array[1] = (byte) ((intValue >> 16) & 0xFF);
679          array[2] = (byte) ((intValue >> 8)  & 0xFF);
680          array[3] = (byte) (intValue & 0xFF);
681        }
682    
683        return array;
684      }
685    }
686