From e038f697b622bbe1b55fe661f1fc47bd48cf6477 Mon Sep 17 00:00:00 2001
From: Matthew Swift <matthew.swift@forgerock.com>
Date: Fri, 25 Jan 2013 17:41:37 +0000
Subject: [PATCH] Fix OPENDJ-169: Modifying an existing object class definition requires server restart

---
 opendj-sdk/opends/src/server/org/opends/server/api/CompressedSchema.java |  635 ++++++++++++++++++++++++++++++++++++++++++++++++++++-----
 1 files changed, 577 insertions(+), 58 deletions(-)

diff --git a/opendj-sdk/opends/src/server/org/opends/server/api/CompressedSchema.java b/opendj-sdk/opends/src/server/org/opends/server/api/CompressedSchema.java
index 7d30c84..f18490a 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/api/CompressedSchema.java
+++ b/opendj-sdk/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;
+  }
 }
-

--
Gitblit v1.10.0