mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

Matthew Swift
25.41.2013 f653eea946872180a28013f3951ea85b3bda11a7
opends/src/server/org/opends/server/core/DefaultCompressedSchema.java
@@ -23,33 +23,35 @@
 *
 *
 *      Copyright 2008 Sun Microsystems, Inc.
 *      Portions copyright 2013 ForgeRock AS.
 */
package org.opends.server.core;
import static org.opends.messages.CoreMessages.*;
import static org.opends.server.config.ConfigConstants.*;
import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
import static org.opends.server.loggers.debug.DebugLogger.getTracer;
import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Map.Entry;
import org.opends.messages.Message;
import org.opends.server.api.CompressedSchema;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.protocols.asn1.ASN1Reader;
import org.opends.server.protocols.asn1.ASN1;
import org.opends.server.protocols.asn1.ASN1Reader;
import org.opends.server.protocols.asn1.ASN1Writer;
import org.opends.server.types.*;
import static org.opends.server.config.ConfigConstants.*;
import static org.opends.server.loggers.debug.DebugLogger.*;
import static org.opends.messages.CoreMessages.*;
import static org.opends.server.util.StaticUtils.*;
import org.opends.server.types.ByteString;
import org.opends.server.types.DebugLogLevel;
import org.opends.server.types.DirectoryException;
@@ -58,39 +60,15 @@
 * that will store the schema definitions in a binary file
 * (config/schematokens.dat).
 */
public final class DefaultCompressedSchema
       extends CompressedSchema
