/*
* 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 legal-notices/CDDLv1_0.txt
* or http://forgerock.org/license/CDDLv1.0.html.
* 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 legal-notices/CDDLv1_0.txt.
* 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.
* Portions Copyright 2013-2015 ForgeRock AS.
*/
package org.opends.server.backends.pluggable;
import java.io.IOException;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.forgerock.opendj.io.ASN1;
import org.forgerock.opendj.io.ASN1Reader;
import org.forgerock.opendj.io.ASN1Writer;
import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.opendj.ldap.ByteStringBuilder;
import org.opends.server.api.CompressedSchema;
import org.opends.server.backends.pluggable.spi.AccessMode;
import org.opends.server.backends.pluggable.spi.Cursor;
import org.opends.server.backends.pluggable.spi.Storage;
import org.opends.server.backends.pluggable.spi.StorageRuntimeException;
import org.opends.server.backends.pluggable.spi.TreeName;
import org.opends.server.backends.pluggable.spi.WriteOperation;
import org.opends.server.backends.pluggable.spi.WriteableTransaction;
import org.opends.server.core.DirectoryServer;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.InitializationException;
import static org.opends.messages.BackendMessages.*;
/**
* This class provides a compressed schema implementation whose definitions are
* persisted in a tree.
*/
final class PersistentCompressedSchema extends CompressedSchema
{
private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
/** The name of the tree used to store compressed attribute description definitions. */
private static final String DB_NAME_AD = "compressed_attributes";
/** The name of the tree used to store compressed object class set definitions. */
private static final String DB_NAME_OC = "compressed_object_classes";
/** The compressed attribute description schema tree. */
private static final TreeName adTreeName = new TreeName("compressed_schema", DB_NAME_AD);
/** The compressed object class set schema tree. */
private static final TreeName ocTreeName = new TreeName("compressed_schema", DB_NAME_OC);
/** The storage in which the trees are held. */
private final Storage storage;
private final ByteStringBuilder storeAttributeWriterBuffer = new ByteStringBuilder();
private final ASN1Writer storeAttributeWriter = ASN1.getWriter(storeAttributeWriterBuffer);
private final ByteStringBuilder storeObjectClassesWriterBuffer = new ByteStringBuilder();
private final ASN1Writer storeObjectClassesWriter = ASN1.getWriter(storeObjectClassesWriterBuffer);
/**
* Creates a new instance of this compressed schema manager.
*
* @param storage
* A reference to the storage in which the trees will be held.
* @param txn a non null transaction
* @param accessMode specifies how the storage has been opened (read only or read/write)
*
* @throws StorageRuntimeException
* If a problem occurs while loading the compressed schema
* definitions from the tree.
* @throws InitializationException
* If an error occurs while loading and processing the compressed
* schema definitions.
*/
PersistentCompressedSchema(final Storage storage, WriteableTransaction txn, AccessMode accessMode)
throws StorageRuntimeException, InitializationException
{
this.storage = storage;
load(txn, accessMode.isWriteable());
}
@Override
protected void storeAttribute(final byte[] encodedAttribute,
final String attributeName, final Collection<String> attributeOptions)
throws DirectoryException
{
try
{
storeAttributeWriterBuffer.clear();
storeAttributeWriter.writeStartSequence();
storeAttributeWriter.writeOctetString(attributeName);
for (final String option : attributeOptions)
{
storeAttributeWriter.writeOctetString(option);
}
storeAttributeWriter.writeEndSequence();
store(adTreeName, encodedAttribute, storeAttributeWriterBuffer);
}
catch (final IOException e)
{
// TODO: Shouldn't happen but should log a message
}
}
@Override
protected void storeObjectClasses(final byte[] encodedObjectClasses,
final Collection<String> objectClassNames) throws DirectoryException
{
try
{
storeObjectClassesWriterBuffer.clear();
storeObjectClassesWriter.writeStartSequence();
for (final String ocName : objectClassNames)
{
storeObjectClassesWriter.writeOctetString(ocName);
}
storeObjectClassesWriter.writeEndSequence();
store(ocTreeName, encodedObjectClasses, storeObjectClassesWriterBuffer);
}
catch (final IOException e)
{
// TODO: Shouldn't happen but should log a message
}
}
private void load(WriteableTransaction txn, boolean shouldCreate)
throws StorageRuntimeException, InitializationException
{
txn.openTree(adTreeName, shouldCreate);
txn.openTree(ocTreeName, shouldCreate);
// 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.
final Cursor<ByteString, ByteString> ocCursor = txn.openCursor(ocTreeName);
try
{
while (ocCursor.next())
{
final byte[] encodedObjectClasses = ocCursor.getKey().toByteArray();
final ASN1Reader reader = ASN1.getReader(ocCursor.getValue());
reader.readStartSequence();
final List<String> objectClassNames = new LinkedList<>();
while (reader.hasNextElement())
{
objectClassNames.add(reader.readOctetStringAsString());
}
reader.readEndSequence();
loadObjectClasses(encodedObjectClasses, objectClassNames);
}
}
catch (final IOException e)
{
logger.traceException(e);
throw new InitializationException(ERR_COMPSCHEMA_CANNOT_DECODE_OC_TOKEN.get(e.getMessage()), e);
}
finally
{
ocCursor.close();
}
// Cursor through the attribute description database and load the attribute
// set definitions.
final Cursor<ByteString, ByteString> adCursor = txn.openCursor(adTreeName);
try
{
while (adCursor.next())
{
final byte[] encodedAttribute = adCursor.getKey().toByteArray();
final ASN1Reader reader = ASN1.getReader(adCursor.getValue());
reader.readStartSequence();
final String attributeName = reader.readOctetStringAsString();
final List<String> attributeOptions = new LinkedList<>();
while (reader.hasNextElement())
{
attributeOptions.add(reader.readOctetStringAsString());
}
reader.readEndSequence();
loadAttribute(encodedAttribute, attributeName, attributeOptions);
}
}
catch (final IOException e)
{
logger.traceException(e);
throw new InitializationException(ERR_COMPSCHEMA_CANNOT_DECODE_AD_TOKEN.get(e.getMessage()), e);
}
finally
{
adCursor.close();
}
}
private boolean store(final TreeName treeName, final byte[] key, final ByteStringBuilder value)
throws DirectoryException
{
final ByteString keyEntry = ByteString.wrap(key);
try
{
storage.write(new WriteOperation()
{
@Override
public void run(WriteableTransaction txn) throws Exception
{
txn.put(treeName, keyEntry, value);
}
});
return true;
}
catch (final Exception e)
{
throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
ERR_COMPSCHEMA_CANNOT_STORE_EX.get(e.getMessage()), e);
}
}
}