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