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

Matthew Swift
25.41.2013 f653eea946872180a28013f3951ea85b3bda11a7
Fix OPENDJ-169: Modifying an existing object class definition requires server restart

* mark object classes and attribute types as dirty when they are removed/replaced in the schema
* pull up duplicate code in default and JE compressed schema to parent class and optimize
* ensure that correct schema is used when rebuilding dependent schema elements.
15 files modified
2447 ■■■■ changed files
opends/src/server/org/opends/server/api/CompressedSchema.java 635 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/JECompressedSchema.java 753 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/DefaultCompressedSchema.java 620 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/DirectoryServer.java 18 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/types/AttributeType.java 51 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/types/DITContentRule.java 19 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/types/DITStructureRule.java 19 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/types/DirectoryConfig.java 3 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/types/LDAPSyntaxDescription.java 20 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/types/MatchingRuleUse.java 19 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/types/NameForm.java 18 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/types/ObjectClass.java 50 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/types/Schema.java 73 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/types/SchemaFileElement.java 34 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/core/ModifyOperationTestCase.java 115 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/api/CompressedSchema.java
@@ -23,95 +23,614 @@
 *
 *
 *      Copyright 2009 Sun Microsystems, Inc.
 *      Portions copyright 2013 ForgeRock AS.
 */
package org.opends.server.api;
import java.util.Map;
import static org.opends.messages.CoreMessages.*;
import static org.opends.server.util.StaticUtils.toLowerCase;
import org.opends.server.types.*;
import java.util.AbstractMap.SimpleImmutableEntry;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import org.opends.messages.Message;
import org.opends.server.core.DirectoryServer;
import org.opends.server.types.Attribute;
import org.opends.server.types.AttributeBuilder;
import org.opends.server.types.AttributeType;
import org.opends.server.types.AttributeValue;
import org.opends.server.types.AttributeValues;
import org.opends.server.types.Attributes;
import org.opends.server.types.ByteSequenceReader;
import org.opends.server.types.ByteString;
import org.opends.server.types.ByteStringBuilder;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.ObjectClass;
/**
 * This class provides a utility for interacting with compressed
 * representations of schema elements.
 * This class provides a utility for interacting with compressed representations
 * of schema elements. The default implementation does not persist encoded
 * attributes and object classes.
 */
@org.opends.server.types.PublicAPI(
     stability=org.opends.server.types.StabilityLevel.UNCOMMITTED,
     mayInstantiate=false,
     mayExtend=true,
     mayInvoke=false)
public abstract class CompressedSchema
    stability = org.opends.server.types.StabilityLevel.UNCOMMITTED,
    mayInstantiate = false,
    mayExtend = true,
    mayInvoke = false)
