From 8b3cd28204e15e0a98ce038b355f100cd7c44e3c Mon Sep 17 00:00:00 2001
From: Nicolas Capponi <capponi.nicolas@gmail.com>
Date: Thu, 28 Jan 2016 08:28:20 +0000
Subject: [PATCH] OPENDJ-1632 (PR-201) Migrate AttributeType in one shot

---
 opendj-server-legacy/src/main/java/org/opends/server/api/CompressedSchema.java |  491 ++++++++++++++++++++++++++++++++++++++----------------
 1 files changed, 346 insertions(+), 145 deletions(-)

diff --git a/opendj-server-legacy/src/main/java/org/opends/server/api/CompressedSchema.java b/opendj-server-legacy/src/main/java/org/opends/server/api/CompressedSchema.java
index 553a33b..17f6ece 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/api/CompressedSchema.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/api/CompressedSchema.java
@@ -22,7 +22,7 @@
  *
  *
  *      Copyright 2009 Sun Microsystems, Inc.
- *      Portions Copyright 2013-2015 ForgeRock AS.
+ *      Portions Copyright 2013-2016 ForgeRock AS.
  */
 package org.opends.server.api;
 
@@ -41,18 +41,25 @@
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
 
+import net.jcip.annotations.GuardedBy;
+
+import org.forgerock.opendj.ldap.ByteSequenceReader;
+import org.forgerock.opendj.ldap.ByteString;
+import org.forgerock.opendj.ldap.ByteStringBuilder;
+import org.forgerock.opendj.ldap.schema.AttributeType;
+import org.forgerock.opendj.ldap.schema.Schema;
 import org.opends.server.core.DirectoryServer;
+import org.opends.server.core.ServerContext;
 import org.opends.server.types.Attribute;
 import org.opends.server.types.AttributeBuilder;
-import org.opends.server.types.AttributeType;
-import org.forgerock.opendj.ldap.ByteString;
 import org.opends.server.types.Attributes;
-import org.forgerock.opendj.ldap.ByteSequence;
-import org.forgerock.opendj.ldap.ByteSequenceReader;
-import org.forgerock.opendj.ldap.ByteStringBuilder;
 import org.opends.server.types.DirectoryException;
 import org.opends.server.types.ObjectClass;
