opends/src/server/org/opends/server/backends/jeb/ID2Entry.java
@@ -41,6 +41,7 @@ import com.sleepycat.je.OperationStatus; import com.sleepycat.je.Transaction; import org.opends.server.types.DirectoryException; import org.opends.server.types.Entry; /** @@ -140,8 +141,12 @@ * * @param entry The LDAP entry to be converted. * @return The database entry. * * @throws DirectoryException If a problem occurs while attempting to encode * the entry. */ private DatabaseEntry entryData(Entry entry) throws DirectoryException { byte[] entryBytes; entryBytes = JebFormat.entryToDatabase(entry, dataConfig); @@ -157,9 +162,11 @@ * @return true if the entry was inserted, false if a record with that * ID already existed. * @throws DatabaseException If an error occurs in the JE database. * @throws DirectoryException If a problem occurs while attempting to encode * the entry. */ public boolean insert(Transaction txn, EntryID id, Entry entry) throws DatabaseException throws DatabaseException, DirectoryException { DatabaseEntry key = id.getDatabaseEntry(); DatabaseEntry data = entryData(entry); @@ -181,9 +188,11 @@ * @param entry The LDAP entry. * @return true if the entry was written, false if it was not. * @throws DatabaseException If an error occurs in the JE database. * @throws DirectoryException If a problem occurs while attempting to encode * the entry. */ public boolean put(Transaction txn, EntryID id, Entry entry) throws DatabaseException throws DatabaseException, DirectoryException { DatabaseEntry key = id.getDatabaseEntry(); DatabaseEntry data = entryData(entry); opends/src/server/org/opends/server/backends/jeb/ImportThread.java
@@ -31,6 +31,7 @@ import org.opends.server.types.DebugLogLevel; import org.opends.server.api.DirectoryThread; import org.opends.server.types.AttributeType; import org.opends.server.types.DirectoryException; import org.opends.server.types.Entry; import com.sleepycat.je.Transaction; @@ -323,6 +324,13 @@ TRACER.debugCaught(DebugLogLevel.ERROR, e); } } catch (DirectoryException e) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); } } catch (IOException e) { if (debugEnabled()) opends/src/server/org/opends/server/backends/jeb/JebFormat.java
@@ -36,6 +36,7 @@ import org.opends.server.types.CryptoManager; import org.opends.server.types.DirectoryException; import org.opends.server.types.Entry; import org.opends.server.types.EntryEncodeConfig; import org.opends.server.types.LDAPException; import static org.opends.server.loggers.debug.DebugLogger.*; @@ -72,6 +73,11 @@ public static final byte TAG_DIRECTORY_SERVER_ENTRY = 0x61; /** * The configuration to use when encoding entries in the database. */ private static EntryEncodeConfig encodeConfig = new EntryEncodeConfig(); /** * Decode a DatabaseEntry. The encoded bytes may be compressed and/or * encrypted. * @@ -236,8 +242,12 @@ * @param entry The entry to encode. * @param dataConfig Compression and cryptographic options. * @return A byte array containing the encoded database value. * * @throws DirectoryException If a problem occurs while attempting to encode * the entry. */ static public byte[] entryToDatabase(Entry entry, DataConfig dataConfig) throws DirectoryException { byte[] uncompressedBytes = encodeDirectoryServerEntry(entry); return encodeDatabaseEntry(uncompressedBytes, dataConfig); @@ -248,8 +258,12 @@ * * @param entry The entry to encode. * @return A byte array containing the encoded database value. * * @throws DirectoryException If a problem occurs while attempting to encode * the entry. */ static public byte[] entryToDatabase(Entry entry) throws DirectoryException { return entryToDatabase(entry, new DataConfig()); } @@ -259,10 +273,14 @@ * * @param entry The entry to encode. * @return A byte array containing the encoded DirectoryServerEntry. * * @throws DirectoryException If a problem occurs while attempting to encode * the entry. */ static private byte[] encodeDirectoryServerEntry(Entry entry) throws DirectoryException { return entry.encode(); return entry.encode(encodeConfig); } /** opends/src/server/org/opends/server/config/ConfigConstants.java
@@ -2674,6 +2674,15 @@ /** * The base name (with no path information) of the file that will be used to * hold schema tokens used for compressed schema elements. */ public static final String COMPRESSED_SCHEMA_FILE_NAME = "schematokens.dat"; /** * The base name (with no path information) of the directory that will hold * the archived versions of previous configurations. */ opends/src/server/org/opends/server/messages/CoreMessages.java
@@ -6171,6 +6171,47 @@ /** * The message ID for the message that will be used if an attribute used an * undefined token. This takes a single argument, which is the hex * representation of the token bytes. */ public static final int MSGID_COMPRESSEDSCHEMA_UNRECOGNIZED_AD_TOKEN = CATEGORY_MASK_CORE | SEVERITY_MASK_SEVERE_ERROR | 620; /** * The message ID for the message that will be used if an object class set * used an undefined token. This takes a single argument, which is the hex * representation of the token bytes. */ public static final int MSGID_COMPRESSEDSCHEMA_UNKNOWN_OC_TOKEN = CATEGORY_MASK_CORE | SEVERITY_MASK_SEVERE_ERROR | 621; /** * The message ID for the message that will be used if an error occurs while * trying to write the updated compressed schema definitions. This takes a * single argument, which is a string representation of the exception that was * caught. */ public static final int MSGID_COMPRESSEDSCHEMA_CANNOT_WRITE_UPDATED_DATA = CATEGORY_MASK_CORE | SEVERITY_MASK_SEVERE_ERROR | 622; /** * The message ID for the message that will be used if an error occurs while * trying to decode an entry encode config object because it had an invalid * length. This does not take any arguments. */ public static final int MSGID_ENTRYENCODECFG_INVALID_LENGTH = CATEGORY_MASK_CORE | SEVERITY_MASK_SEVERE_ERROR | 623; /** * Associates a set of generic messages with the message IDs defined * in this class. */ @@ -8401,6 +8442,22 @@ registerMessage(MSGID_DIRECTORY_SERVER_LEAVING_LOCKDOWN_MODE, "The Directory Server is leaving lockdown mode and will " + "resume normal operation"); registerMessage(MSGID_COMPRESSEDSCHEMA_UNRECOGNIZED_AD_TOKEN, "Unable to decode the provided attribute because it " + "used an undefined attribute description token %s"); registerMessage(MSGID_COMPRESSEDSCHEMA_UNKNOWN_OC_TOKEN, "Unable to decode the provided object class set because " + "it used an undefined token %s"); registerMessage(MSGID_COMPRESSEDSCHEMA_CANNOT_WRITE_UPDATED_DATA, "Unable to write the updated compressed schema token " + "data: %s"); registerMessage(MSGID_ENTRYENCODECFG_INVALID_LENGTH, "Unable to decode the provided entry encode " + "configuration element because it has an invalid length"); } } opends/src/server/org/opends/server/types/ByteArray.java
New file @@ -0,0 +1,127 @@ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at * trunk/opends/resource/legal-notices/OpenDS.LICENSE * or https://OpenDS.dev.java.net/OpenDS.LICENSE. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, * add the following below this CDDL HEADER, with the fields enclosed * by brackets "[]" replaced with your own identifying information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Portions Copyright 2007 Sun Microsystems, Inc. */ package org.opends.server.types; import java.util.Arrays; /** * This class provides a data structure that holds a byte array but * also includes the necessary {@code equals} and {@code hashCode} * methods to make it suitable for use in maps. */ public class ByteArray { // The array that will be wrapped by this object. private final byte[] array; /** * Creates a new {@code ByteArray} object that wraps the provided * array. * * @param array The array to be wrapped with this * {@code ByteArray}. */ public ByteArray(byte[] array) { this.array = array; } /** * Retrieves the array wrapped by this {@code ByteArray} object. * * @return The array wrapped by this {@code ByteArray} object. */ public byte[] array() { return array; } /** * Retrieves a hash code for this {@code ByteArray}. It will be the * sum of all of the bytes contained in the wrapped array. * * @return A hash code for this {@code ByteArray}. */ public int hashCode() { int hashCode = 0; for (int i=0; i < array.length; i++) { hashCode += array[i]; } return hashCode; } /** * Indicates whether the provided object is equal to this * {@code ByteArray}. In order for it to be considered equal, the * provided object must be a non-null {@code ByteArray} object with * a wrapped array containing the same bytes in the same order. * * @param o The object for which to make the determination. * * @return {@code true} if the provided object is a * {@code ByteArray} whose content is equal to that of this * {@code ByteArray}, or {@code false} if not. */ public boolean equals(Object o) { if (o == this) { return true; } if (o == null) { return false; } if (o instanceof ByteArray) { ByteArray ba = (ByteArray) o; return Arrays.equals(array, ba.array); } else { return false; } } } opends/src/server/org/opends/server/types/CompressedSchema.java
New file @@ -0,0 +1,758 @@ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at * trunk/opends/resource/legal-notices/OpenDS.LICENSE * or https://OpenDS.dev.java.net/OpenDS.LICENSE. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, * add the following below this CDDL HEADER, with the fields enclosed * by brackets "[]" replaced with your own identifying information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Portions Copyright 2007 Sun Microsystems, Inc. */ package org.opends.server.types; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; import org.opends.server.core.DirectoryServer; import org.opends.server.loggers.debug.DebugTracer; import org.opends.server.protocols.asn1.ASN1Element; import org.opends.server.protocols.asn1.ASN1Integer; import org.opends.server.protocols.asn1.ASN1OctetString; import org.opends.server.protocols.asn1.ASN1Reader; import org.opends.server.protocols.asn1.ASN1Sequence; import org.opends.server.protocols.asn1.ASN1Writer; import static org.opends.server.config.ConfigConstants.*; import static org.opends.server.loggers.debug.DebugLogger.*; import static org.opends.server.messages.CoreMessages.*; import static org.opends.server.messages.MessageHandler.*; import static org.opends.server.util.StaticUtils.*; /** * This class provides a utility for interacting with compressed * representations of schema elements. */ public class CompressedSchema { /** * The tracer object for the debug logger. */ private static final DebugTracer TRACER = getTracer(); /** * The singleton instance that will be used to perform the mapping. */ private static CompressedSchema instance = new CompressedSchema(); // 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<ByteArray,AttributeType> atDecodeMap; // The map between encoded representations and attribute options. private ConcurrentHashMap<ByteArray, LinkedHashSet<String>> aoDecodeMap; // The map between encoded representations and object class sets. private ConcurrentHashMap<ByteArray, Map<ObjectClass,String>> ocDecodeMap; // The map between attribute descriptions and their encoded // representations. private ConcurrentHashMap<AttributeType, ConcurrentHashMap<LinkedHashSet<String>,ByteArray>> adEncodeMap; // The map between object class sets and encoded representations. private ConcurrentHashMap<Map<ObjectClass,String>, ByteArray> ocEncodeMap; /** * Creates a new instance of this compressed schema manager. */ private CompressedSchema() { atDecodeMap = new ConcurrentHashMap<ByteArray,AttributeType>(); aoDecodeMap = new ConcurrentHashMap<ByteArray, LinkedHashSet<String>>(); ocDecodeMap = new ConcurrentHashMap<ByteArray, Map<ObjectClass,String>>(); adEncodeMap = new ConcurrentHashMap<AttributeType, ConcurrentHashMap<LinkedHashSet<String>, ByteArray>>(); ocEncodeMap = new ConcurrentHashMap<Map<ObjectClass,String>, ByteArray>(); adCounter = new AtomicInteger(1); ocCounter = new AtomicInteger(1); load(); } /** * Loads the compressed schema information from disk. */ private void load() { ASN1Reader reader = null; 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 file doesn't exist, then don't // do anything. String path = DirectoryServer.getServerRoot() + File.separator + CONFIG_DIR_NAME + File.separator + COMPRESSED_SCHEMA_FILE_NAME; if (! new File(path).exists()) { return; } FileInputStream inputStream = new FileInputStream(path); reader = new ASN1Reader(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 strings, where the first one is the token and the // remaining elements are the names of the associated object // classes. ASN1Sequence ocSequence = reader.readElement().decodeAsSequence(); for (ASN1Element element : ocSequence.elements()) { ArrayList<ASN1Element> elements = element.decodeAsSequence().elements(); ASN1OctetString os = elements.get(0).decodeAsOctetString(); ByteArray token = new ByteArray(os.value()); LinkedHashMap<ObjectClass,String> ocMap = new LinkedHashMap<ObjectClass,String>(elements.size()-1); for (int i=1; i < elements.size(); i++) { os = elements.get(i).decodeAsOctetString(); String ocName = os.stringValue(); String lowerName = toLowerCase(ocName); ObjectClass oc = DirectoryServer.getObjectClass(lowerName, true); ocMap.put(oc, ocName); } ocEncodeMap.put(ocMap, token); ocDecodeMap.put(token, ocMap); } // The second element in the file should be an integer element // that holds the value to use to initialize the object class // counter. ASN1Element counterElement = reader.readElement(); ocCounter.set(counterElement.decodeAsInteger().intValue()); // 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. ASN1Sequence adSequence = reader.readElement().decodeAsSequence(); for (ASN1Element element : adSequence.elements()) { ArrayList<ASN1Element> elements = element.decodeAsSequence().elements(); ASN1OctetString os = elements. get(0).decodeAsOctetString(); ByteArray token = new ByteArray(os.value()); os = elements.get(1).decodeAsOctetString(); String attrName = os.stringValue(); String lowerName = toLowerCase(attrName); AttributeType attrType = DirectoryServer.getAttributeType(lowerName, true); LinkedHashSet<String> options = new LinkedHashSet<String>(elements.size()-2); for (int i=2; i < elements.size(); i++) { os = elements.get(i).decodeAsOctetString(); options.add(os.stringValue()); } atDecodeMap.put(token, attrType); aoDecodeMap.put(token, options); ConcurrentHashMap<LinkedHashSet<String>,ByteArray> map = adEncodeMap.get(attrType); if (map == null) { map = new ConcurrentHashMap<LinkedHashSet<String>, ByteArray>(1); map.put(options, token); adEncodeMap.put(attrType, map); } else { map.put(options, token); } } // The fourth element in the file should be an integer element // that holds the value to use to initialize the attribute // description counter. counterElement = reader.readElement(); adCounter.set(counterElement.decodeAsInteger().intValue()); } catch (Exception e) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); } // FIXME -- Should we do something else here? throw new RuntimeException(e); } finally { try { if (reader != null) { reader.close(); } } catch (Exception e) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); } } } } /** * Writes the compressed schema information to disk. * * @throws DirectoryException If a problem occurs while writing * the updated information. */ private void save() throws DirectoryException { ASN1Writer writer = null; try { // 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.getServerRoot() + File.separator + CONFIG_DIR_NAME + File.separator + COMPRESSED_SCHEMA_FILE_NAME; String tempPath = path + ".tmp"; FileOutputStream outputStream = new FileOutputStream(tempPath); writer = new ASN1Writer(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. ArrayList<ASN1Element> ocElements = new ArrayList<ASN1Element>(ocDecodeMap.size()); for (Map.Entry<ByteArray,Map<ObjectClass,String>> mapEntry : ocDecodeMap.entrySet()) { ByteArray token = mapEntry.getKey(); Map<ObjectClass,String> ocMap = mapEntry.getValue(); ArrayList<ASN1Element> elements = new ArrayList<ASN1Element>(ocMap.size()+1); elements.add(new ASN1OctetString(token.array())); for (String ocName : ocMap.values()) { elements.add(new ASN1OctetString(ocName)); } ocElements.add(new ASN1Sequence(elements)); } writer.writeElement(new ASN1Sequence(ocElements)); // The second element in the file should be an integer element // that holds the value to use to initialize the object class // counter. writer.writeElement(new ASN1Integer(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. ArrayList<ASN1Element> adElements = new ArrayList<ASN1Element>(atDecodeMap.size()); for (ByteArray token : atDecodeMap.keySet()) { AttributeType attrType = atDecodeMap.get(token); LinkedHashSet<String> options = aoDecodeMap.get(token); ArrayList<ASN1Element> elements = new ArrayList<ASN1Element>(options.size()+2); elements.add(new ASN1OctetString(token.array())); elements.add(new ASN1OctetString(attrType.getNameOrOID())); for (String option : options) { elements.add(new ASN1OctetString(option)); } adElements.add(new ASN1Sequence(elements)); } writer.writeElement(new ASN1Sequence(adElements)); // The fourth element in the file should be an integer element // that holds the value to use to initialize the attribute // description counter. writer.writeElement(new ASN1Integer(adCounter.get())); // Close the writer and swing the temp file into place. writer.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); } int msgID = MSGID_COMPRESSEDSCHEMA_CANNOT_WRITE_UPDATED_DATA; String message = getMessage(msgID, stackTraceToSingleLineString(e)); throw new DirectoryException( DirectoryServer.getServerErrorResultCode(), message, msgID, e); } finally { try { if (writer != null) { writer.close(); } } catch (Exception e) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); } } } } /** * 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 objectClasses The set of object classes for which to * retrieve the corresponding byte array * token. * * @return A byte array containing the identifier assigned to the * object class set. * * @throws DirectoryException If a problem occurs while attempting * to determine the appropriate * identifier. */ public static byte[] encodeObjectClasses(Map<ObjectClass,String> objectClasses) throws DirectoryException { ByteArray encodedClasses = instance.ocEncodeMap.get(objectClasses); if (encodedClasses == null) { synchronized (instance.ocEncodeMap) { int setValue = instance.ocCounter.getAndIncrement(); byte[] array = encodeInt(setValue); encodedClasses = new ByteArray(array); instance.ocEncodeMap.put(objectClasses, encodedClasses); instance.ocDecodeMap.put(encodedClasses, objectClasses); instance.save(); return array; } } else { return encodedClasses.array(); } } /** * Decodes an object class set from the provided byte array. * * @param encodedObjectClasses The byte array 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. */ public static Map<ObjectClass,String> decodeObjectClasses(byte[] encodedObjectClasses) throws DirectoryException { ByteArray byteArray = new ByteArray(encodedObjectClasses); Map<ObjectClass,String> ocMap = instance.ocDecodeMap.get(byteArray); if (ocMap == null) { int msgID = MSGID_COMPRESSEDSCHEMA_UNKNOWN_OC_TOKEN; String message = getMessage(msgID, bytesToHex(encodedObjectClasses)); throw new DirectoryException( DirectoryServer.getServerErrorResultCode(), message, msgID); } else { return ocMap; } } /** * Encodes the information in the provided attribute to a byte * array. * * @param attribute The attribute to be encoded. * * @return An encoded representation of the provided attribute. * * @throws DirectoryException If a problem occurs while attempting * to determine the appropriate * identifier. */ public static byte[] encodeAttribute(Attribute attribute) throws DirectoryException { AttributeType type = attribute.getAttributeType(); LinkedHashSet<String> options = attribute.getOptions(); ConcurrentHashMap<LinkedHashSet<String>,ByteArray> map = instance.adEncodeMap.get(type); if (map == null) { byte[] array; synchronized (instance.adEncodeMap) { map = new ConcurrentHashMap<LinkedHashSet<String>, ByteArray>(1); int intValue = instance.adCounter.getAndIncrement(); array = encodeInt(intValue); ByteArray byteArray = new ByteArray(array); map.put(options,byteArray); instance.adEncodeMap.put(type, map); instance.atDecodeMap.put(byteArray, type); instance.aoDecodeMap.put(byteArray, options); instance.save(); } return encodeAttribute(array, attribute); } else { ByteArray byteArray = map.get(options); if (byteArray == null) { byte[] array; synchronized (map) { int intValue = instance.adCounter.getAndIncrement(); array = encodeInt(intValue); byteArray = new ByteArray(array); map.put(options,byteArray); instance.atDecodeMap.put(byteArray, type); instance.aoDecodeMap.put(byteArray, options); instance.save(); } return encodeAttribute(array, attribute); } else { return encodeAttribute(byteArray.array(), attribute); } } } /** * Encodes the information in the provided attribute to a byte * array. * * @param adArray The byte array that is a placeholder for the * attribute type and set of options. * @param attribute The attribute to be encoded. * * @return An encoded representation of the provided attribute. */ private static byte[] encodeAttribute(byte[] adArray, Attribute attribute) { LinkedHashSet<AttributeValue> values = attribute.getValues(); int totalValuesLength = 0; byte[][] subArrays = new byte[values.size()*2][]; int pos = 0; for (AttributeValue v : values) { byte[] vBytes = v.getValueBytes(); byte[] lBytes = ASN1Element.encodeLength(vBytes.length); subArrays[pos++] = lBytes; subArrays[pos++] = vBytes; totalValuesLength += lBytes.length + vBytes.length; } byte[] adArrayLength = ASN1Element.encodeLength(adArray.length); byte[] countBytes = ASN1Element.encodeLength(values.size()); int totalLength = adArrayLength.length + adArray.length + countBytes.length + totalValuesLength; byte[] array = new byte[totalLength]; System.arraycopy(adArrayLength, 0, array, 0, adArrayLength.length); pos = adArrayLength.length; System.arraycopy(adArray, 0, array, pos, adArray.length); pos += adArray.length; System.arraycopy(countBytes, 0, array, pos, countBytes.length); pos += countBytes.length; for (int i=0; i < subArrays.length; i++) { System.arraycopy(subArrays[i], 0, array, pos, subArrays[i].length); pos += subArrays[i].length; } return array; } /** * Decodes the contents of the provided array as an attribute. * * @param encodedEntry The byte array containing the encoded * entry. * @param startPos The position within the array of the first * byte for the attribute to decode. * @param length The number of bytes contained in the * encoded attribute. * * @return The decoded attribute. * * @throws DirectoryException If the attribute could not be * decoded properly for some reason. */ public static Attribute decodeAttribute(byte[] encodedEntry, int startPos, int length) throws DirectoryException { // Figure out how many bytes are in the token that is the // placeholder for the attribute description. int pos = startPos; int adArrayLength = encodedEntry[pos] & 0x7F; if (adArrayLength != encodedEntry[pos++]) { int numLengthBytes = adArrayLength; adArrayLength = 0; for (int i=0; i < numLengthBytes; i++, pos++) { adArrayLength = (adArrayLength << 8) | (encodedEntry[pos] & 0xFF); } } // Get the attribute description token and make sure it resolves // to an attribute type and option set. ByteArray adArray = new ByteArray(new byte[adArrayLength]); System.arraycopy(encodedEntry, pos, adArray.array(), 0, adArrayLength); pos += adArrayLength; AttributeType attrType = instance.atDecodeMap.get(adArray); LinkedHashSet<String> options = instance.aoDecodeMap.get(adArray); if ((attrType == null) || (options == null)) { int msgID = MSGID_COMPRESSEDSCHEMA_UNRECOGNIZED_AD_TOKEN; String message = getMessage(msgID, bytesToHex(adArray.array())); throw new DirectoryException( DirectoryServer.getServerErrorResultCode(), message, msgID); } // Determine the number of values for the attribute. int numValues = encodedEntry[pos] & 0x7F; if (numValues != encodedEntry[pos++]) { int numValuesBytes = numValues; numValues = 0; for (int i=0; i < numValuesBytes; i++, pos++) { numValues = (numValues << 8) | (encodedEntry[pos] & 0xFF); } } // Read the appropriate number of values. LinkedHashSet<AttributeValue> values = new LinkedHashSet<AttributeValue>(numValues); for (int i=0; i < numValues; i++) { int valueLength = encodedEntry[pos] & 0x7F; if (valueLength != encodedEntry[pos++]) { int valueLengthBytes = valueLength; valueLength = 0; for (int j=0; j < valueLengthBytes; j++, pos++) { valueLength = (valueLength << 8) | (encodedEntry[pos] & 0xFF); } } byte[] valueBytes = new byte[valueLength]; System.arraycopy(encodedEntry, pos, valueBytes, 0, valueLength); pos += valueLength; values.add(new AttributeValue(attrType, new ASN1OctetString(valueBytes))); } return new Attribute(attrType, attrType.getPrimaryName(), options, values); } /** * 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 static 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/types/Entry.java
@@ -3855,10 +3855,29 @@ * that if the way we store entries changes in the future we will * still be able to read entries encoded in an older format. * * @param config The configuration that may be used to control how * the entry is encoded. * * @return The entry encoded in a form that is suitable for * long-term persistent storage. * * @throws DirectoryException If a problem occurs while attempting * to encode the entry. */ public byte[] encode() public byte[] encode(EntryEncodeConfig config) throws DirectoryException { return encodeV2(config); } /** * Encodes this entry using the V1 encoding. * * @return The entry encoded in the V1 encoding. */ public byte[] encodeV1() { // The version number will be one byte. We'll add that later. @@ -4068,6 +4087,323 @@ /** * Encodes this entry using the V2 encoding. * * @param config The configuration that should be used to encode * the entry. * * @return The entry encoded in the V2 encoding. * * @throws DirectoryException If a problem occurs while attempting * to encode the entry. */ public byte[] encodeV2(EntryEncodeConfig config) throws DirectoryException { // The version number will be one byte. We'll add that later. // Get the encoded respresentation of the config. byte[] configBytes = config.encode(); byte[] configLength = ASN1Element.encodeLength(configBytes.length); int totalBytes = 1 + configBytes.length + configLength.length; // If we should include the DN, then it will be encoded as a // one-to-five byte length followed by the UTF-8 byte // representation. byte[] dnBytes = null; byte[] dnLength = null; if (! config.excludeDN()) { dnBytes = getBytes(dn.toString()); dnLength = ASN1Element.encodeLength(dnBytes.length); totalBytes += dnBytes.length + dnLength.length; } // Encode the object classes in the appropriate manner. byte[] ocLength; LinkedList<byte[]> ocBytes = new LinkedList<byte[]>(); if (config.compressObjectClassSets()) { byte[] b = CompressedSchema.encodeObjectClasses(objectClasses); ocBytes.add(b); ocLength = ASN1Element.encodeLength(b.length); totalBytes += ocLength.length + b.length; } else { int totalOCBytes = objectClasses.size() - 1; for (String ocName : objectClasses.values()) { byte[] b = getBytes(ocName); ocBytes.add(b); totalOCBytes += b.length; } ocLength = ASN1Element.encodeLength(totalOCBytes); totalBytes += totalOCBytes + ocLength.length; } // Encode the user attributes in the appropriate manner. int numUserAttributes = 0; int totalUserAttrBytes = 0; LinkedList<byte[]> userAttrBytes = new LinkedList<byte[]>(); if (config.compressAttributeDescriptions()) { for (List<Attribute> attrList : userAttributes.values()) { for (Attribute a : attrList) { if (a.isVirtual() || (! a.hasValue())) { continue; } numUserAttributes++; byte[] attrBytes = CompressedSchema.encodeAttribute(a); byte[] lengthBytes = ASN1Element.encodeLength(attrBytes.length); userAttrBytes.add(lengthBytes); userAttrBytes.add(attrBytes); totalUserAttrBytes += lengthBytes.length + attrBytes.length; } } } else { // The user attributes will be encoded as a one-to-five byte // number of attributes followed by a sequence of: // - A UTF-8 byte representation of the attribute name. // - A zero delimiter // - A one-to-five byte number of values for the attribute // - A sequence of: // - A one-to-five byte length for the value // - A UTF-8 byte representation for the value for (List<Attribute> attrList : userAttributes.values()) { for (Attribute a : attrList) { if (a.isVirtual() || (! a.hasValue())) { continue; } numUserAttributes++; byte[] nameBytes = getBytes(a.getNameWithOptions()); int numValues = 0; int totalValueBytes = 0; LinkedList<byte[]> valueBytes = new LinkedList<byte[]>(); for (AttributeValue v : a.getValues()) { numValues++; byte[] vBytes = v.getValueBytes(); byte[] vLength = ASN1Element.encodeLength(vBytes.length); valueBytes.add(vLength); valueBytes.add(vBytes); totalValueBytes += vLength.length + vBytes.length; } byte[] numValuesBytes = ASN1Element.encodeLength(numValues); byte[] attrBytes = new byte[nameBytes.length + numValuesBytes.length + totalValueBytes + 1]; System.arraycopy(nameBytes, 0, attrBytes, 0, nameBytes.length); int pos = nameBytes.length+1; System.arraycopy(numValuesBytes, 0, attrBytes, pos, numValuesBytes.length); pos += numValuesBytes.length; for (byte[] b : valueBytes) { System.arraycopy(b, 0, attrBytes, pos, b.length); pos += b.length; } userAttrBytes.add(attrBytes); totalUserAttrBytes += attrBytes.length; } } } byte[] userAttrCount = ASN1OctetString.encodeLength(numUserAttributes); totalBytes += totalUserAttrBytes + userAttrCount.length; // Encode the operational attributes in the appropriate manner. int numOperationalAttributes = 0; int totalOperationalAttrBytes = 0; LinkedList<byte[]> operationalAttrBytes = new LinkedList<byte[]>(); if (config.compressAttributeDescriptions()) { for (List<Attribute> attrList : operationalAttributes.values()) { for (Attribute a : attrList) { if (a.isVirtual() || (! a.hasValue())) { continue; } numOperationalAttributes++; byte[] attrBytes = CompressedSchema.encodeAttribute(a); byte[] lengthBytes = ASN1Element.encodeLength(attrBytes.length); operationalAttrBytes.add(lengthBytes); operationalAttrBytes.add(attrBytes); totalOperationalAttrBytes += lengthBytes.length + attrBytes.length; } } } else { // Encode the operational attributes in the same way as the user // attributes. for (List<Attribute> attrList : operationalAttributes.values()) { for (Attribute a : attrList) { if (a.isVirtual() || (! a.hasValue())) { continue; } numOperationalAttributes++; byte[] nameBytes = getBytes(a.getNameWithOptions()); int numValues = 0; int totalValueBytes = 0; LinkedList<byte[]> valueBytes = new LinkedList<byte[]>(); for (AttributeValue v : a.getValues()) { numValues++; byte[] vBytes = v.getValueBytes(); byte[] vLength = ASN1Element.encodeLength(vBytes.length); valueBytes.add(vLength); valueBytes.add(vBytes); totalValueBytes += vLength.length + vBytes.length; } byte[] numValuesBytes = ASN1Element.encodeLength(numValues); byte[] attrBytes = new byte[nameBytes.length + numValuesBytes.length + totalValueBytes + 1]; System.arraycopy(nameBytes, 0, attrBytes, 0, nameBytes.length); int pos = nameBytes.length+1; System.arraycopy(numValuesBytes, 0, attrBytes, pos, numValuesBytes.length); pos += numValuesBytes.length; for (byte[] b : valueBytes) { System.arraycopy(b, 0, attrBytes, pos, b.length); pos += b.length; } operationalAttrBytes.add(attrBytes); totalOperationalAttrBytes += attrBytes.length; } } } byte[] operationalAttrCount = ASN1OctetString.encodeLength(numOperationalAttributes); totalBytes += totalOperationalAttrBytes + operationalAttrCount.length; // Now we've got all the data that we need. Create a big byte // array to hold it all and pack it in. byte[] entryBytes = new byte[totalBytes]; // Add the entry version number as the first byte. entryBytes[0] = 0x02; // Next, add the encoded config. System.arraycopy(configLength, 0, entryBytes, 1, configLength.length); int pos = 1 + configLength.length; System.arraycopy(configBytes, 0, entryBytes, pos, configBytes.length); pos += configBytes.length; // Next, add the DN length and value. if (! config.excludeDN()) { System.arraycopy(dnLength, 0, entryBytes, pos, dnLength.length); pos += dnLength.length; System.arraycopy(dnBytes, 0, entryBytes, pos, dnBytes.length); pos += dnBytes.length; } // Next, add the object classes length and values. System.arraycopy(ocLength, 0, entryBytes, pos, ocLength.length); pos += ocLength.length; if (config.compressObjectClassSets()) { for (byte[] b : ocBytes) { System.arraycopy(b, 0, entryBytes, pos, b.length); pos += b.length; } } else { for (byte[] b : ocBytes) { System.arraycopy(b, 0, entryBytes, pos, b.length); pos += b.length + 1; } // We need to back up one because there's no zero-teriminator // after the last object class name. pos--; } // Next, add the user attribute count and the user attribute // data. System.arraycopy(userAttrCount, 0, entryBytes, pos, userAttrCount.length); pos += userAttrCount.length; for (byte[] b : userAttrBytes) { System.arraycopy(b, 0, entryBytes, pos, b.length); pos += b.length; } // Finally, add the operational attribute count and the // operational attribute data. System.arraycopy(operationalAttrCount, 0, entryBytes, pos, operationalAttrCount.length); pos += operationalAttrCount.length; for (byte[] b : operationalAttrBytes) { System.arraycopy(b, 0, entryBytes, pos, b.length); pos += b.length; } return entryBytes; } /** * Decodes the provided byte array as an entry. * * @param entryBytes The byte array containing the data to be @@ -4081,12 +4417,43 @@ public static Entry decode(byte[] entryBytes) throws DirectoryException { switch(entryBytes[0]) { case 0x01: return decodeV1(entryBytes); case 0x02: return decodeV2(entryBytes); default: int msgID = MSGID_ENTRY_DECODE_UNRECOGNIZED_VERSION; String message = getMessage(msgID, byteToHex(entryBytes[0])); throw new DirectoryException( DirectoryServer.getServerErrorResultCode(), message, msgID); } } /** * Decodes the provided byte array as an entry using the V1 * encoding. * * @param entryBytes The byte array containing the data to be * decoded. * * @return The decoded entry. * * @throws DirectoryException If the provided byte array cannot be * decoded as an entry. */ public static Entry decodeV1(byte[] entryBytes) throws DirectoryException { try { // The first byte must be the entry version. If it's not one // we recognize, then that's an error. // NOTE: If we ever change the encoding, then we'll need a // new method for each one. if (entryBytes[0] != 0x01) { int msgID = MSGID_ENTRY_DECODE_UNRECOGNIZED_VERSION; @@ -4436,6 +4803,490 @@ /** * Decodes the provided byte array as an entry using the V2 * encoding. * * @param entryBytes The byte array containing the data to be * decoded. * * @return The decoded entry. * * @throws DirectoryException If the provided byte array cannot be * decoded as an entry. */ public static Entry decodeV2(byte[] entryBytes) throws DirectoryException { try { // The first byte must be the entry version. If it's not one // we recognize, then that's an error. if (entryBytes[0] != 0x02) { int msgID = MSGID_ENTRY_DECODE_UNRECOGNIZED_VERSION; String message = getMessage(msgID, byteToHex(entryBytes[0])); throw new DirectoryException( DirectoryServer.getServerErrorResultCode(), message, msgID); } // Next is the length of the encoded configuration. It may be a // single byte or multiple bytes. int pos = 1; int configLength = entryBytes[pos] & 0x7F; if (entryBytes[pos++] != configLength) { int numLengthBytes = configLength; configLength = 0; for (int i=0; i < numLengthBytes; i++, pos++) { configLength = (configLength << 8) | (entryBytes[pos] & 0xFF); } } // Next is the encoded configuration itself. EntryEncodeConfig config = EntryEncodeConfig.decode(entryBytes, pos, configLength); pos += configLength; // If we should have included the DN in the entry, then it's // next. DN dn; if (config.excludeDN()) { dn = DN.NULL_DN; } else { // Next is the length of the DN. It may be a single byte or // multiple bytes. int dnLength = entryBytes[pos] & 0x7F; if (entryBytes[pos++] != dnLength) { int numLengthBytes = dnLength; dnLength = 0; for (int i=0; i < numLengthBytes; i++, pos++) { dnLength = (dnLength << 8) | (entryBytes[pos] & 0xFF); } } // Next is the DN itself. byte[] dnBytes = new byte[dnLength]; System.arraycopy(entryBytes, pos, dnBytes, 0, dnLength); pos += dnLength; dn = DN.decode(new ASN1OctetString(dnBytes)); } // Next is the length of the object classes. It may be a single // byte or multiple bytes. int ocLength = entryBytes[pos] & 0x7F; if (entryBytes[pos++] != ocLength) { int numLengthBytes = ocLength; ocLength = 0; for (int i=0; i < numLengthBytes; i++, pos++) { ocLength = (ocLength << 8) | (entryBytes[pos] & 0xFF); } } // Next is the set of encoded object classes. The encoding will // depend on the configuration. Map<ObjectClass,String> objectClasses; if (config.compressObjectClassSets()) { byte[] ocBytes = new byte[ocLength]; System.arraycopy(entryBytes, pos, ocBytes, 0, ocLength); objectClasses = CompressedSchema.decodeObjectClasses(ocBytes); pos += ocLength; } else { // The set of object classes will be encoded as a single // string with the oibject class names separated by zeros. objectClasses = new LinkedHashMap<ObjectClass,String>(); int startPos = pos; for (int i=0; i < ocLength; i++,pos++) { if (entryBytes[pos] == 0x00) { String name = new String(entryBytes, startPos, pos-startPos, "UTF-8"); String lowerName = toLowerCase(name); ObjectClass oc = DirectoryServer.getObjectClass(lowerName, true); objectClasses.put(oc, name); startPos = pos+1; } } String name = new String(entryBytes, startPos, pos-startPos, "UTF-8"); String lowerName = toLowerCase(name); ObjectClass oc = DirectoryServer.getObjectClass(lowerName, true); objectClasses.put(oc, name); } // Next is the total number of user attributes. It may be a // single byte or multiple bytes. int numUserAttrs = entryBytes[pos] & 0x7F; if (entryBytes[pos++] != numUserAttrs) { int numLengthBytes = numUserAttrs; numUserAttrs = 0; for (int i=0; i < numLengthBytes; i++, pos++) { numUserAttrs = (numUserAttrs << 8) | (entryBytes[pos] & 0xFF); } } // Now, we should iterate through the user attributes and decode // each one. LinkedHashMap<AttributeType,List<Attribute>> userAttributes = new LinkedHashMap<AttributeType,List<Attribute>>(); if (config.compressAttributeDescriptions()) { for (int i=0; i < numUserAttrs; i++) { // Get the length of the attribute. int attrLength = entryBytes[pos] & 0x7F; if (entryBytes[pos++] != attrLength) { int attrLengthBytes = attrLength; attrLength = 0; for (int j=0; j < attrLengthBytes; j++, pos++) { attrLength = (attrLength << 8) | (entryBytes[pos] & 0xFF); } } // Decode the attribute. Attribute a = CompressedSchema.decodeAttribute(entryBytes, pos, attrLength); List<Attribute> attrList = userAttributes.get(a.getAttributeType()); if (attrList == null) { attrList = new ArrayList<Attribute>(1); userAttributes.put(a.getAttributeType(), attrList); } attrList.add(a); pos += attrLength; } } else { for (int i=0; i < numUserAttrs; i++) { // First, we have the zero-terminated attribute name. int startPos = pos; while (entryBytes[pos] != 0x00) { pos++; } String lowerName; String name = new String(entryBytes, startPos, pos-startPos, "UTF-8"); LinkedHashSet<String> options; int semicolonPos = name.indexOf(';'); if (semicolonPos > 0) { String baseName = name.substring(0, semicolonPos); lowerName = toLowerCase(baseName); options = new LinkedHashSet<String>(); int nextPos = name.indexOf(';', semicolonPos+1); while (nextPos > 0) { String option = name.substring(semicolonPos+1, nextPos); if (option.length() > 0) { options.add(option); } semicolonPos = nextPos; nextPos = name.indexOf(';', semicolonPos+1); } String option = name.substring(semicolonPos+1); if (option.length() > 0) { options.add(option); } name = baseName; } else { lowerName = toLowerCase(name); options = new LinkedHashSet<String>(0); } AttributeType attributeType = DirectoryServer.getAttributeType(lowerName, true); // Next, we have the number of values. int numValues = entryBytes[++pos] & 0x7F; if (entryBytes[pos++] != numValues) { int numLengthBytes = numValues; numValues = 0; for (int j=0; j < numLengthBytes; j++, pos++) { numValues = (numValues << 8) | (entryBytes[pos] & 0xFF); } } // Next, we have the sequence of length-value pairs. LinkedHashSet<AttributeValue> values = new LinkedHashSet<AttributeValue>(numValues); for (int j=0; j < numValues; j++) { int valueLength = entryBytes[pos] & 0x7F; if (entryBytes[pos++] != valueLength) { int numLengthBytes = valueLength; valueLength = 0; for (int k=0; k < numLengthBytes; k++, pos++) { valueLength = (valueLength << 8) | (entryBytes[pos] & 0xFF); } } byte[] valueBytes = new byte[valueLength]; System.arraycopy(entryBytes, pos, valueBytes, 0, valueLength); values.add(new AttributeValue(attributeType, new ASN1OctetString(valueBytes))); pos += valueLength; } // Create the attribute and add it to the set of user // attributes. Attribute a = new Attribute(attributeType, name, options, values); List<Attribute> attrList = userAttributes.get(attributeType); if (attrList == null) { attrList = new ArrayList<Attribute>(1); attrList.add(a); userAttributes.put(attributeType, attrList); } else { attrList.add(a); } } } // Next is the total number of operational attributes. It may // be a single byte or multiple bytes. int numOperationalAttrs = entryBytes[pos] & 0x7F; if (entryBytes[pos++] != numOperationalAttrs) { int numLengthBytes = numOperationalAttrs; numOperationalAttrs = 0; for (int i=0; i < numLengthBytes; i++, pos++) { numOperationalAttrs = (numOperationalAttrs << 8) | (entryBytes[pos] & 0xFF); } } // Now, we should iterate through the operational attributes and // decode each one. LinkedHashMap<AttributeType,List<Attribute>> operationalAttributes = new LinkedHashMap<AttributeType,List<Attribute>>(); if (config.compressAttributeDescriptions()) { for (int i=0; i < numOperationalAttrs; i++) { // Get the length of the attribute. int attrLength = entryBytes[pos] & 0x7F; if (entryBytes[pos++] != attrLength) { int attrLengthBytes = attrLength; attrLength = 0; for (int j=0; j < attrLengthBytes; j++, pos++) { attrLength = (attrLength << 8) | (entryBytes[pos] & 0xFF); } } // Decode the attribute. Attribute a = CompressedSchema.decodeAttribute(entryBytes, pos, attrLength); List<Attribute> attrList = operationalAttributes.get(a.getAttributeType()); if (attrList == null) { attrList = new ArrayList<Attribute>(1); operationalAttributes.put(a.getAttributeType(), attrList); } attrList.add(a); pos += attrLength; } } else { for (int i=0; i < numOperationalAttrs; i++) { // First, we have the zero-terminated attribute name. int startPos = pos; while (entryBytes[pos] != 0x00) { pos++; } String lowerName; String name = new String(entryBytes, startPos, pos-startPos, "UTF-8"); LinkedHashSet<String> options; int semicolonPos = name.indexOf(';'); if (semicolonPos > 0) { String baseName = name.substring(0, semicolonPos); lowerName = toLowerCase(baseName); options = new LinkedHashSet<String>(); int nextPos = name.indexOf(';', semicolonPos+1); while (nextPos > 0) { String option = name.substring(semicolonPos+1, nextPos); if (option.length() > 0) { options.add(option); } semicolonPos = nextPos; nextPos = name.indexOf(';', semicolonPos+1); } String option = name.substring(semicolonPos+1); if (option.length() > 0) { options.add(option); } name = baseName; } else { lowerName = toLowerCase(name); options = new LinkedHashSet<String>(0); } AttributeType attributeType = DirectoryServer.getAttributeType(lowerName, true); // Next, we have the number of values. int numValues = entryBytes[++pos] & 0x7F; if (entryBytes[pos++] != numValues) { int numLengthBytes = numValues; numValues = 0; for (int j=0; j < numLengthBytes; j++, pos++) { numValues = (numValues << 8) | (entryBytes[pos] & 0xFF); } } // Next, we have the sequence of length-value pairs. LinkedHashSet<AttributeValue> values = new LinkedHashSet<AttributeValue>(numValues); for (int j=0; j < numValues; j++) { int valueLength = entryBytes[pos] & 0x7F; if (entryBytes[pos++] != valueLength) { int numLengthBytes = valueLength; valueLength = 0; for (int k=0; k < numLengthBytes; k++, pos++) { valueLength = (valueLength << 8) | (entryBytes[pos] & 0xFF); } } byte[] valueBytes = new byte[valueLength]; System.arraycopy(entryBytes, pos, valueBytes, 0, valueLength); values.add(new AttributeValue(attributeType, new ASN1OctetString(valueBytes))); pos += valueLength; } // Create the attribute and add it to the set of operational // attributes. Attribute a = new Attribute(attributeType, name, options, values); List<Attribute> attrList = operationalAttributes.get(attributeType); if (attrList == null) { attrList = new ArrayList<Attribute>(1); attrList.add(a); operationalAttributes.put(attributeType, attrList); } else { attrList.add(a); } } } // We've got everything that we need, so create and return the // entry. return new Entry(dn, objectClasses, userAttributes, operationalAttributes); } catch (DirectoryException de) { throw de; } catch (Exception e) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); } int msgID = MSGID_ENTRY_DECODE_EXCEPTION; String message = getMessage(msgID, getExceptionMessage(e)); throw new DirectoryException( DirectoryServer.getServerErrorResultCode(), message, msgID, e); } } /** * Retrieves a list of the lines for this entry in LDIF form. Long * lines will not be wrapped automatically. * @@ -4825,6 +5676,121 @@ /** * Retrieves a hash code for this entry. * * @return The hash code for this entry. */ @Override() public int hashCode() { int hashCode = dn.hashCode(); for (ObjectClass oc : objectClasses.keySet()) { hashCode += oc.hashCode(); } for (List<Attribute> attrList : userAttributes.values()) { for (Attribute a : attrList) { hashCode += a.hashCode(); } } for (List<Attribute> attrList : operationalAttributes.values()) { for (Attribute a : attrList) { hashCode += a.hashCode(); } } return hashCode; } /** * Indicates whether the provided object is equal to this entry. In * order for the object to be considered equal, it must be an entry * with the same DN, set of object classes, and set of user and * operational attributes. * * @param o The object for which to make the determination. * * @return {@code true} if the provided object may be considered * equal to this entry, or {@code false} if not. */ @Override() public boolean equals(Object o) { if (this == o) { return true; } if (o == null) { return false; } if (! (o instanceof Entry)) { return false; } Entry e = (Entry) o; if (! dn.equals(e.dn)) { return false; } if (! objectClasses.keySet().equals(e.objectClasses.keySet())) { return false; } for (AttributeType at : userAttributes.keySet()) { List<Attribute> list1 = userAttributes.get(at); List<Attribute> list2 = e.userAttributes.get(at); if ((list2 == null) || (list1.size() != list2.size())) { return false; } for (Attribute a : list1) { if (! list2.contains(a)) { return false; } } } for (AttributeType at : operationalAttributes.keySet()) { List<Attribute> list1 = operationalAttributes.get(at); List<Attribute> list2 = e.operationalAttributes.get(at); if ((list2 == null) || (list1.size() != list2.size())) { return false; } for (Attribute a : list1) { if (! list2.contains(a)) { return false; } } } return true; } /** * Retrieves a string representation of this protocol element. * * @return A string representation of this protocol element. opends/src/server/org/opends/server/types/EntryEncodeConfig.java
New file @@ -0,0 +1,291 @@ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at * trunk/opends/resource/legal-notices/OpenDS.LICENSE * or https://OpenDS.dev.java.net/OpenDS.LICENSE. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, * add the following below this CDDL HEADER, with the fields enclosed * by brackets "[]" replaced with your own identifying information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Portions Copyright 2007 Sun Microsystems, Inc. */ package org.opends.server.types; import org.opends.server.core.DirectoryServer; import static org.opends.server.messages.CoreMessages.*; import static org.opends.server.messages.MessageHandler.*; /** * This class defines a data structure that contains configuration * information about how an entry should be encoded. */ public class EntryEncodeConfig { /** * The encode mask value that can be used to indicate that the * encoded entry should not contain a DN. */ private static final byte ENCODE_FLAG_EXCLUDE_DN = 0x01; /** * The encode mask value that can be used that the encoded * representation should compress the set of object classes. */ private static final byte ENCODE_FLAG_COMPRESS_OCS = 0x02; /** * The encode mask value that can be used that the encoded * representation should compress the set of attribute descriptions * to conserve space and improve performance. */ private static final byte ENCODE_FLAG_COMPRESS_ADS = 0x04; /** * A reference to an entry encode configuration with all the default * settings. */ public static final EntryEncodeConfig DEFAULT_CONFIG = new EntryEncodeConfig(); // Indicates whether to compress the attribute descriptions. private final boolean compressAttrDescriptions; // Indicates whether to compress the object class sets. private final boolean compressObjectClassSets; // Indicates whether to exclude the DN. private final boolean excludeDN; // The encoded representation of this encode configuration. private final byte[] encodedRepresentation; /** * Creates a new encoded entry configuration wtih the default * settings. */ public EntryEncodeConfig() { excludeDN = false; compressAttrDescriptions = false; compressObjectClassSets = false; encodedRepresentation = new byte[] { 0x00 }; } /** * Creates a new encoded entry configuration wtih the specified * settings. * * @param excludeDN Indicates whether to exclude * the DN from the encoded entry. * @param compressAttrDescriptions Indicates whether to compress * attribute descriptions. * @param compressObjectClassSets Indicates whether to compress * object class sets. */ public EntryEncodeConfig(boolean excludeDN, boolean compressAttrDescriptions, boolean compressObjectClassSets) { this.excludeDN = excludeDN; this.compressAttrDescriptions = compressAttrDescriptions; this.compressObjectClassSets = compressObjectClassSets; byte flagByte = 0x00; if (excludeDN) { flagByte |= ENCODE_FLAG_EXCLUDE_DN; } if (compressAttrDescriptions) { flagByte |= ENCODE_FLAG_COMPRESS_ADS; } if (compressObjectClassSets) { flagByte |= ENCODE_FLAG_COMPRESS_OCS; } encodedRepresentation = new byte[] { flagByte }; } /** * Indicates whether the encoded entry should exclude the DN. * * @return {@code true} if the encoded entry should exclude the DN, * or {@code false} if not. */ public boolean excludeDN() { return excludeDN; } /** * Indicates whether the encoded entry should use compressed * attribute descriptions. * * @return {@code true} if the encoded entry should use compressed * attribute descriptions, or {@code false} if not. */ public boolean compressAttributeDescriptions() { return compressAttrDescriptions; } /** * Indicates whether the encoded entry should use compressed object * class sets. * * @return {@code true} if the encoded entry should use compressed * object class sets, or {@code false} if not. */ public boolean compressObjectClassSets() { return compressObjectClassSets; } /** * Encodes this entry encode configuration into a byte array * suitable for inclusion in the encoded entry. * * @return A byte array containing the encoded configuration. */ public byte[] encode() { return encodedRepresentation; } /** * Decodes the entry encode configuration from the specified portion * of the given byte array. * * @param encodedEntry The byte array containing the encoded * entry. * @param startPos The position at which to start decoding the * encode configuration. * @param length The number of bytes contained in the encode * configuration. * * @return The decoded configuration. * * @throws DirectoryException If the configuration cannot be * properly decoded. */ public static EntryEncodeConfig decode(byte[] encodedEntry, int startPos, int length) throws DirectoryException { if (length != 1) { int msgID = MSGID_ENTRYENCODECFG_INVALID_LENGTH; String message = getMessage(msgID); throw new DirectoryException( DirectoryServer.getServerErrorResultCode(), message, msgID); } boolean excludeDN = false; if ((encodedEntry[startPos] & ENCODE_FLAG_EXCLUDE_DN) == ENCODE_FLAG_EXCLUDE_DN) { excludeDN = true; } boolean compressAttrDescriptions = false; if ((encodedEntry[startPos] & ENCODE_FLAG_COMPRESS_ADS) == ENCODE_FLAG_COMPRESS_ADS) { compressAttrDescriptions = true; } boolean compressObjectClassSets = false; if ((encodedEntry[startPos] & ENCODE_FLAG_COMPRESS_OCS) == ENCODE_FLAG_COMPRESS_OCS) { compressObjectClassSets = true; } return new EntryEncodeConfig(excludeDN, compressAttrDescriptions, compressObjectClassSets); } /** * Retrieves a string representation of this entry encode * configuration. * * @return A string representation of this entry encode * configuration. */ public String toString() { StringBuilder buffer = new StringBuilder(); toString(buffer); return buffer.toString(); } /** * Appends a string representation of this entry encode * configuration to the provided buffer. * * @param buffer The buffer to which the information should be * appended. */ public void toString(StringBuilder buffer) { buffer.append("EntryEncodeConfig(excludeDN="); buffer.append(excludeDN); buffer.append(", compressAttrDescriptions="); buffer.append(compressAttrDescriptions); buffer.append(", compressObjectClassSets="); buffer.append(compressObjectClassSets); buffer.append(")"); } } opends/tests/unit-tests-testng/src/server/org/opends/server/backends/jeb/TestJebFormat.java
@@ -38,10 +38,13 @@ import org.opends.server.types.Attribute; import org.opends.server.types.AttributeType; import org.opends.server.types.Entry; import org.opends.server.types.EntryEncodeConfig; import org.opends.server.types.LDIFImportConfig; import org.opends.server.types.ObjectClass; import org.opends.server.util.LDIFReader; import org.opends.server.util.StaticUtils; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; /** @@ -235,24 +238,112 @@ assertEquals(listBefore.size(), listAfter.size()); for (Attribute attrBefore : listBefore) { boolean found = false; for (Attribute attrAfter : listAfter) { if (attrAfter.optionsEqual(attrBefore.getOptions())) { // Found the corresponding attribute String beforeAttrString = attrBefore.toString(); String afterAttrString = attrAfter.toString(); if (!beforeAttrString.equals(afterAttrString)) { System.out.printf( "Original attr:\n%s\nRetrieved attr:\n%s\n\n", beforeAttrString, afterAttrString); assertEquals(attrBefore, attrAfter); found = true; } } assertEquals(beforeAttrString, afterAttrString); assertTrue(found); } } } reader.close(); } /** * Tests the entry encoding and decoding process the version 1 encoding. * * @throws Exception * If the test failed unexpectedly. */ @Test() public void testEntryToAndFromDatabaseV1() throws Exception { // Make sure that the server is up and running. TestCaseUtils.startServer(); // Convert the test LDIF string to a byte array byte[] originalLDIFBytes = StaticUtils.getBytes(ldifString); LDIFReader reader = new LDIFReader(new LDIFImportConfig( new ByteArrayInputStream(originalLDIFBytes))); Entry entryBefore, entryAfterGeneric, entryAfterV1; while ((entryBefore = reader.readEntry(false)) != null) { byte[] entryBytes = entryBefore.encodeV1(); entryAfterGeneric = Entry.decode(entryBytes); assertEquals(entryBefore, entryAfterGeneric); entryAfterV1 = Entry.decodeV1(entryBytes); assertEquals(entryBefore, entryAfterV1); assertEquals(entryAfterGeneric, entryAfterV1); } reader.close(); } /** * Retrieves a set of entry encode configurations that may be used to test the * entry encoding and decoding capabilities. */ @DataProvider(name = "encodeConfigs") public Object[][] getEntryEncodeConfigs() { return new Object[][] { new Object[] { new EntryEncodeConfig() }, new Object[] { new EntryEncodeConfig(false, false, false) }, new Object[] { new EntryEncodeConfig(true, false, false) }, new Object[] { new EntryEncodeConfig(false, true, false) }, new Object[] { new EntryEncodeConfig(false, false, true) }, new Object[] { new EntryEncodeConfig(true, true, false) }, new Object[] { new EntryEncodeConfig(true, false, true) }, new Object[] { new EntryEncodeConfig(false, true, true) }, new Object[] { new EntryEncodeConfig(true, true, true) }, }; } /** * Tests the entry encoding and decoding process the version 1 encoding. * * @throws Exception * If the test failed unexpectedly. */ @Test(dataProvider = "encodeConfigs") public void testEntryToAndFromDatabaseV2(EntryEncodeConfig config) throws Exception { // Make sure that the server is up and running. TestCaseUtils.startServer(); // Convert the test LDIF string to a byte array byte[] originalLDIFBytes = StaticUtils.getBytes(ldifString); LDIFReader reader = new LDIFReader(new LDIFImportConfig( new ByteArrayInputStream(originalLDIFBytes))); Entry entryBefore, entryAfterGeneric, entryAfterV2; while ((entryBefore = reader.readEntry(false)) != null) { byte[] entryBytes = entryBefore.encodeV2(config); entryAfterGeneric = Entry.decode(entryBytes); if (config.excludeDN()) { entryAfterGeneric.setDN(entryBefore.getDN()); } assertEquals(entryBefore, entryAfterGeneric); entryAfterV2 = Entry.decodeV2(entryBytes); if (config.excludeDN()) { entryAfterV2.setDN(entryBefore.getDN()); } assertEquals(entryBefore, entryAfterV2); assertEquals(entryAfterGeneric, entryAfterV2); } reader.close(); }