public class CompressedSchema
{
  // Maps attribute description to ID.
  private final List<Entry<AttributeType, Set<String>>> adDecodeMap;
  // Maps ID to attribute description.
  private final Map<Entry<AttributeType, Set<String>>, Integer> adEncodeMap;
  // The map between encoded representations and object class sets.
  private final List<Map<ObjectClass, String>> ocDecodeMap;
  // The map between object class sets and encoded representations.
  private final Map<Map<ObjectClass, String>, Integer> ocEncodeMap;
  /**
   * Encodes the provided set of object classes to a byte array.  If
   * the same set had been previously encoded, then the cached value
   * will be used.  Otherwise, a new value will be created.
   *
   * @param  entryBuffer   The buffer to encode the object classes to.
   * @param  objectClasses The set of object classes for which to
   *                       retrieve the corresponding byte array
   *                       token.
   *
   * @throws  DirectoryException  If a problem occurs while attempting
   *                              to determine the appropriate
   *                              identifier.
   * Creates a new empty instance of this compressed schema.
   */
  public abstract void
         encodeObjectClasses(ByteStringBuilder entryBuffer,
                             Map<ObjectClass,String> objectClasses)
         throws DirectoryException;
  public CompressedSchema()
  {
    adDecodeMap = new CopyOnWriteArrayList<Entry<AttributeType, Set<String>>>();
    ocDecodeMap = new CopyOnWriteArrayList<Map<ObjectClass, String>>();
    adEncodeMap = new ConcurrentHashMap<Entry<AttributeType, Set<String>>,
                                        Integer>();
    ocEncodeMap = new ConcurrentHashMap<Map<ObjectClass, String>, Integer>();
  }
  /**
   * Decodes the contents of the provided array as an attribute at the current
   * position.
   *
   * @param reader
   *          The byte string reader containing the encoded entry.
   * @return The decoded attribute.
   * @throws DirectoryException
   *           If the attribute could not be decoded properly for some reason.
   */
  public final Attribute decodeAttribute(final ByteSequenceReader reader)
      throws DirectoryException
  {
    // First decode the encoded attribute description id.
    final int length = reader.getBERLength();
    final byte[] idBytes = new byte[length];
    reader.get(idBytes);
    final int id = decodeId(idBytes);
    // Look up the attribute description.
    Entry<AttributeType, Set<String>> ad = adDecodeMap.get(id);
    if (ad == null)
    {
      final Message message = ERR_COMPRESSEDSCHEMA_UNRECOGNIZED_AD_TOKEN
          .get(String.valueOf(id));
      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
          message);
    }
    // Before returning the attribute, make sure that the attribute type is not
    // stale.
    AttributeType attrType = ad.getKey();
    Set<String> options = ad.getValue();
    if (attrType.isDirty())
    {
      ad = loadAttribute(idBytes, attrType.getNameOrOID(), options);
      attrType = ad.getKey();
      options = ad.getValue();
    }
    // Determine the number of values for the attribute.
    final int numValues = reader.getBERLength();
    // For the common case of a single value with no options, generate
    // less garbage.
    if (numValues == 1 && options.isEmpty())
    {
      final int valueLength = reader.getBERLength();
      final ByteString valueBytes = reader.getByteSequence(valueLength)
          .toByteString();
      return Attributes.create(attrType,
          AttributeValues.create(attrType, valueBytes));
    }
    else
    {
      // Read the appropriate number of values.
      final AttributeBuilder builder = new AttributeBuilder(attrType);
      builder.setOptions(options);
      builder.setInitialCapacity(numValues);
      for (int i = 0; i < numValues; i++)
      {
        final int valueLength = reader.getBERLength();
        final ByteString valueBytes = reader.getByteSequence(valueLength)
            .toByteString();
        builder.add(AttributeValues.create(attrType, valueBytes));
      }
      return builder.toAttribute();
    }
  }
  /**
   * Decodes an object class set from the provided byte string.
   *
   * @param  entryBuffer         The byte string containing the
   *                             object class set identifier.
   *
   * @return  The decoded object class set.
   *
   * @throws  DirectoryException  If the provided byte array cannot be
   *                              decoded as an object class set.
   * @param reader
   *          The byte string reader containing the object class set identifier.
   * @return The decoded object class set.
   * @throws DirectoryException
   *           If the provided byte string reader cannot be decoded as an object
   *           class set.
   */
  public abstract Map<ObjectClass,String>
         decodeObjectClasses(ByteSequenceReader entryBuffer)
         throws DirectoryException;
  public final Map<ObjectClass, String> decodeObjectClasses(
      final ByteSequenceReader reader) throws DirectoryException
  {
    // First decode the encoded object class id.
    final int length = reader.getBERLength();
    final byte[] idBytes = new byte[length];
    reader.get(idBytes);
    final int id = decodeId(idBytes);
    // Look up the object classes.
    final Map<ObjectClass, String> ocMap = ocDecodeMap.get(id);
    if (ocMap != null)
    {
      // Before returning the object classes, make sure that none of them are
      // stale.
      for (final ObjectClass oc : ocMap.keySet())
      {
        if (oc.isDirty())
        {
          // Found at least one object class which is dirty so refresh them.
          return loadObjectClasses(idBytes, ocMap.values());
        }
      }
      return ocMap;
    }
    else
    {
      final Message message = ERR_COMPRESSEDSCHEMA_UNKNOWN_OC_TOKEN.get(String
          .valueOf(id));
      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
          message);
    }
  }
  /**
   * Encodes the information in the provided attribute to a byte
   * array.
   * Encodes the information in the provided attribute to a byte array.
   *
   * @param  entryBuffer The buffer to encode the attribute to.
   * @param  attribute   The attribute to be encoded.
   *
   * @throws  DirectoryException  If a problem occurs while attempting
   *                              to determine the appropriate
   *                              identifier.
   * @param builder
   *          The buffer to encode the attribute to.
   * @param attribute
   *          The attribute to be encoded.
   * @throws DirectoryException
   *           If a problem occurs while attempting to determine the appropriate
   *           identifier.
   */
  public abstract void encodeAttribute(ByteStringBuilder entryBuffer,
                                       Attribute attribute)
         throws DirectoryException;
  public final void encodeAttribute(final ByteStringBuilder builder,
      final Attribute attribute) throws DirectoryException
  {
    // Re-use or allocate a new ID.
    final AttributeType type = attribute.getAttributeType();
    final Set<String> options = attribute.getOptions();
    final Entry<AttributeType, Set<String>> ad =
        new SimpleImmutableEntry<AttributeType, Set<String>>(type, options);
    // Use double checked locking to avoid lazy registration races.
    Integer id = adEncodeMap.get(ad);
    if (id == null)
    {
      synchronized (adEncodeMap)
      {
        id = adEncodeMap.get(ad);
        if (id == null)
        {
          id = adDecodeMap.size();
          adDecodeMap.add(ad);
          adEncodeMap.put(ad, id);
          storeAttribute(encodeId(id), type.getNameOrOID(), options);
        }
      }
    }
    // Encode the attribute.
    final byte[] idBytes = encodeId(id);
    builder.appendBERLength(idBytes.length);
    builder.append(idBytes);
    builder.appendBERLength(attribute.size());
    for (final AttributeValue v : attribute)
    {
      builder.appendBERLength(v.getValue().length());
      builder.append(v.getValue());
    }
  }
  /**
   * Decodes the contents of the provided array as an attribute at the
   * current position.
   * Encodes the provided set of object classes to a byte array. If the same set
   * had been previously encoded, then the cached value will be used. Otherwise,
   * a new value will be created.
   *
   * @param  entryBuffer   The byte array containing the encoded
   *                       entry.
   *
   * @return  The decoded attribute.
   *
   * @throws  DirectoryException  If the attribute could not be
   *                              decoded properly for some reason.
   * @param builder
   *          The buffer to encode the object classes to.
   * @param objectClasses
   *          The set of object classes for which to retrieve the corresponding
   *          byte array token.
   * @throws DirectoryException
   *           If a problem occurs while attempting to determine the appropriate
   *           identifier.
   */
  public abstract Attribute decodeAttribute(
      ByteSequenceReader entryBuffer) throws DirectoryException;
  public final void encodeObjectClasses(final ByteStringBuilder builder,
      final Map<ObjectClass, String> objectClasses) throws DirectoryException
  {
    // Re-use or allocate a new ID.
    // Use double checked locking to avoid lazy registration races.
    Integer id = ocEncodeMap.get(objectClasses);
    if (id == null)
    {
      synchronized (ocEncodeMap)
      {
        id = ocEncodeMap.get(objectClasses);
        if (id == null)
        {
          id = ocDecodeMap.size();
          ocDecodeMap.add(objectClasses);
          ocEncodeMap.put(objectClasses, id);
          storeObjectClasses(encodeId(id), objectClasses.values());
        }
      }
    }
    // Encode the object classes.
    final byte[] idBytes = encodeId(id);
    builder.appendBERLength(idBytes.length);
    builder.append(idBytes);
  }
  /**
   * Returns a view of the encoded attributes in this compressed schema which
   * can be used for saving the entire content to disk. The iterator returned by
   * this method is not thread safe.
   *
   * @return A view of the encoded attributes in this compressed schema.
   */
  protected final Iterable<Entry<byte[],
                                 Entry<String,
                                       Collection<String>>>> getAllAttributes()
  {
    return new Iterable<Entry<byte[], Entry<String, Collection<String>>>>()
    {
      @Override
      public Iterator<Entry<byte[],
                            Entry<String, Collection<String>>>> iterator()
      {
        return new Iterator<Entry<byte[], Entry<String, Collection<String>>>>()
        {
          private int id = 0;
          @Override
          public boolean hasNext()
          {
            return id < adDecodeMap.size();
          }
          @Override
          public Entry<byte[], Entry<String, Collection<String>>> next()
          {
            final byte[] encodedAttribute = encodeId(id);
            final Entry<AttributeType, Set<String>> ad = adDecodeMap.get(id++);
            return new SimpleImmutableEntry<byte[],
                                            Entry<String, Collection<String>>>(
                encodedAttribute,
                new SimpleImmutableEntry<String, Collection<String>>(ad
                    .getKey().getNameOrOID(), ad.getValue()));
          }
          @Override
          public void remove()
          {
            throw new UnsupportedOperationException();
          }
        };
      }
    };
  }
  /**
   * Returns a view of the encoded object classes in this compressed schema
   * which can be used for saving the entire content to disk. The iterator
   * returned by this method is not thread safe.
   *
   * @return A view of the encoded object classes in this compressed schema.
   */
  protected final Iterable<Entry<byte[],
                                 Collection<String>>> getAllObjectClasses()
  {
    return new Iterable<Entry<byte[], Collection<String>>>()
    {
      @Override
      public Iterator<Entry<byte[], Collection<String>>> iterator()
      {
        return new Iterator<Map.Entry<byte[], Collection<String>>>()
        {
          private int id = 0;
          @Override
          public boolean hasNext()
          {
            return id < ocDecodeMap.size();
          }
          @Override
          public Entry<byte[], Collection<String>> next()
          {
            final byte[] encodedObjectClasses = encodeId(id);
            final Map<ObjectClass, String> ocMap = ocDecodeMap.get(id++);
            return new SimpleImmutableEntry<byte[], Collection<String>>(
                encodedObjectClasses, ocMap.values());
          }
          @Override
          public void remove()
          {
            throw new UnsupportedOperationException();
          }
        };
      }
    };
  }
  /**
   * Loads an encoded attribute into this compressed schema. This method may
   * called by implementations during initialization when loading content from
   * disk.
   *
   * @param encodedAttribute
   *          The encoded attribute description.
   * @param attributeName
   *          The user provided attribute type name.
   * @param attributeOptions
   *          The non-null but possibly empty set of attribute options.
   * @return The attribute type description.
   */
  protected final Entry<AttributeType, Set<String>> loadAttribute(
      final byte[] encodedAttribute, final String attributeName,
      final Collection<String> attributeOptions)
  {
    final AttributeType type = DirectoryServer.getAttributeType(
        toLowerCase(attributeName), true);
    final Set<String> options;
    switch (attributeOptions.size())
    {
    case 0:
      options = Collections.emptySet();
      break;
    case 1:
      options = Collections.singleton(attributeOptions.iterator().next());
      break;
    default:
      options = new LinkedHashSet<String>(attributeOptions);
      break;
    }
    final Entry<AttributeType, Set<String>> ad =
        new SimpleImmutableEntry<AttributeType, Set<String>>(type, options);
    final int id = decodeId(encodedAttribute);
    synchronized (adEncodeMap)
    {
      adEncodeMap.put(ad, id);
      if (id < adDecodeMap.size())
      {
        adDecodeMap.set(id, ad);
      }
      else
      {
        // Grow the decode array.
        while (id > adDecodeMap.size())
        {
          adDecodeMap.add(null);
        }
        adDecodeMap.add(ad);
      }
    }
    return ad;
  }
  /**
   * Loads an encoded object class into this compressed schema. This method may
   * called by implementations during initialization when loading content from
   * disk.
   *
   * @param encodedObjectClasses
   *          The encoded object classes.
   * @param objectClassNames
   *          The user provided set of object class names.
   * @return The object class set.
   */
  protected final Map<ObjectClass, String> loadObjectClasses(
      final byte[] encodedObjectClasses,
      final Collection<String> objectClassNames)
  {
    final LinkedHashMap<ObjectClass, String> ocMap =
        new LinkedHashMap<ObjectClass, String>(objectClassNames.size());
    for (final String name : objectClassNames)
    {
      final String lowerName = toLowerCase(name);
      final ObjectClass oc = DirectoryServer.getObjectClass(lowerName, true);
      ocMap.put(oc, name);
    }
    final int id = decodeId(encodedObjectClasses);
    synchronized (ocEncodeMap)
    {
      ocEncodeMap.put(ocMap, id);
      if (id < ocDecodeMap.size())
      {
        ocDecodeMap.set(id, ocMap);
      }
      else
      {
        // Grow the decode array.
        while (id > ocDecodeMap.size())
        {
          ocDecodeMap.add(null);
        }
        ocDecodeMap.add(ocMap);
      }
    }
    return ocMap;
  }
  /**
   * Persists the provided encoded attribute. The default implementation is to
   * do nothing. Calls to this method are synchronized, so implementations can
   * assume that this method is not being called by other threads. Note that
   * this method is not thread-safe with respect to
   * {@link #storeObjectClasses(byte[], Collection)}.
   *
   * @param encodedAttribute
   *          The encoded attribute description.
   * @param attributeName
   *          The user provided attribute type name.
   * @param attributeOptions
   *          The non-null but possibly empty set of attribute options.
   * @throws DirectoryException
   *           If an error occurred while persisting the encoded attribute.
   */
  protected void storeAttribute(final byte[] encodedAttribute,
      final String attributeName, final Collection<String> attributeOptions)
      throws DirectoryException
  {
    // Do nothing by default.
  }
  /**
   * Persists the provided encoded object classes. The default implementation is
   * to do nothing. Calls to this method are synchronized, so implementations
   * can assume that this method is not being called by other threads. Note that
   * this method is not thread-safe with respect to
   * {@link #storeAttribute(byte[], String, Collection)}.
   *
   * @param encodedObjectClasses
   *          The encoded object classes.
   * @param objectClassNames
   *          The user provided set of object class names.
   * @throws DirectoryException
   *           If an error occurred while persisting the encoded object classes.
   */
  protected void storeObjectClasses(final byte[] encodedObjectClasses,
      final Collection<String> objectClassNames) throws DirectoryException
  {
    // Do nothing by default.
  }
  /**
   * Decodes the provided encoded schema element ID.
   *
   * @param idBytes
   *          The encoded schema element ID.
   * @return The schema element ID.
   */
  private int decodeId(final byte[] idBytes)
  {
    int id = 0;
    for (final byte b : idBytes)
    {
      id <<= 8;
      id |= (b & 0xFF);
    }
    return id - 1; // Subtract 1 to compensate for old behavior.
  }
  /**
   * Encodes the provided schema element ID.
   *
   * @param id
   *          The schema element ID.
   * @return The encoded schema element ID.
   */
  private byte[] encodeId(final int id)
  {
    final int value = id + 1; // Add 1 to compensate for old behavior.
    final byte[] idBytes;
    if (value <= 0xFF)
    {
      idBytes = new byte[1];
      idBytes[0] = (byte) (value & 0xFF);
    }
    else if (value <= 0xFFFF)
    {
      idBytes = new byte[2];
      idBytes[0] = (byte) ((value >> 8) & 0xFF);
      idBytes[1] = (byte) (value & 0xFF);
    }
    else if (value <= 0xFFFFFF)
    {
      idBytes = new byte[3];
      idBytes[0] = (byte) ((value >> 16) & 0xFF);
      idBytes[1] = (byte) ((value >> 8) & 0xFF);
      idBytes[2] = (byte) (value & 0xFF);
    }
    else
    {
      idBytes = new byte[4];
      idBytes[0] = (byte) ((value >> 24) & 0xFF);
      idBytes[1] = (byte) ((value >> 16) & 0xFF);
      idBytes[2] = (byte) ((value >> 8) & 0xFF);
      idBytes[3] = (byte) (value & 0xFF);
    }
    return idBytes;
  }
}
opends/src/server/org/opends/server/backends/jeb/JECompressedSchema.java
@@ -23,29 +23,33 @@
 *
 *
 *      Copyright 2008-2009 Sun Microsystems, Inc.
 *      Portions copyright 2013 ForgeRock AS.
 */
