/* * 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-2009 Sun Microsystems, Inc. */ package org.opends.server.backends.jeb; import static org.opends.messages.JebMessages.*; import static org.opends.server.loggers.debug.DebugLogger.*; import static org.opends.server.util.StaticUtils.*; 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 java.io.IOException; import org.opends.messages.Message; import org.opends.server.api.CompressedSchema; import org.opends.server.core.DirectoryServer; import org.opends.server.loggers.debug.DebugTracer; import org.opends.server.protocols.asn1.*; import org.opends.server.types.*; import com.sleepycat.je.Cursor; import com.sleepycat.je.Database; import com.sleepycat.je.DatabaseConfig; import com.sleepycat.je.DatabaseEntry; import com.sleepycat.je.DatabaseException; import com.sleepycat.je.Environment; import com.sleepycat.je.LockConflictException; import com.sleepycat.je.LockMode; import com.sleepycat.je.OperationStatus; /** * This class provides a compressed schema implementation whose definitions are * stored in a Berkeley DB JE database. */ public final class JECompressedSchema extends CompressedSchema { /** * The tracer object for the debug logger. */ private static final DebugTracer TRACER = getTracer(); /** * The name of the database used to store compressed attribute description * definitions. */ public static final String DB_NAME_AD = "compressed_attributes"; /** * The name of the database used to store compressed object class set * definitions. */ public static final String DB_NAME_OC = "compressed_object_classes"; // 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; // The compressed attribute description schema database. private Database adDatabase; // The compresesd object class set schema database. private Database ocDatabase; // The environment in which the databases are held. private Environment environment; private final ByteStringBuilder storeWriterBuffer; private final ASN1Writer storeWriter; /** * Creates a new instance of this JE compressed schema manager. * * @param environment A reference to the database environment in which the * databases will be held. * * @throws DatabaseException If a database problem occurs while loading * the compressed schema definitions from the * database. * @throws InitializationException If an error occurs while loading and * processing the compressed schema * definitions. */ public JECompressedSchema(Environment environment) throws DatabaseException, InitializationException { this.environment = environment; 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); storeWriterBuffer = new ByteStringBuilder(); storeWriter = ASN1.getWriter(storeWriterBuffer); load(); } /** * Loads the compressed schema information from the database. * * @throws DatabaseException If a database error occurs while * loading the definitions from the * database. * @throws InitializationException If an error occurs while loading * and processing the definitions. */ private void load() throws DatabaseException, InitializationException { DatabaseConfig dbConfig = new DatabaseConfig(); if(environment.getConfig().getReadOnly()) { dbConfig.setReadOnly(true); dbConfig.setAllowCreate(false); dbConfig.setTransactional(false); } else if(!environment.getConfig().getTransactional()) { dbConfig.setAllowCreate(true); dbConfig.setTransactional(false); dbConfig.setDeferredWrite(true); } else { dbConfig.setAllowCreate(true); dbConfig.setTransactional(true); } adDatabase = environment.openDatabase(null, DB_NAME_AD, dbConfig); ocDatabase = environment.openDatabase(null, DB_NAME_OC, dbConfig); // Cursor through the object class database and load the object class set // definitions. At the same time, figure out the highest token value and // initialize the object class counter to one greater than that. Cursor ocCursor = ocDatabase.openCursor(null, null); int highestToken = 0; try { DatabaseEntry keyEntry = new DatabaseEntry(); DatabaseEntry valueEntry = new DatabaseEntry(); OperationStatus status = ocCursor.getFirst(keyEntry, valueEntry, LockMode.READ_UNCOMMITTED); while (status == OperationStatus.SUCCESS) { byte[] tokenBytes = keyEntry.getData(); ByteString token = ByteString.wrap(tokenBytes); highestToken = Math.max(highestToken, decodeInt(tokenBytes)); ASN1Reader reader = ASN1.getReader(valueEntry.getData()); reader.readStartSequence(); 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); status = ocCursor.getNext(keyEntry, valueEntry, LockMode.READ_UNCOMMITTED); } } catch (ASN1Exception ae) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, ae); } Message m = ERR_JEB_COMPSCHEMA_CANNOT_DECODE_OC_TOKEN.get(ae.getMessage()); throw new InitializationException(m, ae); } finally { ocCursor.close(); } ocCounter.set(highestToken+1); // Cursor through the attribute description database and load the attribute // set definitions. Cursor adCursor = adDatabase.openCursor(null, null); highestToken = 0; try { DatabaseEntry keyEntry = new DatabaseEntry(); DatabaseEntry valueEntry = new DatabaseEntry(); OperationStatus status = adCursor.getFirst(keyEntry, valueEntry, LockMode.READ_UNCOMMITTED); while (status == OperationStatus.SUCCESS) { byte[] tokenBytes = keyEntry.getData(); ByteString token = ByteString.wrap(tokenBytes); highestToken = Math.max(highestToken, decodeInt(tokenBytes)); ASN1Reader reader = ASN1.getReader(valueEntry.getData()); reader.readStartSequence(); 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); } status = adCursor.getNext(keyEntry, valueEntry, LockMode.READ_UNCOMMITTED); } } catch (ASN1Exception ae) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, ae); } Message m = ERR_JEB_COMPSCHEMA_CANNOT_DECODE_AD_TOKEN.get(ae.getMessage()); throw new InitializationException(m, ae); } finally { adCursor.close(); } adCounter.set(highestToken+1); } /** * Closes the databases and releases any resources held by this compressed * schema manager. */ public void close() { try { adDatabase.sync(); } catch (Exception e) {} try { adDatabase.close(); } catch (Exception e) {} try { ocDatabase.sync(); } catch (Exception e) {} try { ocDatabase.close(); } catch (Exception e) {} adDatabase = null; ocDatabase = null; environment = null; atDecodeMap = null; aoDecodeMap = null; ocDecodeMap = null; //adEncodeMap = null; //ocEncodeMap = null; adCounter = null; ocCounter = null; } /** * {@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(); byte[] tokenArray = encodeInt(setValue); encodedClasses = ByteString.wrap(tokenArray); storeObjectClass(tokenArray, objectClasses); ocEncodeMap.put(objectClasses, encodedClasses); ocDecodeMap.put(encodedClasses, objectClasses); } } 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_JEB_COMPSCHEMA_UNKNOWN_OC_TOKEN.get(byteArray .toByteString().toHex()); throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message); } else { return ocMap; } } private void storeObjectClass(byte[] token, Map objectClasses) throws DirectoryException { synchronized(storeWriter) { try { storeWriterBuffer.clear(); storeWriter.writeStartSequence(); for (String ocName : objectClasses.values()) { storeWriter.writeOctetString(ocName); } storeWriter.writeEndSequence(); store(ocDatabase, token, storeWriterBuffer); } catch(IOException ioe) { // TODO: Shouldn't happen but should log a message } } } private void storeAttribute(byte[] token, AttributeType attrType, Set options) throws DirectoryException { synchronized(storeWriter) { try { storeWriterBuffer.clear(); storeWriter.writeStartSequence(); storeWriter.writeOctetString(attrType.getNameOrOID()); for (String option : options) { storeWriter.writeOctetString(option); } storeWriter.writeEndSequence(); store(adDatabase, token, storeWriterBuffer); } catch(IOException ioe) { // TODO: Shouldn't happen but should log a message } } } /** * {@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) { byte[] tokenArray; ByteString byteString; synchronized (adEncodeMap) { map = new ConcurrentHashMap, ByteSequence>(1); int intValue = adCounter.getAndIncrement(); tokenArray = encodeInt(intValue); byteString = ByteString.wrap(tokenArray); map.put(options,byteString); storeAttribute(tokenArray, type, options); adEncodeMap.put(type, map); atDecodeMap.put(byteString, type); aoDecodeMap.put(byteString, options); } encodeAttribute(entryBuffer, byteString, attribute); } else { ByteSequence byteArray = map.get(options); if (byteArray == null) { byte[] tokenArray; synchronized (map) { int intValue = adCounter.getAndIncrement(); tokenArray = encodeInt(intValue); byteArray = ByteString.wrap(tokenArray); map.put(options,byteArray); storeAttribute(tokenArray, type, options); atDecodeMap.put(byteArray, type); aoDecodeMap.put(byteArray, options); } } 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_JEB_COMPSCHEMA_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(); } } /** * Stores the provided key-value pair in the specified database container. * * @param database The database in which to store the information. * @param keyBytes The byte array containing the key to store. * @param valueBytes The byte array containing the value to store. * * @throws DirectoryException If a problem occurs while attempting to store * the data. */ private void store(Database database, byte[] keyBytes, ByteStringBuilder valueBytes) throws DirectoryException { boolean successful = false; DatabaseEntry keyEntry = new DatabaseEntry(keyBytes); DatabaseEntry valueEntry = new DatabaseEntry(valueBytes.getBackingArray(), 0, valueBytes.length()); for (int i=0; i < 3; i++) { try { OperationStatus status = database.putNoOverwrite(null, keyEntry, valueEntry); if (status == OperationStatus.SUCCESS) { successful = true; break; } else { Message m = ERR_JEB_COMPSCHEMA_CANNOT_STORE_STATUS.get( status.toString()); throw new DirectoryException( DirectoryServer.getServerErrorResultCode(), m); } } catch (LockConflictException ce) { continue; } catch (DatabaseException de) { Message m = ERR_JEB_COMPSCHEMA_CANNOT_STORE_EX.get(de.getMessage()); throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), m, de); } } if (! successful) { Message m = ERR_JEB_COMPSCHEMA_CANNOT_STORE_MULTIPLE_FAILURES.get(); throw new DirectoryException( DirectoryServer.getServerErrorResultCode(), m); } } /** * 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; } /** * Decodes the contents of the provided byte array as an int. * * @param byteArray The byte array containing the data to decode. * * @return The decoded int value. */ private int decodeInt(byte[] byteArray) { int intValue = 0; for (byte b : byteArray) { intValue <<= 8; intValue |= (b & 0xFF); } return intValue; } }