+import org.opends.server.util.RemoveOnceSDKSchemaIsUsed;
 
 /**
  * This class provides a utility for interacting with compressed representations
@@ -66,14 +73,128 @@
     mayInvoke = false)
 public class CompressedSchema
 {
-  /** Maps attribute description to ID. */
-  private final List<Entry<AttributeType, Set<String>>> adDecodeMap = new CopyOnWriteArrayList<>();
-  /** Maps ID to attribute description. */
-  private final Map<Entry<AttributeType, Set<String>>, Integer> adEncodeMap = new ConcurrentHashMap<>();
-  /** The map between encoded representations and object class sets. */
-  private final List<Map<ObjectClass, String>> ocDecodeMap = new CopyOnWriteArrayList<>();
-  /** The map between object class sets and encoded representations. */
-  private final Map<Map<ObjectClass, String>, Integer> ocEncodeMap = new ConcurrentHashMap<>();
+  /** Encloses all the encode and decode mappings for attribute and object classes. */
+  private static final class Mappings
+  {
+    /** Maps encoded representation's ID to its attribute description (the List's index is the ID). */
+    private final List<Entry<AttributeType, Set<String>>> adDecodeMap = new CopyOnWriteArrayList<>();
+    /** Maps attribute description to its encoded representation's ID. */
+    private final Map<Entry<AttributeType, Set<String>>, Integer> adEncodeMap;
+    /** Maps encoded representation's ID to its object class (the List's index is the ID). */
+    private final List<Map<ObjectClass, String>> ocDecodeMap = new CopyOnWriteArrayList<>();
+    /** Maps object class to its encoded representation's ID. */
+    private final Map<Map<ObjectClass, String>, Integer> ocEncodeMap;
+
+    private Mappings()
+    {
+      this.adEncodeMap = new ConcurrentHashMap<>();
+      this.ocEncodeMap = new ConcurrentHashMap<>();
+    }
+
+    private Mappings(int adEncodeMapSize, int ocEncodeMapSize)
+    {
+      this.adEncodeMap = new ConcurrentHashMap<>(adEncodeMapSize);
+      this.ocEncodeMap = new ConcurrentHashMap<>(ocEncodeMapSize);
+    }
+  }
+
+  private final ServerContext serverContext;
+  /** Lock to update the maps. */
+  final ReadWriteLock lock = new ReentrantReadWriteLock();
+  private final Lock exclusiveLock = lock.writeLock();
+  private final Lock sharedLock = lock.readLock();
+
+  /** Schema used to build the compressed information. */
+  @GuardedBy("lock")
+  private Schema schemaNG;
+  @GuardedBy("lock")
+  private Mappings mappings = new Mappings();
+
+  /**
+   * Creates a new empty instance of this compressed schema.
+   *
+   * @param serverContext
+   *            The server context.
+   */
+  public CompressedSchema(ServerContext serverContext)
+  {
+    this.serverContext = serverContext;
+  }
+
+  private Mappings getMappings()
+  {
+    sharedLock.lock();
+    try
+    {
+      return mappings;
+    }
+    finally
+    {
+      sharedLock.unlock();
+    }
+  }
+
+  private Mappings reloadMappingsIfSchemaChanged(boolean force)
+  {
+    // @RemoveOnceSDKSchemaIsUsed remove the "force" parameter
+    sharedLock.lock();
+    boolean shared = true;
+    try
+    {
+      Schema currentSchema = serverContext.getSchemaNG();
+      if (force || schemaNG != currentSchema)
+      {
+        sharedLock.unlock();
+        exclusiveLock.lock();
+        shared = false;
+
+        currentSchema = serverContext.getSchemaNG();
+        if (force || schemaNG != currentSchema)
+        {
+          // build new maps from existing ones
+          Mappings newMappings = new Mappings(mappings.adEncodeMap.size(), mappings.ocEncodeMap.size());
+          reloadAttributeTypeMaps(mappings, newMappings);
+          reloadObjectClassesMap(mappings, newMappings);
+
+          mappings = newMappings;
+          schemaNG = currentSchema;
+        }
+      }
+      return mappings;
+    }
+    finally
+    {
+      (shared ? sharedLock : exclusiveLock).unlock();
+    }
+  }
+
+  /**
+   * Reload the attribute types maps. This should be called when schema has changed, because some
+   * types may be out dated.
+   */
+  private void reloadAttributeTypeMaps(Mappings mappings, Mappings newMappings)
+  {
+    for (Entry<Entry<AttributeType, Set<String>>, Integer> entry : mappings.adEncodeMap.entrySet())
+    {
+      Entry<AttributeType, Set<String>> ad = entry.getKey();
+      Integer id = entry.getValue();
+      loadAttributeToMaps(id, ad.getKey().getNameOrOID(), ad.getValue(), newMappings);
+    }
+  }
+
+  /**
+   * Reload the object classes maps. This should be called when schema has changed, because some
+   * classes may be out dated.
+   */
+  private void reloadObjectClassesMap(Mappings mappings, Mappings newMappings)
+  {
+    for (Entry<Map<ObjectClass, String>, Integer> entry : mappings.ocEncodeMap.entrySet())
+    {
+      Map<ObjectClass, String> ocMap = entry.getKey();
+      Integer id = entry.getValue();
+      loadObjectClassesToMaps(id, ocMap.values(), newMappings, false);
+    }
+  }
 
   /**
    * Decodes the contents of the provided array as an attribute at the current
@@ -89,40 +210,27 @@
       throws DirectoryException
   {
     // First decode the encoded attribute description id.
-    final int length = reader.readBERLength();
-    final byte[] idBytes = new byte[length];
-    reader.readBytes(idBytes);
-    final int id = decodeId(idBytes);
+    final int id = decodeId(reader);
 
-    // Look up the attribute description.
-    Entry<AttributeType, Set<String>> ad = adDecodeMap.get(id);
+    // Before returning the attribute, make sure that the attribute type is not stale.
+    final Mappings mappings = reloadMappingsIfSchemaChanged(false);
+    final Entry<AttributeType, Set<String>> ad = mappings.adDecodeMap.get(id);
     if (ad == null)
     {
       throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
           ERR_COMPRESSEDSCHEMA_UNRECOGNIZED_AD_TOKEN.get(id));
     }
 
-    // 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.readBERLength();
 
-    // For the common case of a single value with no options, generate
-    // less garbage.
+    // For the common case of a single value with no options, generate less garbage.
     if (numValues == 1 && options.isEmpty())
     {
-      final int valueLength = reader.readBERLength();
-      final ByteSequence valueBytes = reader.readByteSequence(valueLength);
-      return Attributes.create(attrType, valueBytes.toByteString());
+      return Attributes.create(attrType, readValue(reader));
     }
     else
     {
@@ -131,15 +239,16 @@
       builder.setOptions(options);
       for (int i = 0; i < numValues; i++)
       {
-        final int valueLength = reader.readBERLength();
-        final ByteSequence valueBytes = reader.readByteSequence(valueLength);
-        builder.add(valueBytes.toByteString());
+        builder.add(readValue(reader));
       }
       return builder.toAttribute();
     }
   }
 
-
+  private ByteString readValue(final ByteSequenceReader reader)
+  {
+    return reader.readByteSequence(reader.readBERLength()).toByteString();
+  }
 
   /**
    * Decodes an object class set from the provided byte string.
@@ -155,35 +264,44 @@
       final ByteSequenceReader reader) throws DirectoryException
   {
     // First decode the encoded object class id.
-    final int length = reader.readBERLength();
-    final byte[] idBytes = new byte[length];
-    reader.readBytes(idBytes);
-    final int id = decodeId(idBytes);
+    final int id = decodeId(reader);
 
     // Look up the object classes.
-    final Map<ObjectClass, String> ocMap = ocDecodeMap.get(id);
-    if (ocMap != null)
+    final Mappings mappings = getMappings();
+    Map<ObjectClass, String> ocMap = mappings.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
-    {
+      // @RemoveOnceSDKSchemaIsUsed remove this first check (check is performed again later)
       throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
           ERR_COMPRESSEDSCHEMA_UNKNOWN_OC_TOKEN.get(id));
     }
+    // Before returning the object classes, make sure that none of them are stale.
+    boolean forceReload = isAnyObjectClassDirty(ocMap.keySet());
+    final Mappings newMappings = reloadMappingsIfSchemaChanged(forceReload);
+    if (mappings != newMappings)
+    {
+      ocMap = newMappings.ocDecodeMap.get(id);
+      if (ocMap == null)
+      {
+        throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
+            ERR_COMPRESSEDSCHEMA_UNKNOWN_OC_TOKEN.get(id));
+      }
+    }
+    return ocMap;
   }
 
-
+  @RemoveOnceSDKSchemaIsUsed
+  private boolean isAnyObjectClassDirty(Set<ObjectClass> objectClasses)
+  {
+    for (final ObjectClass oc : objectClasses)
+    {
+      if (oc.isDirty())
+      {
+        return true;
+      }
+    }
+    return false;
+  }
 
   /**
    * Encodes the information in the provided attribute to a byte array.
@@ -202,24 +320,7 @@
     // 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<>(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);
-        }
-      }
-    }
+    int id = getAttributeId(new SimpleImmutableEntry<>(type, options));
 
     // Encode the attribute.
     final byte[] idBytes = encodeId(id);
@@ -233,7 +334,38 @@
     }
   }
 
+  private int getAttributeId(final Entry<AttributeType, Set<String>> ad) throws DirectoryException
+  {
+    // avoid lazy registration races
+    boolean shared = true;
+    sharedLock.lock();
+    try
+    {
+      Integer id = mappings.adEncodeMap.get(ad);
+      if (id != null)
+      {
+        return id;
+      }
 
+      sharedLock.unlock();
+      exclusiveLock.lock();
+      shared = false;
+
+      id = mappings.adEncodeMap.get(ad);
+      if (id == null)
+      {
+        id = mappings.adDecodeMap.size();
+        mappings.adDecodeMap.add(ad);
+        mappings.adEncodeMap.put(ad, id);
+        storeAttribute(encodeId(id), ad.getKey().getNameOrOID(), ad.getValue());
+      }
+      return id;
+    }
+    finally
+    {
+      (shared ? sharedLock : exclusiveLock).unlock();
+    }
+  }
 
   /**
    * Encodes the provided set of object classes to a byte array. If the same set
@@ -253,22 +385,7 @@
       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());
-        }
-      }
-    }
+    int id = getObjectClassId(objectClasses);
 
     // Encode the object classes.
     final byte[] idBytes = encodeId(id);
@@ -276,31 +393,58 @@
     builder.appendBytes(idBytes);
   }
 
+  private int getObjectClassId(final Map<ObjectClass, String> objectClasses) throws DirectoryException
+  {
+    // avoid lazy registration races
+    boolean shared = true;
+    sharedLock.lock();
+    try
+    {
+      Integer id = mappings.ocEncodeMap.get(objectClasses);
+      if (id != null)
+      {
+        return id;
+      }
 
+      sharedLock.unlock();
+      exclusiveLock.lock();
+      shared = false;
+
+      id = mappings.ocEncodeMap.get(objectClasses);
+      if (id == null)
+      {
+        id = mappings.ocDecodeMap.size();
+        mappings.ocDecodeMap.add(objectClasses);
+        mappings.ocEncodeMap.put(objectClasses, id);
+        storeObjectClasses(encodeId(id), objectClasses.values());
+      }
+      return id;
+    }
+    finally
+    {
+      (shared ? sharedLock : exclusiveLock).unlock();
+    }
+  }
 
   /**
-   * 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.
+   * Returns a view of the encoded attributes in this compressed schema which can be used for saving
+   * the entire content to disk.
+   * <p>
+   * 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()
+  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()
+      public Iterator<Entry<byte[], Entry<String, Collection<String>>>> iterator()
       {
         return new Iterator<Entry<byte[], Entry<String, Collection<String>>>>()
         {
-          private int id = 0;
-
-
+          private int id;
+          private List<Entry<AttributeType, Set<String>>> adDecodeMap = getMappings().adDecodeMap;
 
           @Override
           public boolean hasNext()
@@ -308,8 +452,6 @@
             return id < adDecodeMap.size();
           }
 
-
-
           @Override
           public Entry<byte[], Entry<String, Collection<String>>> next()
           {
@@ -322,8 +464,6 @@
                     .getKey().getNameOrOID(), ad.getValue()));
           }
 
-
-
           @Override
           public void remove()
           {
@@ -334,27 +474,25 @@
     };
   }
 
-
-
   /**
-   * 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.
+   * Returns a view of the encoded object classes in this compressed schema which can be used for
+   * saving the entire content to disk.
+   * <p>
+   * 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()
+  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;
+          private int id;
+          private final List<Map<ObjectClass, String>> ocDecodeMap = getMappings().ocDecodeMap;
 
           @Override
           public boolean hasNext()
@@ -397,28 +535,52 @@
       final byte[] encodedAttribute, final String attributeName,
       final Collection<String> attributeOptions)
   {
-    final AttributeType type = DirectoryServer.getAttributeTypeOrDefault(toLowerCase(attributeName));
+    final int id = decodeId(encodedAttribute);
+    return loadAttributeToMaps(id, attributeName, attributeOptions, getMappings());
+  }
+
+  /**
+   * Loads an attribute into provided encode and decode maps, given its id, name, and options.
+   *
+   * @param id
+   *          the id computed on the attribute.
+   * @param attributeName
+   *          The user provided attribute type name.
+   * @param attributeOptions
+   *          The non-null but possibly empty set of attribute options.
+   * @param mappings
+   *          attribute description encodeMap and decodeMap maps id to entry
+   * @return The attribute type description.
+   */
+  private Entry<AttributeType, Set<String>> loadAttributeToMaps(final int id, final String attributeName,
+      final Collection<String> attributeOptions, final Mappings mappings)
+  {
+    final AttributeType type = DirectoryServer.getAttributeTypeOrDefault(attributeName);
     final Set<String> options = getOptions(attributeOptions);
     final Entry<AttributeType, Set<String>> ad = new SimpleImmutableEntry<>(type, options);
-    final int id = decodeId(encodedAttribute);
-    synchronized (adEncodeMap)
+    exclusiveLock.lock();
+    try
     {
-      adEncodeMap.put(ad, id);
-      if (id < adDecodeMap.size())
+      mappings.adEncodeMap.put(ad, id);
+      if (id < mappings.adDecodeMap.size())
       {
-        adDecodeMap.set(id, ad);
+        mappings.adDecodeMap.set(id, ad);
       }
       else
       {
         // Grow the decode array.
-        while (id > adDecodeMap.size())
+        while (id > mappings.adDecodeMap.size())
         {
-          adDecodeMap.add(null);
+          mappings.adDecodeMap.add(null);
         }
-        adDecodeMap.add(ad);
+        mappings.adDecodeMap.add(ad);
       }
+      return ad;
     }
-    return ad;
+    finally
+    {
+      exclusiveLock.unlock();
+    }
   }
 
   private Set<String> getOptions(final Collection<String> attributeOptions)