package org.opends.server.backends.jeb;
import static org.opends.messages.JebMessages.*;
import static org.opends.server.loggers.debug.DebugLogger.*;
import static org.opends.server.util.StaticUtils.*;
import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
import static org.opends.server.loggers.debug.DebugLogger.getTracer;
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.io.IOException;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import org.opends.messages.Message;
import org.opends.server.api.CompressedSchema;
import org.opends.server.core.DirectoryServer;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.protocols.asn1.*;
import org.opends.server.types.*;
import org.opends.server.protocols.asn1.ASN1;
import org.opends.server.protocols.asn1.ASN1Exception;
import org.opends.server.protocols.asn1.ASN1Reader;
import org.opends.server.protocols.asn1.ASN1Writer;
import org.opends.server.types.ByteStringBuilder;
import org.opends.server.types.DebugLogLevel;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.InitializationException;
import com.sleepycat.je.Cursor;
import com.sleepycat.je.Database;
@@ -63,129 +67,190 @@
 * This class provides a compressed schema implementation whose definitions are
 * stored in a Berkeley DB JE database.
 */
public final class JECompressedSchema
       extends CompressedSchema
public final class JECompressedSchema extends CompressedSchema
{
  /**
   * The tracer object for the debug logger.
   */
  private static final DebugTracer TRACER = getTracer();
  /**
   * The name of the database used to store compressed attribute description
   * definitions.
   */
  public static final String DB_NAME_AD = "compressed_attributes";
  private static final String DB_NAME_AD = "compressed_attributes";
  /**
   * The name of the database used to store compressed object class set
   * definitions.
   */
  public static final String DB_NAME_OC = "compressed_object_classes";
  private static final String DB_NAME_OC = "compressed_object_classes";
  // 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;
  /**
   * The tracer object for the debug logger.
   */
  private static final DebugTracer TRACER = getTracer();
  // The compressed attribute description schema database.
  private Database adDatabase;
  // The compresesd object class set schema database.
  private Database ocDatabase;
  // The environment in which the databases are held.
  private Environment environment;
  private final ByteStringBuilder storeWriterBuffer;
  private final ASN1Writer storeWriter;
  // The compresesd object class set schema database.
  private Database ocDatabase;
  private final ByteStringBuilder storeAttributeWriterBuffer =
      new ByteStringBuilder();
  private final ASN1Writer storeAttributeWriter = ASN1
      .getWriter(storeAttributeWriterBuffer);
  private final ByteStringBuilder storeObjectClassesWriterBuffer =
      new ByteStringBuilder();
  private final ASN1Writer storeObjectClassesWriter = ASN1
      .getWriter(storeObjectClassesWriterBuffer);
  /**
   * Creates a new instance of this JE compressed schema manager.
   *
   * @param  environment  A reference to the database environment in which the
   *                      databases will be held.
   *
   * @throws DatabaseException       If a database problem occurs while loading
   *                                 the compressed schema definitions from the
   *                                 database.
   * @throws InitializationException If an error occurs while loading and
   *                                 processing the compressed schema
   *                                 definitions.
   * @param environment
   *          A reference to the database environment in which the databases
   *          will be held.
   * @throws DatabaseException
   *           If a database problem occurs while loading the compressed schema
   *           definitions from the database.
   * @throws InitializationException
   *           If an error occurs while loading and processing the compressed
   *           schema definitions.
   */
  public JECompressedSchema(Environment environment)
         throws DatabaseException, InitializationException
  public JECompressedSchema(final Environment environment)
      throws DatabaseException, InitializationException
  {
    this.environment = environment;
    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);
    storeWriterBuffer = new ByteStringBuilder();
    storeWriter = ASN1.getWriter(storeWriterBuffer);
    load();
  }
  /**
   * Closes the databases and releases any resources held by this compressed
   * schema manager.
   */
  public void close()
  {
    try
    {
      adDatabase.sync();
    }
    catch (final Exception e)
    {
      // Ignore.
    }
    try
    {
      adDatabase.close();
    }
    catch (final Exception e)
    {
      // Ignore.
    }
    try
    {
      ocDatabase.sync();
    }
    catch (final Exception e)
    {
      // Ignore.
    }
    try
    {
      ocDatabase.close();
    }
    catch (final Exception e)
    {
      // Ignore.
    }
    adDatabase = null;
    ocDatabase = null;
    environment = null;
  }
  /**
   * {@inheritDoc}
   */
  @Override
  protected void storeAttribute(final byte[] encodedAttribute,
      final String attributeName, final Collection<String> attributeOptions)
      throws DirectoryException
  {
    try
    {
      storeAttributeWriterBuffer.clear();
      storeAttributeWriter.writeStartSequence();
      storeAttributeWriter.writeOctetString(attributeName);
      for (final String option : attributeOptions)
      {
        storeAttributeWriter.writeOctetString(option);
      }
      storeAttributeWriter.writeEndSequence();
      store(adDatabase, encodedAttribute, storeAttributeWriterBuffer);
    }
    catch (final IOException e)
    {
      // TODO: Shouldn't happen but should log a message
    }
  }
  /**
   * {@inheritDoc}
   */
  @Override
  protected void storeObjectClasses(final byte[] encodedObjectClasses,
      final Collection<String> objectClassNames) throws DirectoryException
  {
    try
    {
      storeObjectClassesWriterBuffer.clear();
      storeObjectClassesWriter.writeStartSequence();
      for (final String ocName : objectClassNames)
      {
        storeObjectClassesWriter.writeOctetString(ocName);
      }
      storeObjectClassesWriter.writeEndSequence();
      store(ocDatabase, encodedObjectClasses, storeObjectClassesWriterBuffer);
    }
    catch (final IOException e)
    {
      // TODO: Shouldn't happen but should log a message
    }
  }
  /**
   * Loads the compressed schema information from the database.
   *
   * @throws DatabaseException       If a database error occurs while
   *                                 loading the definitions from the
   *                                 database.
   * @throws InitializationException If an error occurs while loading
   *                                 and processing the definitions.
   * @throws DatabaseException
   *           If a database error occurs while loading the definitions from the
   *           database.
   * @throws InitializationException
   *           If an error occurs while loading and processing the definitions.
   */
  private void load()
          throws DatabaseException, InitializationException
  private void load() throws DatabaseException, InitializationException
  {
    DatabaseConfig dbConfig = new DatabaseConfig();
    final DatabaseConfig dbConfig = new DatabaseConfig();
    if(environment.getConfig().getReadOnly())
    if (environment.getConfig().getReadOnly())
    {
      dbConfig.setReadOnly(true);
      dbConfig.setAllowCreate(false);
      dbConfig.setTransactional(false);
    }
    else if(!environment.getConfig().getTransactional())
    else if (!environment.getConfig().getTransactional())
    {
      dbConfig.setAllowCreate(true);
      dbConfig.setTransactional(false);
@@ -201,53 +266,40 @@
    ocDatabase = environment.openDatabase(null, DB_NAME_OC, dbConfig);
    // Cursor through the object class database and load the object class set
    // definitions.  At the same time, figure out the highest token value and
    // definitions. At the same time, figure out the highest token value and
    // initialize the object class counter to one greater than that.
    Cursor ocCursor = ocDatabase.openCursor(null, null);
    int highestToken = 0;
    final Cursor ocCursor = ocDatabase.openCursor(null, null);
    try
    {
      DatabaseEntry keyEntry   = new DatabaseEntry();
      DatabaseEntry valueEntry = new DatabaseEntry();
      final DatabaseEntry keyEntry = new DatabaseEntry();
      final DatabaseEntry valueEntry = new DatabaseEntry();
      OperationStatus status = ocCursor.getFirst(keyEntry, valueEntry,
                                                 LockMode.READ_UNCOMMITTED);
          LockMode.READ_UNCOMMITTED);
      while (status == OperationStatus.SUCCESS)
      {
        byte[] tokenBytes = keyEntry.getData();
        ByteString token = ByteString.wrap(tokenBytes);
        highestToken = Math.max(highestToken, decodeInt(tokenBytes));
        ASN1Reader reader =
            ASN1.getReader(valueEntry.getData());
        final byte[] encodedObjectClasses = keyEntry.getData();
        final ASN1Reader reader = ASN1.getReader(valueEntry.getData());
        reader.readStartSequence();
        LinkedHashMap<ObjectClass,String> ocMap =
             new LinkedHashMap<ObjectClass,String>();
        while(reader.hasNextElement())
        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);
        status = ocCursor.getNext(keyEntry, valueEntry,
                                  LockMode.READ_UNCOMMITTED);
            LockMode.READ_UNCOMMITTED);
      }
    }
    catch (ASN1Exception ae)
    catch (final ASN1Exception ae)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, ae);
      }
      Message m =
           ERR_JEB_COMPSCHEMA_CANNOT_DECODE_OC_TOKEN.get(ae.getMessage());
      final Message m = ERR_JEB_COMPSCHEMA_CANNOT_DECODE_OC_TOKEN.get(ae
          .getMessage());
      throw new InitializationException(m, ae);
    }
    finally
