/* * 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 * * * Copyright 2008 Sun Microsystems, Inc. */ package org.opends.server.core; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; import org.opends.messages.Message; import org.opends.server.api.CompressedSchema; import org.opends.server.loggers.debug.DebugTracer; import org.opends.server.protocols.asn1.ASN1Reader; import org.opends.server.protocols.asn1.ASN1; import org.opends.server.protocols.asn1.ASN1Writer; import org.opends.server.types.*; import static org.opends.server.config.ConfigConstants.*; import static org.opends.server.loggers.debug.DebugLogger.*; import static org.opends.messages.CoreMessages.*; import static org.opends.server.util.StaticUtils.*; /** * This class provides a default implementation of a compressed schema manager * that will store the schema definitions in a binary file * (config/schematokens.dat). */ public final class DefaultCompressedSchema extends CompressedSchema { /** * The tracer object for the debug logger. */ private static final DebugTracer TRACER = getTracer(); // 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 atDecodeMap; // The map between encoded representations and attribute options. private ConcurrentHashMap> aoDecodeMap; // The map between encoded representations and object class sets. private ConcurrentHashMap> ocDecodeMap; // The map between attribute descriptions and their encoded // representations. private final ConcurrentHashMap, ByteSequence>> adEncodeMap; // The map between object class sets and encoded representations. private final ConcurrentHashMap, ByteSequence> ocEncodeMap; /** * Creates a new instance of this compressed schema manager. */ public DefaultCompressedSchema() { atDecodeMap = new ConcurrentHashMap(); aoDecodeMap = new ConcurrentHashMap>(); ocDecodeMap = new ConcurrentHashMap>(); adEncodeMap = new ConcurrentHashMap , ByteSequence>>(); ocEncodeMap = new ConcurrentHashMap, ByteSequence>(); adCounter = new AtomicInteger(1); ocCounter = new AtomicInteger(1); load(); } /** * Loads the compressed schema information from disk. */ private void load() { FileInputStream inputStream = 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.getInstanceRoot() + File.separator + CONFIG_DIR_NAME + File.separator + COMPRESSED_SCHEMA_FILE_NAME; if (! new File(path).exists()) { return; } inputStream = new FileInputStream(path); ASN1Reader reader = ASN1.getReader(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. reader.readStartSequence(); while(reader.hasNextElement()) { reader.readStartSequence(); ByteSequence token = reader.readOctetString(); LinkedHashMap ocMap = new LinkedHashMap(); while(reader.hasNextElement()) { String ocName = reader.readOctetStringAsString(); String lowerName = toLowerCase(ocName); ObjectClass oc = DirectoryServer.getObjectClass(lowerName, true); ocMap.put(oc, ocName); } reader.readEndSequence(); ocEncodeMap.put(ocMap, token); ocDecodeMap.put(token, ocMap); } reader.readEndSequence(); // The second element in the file should be an integer element that holds // the value to use to initialize the object class counter. ocCounter.set((int)reader.readInteger()); // 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. reader.readStartSequence(); while(reader.hasNextElement()) { reader.readStartSequence(); ByteSequence token = reader.readOctetString(); String attrName = reader.readOctetStringAsString(); String lowerName = toLowerCase(attrName); AttributeType attrType = DirectoryServer.getAttributeType(lowerName, true); LinkedHashSet options = new LinkedHashSet(); while(reader.hasNextElement()) { options.add(reader.readOctetStringAsString()); } reader.readEndSequence(); atDecodeMap.put(token, attrType); aoDecodeMap.put(token, options); ConcurrentHashMap, ByteSequence> map = adEncodeMap .get(attrType); if (map == null) { map = new ConcurrentHashMap, ByteSequence>(1); map.put(options, token); adEncodeMap.put(attrType, map); } else { map.put(options, token); } } reader.readEndSequence(); // The fourth element in the file should be an integer element that holds // the value to use to initialize the attribute description counter. adCounter.set((int)reader.readInteger()); } catch (Exception e) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); } // FIXME -- Should we do something else here? throw new RuntimeException(e); } finally { try { if (inputStream != null) { inputStream.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 { FileOutputStream outputStream = 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.getInstanceRoot() + File.separator + CONFIG_DIR_NAME + File.separator + COMPRESSED_SCHEMA_FILE_NAME; String tempPath = path + ".tmp"; outputStream = new FileOutputStream(tempPath); ASN1Writer writer = ASN1.getWriter(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. writer.writeStartSequence(); for (Map.Entry> mapEntry : ocDecodeMap.entrySet()) { writer.writeStartSequence(); writer.writeOctetString(mapEntry.getKey()); Map ocMap = mapEntry.getValue(); for (String ocName : ocMap.values()) { writer.writeOctetString(ocName); } writer.writeEndSequence(); } writer.writeEndSequence(); // The second element in the file should be an integer element that holds // the value to use to initialize the object class counter. writer.writeInteger(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. writer.writeStartSequence(); for (ByteSequence token : atDecodeMap.keySet()) { writer.writeStartSequence(); AttributeType attrType = atDecodeMap.get(token); Set options = aoDecodeMap.get(token); writer.writeOctetString(token); writer.writeOctetString(attrType.getNameOrOID()); for (String option : options) { writer.writeOctetString(option); } writer.writeEndSequence(); } writer.writeEndSequence(); // The fourth element in the file should be an integer element that holds // the value to use to initialize the attribute description counter. writer.writeInteger(adCounter.get()); // Close the writer and swing the temp file into place. outputStream.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); } Message message = ERR_COMPRESSEDSCHEMA_CANNOT_WRITE_UPDATED_DATA.get( stackTraceToSingleLineString(e)); throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message, e); } finally { try { if (outputStream != null) { outputStream.close(); } } catch (Exception e) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); } } } } /** * {@inheritDoc} */ @Override() public void encodeObjectClasses(ByteStringBuilder entryBuffer, Map objectClasses) throws DirectoryException { ByteSequence encodedClasses = ocEncodeMap.get(objectClasses); if (encodedClasses == null) { synchronized (ocEncodeMap) { int setValue = ocCounter.getAndIncrement(); encodedClasses = ByteString.wrap(encodeInt(setValue)); ocEncodeMap.put(objectClasses, encodedClasses); ocDecodeMap.put(encodedClasses, objectClasses); save(); } } entryBuffer.appendBERLength(encodedClasses.length()); encodedClasses.copyTo(entryBuffer); } /** * {@inheritDoc} */ @Override() public Map decodeObjectClasses( ByteSequenceReader entryBufferReader) throws DirectoryException { int tokenLength = entryBufferReader.getBERLength(); ByteSequence byteArray = entryBufferReader.getByteSequence(tokenLength); Map ocMap = ocDecodeMap.get(byteArray); if (ocMap == null) { Message message = ERR_COMPRESSEDSCHEMA_UNKNOWN_OC_TOKEN.get(byteArray .toByteString().toHex()); throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message); } else { return ocMap; } } /** * {@inheritDoc} */ @Override() public void encodeAttribute(ByteStringBuilder entryBuffer, Attribute attribute) throws DirectoryException { AttributeType type = attribute.getAttributeType(); Set options = attribute.getOptions(); ConcurrentHashMap, ByteSequence> map = adEncodeMap.get(type); if (map == null) { ByteString byteArray; synchronized (adEncodeMap) { map = new ConcurrentHashMap, ByteSequence>(1); int intValue = adCounter.getAndIncrement(); byteArray = ByteString.wrap(encodeInt(intValue)); map.put(options,byteArray); adEncodeMap.put(type, map); atDecodeMap.put(byteArray, type); aoDecodeMap.put(byteArray, options); save(); } encodeAttribute(entryBuffer, byteArray, attribute); } else { ByteSequence byteArray = map.get(options); if (byteArray == null) { synchronized (map) { int intValue = adCounter.getAndIncrement(); byteArray = ByteString.wrap(encodeInt(intValue)); map.put(options,byteArray); atDecodeMap.put(byteArray, type); aoDecodeMap.put(byteArray, options); save(); } } encodeAttribute(entryBuffer, byteArray, attribute); } } /** * Encodes the information in the provided attribute to a byte * array. * * @param buffer The byte buffer to encode the attribute into. * @param adArray The byte array that is a placeholder for the * attribute type and set of options. * @param attribute The attribute to be encoded. */ private void encodeAttribute(ByteStringBuilder buffer, ByteSequence adArray, Attribute attribute) { // Write the length of the adArray followed by the adArray. buffer.appendBERLength(adArray.length()); adArray.copyTo(buffer); // Write the number of attributes buffer.appendBERLength(attribute.size()); // Write the attribute values as length / value pairs for(AttributeValue v : attribute) { buffer.appendBERLength(v.getValue().length()); buffer.append(v.getValue()); } } /** * {@inheritDoc} */ @Override() public Attribute decodeAttribute(ByteSequenceReader entryBufferReader) throws DirectoryException { // Figure out how many bytes are in the token that is the placeholder for // the attribute description. int adArrayLength = entryBufferReader.getBERLength(); // Get the attribute description token and make sure it resolves to an // attribute type and option set. ByteSequence adArray = entryBufferReader.getByteSequence(adArrayLength); AttributeType attrType = atDecodeMap.get(adArray); Set options = aoDecodeMap.get(adArray); if ((attrType == null) || (options == null)) { Message message = ERR_COMPRESSEDSCHEMA_UNRECOGNIZED_AD_TOKEN.get(adArray .toByteString().toHex()); throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message); } // Determine the number of values for the attribute. int numValues = entryBufferReader.getBERLength(); // For the common case of a single value with no options, generate // less garbage. if (numValues == 1 && options.isEmpty()) { int valueLength = entryBufferReader.getBERLength(); ByteString valueBytes = entryBufferReader.getByteSequence(valueLength).toByteString(); return Attributes.create(attrType, AttributeValues.create(attrType,valueBytes)); } else { // Read the appropriate number of values. AttributeBuilder builder = new AttributeBuilder(attrType); builder.setOptions(options); builder.setInitialCapacity(numValues); for (int i = 0; i < numValues; i++) { int valueLength = entryBufferReader.getBERLength(); ByteString valueBytes = entryBufferReader.getByteSequence(valueLength).toByteString(); builder.add(AttributeValues.create(attrType, valueBytes)); } return builder.toAttribute(); } } /** * 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 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; } }