/* * 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. * Portions copyright 2013 ForgeRock AS. */ package org.opends.server.core; import static org.opends.messages.CoreMessages.*; import static org.opends.server.config.ConfigConstants.*; import static org.opends.server.loggers.debug.DebugLogger.debugEnabled; import static org.opends.server.loggers.debug.DebugLogger.getTracer; import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.util.Collection; import java.util.LinkedList; import java.util.List; import java.util.Map.Entry; import org.opends.messages.Message; import org.opends.server.api.CompressedSchema; import org.opends.server.loggers.debug.DebugTracer; import org.opends.server.protocols.asn1.ASN1; import org.opends.server.protocols.asn1.ASN1Reader; import org.opends.server.protocols.asn1.ASN1Writer; import org.opends.server.types.ByteString; import org.opends.server.types.DebugLogLevel; import org.opends.server.types.DirectoryException; /** * 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(); // Synchronizes calls to save. private final Object saveLock = new Object(); /** * Creates a new instance of this compressed schema manager. */ public DefaultCompressedSchema() { load(); } /** * {@inheritDoc} */ @Override protected void storeAttribute(final byte[] encodedAttribute, final String attributeName, final Collection<String> attributeOptions) throws DirectoryException { save(); } /** * {@inheritDoc} */ @Override protected void storeObjectClasses(final byte[] encodedObjectClasses, final Collection<String> objectClassNames) throws DirectoryException { save(); } /** * 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. final 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); final 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(); final byte[] encodedObjectClasses = reader.readOctetString() .toByteArray(); final List<String> objectClassNames = new LinkedList<String>(); while (reader.hasNextElement()) { objectClassNames.add(reader.readOctetStringAsString()); } reader.readEndSequence(); loadObjectClasses(encodedObjectClasses, objectClassNames); } 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. reader.readInteger(); // No longer used. // 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(); final byte[] encodedAttribute = reader.readOctetString().toByteArray(); final String attributeName = reader.readOctetStringAsString(); final List<String> attributeOptions = new LinkedList<String>(); while (reader.hasNextElement()) { attributeOptions.add(reader.readOctetStringAsString()); } reader.readEndSequence(); loadAttribute(encodedAttribute, attributeName, attributeOptions); } 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. reader.readInteger(); // No longer used. } catch (final 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 (final 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 { synchronized (saveLock) { 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. final String path = DirectoryServer.getInstanceRoot() + File.separator + CONFIG_DIR_NAME + File.separator + COMPRESSED_SCHEMA_FILE_NAME; final String tempPath = path + ".tmp"; outputStream = new FileOutputStream(tempPath); final 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(); int ocCounter = 1; for (final Entry<byte[], Collection<String>> mapEntry : getAllObjectClasses()) { writer.writeStartSequence(); writer.writeOctetString(ByteString.wrap(mapEntry.getKey())); final Collection<String> objectClassNames = mapEntry.getValue(); for (final String ocName : objectClassNames) { writer.writeOctetString(ocName); } writer.writeEndSequence(); ocCounter++; } 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); // No longer used. // 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(); int adCounter = 1; for (final Entry<byte[], Entry<String, Collection<String>>> mapEntry : getAllAttributes()) { writer.writeStartSequence(); writer.writeOctetString(ByteString.wrap(mapEntry.getKey())); writer.writeOctetString(mapEntry.getValue().getKey()); for (final String option : mapEntry.getValue().getValue()) { writer.writeOctetString(option); } writer.writeEndSequence(); adCounter++; } 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); // No longer used. // Close the writer and swing the temp file into place. outputStream.close(); final File liveFile = new File(path); final File tempFile = new File(tempPath); if (liveFile.exists()) { final File saveFile = new File(liveFile.getAbsolutePath() + ".save"); if (saveFile.exists()) { saveFile.delete(); } liveFile.renameTo(saveFile); } tempFile.renameTo(liveFile); } catch (final Exception e) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); } final 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 (final Exception e) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); } } } } } }