@@ -255,393 +307,64 @@
      ocCursor.close();
    }
    ocCounter.set(highestToken+1);
    // Cursor through the attribute description database and load the attribute
    // set definitions.
    Cursor adCursor = adDatabase.openCursor(null, null);
    highestToken = 0;
    final Cursor adCursor = adDatabase.openCursor(null, null);
    try
    {
      DatabaseEntry keyEntry   = new DatabaseEntry();
      DatabaseEntry valueEntry = new DatabaseEntry();
      final DatabaseEntry keyEntry = new DatabaseEntry();
      final DatabaseEntry valueEntry = new DatabaseEntry();
      OperationStatus status = adCursor.getFirst(keyEntry, valueEntry,
                                                 LockMode.READ_UNCOMMITTED);
          LockMode.READ_UNCOMMITTED);
      while (status == OperationStatus.SUCCESS)
      {
        byte[] tokenBytes = keyEntry.getData();
        ByteString token = ByteString.wrap(tokenBytes);
        highestToken = Math.max(highestToken, decodeInt(tokenBytes));
        ASN1Reader reader =
            ASN1.getReader(valueEntry.getData());
        final byte[] encodedAttribute = keyEntry.getData();
        final ASN1Reader reader = ASN1.getReader(valueEntry.getData());
        reader.readStartSequence();
        String attrName = reader.readOctetStringAsString();
        String lowerName = toLowerCase(attrName);
        AttributeType attrType =
            DirectoryServer.getAttributeType(lowerName, true);
        LinkedHashSet<String> options =
            new LinkedHashSet<String>();
        while(reader.hasNextElement())
        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);
        status = adCursor.getNext(keyEntry, valueEntry,
                                  LockMode.READ_UNCOMMITTED);
            LockMode.READ_UNCOMMITTED);
      }
    }
    catch (ASN1Exception ae)
    catch (final ASN1Exception ae)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, ae);
      }
      Message m =
           ERR_JEB_COMPSCHEMA_CANNOT_DECODE_AD_TOKEN.get(ae.getMessage());
      final Message m = ERR_JEB_COMPSCHEMA_CANNOT_DECODE_AD_TOKEN.get(ae
          .getMessage());
      throw new InitializationException(m, ae);
    }
    finally
    {
      adCursor.close();
    }
    adCounter.set(highestToken+1);
  }
  /**
   * Closes the databases and releases any resources held by this compressed
   * schema manager.
   */
  public void close()
  {
    try
    {
      adDatabase.sync();
    } catch (Exception e) {}
    try
    {
      adDatabase.close();
    } catch (Exception e) {}
    try
    {
      ocDatabase.sync();
    } catch (Exception e) {}
    try
    {
      ocDatabase.close();
    } catch (Exception e) {}
    adDatabase  = null;
    ocDatabase  = null;
    environment = null;
    atDecodeMap = null;
    aoDecodeMap = null;
    ocDecodeMap = null;
    //adEncodeMap = null;
    //ocEncodeMap = null;
    adCounter   = null;
    ocCounter   = null;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public void encodeObjectClasses(ByteStringBuilder entryBuffer,
                                  Map<ObjectClass,String> objectClasses)
         throws DirectoryException
  {
    ByteSequence encodedClasses = ocEncodeMap.get(objectClasses);
    if (encodedClasses == null)
    {
      synchronized (ocEncodeMap)
      {
        int setValue = ocCounter.getAndIncrement();
        byte[] tokenArray = encodeInt(setValue);
        encodedClasses = ByteString.wrap(tokenArray);
        storeObjectClass(tokenArray, objectClasses);
        ocEncodeMap.put(objectClasses, encodedClasses);
        ocDecodeMap.put(encodedClasses, objectClasses);
      }
    }
    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_JEB_COMPSCHEMA_UNKNOWN_OC_TOKEN.get(byteArray
          .toByteString().toHex());
      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
          message);
    }
    else
    {
      return ocMap;
    }
  }
  private void storeObjectClass(byte[] token,
                                Map<ObjectClass,String> objectClasses)
      throws DirectoryException
  {
    synchronized(storeWriter)
    {
      try
      {
        storeWriterBuffer.clear();
        storeWriter.writeStartSequence();
        for (String ocName : objectClasses.values())
        {
          storeWriter.writeOctetString(ocName);
        }
        storeWriter.writeEndSequence();
        store(ocDatabase, token, storeWriterBuffer);
      }
      catch(IOException ioe)
      {
        // TODO: Shouldn't happen but should log a message
      }
    }
  }
  private void storeAttribute(byte[] token,
                              AttributeType attrType, Set<String> options)
      throws DirectoryException
  {
    synchronized(storeWriter)
    {
      try
      {
        storeWriterBuffer.clear();
        storeWriter.writeStartSequence();
        storeWriter.writeOctetString(attrType.getNameOrOID());
        for (String option : options)
        {
          storeWriter.writeOctetString(option);
        }
        storeWriter.writeEndSequence();
        store(adDatabase, token, storeWriterBuffer);
      }
      catch(IOException ioe)
      {
        // TODO: Shouldn't happen but should log a message
      }
    }
  }
  /**
   * {@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)
    {
      byte[] tokenArray;
      ByteString byteString;
      synchronized (adEncodeMap)
      {
        map = new ConcurrentHashMap<Set<String>, ByteSequence>(1);
        int intValue = adCounter.getAndIncrement();
        tokenArray = encodeInt(intValue);
        byteString = ByteString.wrap(tokenArray);
        map.put(options,byteString);
        storeAttribute(tokenArray, type, options);
        adEncodeMap.put(type, map);
        atDecodeMap.put(byteString, type);
        aoDecodeMap.put(byteString, options);
      }
      encodeAttribute(entryBuffer, byteString, attribute);
    }
    else
    {
      ByteSequence byteArray = map.get(options);
      if (byteArray == null)
      {
        byte[] tokenArray;
        synchronized (map)
        {
          int intValue = adCounter.getAndIncrement();
          tokenArray = encodeInt(intValue);
          byteArray = ByteString.wrap(tokenArray);
          map.put(options,byteArray);
          storeAttribute(tokenArray, type, options);
          atDecodeMap.put(byteArray, type);
          aoDecodeMap.put(byteArray, options);
        }
      }
      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_JEB_COMPSCHEMA_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();
    }
  }
  /**
   * Stores the provided key-value pair in the specified database container.
   *
   * @param  database    The database in which to store the information.
   * @param  keyBytes    The byte array containing the key to store.
   * @param  valueBytes  The byte array containing the value to store.
   *
   * @throws  DirectoryException  If a problem occurs while attempting to store
   *                              the data.
   */
  private void store(Database database, byte[] keyBytes,
                     ByteStringBuilder valueBytes)
          throws DirectoryException
  private void store(final Database database, final byte[] key,
      final ByteStringBuilder value) throws DirectoryException
  {
    boolean successful = false;
    DatabaseEntry keyEntry   = new DatabaseEntry(keyBytes);
    DatabaseEntry valueEntry = new DatabaseEntry(valueBytes.getBackingArray(),
        0, valueBytes.length());
    for (int i=0; i < 3; i++)
    final DatabaseEntry keyEntry = new DatabaseEntry(key);
    final DatabaseEntry valueEntry = new DatabaseEntry(value.getBackingArray(),
        0, value.length());
    for (int i = 0; i < 3; i++)
    {
      try
      {
        OperationStatus status = database.putNoOverwrite(null, keyEntry,
                                                         valueEntry);
        final OperationStatus status = database.putNoOverwrite(null, keyEntry,
            valueEntry);
        if (status == OperationStatus.SUCCESS)
        {
          successful = true;
@@ -649,95 +372,31 @@
        }
        else
        {
          Message m = ERR_JEB_COMPSCHEMA_CANNOT_STORE_STATUS.get(
                           status.toString());
          final Message m = ERR_JEB_COMPSCHEMA_CANNOT_STORE_STATUS.get(status
              .toString());
          throw new DirectoryException(
                         DirectoryServer.getServerErrorResultCode(), m);
              DirectoryServer.getServerErrorResultCode(), m);
        }
      }
      catch (LockConflictException ce)
      catch (final LockConflictException ce)
      {
        continue;
      }
      catch (DatabaseException de)
      catch (final DatabaseException de)
      {
        Message m = ERR_JEB_COMPSCHEMA_CANNOT_STORE_EX.get(de.getMessage());
        throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
                                     m, de);
        final Message m = ERR_JEB_COMPSCHEMA_CANNOT_STORE_EX.get(de
            .getMessage());
        throw new DirectoryException(
            DirectoryServer.getServerErrorResultCode(), m, de);
      }
    }
    if (! successful)
    if (!successful)
    {
      Message m = ERR_JEB_COMPSCHEMA_CANNOT_STORE_MULTIPLE_FAILURES.get();
      throw new DirectoryException(
                     DirectoryServer.getServerErrorResultCode(), m);
      final Message m = ERR_JEB_COMPSCHEMA_CANNOT_STORE_MULTIPLE_FAILURES.get();
      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
          m);
    }
  }
  /**
   * 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;
  }
  /**
   * Decodes the contents of the provided byte array as an int.
   *
   * @param  byteArray  The byte array containing the data to decode.
   *
   * @return  The decoded int value.
   */
  private int decodeInt(byte[] byteArray)
  {
    int intValue = 0;
    for (byte b : byteArray)
    {
      intValue <<= 8;
      intValue |= (b & 0xFF);
    }
    return intValue;
  }
}
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;
  }
}
opends/src/server/org/opends/server/core/DirectoryServer.java
@@ -23,7 +23,7 @@
 *
 *
 *      Copyright 2006-2010 Sun Microsystems, Inc.
 *      Portions Copyright 2010-2012 ForgeRock AS.
 *      Portions Copyright 2010-2013 ForgeRock AS.
 */