@@ -449,6 +611,29 @@
       final byte[] encodedObjectClasses,
       final Collection<String> objectClassNames)
   {
+    final int id = decodeId(encodedObjectClasses);
+    return loadObjectClassesToMaps(id, objectClassNames, mappings, true);
+  }
+
+  /**
+   * Loads a set of object classes into provided encode and decode maps, given the id and set of
+   * names.
+   *
+   * @param id
+   *          the id computed on the object classes set.
+   * @param objectClassNames
+   *          The user provided set of object class names.
+   * @param mappings
+   *          .ocEncodeMap maps id to entry
+   * @param mappings
+   *          .ocDecodeMap maps entry to id
+   * @param sync
+   *          indicates if update of maps should be synchronized
+   * @return The object class set.
+   */
+  private final Map<ObjectClass, String> loadObjectClassesToMaps(int id, final Collection<String> objectClassNames,
+      Mappings mappings, boolean sync)
+  {
     final LinkedHashMap<ObjectClass, String> ocMap = new LinkedHashMap<>(objectClassNames.size());
     for (final String name : objectClassNames)
     {
@@ -456,28 +641,42 @@
       final ObjectClass oc = DirectoryServer.getObjectClass(lowerName, true);
       ocMap.put(oc, name);
     }
-    final int id = decodeId(encodedObjectClasses);
-    synchronized (ocEncodeMap)
+    if (sync)
     {
-      ocEncodeMap.put(ocMap, id);
-      if (id < ocDecodeMap.size())
+      exclusiveLock.lock();
+      try
       {
-        ocDecodeMap.set(id, ocMap);
+        updateObjectClassesMaps(id, mappings, ocMap);
       }
-      else
+      finally
       {
-        // Grow the decode array.
-        while (id > ocDecodeMap.size())
-        {
-          ocDecodeMap.add(null);
-        }
-        ocDecodeMap.add(ocMap);
+        exclusiveLock.unlock();
       }
     }
+    else
+    {
+      updateObjectClassesMaps(id, mappings, ocMap);
+    }
     return ocMap;
   }
 
