001/*
002 * CDDL HEADER START
003 *
004 * The contents of this file are subject to the terms of the
005 * Common Development and Distribution License, Version 1.0 only
006 * (the "License").  You may not use this file except in compliance
007 * with the License.
008 *
009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
010 * or http://forgerock.org/license/CDDLv1.0.html.
011 * See the License for the specific language governing permissions
012 * and limitations under the License.
013 *
014 * When distributing Covered Code, include this CDDL HEADER in each
015 * file and include the License file at legal-notices/CDDLv1_0.txt.
016 * If applicable, add the following below this CDDL HEADER, with the
017 * fields enclosed by brackets "[]" replaced with your own identifying
018 * information:
019 *      Portions Copyright [yyyy] [name of copyright owner]
020 *
021 * CDDL HEADER END
022 *
023 *
024 *      Copyright 2008 Sun Microsystems, Inc.
025 *      Portions Copyright 2013-2015 ForgeRock AS.
026 */
027package org.opends.server.core;
028
029import java.io.File;
030import java.io.FileInputStream;
031import java.io.FileOutputStream;
032import java.util.Collection;
033import java.util.LinkedList;
034import java.util.List;
035import java.util.Map.Entry;
036
037import org.forgerock.i18n.LocalizableMessage;
038import org.forgerock.i18n.slf4j.LocalizedLogger;
039import org.forgerock.opendj.io.ASN1;
040import org.forgerock.opendj.io.ASN1Reader;
041import org.forgerock.opendj.io.ASN1Writer;
042import org.forgerock.opendj.ldap.ByteString;
043import org.opends.server.api.CompressedSchema;
044import org.opends.server.types.DirectoryException;
045
046import static org.opends.messages.CoreMessages.*;
047import static org.opends.server.config.ConfigConstants.*;
048import static org.opends.server.util.StaticUtils.*;
049
050/**
051 * This class provides a default implementation of a compressed schema manager
052 * that will store the schema definitions in a binary file
053 * (config/schematokens.dat).
054 */
055public final class DefaultCompressedSchema extends CompressedSchema
056{
057  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
058
059  /** Synchronizes calls to save. */
060  private final Object saveLock = new Object();
061
062
063
064  /**
065   * Creates a new instance of this compressed schema manager.
066   */
067  public DefaultCompressedSchema()
068  {
069    load();
070  }
071
072
073
074  /** {@inheritDoc} */
075  @Override
076  protected void storeAttribute(final byte[] encodedAttribute,
077      final String attributeName, final Collection<String> attributeOptions)
078      throws DirectoryException
079  {
080    save();
081  }
082
083
084
085  /** {@inheritDoc} */
086  @Override
087  protected void storeObjectClasses(final byte[] encodedObjectClasses,
088      final Collection<String> objectClassNames) throws DirectoryException
089  {
090    save();
091  }
092
093
094
095  /**
096   * Loads the compressed schema information from disk.
097   */
098  private void load()
099  {
100    FileInputStream inputStream = null;
101
102    try
103    {
104      // Determine the location of the compressed schema data file. It should
105      // be in the config directory with a name of "schematokens.dat". If that
106      // file doesn't exist, then don't do anything.
107      final String path = DirectoryServer.getInstanceRoot() + File.separator
108          + CONFIG_DIR_NAME + File.separator + COMPRESSED_SCHEMA_FILE_NAME;
109      if (!new File(path).exists())
110      {
111        return;
112      }
113      inputStream = new FileInputStream(path);
114      final ASN1Reader reader = ASN1.getReader(inputStream);
115
116      // The first element in the file should be a sequence of object class
117      // sets. Each object class set will itself be a sequence of octet
118      // strings, where the first one is the token and the remaining elements
119      // are the names of the associated object classes.
120      reader.readStartSequence();
121      while (reader.hasNextElement())
122      {
123        reader.readStartSequence();
124        final byte[] encodedObjectClasses = reader.readOctetString()
125            .toByteArray();
126        final List<String> objectClassNames = new LinkedList<>();
127        while (reader.hasNextElement())
128        {
129          objectClassNames.add(reader.readOctetStringAsString());
130        }
131        reader.readEndSequence();
132        loadObjectClasses(encodedObjectClasses, objectClassNames);
133      }
134      reader.readEndSequence();
135
136      // The second element in the file should be an integer element that holds
137      // the value to use to initialize the object class counter.
138      reader.readInteger(); // No longer used.
139
140      // The third element in the file should be a sequence of attribute
141      // description components. Each attribute description component will
142      // itself be a sequence of octet strings, where the first one is the
143      // token, the second is the attribute name, and all remaining elements are
144      // the attribute options.
145      reader.readStartSequence();
146      while (reader.hasNextElement())
147      {
148        reader.readStartSequence();
149        final byte[] encodedAttribute = reader.readOctetString().toByteArray();
150        final String attributeName = reader.readOctetStringAsString();
151        final List<String> attributeOptions = new LinkedList<>();
152        while (reader.hasNextElement())
153        {
154          attributeOptions.add(reader.readOctetStringAsString());
155        }
156        reader.readEndSequence();
157        loadAttribute(encodedAttribute, attributeName, attributeOptions);
158      }
159      reader.readEndSequence();
160
161      // The fourth element in the file should be an integer element that holds
162      // the value to use to initialize the attribute description counter.
163      reader.readInteger(); // No longer used.
164    }
165    catch (final Exception e)
166    {
167      logger.traceException(e);
168
169      // FIXME -- Should we do something else here?
170      throw new RuntimeException(e);
171    }
172    finally
173    {
174      close(inputStream);
175    }
176  }
177
178
179
180  /**
181   * Writes the compressed schema information to disk.
182   *
183   * @throws DirectoryException
184   *           If a problem occurs while writing the updated information.
185   */
186  private void save() throws DirectoryException
187  {
188    synchronized (saveLock)
189    {
190      FileOutputStream outputStream = null;
191      try
192      {
193        // Determine the location of the "live" compressed schema data file, and
194        // then append ".tmp" to get the name of the temporary file that we will
195        // use.
196        final String path = DirectoryServer.getInstanceRoot() + File.separator
197            + CONFIG_DIR_NAME + File.separator + COMPRESSED_SCHEMA_FILE_NAME;
198        final String tempPath = path + ".tmp";
199
200        outputStream = new FileOutputStream(tempPath);
201        final ASN1Writer writer = ASN1.getWriter(outputStream);
202
203        // The first element in the file should be a sequence of object class
204        // sets. Each object class set will itself be a sequence of octet
205        // strings, where the first one is the token and the remaining elements
206        // are the names of the associated object classes.
207        writer.writeStartSequence();
208        int ocCounter = 1;
209        for (final Entry<byte[], Collection<String>> mapEntry :
210            getAllObjectClasses())
211        {
212          writer.writeStartSequence();
213          writer.writeOctetString(ByteString.wrap(mapEntry.getKey()));
214          final Collection<String> objectClassNames = mapEntry.getValue();
215          for (final String ocName : objectClassNames)
216          {
217            writer.writeOctetString(ocName);
218          }
219          writer.writeEndSequence();
220          ocCounter++;
221        }
222        writer.writeEndSequence();
223
224        // The second element in the file should be an integer element that
225        // holds the value to use to initialize the object class counter.
226        writer.writeInteger(ocCounter); // No longer used.
227
228        // The third element in the file should be a sequence of attribute
229        // description components. Each attribute description component will
230        // itself be a sequence of octet strings, where the first one is the
231        // token, the second is the attribute name, and all remaining elements
232        // are the attribute options.
233        writer.writeStartSequence();
234        int adCounter = 1;
235        for (final Entry<byte[], Entry<String, Collection<String>>> mapEntry :
236            getAllAttributes())
237        {
238          writer.writeStartSequence();
239          writer.writeOctetString(ByteString.wrap(mapEntry.getKey()));
240          writer.writeOctetString(mapEntry.getValue().getKey());
241          for (final String option : mapEntry.getValue().getValue())
242          {
243            writer.writeOctetString(option);
244          }
245          writer.writeEndSequence();
246          adCounter++;
247        }
248        writer.writeEndSequence();
249
250        // The fourth element in the file should be an integer element that
251        // holds the value to use to initialize the attribute description
252        // counter.
253        writer.writeInteger(adCounter); // No longer used.
254
255        // Close the writer and swing the temp file into place.
256        outputStream.close();
257        final File liveFile = new File(path);
258        final File tempFile = new File(tempPath);
259
260        if (liveFile.exists())
261        {
262          final File saveFile = new File(liveFile.getAbsolutePath() + ".save");
263          if (saveFile.exists())
264          {
265            saveFile.delete();
266          }
267          liveFile.renameTo(saveFile);
268        }
269        tempFile.renameTo(liveFile);
270      }
271      catch (final Exception e)
272      {
273        logger.traceException(e);
274
275        final LocalizableMessage message = ERR_COMPRESSEDSCHEMA_CANNOT_WRITE_UPDATED_DATA
276            .get(stackTraceToSingleLineString(e));
277        throw new DirectoryException(
278            DirectoryServer.getServerErrorResultCode(), message, e);
279      }
280      finally
281      {
282        close(outputStream);
283      }
284    }
285  }
286
287}