package org.opends.server.core;
@@ -3681,11 +3681,11 @@
      String oid        = lowerName + "-oid";
      String definition = "( " + oid + " NAME '" + name + "' ABSTRACT )";
      // Temporary object classes are immediately dirty.
      objectClass = new ObjectClass(definition, name,
                                    Collections.singleton(name), oid, null,
                                    Collections.singleton(getTopObjectClass()),
                                    null, null,
                                    ObjectClassType.ABSTRACT, false, null);
          Collections.singleton(name), oid, null,
          Collections.singleton(getTopObjectClass()), null, null,
          ObjectClassType.ABSTRACT, false, null).setDirty();
    }
    return objectClass;
@@ -3936,10 +3936,11 @@
    String definition = "( " + oid + " NAME '" + name + "' SYNTAX " +
                        syntax.getOID() + " )";
    // Temporary attribute types are immediately dirty.
    return new AttributeType(definition, name, Collections.singleton(name),
                             oid, null, null, syntax,
                             AttributeUsage.USER_APPLICATIONS, false, false,
                             false, false);
                             false, false).setDirty();
  }
@@ -3947,9 +3948,10 @@
  /**
   * Retrieves the set of attribute syntaxes defined in the Directory Server.
   *
   * @return  The set of attribute syntaxes defined in the Directory Server.
   * @return The set of attribute syntaxes defined in the Directory Server.
   */
  public static ConcurrentHashMap<String,AttributeSyntax> getAttributeSyntaxes()
  public static ConcurrentHashMap<String,
                                  AttributeSyntax<?>> getAttributeSyntaxes()
  {
    return directoryServer.schema.getSyntaxes();
  }
opends/src/server/org/opends/server/types/AttributeType.java
@@ -23,7 +23,7 @@
 *
 *
 *      Copyright 2006-2009 Sun Microsystems, Inc.
 *      Portions Copyright 2011-2012 ForgeRock AS
 *      Portions Copyright 2011-2013 ForgeRock AS
 */
package org.opends.server.types;
@@ -127,6 +127,9 @@
  // The substring matching rule for this attribute type.
  private final SubstringMatchingRule substringMatchingRule;
  // True once this attribute type has been removed from the schema.
  private volatile boolean isDirty = false;
  /**
@@ -431,31 +434,17 @@
  }
  /**
   * Creates a new instance of this attribute type based on the
   * definition string.  It will also preserve other state information
   * associated with this attribute type that is not included in the
   * definition string (e.g., the name of the schema file with which
   * it is associated).
   *
   * @return  The new instance of this attribute type based on the
   *          definition string.
   *
   * @throws  DirectoryException  If a problem occurs while attempting
   *                              to create a new attribute type
   *                              instance from the definition string.
   * {@inheritDoc}
   */
  public AttributeType recreateFromDefinition()
  public AttributeType recreateFromDefinition(Schema schema)
         throws DirectoryException
  {
    ByteString value  = ByteString.valueOf(definition);
    Schema     schema = DirectoryServer.getSchema();
    AttributeType at =
         AttributeTypeSyntax.decodeAttributeType(value, schema,
                                              false);
    at.setSchemaFile(getSchemaFile());
    at.mayHaveSubordinateTypes = mayHaveSubordinateTypes;
    return at;
  }
@@ -772,5 +761,33 @@
    return getNormalizedPrimaryNameOrOID().compareTo(
      o.getNormalizedPrimaryNameOrOID());
  }
  /**
   * Marks this attribute type as dirty, indicating that it has been removed or
   * replaced in the schema.
   *
   * @return A reference to this attribute type.
   */
  public AttributeType setDirty()
  {
    isDirty = true;
    return this;
  }
  /**
   * Returns {@code true} if this attribute type has been removed or replaced in
   * the schema.
   *
   * @return {@code true} if this attribute type has been removed or replaced in
   *         the schema.
   */
  public boolean isDirty()
  {
    return isDirty;
  }
}
opends/src/server/org/opends/server/types/DITContentRule.java
@@ -23,6 +23,7 @@
 *
 *
 *      Copyright 2006-2008 Sun Microsystems, Inc.
 *      Portions copyright 2013 ForgeRock AS.
 */