-
+  private void updateObjectClassesMaps(int id, Mappings mappings, LinkedHashMap<ObjectClass, String> ocMap)
+  {
+    mappings.ocEncodeMap.put(ocMap, id);
+    if (id < mappings.ocDecodeMap.size())
+    {
+      mappings.ocDecodeMap.set(id, ocMap);
+    }
+    else
+    {
+      // Grow the decode array.
+      while (id > mappings.ocDecodeMap.size())
+      {
+        mappings.ocDecodeMap.add(null);
+      }
+      mappings.ocDecodeMap.add(ocMap);
+    }
+  }
 
   /**
    * Persists the provided encoded attribute. The default implementation is to
@@ -502,8 +701,6 @@
     // 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
@@ -524,8 +721,6 @@
     // Do nothing by default.
   }
 
-
-
   /**
    * Decodes the provided encoded schema element ID.
    *
@@ -544,7 +739,13 @@
     return id - 1; // Subtract 1 to compensate for old behavior.
   }
 
-
+  private int decodeId(final ByteSequenceReader reader)
+  {
+    final int length = reader.readBERLength();
+    final byte[] idBytes = new byte[length];
+    reader.readBytes(idBytes);
+    return decodeId(idBytes);
+  }
 
   /**
    * Encodes the provided schema element ID.

--
Gitblit v1.10.0