public final class DefaultCompressedSchema extends CompressedSchema
{
  /**
   * The tracer object for the debug logger.
   */
  private static final DebugTracer TRACER = getTracer();
  // The counter used for attribute descriptions.
  private AtomicInteger adCounter;
  // The counter used for object class sets.
  private AtomicInteger ocCounter;
  // The map between encoded representations and attribute types.
  private ConcurrentHashMap<ByteSequence,AttributeType> atDecodeMap;
  // The map between encoded representations and attribute options.
  private ConcurrentHashMap<ByteSequence,Set<String>> aoDecodeMap;
  // The map between encoded representations and object class sets.
  private ConcurrentHashMap<ByteSequence,Map<ObjectClass,String>> ocDecodeMap;
  // The map between attribute descriptions and their encoded
  // representations.
  private final ConcurrentHashMap<AttributeType,
                ConcurrentHashMap<Set<String>, ByteSequence>> adEncodeMap;
  // The map between object class sets and encoded representations.
  private final ConcurrentHashMap<Map<ObjectClass,String>,
      ByteSequence> ocEncodeMap;
  // Synchronizes calls to save.
  private final Object saveLock = new Object();
@@ -99,24 +77,37 @@
   */
  public DefaultCompressedSchema()
  {
    atDecodeMap = new ConcurrentHashMap<ByteSequence, AttributeType>();
    aoDecodeMap = new ConcurrentHashMap<ByteSequence, Set<String>>();
    ocDecodeMap =
        new ConcurrentHashMap<ByteSequence, Map<ObjectClass, String>>();
    adEncodeMap = new ConcurrentHashMap
      <AttributeType, ConcurrentHashMap<Set<String>, ByteSequence>>();
    ocEncodeMap = new ConcurrentHashMap<Map<ObjectClass, String>,
        ByteSequence>();
    adCounter = new AtomicInteger(1);
    ocCounter = new AtomicInteger(1);
    load();
  }
  /**
   * {@inheritDoc}
   */
  @Override
  protected void storeAttribute(final byte[] encodedAttribute,
      final String attributeName, final Collection<String> attributeOptions)
      throws DirectoryException
  {
    save();
  }
  /**
   * {@inheritDoc}
   */
  @Override
  protected void storeObjectClasses(final byte[] encodedObjectClasses,
      final Collection<String> objectClassNames) throws DirectoryException
  {
    save();
  }
  /**
   * Loads the compressed schema information from disk.
   */
  private void load()
@@ -125,99 +116,68 @@
    try
    {
      // Determine the location of the compressed schema data file.  It should
      // be in the config directory with a name of "schematokens.dat".  If that
      // Determine the location of the compressed schema data file. It should
      // be in the config directory with a name of "schematokens.dat". If that
      // file doesn't exist, then don't do anything.
      String path = DirectoryServer.getInstanceRoot() + File.separator +
                    CONFIG_DIR_NAME + File.separator +
                    COMPRESSED_SCHEMA_FILE_NAME;
      if (! new File(path).exists())
      final String path = DirectoryServer.getInstanceRoot() + File.separator
          + CONFIG_DIR_NAME + File.separator + COMPRESSED_SCHEMA_FILE_NAME;
      if (!new File(path).exists())
      {
        return;
      }
      inputStream = new FileInputStream(path);
      ASN1Reader reader = ASN1.getReader(inputStream);
      final ASN1Reader reader = ASN1.getReader(inputStream);
      // The first element in the file should be a sequence of object class
      // sets.  Each object class set will itself be a sequence of octet
      // sets. Each object class set will itself be a sequence of octet
      // strings, where the first one is the token and the remaining elements
      // are the names of the associated object classes.
      reader.readStartSequence();
      while(reader.hasNextElement())
      while (reader.hasNextElement())
      {
        reader.readStartSequence();
        ByteSequence token = reader.readOctetString();
        LinkedHashMap<ObjectClass,String> ocMap =
             new LinkedHashMap<ObjectClass,String>();
        while(reader.hasNextElement())
        final byte[] encodedObjectClasses = reader.readOctetString()
            .toByteArray();
        final List<String> objectClassNames = new LinkedList<String>();
        while (reader.hasNextElement())
        {
          String ocName = reader.readOctetStringAsString();
          String lowerName = toLowerCase(ocName);
          ObjectClass oc = DirectoryServer.getObjectClass(lowerName, true);
          ocMap.put(oc, ocName);
          objectClassNames.add(reader.readOctetStringAsString());
        }
        reader.readEndSequence();
        ocEncodeMap.put(ocMap, token);
        ocDecodeMap.put(token, ocMap);
        loadObjectClasses(encodedObjectClasses, objectClassNames);
      }
      reader.readEndSequence();
      // The second element in the file should be an integer element that holds
      // the value to use to initialize the object class counter.
      ocCounter.set((int)reader.readInteger());
      reader.readInteger(); // No longer used.
      // The third element in the file should be a sequence of attribute
      // description components.  Each attribute description component will
      // description components. Each attribute description component will
      // itself be a sequence of octet strings, where the first one is the
      // token, the second is the attribute name, and all remaining elements are
      // the attribute options.
      reader.readStartSequence();
      while(reader.hasNextElement())
      while (reader.hasNextElement())
      {
        reader.readStartSequence();
        ByteSequence token = reader.readOctetString();
        String attrName = reader.readOctetStringAsString();
        String lowerName = toLowerCase(attrName);
        AttributeType attrType =
            DirectoryServer.getAttributeType(lowerName, true);
        LinkedHashSet<String> options =
            new LinkedHashSet<String>();
        while(reader.hasNextElement())
        final byte[] encodedAttribute = reader.readOctetString().toByteArray();
        final String attributeName = reader.readOctetStringAsString();
        final List<String> attributeOptions = new LinkedList<String>();
        while (reader.hasNextElement())
        {
          options.add(reader.readOctetStringAsString());
          attributeOptions.add(reader.readOctetStringAsString());
        }
        reader.readEndSequence();
        atDecodeMap.put(token, attrType);
        aoDecodeMap.put(token, options);
        ConcurrentHashMap<Set<String>, ByteSequence> map = adEncodeMap
            .get(attrType);
        if (map == null)
        {
          map = new ConcurrentHashMap<Set<String>, ByteSequence>(1);
          map.put(options, token);
          adEncodeMap.put(attrType, map);
        }
        else
        {
          map.put(options, token);
        }
        loadAttribute(encodedAttribute, attributeName, attributeOptions);
      }
      reader.readEndSequence();
      // The fourth element in the file should be an integer element that holds
      // the value to use to initialize the attribute description counter.
      adCounter.set((int)reader.readInteger());
      reader.readInteger(); // No longer used.
    }
    catch (Exception e)
    catch (final Exception e)
    {
      if (debugEnabled())
      {
@@ -236,7 +196,7 @@
          inputStream.close();
        }
      }
      catch (Exception e)
      catch (final Exception e)
      {
        if (debugEnabled())
        {
@@ -251,368 +211,124 @@
  /**
   * Writes the compressed schema information to disk.
   *
   * @throws  DirectoryException  If a problem occurs while writing the updated
   *                              information.
   * @throws DirectoryException
   *           If a problem occurs while writing the updated information.
   */
  private void save()
          throws DirectoryException
  private void save() throws DirectoryException
  {
    FileOutputStream outputStream = null;
    try
    synchronized (saveLock)
    {
      // Determine the location of the "live" compressed schema data file, and
      // then append ".tmp" to get the name of the temporary file that we will
      // use.
      String path = DirectoryServer.getInstanceRoot() + File.separator +
                    CONFIG_DIR_NAME + File.separator +
                    COMPRESSED_SCHEMA_FILE_NAME;
      String tempPath = path + ".tmp";
      outputStream = new FileOutputStream(tempPath);
      ASN1Writer writer = ASN1.getWriter(outputStream);
      // The first element in the file should be a sequence of object class
      // sets.  Each object class set will itself be a sequence of octet
      // strings, where the first one is the token and the remaining elements
      // are the names of the associated object classes.
      writer.writeStartSequence();
      for (Map.Entry<ByteSequence,Map<ObjectClass,String>> mapEntry :
           ocDecodeMap.entrySet())
      {
        writer.writeStartSequence();
        writer.writeOctetString(mapEntry.getKey());
        Map<ObjectClass,String> ocMap = mapEntry.getValue();
        for (String ocName : ocMap.values())
        {
          writer.writeOctetString(ocName);
        }
        writer.writeEndSequence();
      }
      writer.writeEndSequence();
      // The second element in the file should be an integer element that holds
      // the value to use to initialize the object class counter.
      writer.writeInteger(ocCounter.get());
      // The third element in the file should be a sequence of attribute
      // description components.  Each attribute description component will
      // itself be a sequence of octet strings, where the first one is the
      // token, the second is the attribute name, and all remaining elements are
      // the attribute options.
      writer.writeStartSequence();
      for (ByteSequence token : atDecodeMap.keySet())
      {
        writer.writeStartSequence();
        AttributeType attrType = atDecodeMap.get(token);
        Set<String> options = aoDecodeMap.get(token);
        writer.writeOctetString(token);
        writer.writeOctetString(attrType.getNameOrOID());
        for (String option : options)
        {
          writer.writeOctetString(option);
        }
        writer.writeEndSequence();
      }
      writer.writeEndSequence();
      // The fourth element in the file should be an integer element that holds
      // the value to use to initialize the attribute description counter.
      writer.writeInteger(adCounter.get());
      // Close the writer and swing the temp file into place.
      outputStream.close();
      File liveFile = new File(path);
      File tempFile = new File(tempPath);
      if (liveFile.exists())
      {
        File saveFile = new File(liveFile.getAbsolutePath() + ".save");
        if (saveFile.exists())
        {
          saveFile.delete();
        }
        liveFile.renameTo(saveFile);
      }
      tempFile.renameTo(liveFile);
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      Message message = ERR_COMPRESSEDSCHEMA_CANNOT_WRITE_UPDATED_DATA.get(
                             stackTraceToSingleLineString(e));
      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
                                   message, e);
    }
    finally
    {
      FileOutputStream outputStream = null;
      try
      {
        if (outputStream != null)
        // Determine the location of the "live" compressed schema data file, and
        // then append ".tmp" to get the name of the temporary file that we will
        // use.
        final String path = DirectoryServer.getInstanceRoot() + File.separator
            + CONFIG_DIR_NAME + File.separator + COMPRESSED_SCHEMA_FILE_NAME;
        final String tempPath = path + ".tmp";
        outputStream = new FileOutputStream(tempPath);
        final ASN1Writer writer = ASN1.getWriter(outputStream);
        // The first element in the file should be a sequence of object class
        // sets. Each object class set will itself be a sequence of octet
        // strings, where the first one is the token and the remaining elements
        // are the names of the associated object classes.
        writer.writeStartSequence();
        int ocCounter = 1;
        for (final Entry<byte[], Collection<String>> mapEntry :
            getAllObjectClasses())
        {
          outputStream.close();
          writer.writeStartSequence();
          writer.writeOctetString(ByteString.wrap(mapEntry.getKey()));
          final Collection<String> objectClassNames = mapEntry.getValue();
          for (final String ocName : objectClassNames)
          {
            writer.writeOctetString(ocName);
          }
          writer.writeEndSequence();
          ocCounter++;
        }
        writer.writeEndSequence();
        // The second element in the file should be an integer element that
        // holds the value to use to initialize the object class counter.
        writer.writeInteger(ocCounter); // No longer used.
        // The third element in the file should be a sequence of attribute
        // description components. Each attribute description component will
        // itself be a sequence of octet strings, where the first one is the
        // token, the second is the attribute name, and all remaining elements
        // are the attribute options.
        writer.writeStartSequence();
        int adCounter = 1;
        for (final Entry<byte[], Entry<String, Collection<String>>> mapEntry :
            getAllAttributes())
        {
          writer.writeStartSequence();
          writer.writeOctetString(ByteString.wrap(mapEntry.getKey()));
          writer.writeOctetString(mapEntry.getValue().getKey());
          for (final String option : mapEntry.getValue().getValue())
          {
            writer.writeOctetString(option);
          }
          writer.writeEndSequence();
          adCounter++;
        }
        writer.writeEndSequence();
        // The fourth element in the file should be an integer element that
        // holds the value to use to initialize the attribute description
        // counter.
        writer.writeInteger(adCounter); // No longer used.
        // Close the writer and swing the temp file into place.
        outputStream.close();
        final File liveFile = new File(path);
        final File tempFile = new File(tempPath);
        if (liveFile.exists())
        {
          final File saveFile = new File(liveFile.getAbsolutePath() + ".save");
          if (saveFile.exists())
          {
            saveFile.delete();
          }
          liveFile.renameTo(saveFile);
        }
        tempFile.renameTo(liveFile);
      }
      catch (Exception e)
      catch (final Exception e)
      {
        if (debugEnabled())
        {
          TRACER.debugCaught(DebugLogLevel.ERROR, e);
        }
        final Message message = ERR_COMPRESSEDSCHEMA_CANNOT_WRITE_UPDATED_DATA
            .get(stackTraceToSingleLineString(e));
        throw new DirectoryException(
            DirectoryServer.getServerErrorResultCode(), message, e);
      }
    }
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public void encodeObjectClasses(ByteStringBuilder entryBuffer,
                                  Map<ObjectClass,String> objectClasses)
         throws DirectoryException
  {
    ByteSequence encodedClasses = ocEncodeMap.get(objectClasses);
    if (encodedClasses == null)
    {
      synchronized (ocEncodeMap)
      finally
      {
        int setValue = ocCounter.getAndIncrement();
        encodedClasses = ByteString.wrap(encodeInt(setValue));
        ocEncodeMap.put(objectClasses, encodedClasses);
        ocDecodeMap.put(encodedClasses, objectClasses);
        save();
      }
    }
    entryBuffer.appendBERLength(encodedClasses.length());
    encodedClasses.copyTo(entryBuffer);
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public Map<ObjectClass,String> decodeObjectClasses(
      ByteSequenceReader entryBufferReader) throws DirectoryException
  {
    int tokenLength = entryBufferReader.getBERLength();
    ByteSequence byteArray = entryBufferReader.getByteSequence(tokenLength);
    Map<ObjectClass,String> ocMap = ocDecodeMap.get(byteArray);
    if (ocMap == null)
    {
      Message message = ERR_COMPRESSEDSCHEMA_UNKNOWN_OC_TOKEN.get(byteArray
          .toByteString().toHex());
      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
          message);
    }
    else
    {
      return ocMap;
    }
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public void encodeAttribute(ByteStringBuilder entryBuffer,
                              Attribute attribute) throws DirectoryException
  {
    AttributeType type = attribute.getAttributeType();
    Set<String> options = attribute.getOptions();
    ConcurrentHashMap<Set<String>, ByteSequence> map = adEncodeMap.get(type);
    if (map == null)
    {
      ByteString byteArray;
      synchronized (adEncodeMap)
      {
        map = new ConcurrentHashMap<Set<String>, ByteSequence>(1);
        int intValue = adCounter.getAndIncrement();
        byteArray = ByteString.wrap(encodeInt(intValue));
        map.put(options,byteArray);
        adEncodeMap.put(type, map);
        atDecodeMap.put(byteArray, type);
        aoDecodeMap.put(byteArray, options);
        save();
      }
      encodeAttribute(entryBuffer, byteArray, attribute);
    }
    else
    {
      ByteSequence byteArray = map.get(options);
      if (byteArray == null)
      {
        synchronized (map)
        try
        {
          int intValue = adCounter.getAndIncrement();
          byteArray = ByteString.wrap(encodeInt(intValue));
          map.put(options,byteArray);
          atDecodeMap.put(byteArray, type);
          aoDecodeMap.put(byteArray, options);
          save();
          if (outputStream != null)
          {
            outputStream.close();
          }
        }
        catch (final Exception e)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, e);
          }
        }
      }
      encodeAttribute(entryBuffer, byteArray, attribute);
    }
  }
  /**
   * Encodes the information in the provided attribute to a byte
   * array.
   *
   * @param  buffer     The byte buffer to encode the attribute into.
   * @param  adArray    The byte array that is a placeholder for the
   *                    attribute type and set of options.
   * @param  attribute  The attribute to be encoded.
   */
  private void encodeAttribute(ByteStringBuilder buffer, ByteSequence adArray,
                               Attribute attribute)
  {
    // Write the length of the adArray followed by the adArray.
    buffer.appendBERLength(adArray.length());
    adArray.copyTo(buffer);
    // Write the number of attributes
    buffer.appendBERLength(attribute.size());
    // Write the attribute values as length / value pairs
    for(AttributeValue v : attribute)
    {
      buffer.appendBERLength(v.getValue().length());
      buffer.append(v.getValue());
    }
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public Attribute decodeAttribute(ByteSequenceReader entryBufferReader)
         throws DirectoryException
  {
    // Figure out how many bytes are in the token that is the placeholder for
    // the attribute description.
    int adArrayLength = entryBufferReader.getBERLength();
    // Get the attribute description token and make sure it resolves to an
    // attribute type and option set.
    ByteSequence adArray = entryBufferReader.getByteSequence(adArrayLength);
    AttributeType attrType = atDecodeMap.get(adArray);
    Set<String> options = aoDecodeMap.get(adArray);
    if ((attrType == null) || (options == null))
    {
      Message message = ERR_COMPRESSEDSCHEMA_UNRECOGNIZED_AD_TOKEN.get(adArray
          .toByteString().toHex());
      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
          message);
    }
    // Determine the number of values for the attribute.
    int numValues = entryBufferReader.getBERLength();
    // For the common case of a single value with no options, generate
    // less garbage.
    if (numValues == 1 && options.isEmpty())
    {
      int valueLength = entryBufferReader.getBERLength();
      ByteString valueBytes =
          entryBufferReader.getByteSequence(valueLength).toByteString();
      return Attributes.create(attrType,
          AttributeValues.create(attrType,valueBytes));
    }
    else
    {
      // Read the appropriate number of values.
      AttributeBuilder builder = new AttributeBuilder(attrType);
      builder.setOptions(options);
      builder.setInitialCapacity(numValues);
      for (int i = 0; i < numValues; i++)
      {
        int valueLength = entryBufferReader.getBERLength();
        ByteString valueBytes =
            entryBufferReader.getByteSequence(valueLength).toByteString();
        builder.add(AttributeValues.create(attrType,
            valueBytes));
      }
      return builder.toAttribute();
    }
  }
  /**
   * Encodes the provided int value to a byte array.
   *
   * @param  intValue  The int value to be encoded.
   *
   * @return  The byte array containing the encoded int value.
   */
  private byte[] encodeInt(int intValue)
  {
    byte[] array;
    if (intValue <= 0xFF)
    {
      array = new byte[1];
      array[0] = (byte) (intValue & 0xFF);
    }
    else if (intValue <= 0xFFFF)
    {
      array = new byte[2];
      array[0] = (byte) ((intValue >> 8)  & 0xFF);
      array[1] = (byte) (intValue & 0xFF);
    }
    else if (intValue <= 0xFFFFFF)
    {
      array = new byte[3];
      array[0] = (byte) ((intValue >> 16) & 0xFF);
      array[1] = (byte) ((intValue >> 8)  & 0xFF);
      array[2] = (byte) (intValue & 0xFF);
    }
    else
    {
      array = new byte[4];
      array[0] = (byte) ((intValue >> 24) & 0xFF);
      array[1] = (byte) ((intValue >> 16) & 0xFF);
      array[2] = (byte) ((intValue >> 8)  & 0xFF);
      array[3] = (byte) (intValue & 0xFF);
    }
    return array;
  }
}