package org.opends.server.types;
@@ -252,30 +253,16 @@
  /**
   * Creates a new instance of this DIT content rule based on the
   * definition string.  It will also preserve other state information
   * associated with this DIT content rule that is not included in the
   * definition string (e.g., the name of the schema file with which
   * it is associated).
   *
   * @return  The new instance of this DIT content rule based on the
   *          definition string.
   *
   * @throws  DirectoryException  If a problem occurs while attempting
   *                              to create a new DIT content rule
   *                              instance from the definition string.
   * {@inheritDoc}
   */
  public DITContentRule recreateFromDefinition()
  public DITContentRule recreateFromDefinition(Schema schema)
         throws DirectoryException
  {
    ByteString value  = ByteString.valueOf(definition);
    Schema     schema = DirectoryConfig.getSchema();
    DITContentRule dcr =
         DITContentRuleSyntax.decodeDITContentRule(value, schema,
                                                   false);
    dcr.setSchemaFile(getSchemaFile());
    return dcr;
  }
opends/src/server/org/opends/server/types/DITStructureRule.java
@@ -23,6 +23,7 @@
 *
 *
 *      Copyright 2006-2008 Sun Microsystems, Inc.
 *      Portions copyright 2013 ForgeRock AS.
 */
package org.opends.server.types;
@@ -202,30 +203,16 @@
  /**
   * Creates a new instance of this DIT structure rule based on the
   * definition string.  It will also preserve other state information
   * associated with this DIT structure rule that is not included in
   * the definition string (e.g., the name of the schema file with
   * which it is associated).
   *
   * @return  The new instance of this DIT structure rule based on the
   *          definition string.
   *
   * @throws  DirectoryException  If a problem occurs while attempting
   *                              to create a new DIT structure rule
   *                              instance from the definition string.
   * {@inheritDoc}
   */
  public DITStructureRule recreateFromDefinition()
  public DITStructureRule recreateFromDefinition(Schema schema)
         throws DirectoryException
  {
    ByteString value  = ByteString.valueOf(definition);
    Schema     schema = DirectoryConfig.getSchema();
    DITStructureRule dsr =
         DITStructureRuleSyntax.decodeDITStructureRule(value, schema,
                                                       false);
    dsr.setSchemaFile(getSchemaFile());
    return dsr;
  }
opends/src/server/org/opends/server/types/DirectoryConfig.java
@@ -23,6 +23,7 @@
 *
 *
 *      Copyright 2006-2009 Sun Microsystems, Inc.
 *      Portions copyright 2013 ForgeRock AS.
 */
package org.opends.server.types;
import java.util.List;
@@ -411,7 +412,7 @@
   * @return  The set of attribute syntaxes defined in the Directory
   *          Server.
   */
  public static Map<String,AttributeSyntax>
  public static Map<String,AttributeSyntax<?>>
       getAttributeSyntaxes()
  {
    return DirectoryServer.getAttributeSyntaxes();
opends/src/server/org/opends/server/types/LDAPSyntaxDescription.java
@@ -23,6 +23,7 @@
 *
 *
 *      Copyright 2009 Sun Microsystems, Inc.
 *      Portions copyright 2013 ForgeRock AS.
 */
@@ -166,31 +167,16 @@
  /**
   * Creates a new instance of this ldap syntax based on the
   * definition string.  It will also preserve other state
   * information associated with this ldap syntax that is not
   * included in the definition string (e.g., the name of the schema
   * file with which it is associated).
   *
   * @return  The new instance of this ldap syntax based on the
   *          definition string.
   *
   * @throws  DirectoryException  If a problem occurs while
   *                              attempting to create a new ldap
   *                              syntax instance from the definition
   *                              string.
   * {@inheritDoc}
   */
  public LDAPSyntaxDescription recreateFromDefinition()
  public LDAPSyntaxDescription recreateFromDefinition(Schema schema)
         throws DirectoryException
  {
    ByteString value  = ByteString.valueOf(definition);
    Schema     schema = DirectoryConfig.getSchema();
    LDAPSyntaxDescription ls =
            LDAPSyntaxDescriptionSyntax.decodeLDAPSyntax(value,
            schema, false);
    ls.setSchemaFile(getSchemaFile());
    return ls;
  }
opends/src/server/org/opends/server/types/MatchingRuleUse.java
@@ -23,6 +23,7 @@
 *
 *
 *      Copyright 2006-2008 Sun Microsystems, Inc.
 *      Portions copyright 2013 ForgeRock AS.
 */
package org.opends.server.types;
@@ -202,30 +203,16 @@
  /**
   * Creates a new instance of this matching rule use based on the
   * definition string.  It will also preserve other state information
   * associated with this matching rule use that is not included in
   * the definition string (e.g., the name of the schema file with
   * which it is associated).
   *
   * @return  The new instance of this matching rule use based on the
   *          definition string.
   *
   * @throws  DirectoryException  If a problem occurs while attempting
   *                              to create a new matching rule use
   *                              instance from the definition string.
   * {@inheritDoc}
   */
  public MatchingRuleUse recreateFromDefinition()
  public MatchingRuleUse recreateFromDefinition(Schema schema)
         throws DirectoryException
  {
    ByteString value  = ByteString.valueOf(definition);
    Schema     schema = DirectoryConfig.getSchema();
    MatchingRuleUse mru =
         MatchingRuleUseSyntax.decodeMatchingRuleUse(value, schema,
                                                     false);
    mru.setSchemaFile(getSchemaFile());
    return mru;
  }
opends/src/server/org/opends/server/types/NameForm.java
@@ -23,6 +23,7 @@
 *
 *
 *      Copyright 2006-2008 Sun Microsystems, Inc.
 *      Portions copyright 2013 ForgeRock AS.
 */
package org.opends.server.types;
@@ -218,27 +219,14 @@
  /**
   * Creates a new instance of this name form based on the definition
   * string.  It will also preserve other state information associated
   * with this name form that is not included in the definition string
   * (e.g., the name of the schema file with which it is associated).
   *
   * @return  The new instance of this name form based on the
   *          definition string.
   *
   * @throws  DirectoryException  If a problem occurs while attempting
   *                              to create a new name form instance
   *                              from the definition string.
   * {@inheritDoc}
   */
  public NameForm recreateFromDefinition()
  public NameForm recreateFromDefinition(Schema schema)
         throws DirectoryException
  {
    ByteString value  = ByteString.valueOf(definition);
    Schema     schema = DirectoryConfig.getSchema();
    NameForm nf = NameFormSyntax.decodeNameForm(value, schema, false);
    nf.setSchemaFile(getSchemaFile());
    return nf;
  }
opends/src/server/org/opends/server/types/ObjectClass.java
@@ -23,6 +23,7 @@
 *
 *
 *      Copyright 2006-2010 Sun Microsystems, Inc.
 *      Portions copyright 2013 ForgeRock AS.
 */
package org.opends.server.types;
@@ -106,6 +107,9 @@
  // The definition string used to create this objectclass.
  private final String definition;
  // True once this object class has been removed from the schema.
  private volatile boolean isDirty = false;
  /**
@@ -312,29 +316,15 @@
  /**
   * Creates a new instance of this objectclass based on the
   * definition string.  It will also preserve other state information
   * associated with this objectclass that is not included in the
   * definition string (e.g., the name of the schema file with which
   * it is associated).
   *
   * @return  The new instance of this objectclass based on the
   *          definition string.
   *
   * @throws  DirectoryException  If a problem occurs while attempting
   *                              to create a new objectclass instance
   *                              from the definition string.
   * {@inheritDoc}
   */
  public ObjectClass recreateFromDefinition()
  public ObjectClass recreateFromDefinition(Schema schema)
         throws DirectoryException
  {
    ByteString value  = ByteString.valueOf(definition);
    Schema     schema = DirectoryConfig.getSchema();
    ObjectClass oc = ObjectClassSyntax.decodeObjectClass(value,
                                            schema, false);
    oc.setSchemaFile(getSchemaFile());
    return oc;
  }
@@ -608,4 +598,32 @@
      }
    }
  }
  /**
   * Marks this object class as dirty, indicating that it has been removed or
   * replaced in the schema.
   *
   * @return A reference to this object class.
   */
  public ObjectClass setDirty()
  {
    isDirty = true;
    return this;
  }
  /**
   * Returns {@code true} if this object class has been removed or replaced in
   * the schema.
   *
   * @return {@code true} if this object class has been removed or replaced in
   *         the schema.
   */
  public boolean isDirty()
  {
    return isDirty;
  }
}
opends/src/server/org/opends/server/types/Schema.java
@@ -23,7 +23,7 @@
 *
 *
 *      Copyright 2006-2010 Sun Microsystems, Inc.
 *      Portions Copyright 2011 ForgeRock AS
 *      Portions Copyright 2011-2013 ForgeRock AS
 */
package org.opends.server.types;
@@ -120,7 +120,7 @@
  // The set of attribute syntaxes for this schema, mapped between the
  // OID for the syntax and the syntax itself.
  private ConcurrentHashMap<String,AttributeSyntax> syntaxes;
  private ConcurrentHashMap<String,AttributeSyntax<?>> syntaxes;
  // The entire set of matching rules for this schema, mapped between
  // the lowercase names and OID for the definition and the matching
