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

Matthew Swift
25.41.2013 e038f697b622bbe1b55fe661f1fc47bd48cf6477
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;
  }
}