@@ -246,7 +246,7 @@
  {
    attributeTypes = new ConcurrentHashMap<String,AttributeType>();
    objectClasses = new ConcurrentHashMap<String,ObjectClass>();
    syntaxes = new ConcurrentHashMap<String,AttributeSyntax>();
    syntaxes = new ConcurrentHashMap<String,AttributeSyntax<?>>();
    matchingRules = new ConcurrentHashMap<String,MatchingRule>();
    approximateMatchingRules =
         new ConcurrentHashMap<String,ApproximateMatchingRule>();
@@ -406,8 +406,14 @@
        }
      }
      attributeTypes.put(toLowerCase(attributeType.getOID()),
                         attributeType);
      AttributeType old = attributeTypes.put(
          toLowerCase(attributeType.getOID()), attributeType);
      if (old != null && old != attributeType)
      {
        // Mark the old attribute type as stale so that caches (such as
        // compressed schema) can detect changes.
        old.setDirty();
      }
      for (String name : attributeType.getNormalizedNames())
      {
@@ -446,8 +452,13 @@
  {
    synchronized (attributeTypes)
    {
      attributeTypes.remove(toLowerCase(attributeType.getOID()),
                            attributeType);
      if (attributeTypes.remove(toLowerCase(attributeType.getOID()),
          attributeType))
      {
        // Mark the old attribute type as stale so that caches (such as
        // compressed schema) can detect changes.
        attributeType.setDirty();
      }
      for (String name : attributeType.getNormalizedNames())
      {
@@ -693,8 +704,14 @@
        }
      }
      objectClasses.put(toLowerCase(objectClass.getOID()),
                        objectClass);
      ObjectClass old = objectClasses.put(toLowerCase(objectClass.getOID()),
          objectClass);
      if (old != null && old != objectClass)
      {
        // Mark the old object class as stale so that caches (such as compressed
        // schema) can detect changes.
        old.setDirty();
      }
      for (String name : objectClass.getNormalizedNames())
      {
@@ -725,8 +742,12 @@
  {
    synchronized (objectClasses)
    {
      objectClasses.remove(toLowerCase(objectClass.getOID()),
                           objectClass);
      if (objectClasses.remove(toLowerCase(objectClass.getOID()), objectClass))
      {
        // Mark the old object class as stale so that caches (such as
        // compressed schema) can detect changes.
        objectClass.setDirty();
      }
      for (String name : objectClass.getNormalizedNames())
      {
@@ -770,7 +791,7 @@
   *
   * @return  The attribute syntax definitions for this schema.
   */
  public ConcurrentHashMap<String,AttributeSyntax> getSyntaxes()
  public ConcurrentHashMap<String,AttributeSyntax<?>> getSyntaxes()
  {
    return syntaxes;
  }
@@ -816,7 +837,7 @@
   * @return  The requested attribute syntax, or <CODE>null</CODE> if
   *          no syntax is registered with the provided OID.
   */
  public AttributeSyntax getSyntax(String lowerName)
  public AttributeSyntax<?> getSyntax(String lowerName)
  {
    return syntaxes.get(lowerName);
  }
@@ -838,7 +859,7 @@
   *                              <CODE>overwriteExisting</CODE> flag
   *                              is set to <CODE>false</CODE>
   */
  public void registerSyntax(AttributeSyntax syntax,
  public void registerSyntax(AttributeSyntax<?> syntax,
                             boolean overwriteExisting)
         throws DirectoryException
  {
@@ -849,7 +870,7 @@
        String oid = toLowerCase(syntax.getOID());
        if (syntaxes.containsKey(oid))
        {
          AttributeSyntax conflictingSyntax = syntaxes.get(oid);
          AttributeSyntax<?> conflictingSyntax = syntaxes.get(oid);
          Message message = ERR_SCHEMA_CONFLICTING_SYNTAX_OID.
              get(syntax.getSyntaxName(), oid,
@@ -882,7 +903,7 @@
   * @param  syntax  The attribute syntax to deregister with this
   *                 schema.
   */
  public void deregisterSyntax(AttributeSyntax syntax)
  public void deregisterSyntax(AttributeSyntax<?> syntax)
  {
    synchronized (syntaxes)
    {
@@ -3160,7 +3181,7 @@
        if ((at.getSuperiorType() != null) &&
            at.getSuperiorType().equals(t))
        {
          AttributeType newAT = at.recreateFromDefinition();
          AttributeType newAT = at.recreateFromDefinition(this);
          deregisterAttributeType(at);
          registerAttributeType(newAT, true);
          rebuildDependentElements(at, depth+1);
@@ -3172,7 +3193,7 @@
        if (oc.getRequiredAttributes().contains(t) ||
            oc.getOptionalAttributes().contains(t))
        {
          ObjectClass newOC = oc.recreateFromDefinition();
          ObjectClass newOC = oc.recreateFromDefinition(this);
          deregisterObjectClass(oc);
          registerObjectClass(newOC, true);
          rebuildDependentElements(oc, depth+1);
@@ -3186,7 +3207,7 @@
          if (nf.getRequiredAttributes().contains(t) ||
              nf.getOptionalAttributes().contains(t))
          {
            NameForm newNF = nf.recreateFromDefinition();
            NameForm newNF = nf.recreateFromDefinition(this);
            deregisterNameForm(nf);
            registerNameForm(newNF, true);
            rebuildDependentElements(nf, depth+1);
@@ -3200,7 +3221,7 @@
            dcr.getOptionalAttributes().contains(t) ||
            dcr.getProhibitedAttributes().contains(t))
        {
          DITContentRule newDCR = dcr.recreateFromDefinition();
          DITContentRule newDCR = dcr.recreateFromDefinition(this);
          deregisterDITContentRule(dcr);
          registerDITContentRule(newDCR, true);
          rebuildDependentElements(dcr, depth+1);
@@ -3211,7 +3232,7 @@
      {
        if (mru.getAttributes().contains(t))
        {
          MatchingRuleUse newMRU = mru.recreateFromDefinition();
          MatchingRuleUse newMRU = mru.recreateFromDefinition(this);
          deregisterMatchingRuleUse(mru);
          registerMatchingRuleUse(newMRU, true);
          rebuildDependentElements(mru, depth+1);
@@ -3226,7 +3247,7 @@
      {
        if (oc.getSuperiorClasses().contains(c))
        {
          ObjectClass newOC = oc.recreateFromDefinition();
          ObjectClass newOC = oc.recreateFromDefinition(this);
          deregisterObjectClass(oc);
          registerObjectClass(newOC, true);
          rebuildDependentElements(oc, depth+1);
@@ -3240,7 +3261,7 @@
        {
          if (nf != null)
          {
            NameForm newNF = nf.recreateFromDefinition();
            NameForm newNF = nf.recreateFromDefinition(this);
            deregisterNameForm(nf);
            registerNameForm(newNF, true);
            rebuildDependentElements(nf, depth+1);
@@ -3253,7 +3274,7 @@
        if (dcr.getStructuralClass().equals(c) ||
            dcr.getAuxiliaryClasses().contains(c))
        {
          DITContentRule newDCR = dcr.recreateFromDefinition();
          DITContentRule newDCR = dcr.recreateFromDefinition(this);
          deregisterDITContentRule(dcr);
          registerDITContentRule(newDCR, true);
          rebuildDependentElements(dcr, depth+1);
@@ -3266,7 +3287,7 @@
      DITStructureRule dsr = ditStructureRulesByNameForm.get(n);
      if (dsr != null)
      {
        DITStructureRule newDSR = dsr.recreateFromDefinition();
        DITStructureRule newDSR = dsr.recreateFromDefinition(this);
        deregisterDITStructureRule(dsr);
        registerDITStructureRule(newDSR, true);
        rebuildDependentElements(dsr, depth+1);
@@ -3279,7 +3300,7 @@
      {
        if (dsr.getSuperiorRules().contains(d))
        {
          DITStructureRule newDSR = dsr.recreateFromDefinition();
          DITStructureRule newDSR = dsr.recreateFromDefinition(this);
          deregisterDITStructureRule(dsr);
          registerDITStructureRule(newDSR, true);
          rebuildDependentElements(dsr, depth+1);
opends/src/server/org/opends/server/types/SchemaFileElement.java
@@ -23,6 +23,7 @@
 *
 *
 *      Copyright 2008 Sun Microsystems, Inc.
 *      Portions copyright 2013 ForgeRock AS.
 */
package org.opends.server.types;
@@ -88,25 +89,24 @@
  /**
   * Creates a new instance of this schema element based on the
   * definition from the schema file.  The new instance should also
   * be created with all appropriate state information that may not
   * be directly represented in the schema definition (e.g., the name
   * of the schema file containing the definition).
   * <BR><BR>
   * Whenever an existing schema file element is modified with the
   * server online, this method will be invoked to recreate any
   * schema elements that might have been dependent upon the
   * modified element.
   * Creates a new instance of this schema element based on the definition from
   * the schema file. The new instance should also be created with all
   * appropriate state information that may not be directly represented in the
   * schema definition (e.g., the name of the schema file containing the
   * definition). <BR>
   * <BR>
   * Whenever an existing schema file element is modified with the server
   * online, this method will be invoked to recreate any schema elements that
   * might have been dependent upon the modified element.
   *
   * @return  A new instance of this schema element based on the
   *          definition.
   *
   * @throws  DirectoryException  If a problem occurs while attempting
   *                              to create the new instance of this
   *                              schema element.
   * @param schema
   *          The schema which should be used for resolving dependencies.
   * @return A new instance of this schema element based on the definition.
   * @throws DirectoryException
   *           If a problem occurs while attempting to create the new instance
   *           of this schema element.
   */
  public SchemaFileElement recreateFromDefinition()
  public SchemaFileElement recreateFromDefinition(Schema schema)
         throws DirectoryException;
}
opends/tests/unit-tests-testng/src/server/org/opends/server/core/ModifyOperationTestCase.java
@@ -23,7 +23,7 @@
 *
 *
 *      Copyright 2006-2011 Sun Microsystems, Inc.
 *      Portions Copyright 2011-2012 ForgeRock AS
 *      Portions Copyright 2011-2013 ForgeRock AS
 */
package org.opends.server.core;
@@ -66,6 +66,7 @@
import static org.testng.Assert.*;
import static org.opends.server.TestCaseUtils.TEST_BACKEND_ID;
import static org.opends.server.TestCaseUtils.applyModifications;
import static org.opends.server.protocols.ldap.LDAPConstants.*;
@@ -1298,7 +1299,7 @@
    ModifyOperation modifyOperation =
         conn.processModify(ByteString.valueOf("uid=test.user," + baseDN),
                            mods);
    assertFalse(modifyOperation.getResultCode() == ResultCode.SUCCESS);
    assertEquals(modifyOperation.getResultCode(), ResultCode.OBJECTCLASS_VIOLATION);
    retrieveFailedOperationElements(modifyOperation);
  }
@@ -5164,7 +5165,7 @@
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    String certificateValue =
    String certificateValue =
      "MIICpTCCAg6gAwIBAgIJALeoA6I3ZC/cMA0GCSqGSIb3DQEBBQUAMFYxCzAJBgNV" +
      "BAYTAlVTMRMwEQYDVQQHEwpDdXBlcnRpb25lMRwwGgYDVQQLExNQcm9kdWN0IERl" +
      "dmVsb3BtZW50MRQwEgYDVQQDEwtCYWJzIEplbnNlbjAeFw0xMjA1MDIxNjM0MzVa" +
@@ -5177,8 +5178,8 @@
      "hvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYE" +
      "FLlZD3aKDa8jdhzoByOFMAJDs2osMB8GA1UdIwQYMBaAFLlZD3aKDa8jdhzoByOF" +
      "MAJDs2osMA0GCSqGSIb3DQEBBQUAA4GBAE5vccY8Ydd7by2bbwiDKgQqVyoKrkUg" +
      "6CD0WRmc2pBeYX2z94/PWO5L3Fx+eIZh2wTxScF+FdRWJzLbUaBuClrxuy0Y5ifj" +
      "axuJ8LFNbZtsp1ldW3i84+F5+SYT+xI67ZcoAtwx/VFVI9s5I/Gkmu9f9nxjPpK7" +
      "6CD0WRmc2pBeYX2z94/PWO5L3Fx+eIZh2wTxScF+FdRWJzLbUaBuClrxuy0Y5ifj" +
      "axuJ8LFNbZtsp1ldW3i84+F5+SYT+xI67ZcoAtwx/VFVI9s5I/Gkmu9f9nxjPpK7" +
      "1AIUXiE3Qcck";
    ArrayList<ByteString> values = new ArrayList<ByteString>();
@@ -5205,5 +5206,109 @@
    assertEquals(Base64.encode(a.iterator().next().getValue()), certificateValue);
  }
  /**
   * Tests to ensure that the compressed schema is refreshed after an object
   * class is changed (OPENDJ-169).
   *
   * @param baseDN
   *          The base DN to use.
   * @throws Exception
   *           If an unexpected problem occurs.
   */
  @Test(dataProvider = "baseDNs")
  public void testCompressedSchemaRefresh(String baseDN) throws Exception
  {
    TestCaseUtils.clearJEBackend(true, "userRoot", baseDN);
    Entry entry = TestCaseUtils.makeEntry("dn: cn=Test User," + baseDN,
        "objectClass: top", "objectClass: person",
        "objectClass: organizationalPerson", "sn: User", "cn: Test User");
    InternalClientConnection conn = InternalClientConnection
        .getRootConnection();
    AddOperation addOperation = conn.processAdd(entry.getDN(),
        entry.getObjectClasses(), entry.getUserAttributes(),
        entry.getOperationalAttributes());
    assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);
    // First check that adding "dc" fails because it is not allowed by
    // inetOrgPerson.
    ArrayList<ByteString> values = new ArrayList<ByteString>();
    values.add(ByteString.valueOf("foo"));
    LDAPAttribute attr = new LDAPAttribute("dc", values);
    ArrayList<RawModification> mods = new ArrayList<RawModification>();
    mods.add(new LDAPModification(ModificationType.ADD, attr));
    ModifyOperation modifyOperation = conn.processModify(
        ByteString.valueOf("cn=Test User," + baseDN), mods);
    assertEquals(modifyOperation.getResultCode(), ResultCode.OBJECTCLASS_VIOLATION);
    assertEquals(
        applyModifications(
            false,
            "dn: cn=schema",
            "changetype: modify",
            "delete: objectclasses",
            "objectClasses: ( 2.5.6.6 NAME 'person' SUP top STRUCTURAL MUST ( sn $ cn )"
                + "  MAY ( userPassword $ telephoneNumber $ seeAlso $ description )"
                + "  X-ORIGIN 'RFC 4519' )",
            "-",
            "add: objectclasses",
            "objectClasses: ( 2.5.6.6 NAME 'person' SUP top STRUCTURAL MUST ( sn $ cn )"
                + "  MAY ( dc $ userPassword $ telephoneNumber $ seeAlso $ description )"
                + "  X-ORIGIN 'RFC 4519' )"), 0, "Schema update failed");
    try
    {
      // Modify existing entry.
      modifyOperation = conn.processModify(
          ByteString.valueOf("cn=Test User," + baseDN), mods);
      assertEquals(modifyOperation.getResultCode(), ResultCode.SUCCESS);
      // Add new entry and modify.
      entry = TestCaseUtils.makeEntry("dn: cn=Test User2," + baseDN,
          "objectClass: top", "objectClass: person",
          "objectClass: organizationalPerson", "sn: User2", "cn: Test User2");
      addOperation = conn.processAdd(entry.getDN(), entry.getObjectClasses(),
          entry.getUserAttributes(), entry.getOperationalAttributes());
      assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);
      modifyOperation = conn.processModify(
          ByteString.valueOf("cn=Test User2," + baseDN), mods);
      assertEquals(modifyOperation.getResultCode(), ResultCode.SUCCESS);
    }
    finally
    {
      assertEquals(
          applyModifications(
              false,
              "dn: cn=schema",
              "changetype: modify",
              "delete: objectclasses",
              "objectClasses: ( 2.5.6.6 NAME 'person' SUP top STRUCTURAL MUST ( sn $ cn )"
                  + "  MAY ( dc $ userPassword $ telephoneNumber $ seeAlso $ description )"
                  + "  X-ORIGIN 'RFC 4519' )",
              "-",
              "add: objectclasses",
              "objectClasses: ( 2.5.6.6 NAME 'person' SUP top STRUCTURAL MUST ( sn $ cn )"
                  + "  MAY ( userPassword $ telephoneNumber $ seeAlso $ description )"
                  + "  X-ORIGIN 'RFC 4519' )"), 0, "Schema update failed");
      // Add new entry and modify (this time it should fail).
      entry = TestCaseUtils.makeEntry("dn: cn=Test User3," + baseDN,
          "objectClass: top", "objectClass: person",
          "objectClass: organizationalPerson", "sn: User3", "cn: Test User3");
      addOperation = conn.processAdd(entry.getDN(), entry.getObjectClasses(),
          entry.getUserAttributes(), entry.getOperationalAttributes());
      assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);
      modifyOperation = conn.processModify(
          ByteString.valueOf("cn=Test User3," + baseDN), mods);
      assertEquals(modifyOperation.getResultCode(), ResultCode.OBJECTCLASS_VIOLATION);
    }
  }
}