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-2009 Sun Microsystems, Inc.
025 *      Portions Copyright 2013-2015 ForgeRock AS.
026 */
027package org.opends.server.backends.jeb;
028
029import java.io.IOException;
030import java.util.Collection;
031import java.util.LinkedList;
032import java.util.List;
033
034import org.forgerock.i18n.LocalizableMessage;
035import org.forgerock.i18n.slf4j.LocalizedLogger;
036import org.forgerock.opendj.io.ASN1;
037import org.forgerock.opendj.io.ASN1Reader;
038import org.forgerock.opendj.io.ASN1Writer;
039import org.forgerock.opendj.ldap.ByteStringBuilder;
040import org.opends.server.api.CompressedSchema;
041import org.opends.server.core.DirectoryServer;
042import org.opends.server.types.DirectoryException;
043import org.opends.server.types.InitializationException;
044import org.opends.server.util.StaticUtils;
045
046import com.sleepycat.je.Cursor;
047import com.sleepycat.je.Database;
048import com.sleepycat.je.DatabaseConfig;
049import com.sleepycat.je.DatabaseEntry;
050import com.sleepycat.je.DatabaseException;
051import com.sleepycat.je.Environment;
052import com.sleepycat.je.LockConflictException;
053import com.sleepycat.je.OperationStatus;
054
055import static com.sleepycat.je.LockMode.*;
056import static com.sleepycat.je.OperationStatus.*;
057
058import static org.opends.messages.BackendMessages.*;
059
060/**
061 * This class provides a compressed schema implementation whose definitions are
062 * stored in a Berkeley DB JE database.
063 */
064public final class JECompressedSchema extends CompressedSchema
065{
066  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
067
068  /** The name of the database used to store compressed attribute description definitions. */
069  private static final String DB_NAME_AD = "compressed_attributes";
070  /** The name of the database used to store compressed object class set definitions. */
071  private static final String DB_NAME_OC = "compressed_object_classes";
072
073  /** The compressed attribute description schema database. */
074  private Database adDatabase;
075  /** The environment in which the databases are held. */
076  private Environment environment;
077  /** The compressed object class set schema database. */
078  private Database ocDatabase;
079
080  private final ByteStringBuilder storeAttributeWriterBuffer = new ByteStringBuilder();
081  private final ASN1Writer storeAttributeWriter = ASN1.getWriter(storeAttributeWriterBuffer);
082  private final ByteStringBuilder storeObjectClassesWriterBuffer = new ByteStringBuilder();
083  private final ASN1Writer storeObjectClassesWriter = ASN1.getWriter(storeObjectClassesWriterBuffer);
084
085
086
087  /**
088   * Creates a new instance of this JE compressed schema manager.
089   *
090   * @param environment
091   *          A reference to the database environment in which the databases
092   *          will be held.
093   * @throws DatabaseException
094   *           If a database problem occurs while loading the compressed schema
095   *           definitions from the database.
096   * @throws InitializationException
097   *           If an error occurs while loading and processing the compressed
098   *           schema definitions.
099   */
100  public JECompressedSchema(final Environment environment)
101      throws DatabaseException, InitializationException
102  {
103    this.environment = environment;
104    load();
105  }
106
107
108
109  /**
110   * Closes the databases and releases any resources held by this compressed
111   * schema manager.
112   */
113  public void close()
114  {
115    close0(adDatabase);
116    close0(ocDatabase);
117
118    adDatabase = null;
119    ocDatabase = null;
120    environment = null;
121  }
122
123  private void close0(Database database)
124  {
125    try
126    {
127      database.sync();
128    }
129    catch (final Exception e)
130    {
131      // Ignore.
132    }
133    StaticUtils.close(database);
134  }
135
136
137
138  /** {@inheritDoc} */
139  @Override
140  protected void storeAttribute(final byte[] encodedAttribute,
141      final String attributeName, final Collection<String> attributeOptions)
142      throws DirectoryException
143  {
144    try
145    {
146      storeAttributeWriterBuffer.clear();
147      storeAttributeWriter.writeStartSequence();
148      storeAttributeWriter.writeOctetString(attributeName);
149      for (final String option : attributeOptions)
150      {
151        storeAttributeWriter.writeOctetString(option);
152      }
153      storeAttributeWriter.writeEndSequence();
154      store(adDatabase, encodedAttribute, storeAttributeWriterBuffer);
155    }
156    catch (final IOException e)
157    {
158      // TODO: Shouldn't happen but should log a message
159    }
160  }
161
162
163
164  /** {@inheritDoc} */
165  @Override
166  protected void storeObjectClasses(final byte[] encodedObjectClasses,
167      final Collection<String> objectClassNames) throws DirectoryException
168  {
169    try
170    {
171      storeObjectClassesWriterBuffer.clear();
172      storeObjectClassesWriter.writeStartSequence();
173      for (final String ocName : objectClassNames)
174      {
175        storeObjectClassesWriter.writeOctetString(ocName);
176      }
177      storeObjectClassesWriter.writeEndSequence();
178      store(ocDatabase, encodedObjectClasses, storeObjectClassesWriterBuffer);
179    }
180    catch (final IOException e)
181    {
182      // TODO: Shouldn't happen but should log a message
183    }
184  }
185
186
187
188  /**
189   * Loads the compressed schema information from the database.
190   *
191   * @throws DatabaseException
192   *           If a database error occurs while loading the definitions from the
193   *           database.
194   * @throws InitializationException
195   *           If an error occurs while loading and processing the definitions.
196   */
197  private void load() throws DatabaseException, InitializationException
198  {
199    final DatabaseConfig dbConfig = JEBUtils.toDatabaseConfigNoDuplicates(environment);
200
201    adDatabase = environment.openDatabase(null, DB_NAME_AD, dbConfig);
202    ocDatabase = environment.openDatabase(null, DB_NAME_OC, dbConfig);
203
204    // Cursor through the object class database and load the object class set
205    // definitions. At the same time, figure out the highest token value and
206    // initialize the object class counter to one greater than that.
207    final Cursor ocCursor = ocDatabase.openCursor(null, null);
208    try
209    {
210      final DatabaseEntry keyEntry = new DatabaseEntry();
211      final DatabaseEntry valueEntry = new DatabaseEntry();
212      OperationStatus status = ocCursor.getFirst(keyEntry, valueEntry, READ_UNCOMMITTED);
213      while (status == SUCCESS)
214      {
215        final byte[] encodedObjectClasses = keyEntry.getData();
216        final ASN1Reader reader = ASN1.getReader(valueEntry.getData());
217        reader.readStartSequence();
218        final List<String> objectClassNames = new LinkedList<>();
219        while (reader.hasNextElement())
220        {
221          objectClassNames.add(reader.readOctetStringAsString());
222        }
223        reader.readEndSequence();
224        loadObjectClasses(encodedObjectClasses, objectClassNames);
225        status = ocCursor.getNext(keyEntry, valueEntry, READ_UNCOMMITTED);
226      }
227    }
228    catch (final IOException e)
229    {
230      logger.traceException(e);
231      throw new InitializationException(
232          ERR_COMPSCHEMA_CANNOT_DECODE_OC_TOKEN.get(e.getMessage()), e);
233    }
234    finally
235    {
236      ocCursor.close();
237    }
238
239    // Cursor through the attribute description database and load the attribute
240    // set definitions.
241    final Cursor adCursor = adDatabase.openCursor(null, null);
242    try
243    {
244      final DatabaseEntry keyEntry = new DatabaseEntry();
245      final DatabaseEntry valueEntry = new DatabaseEntry();
246      OperationStatus status = adCursor.getFirst(keyEntry, valueEntry, READ_UNCOMMITTED);
247      while (status == SUCCESS)
248      {
249        final byte[] encodedAttribute = keyEntry.getData();
250        final ASN1Reader reader = ASN1.getReader(valueEntry.getData());
251        reader.readStartSequence();
252        final String attributeName = reader.readOctetStringAsString();
253        final List<String> attributeOptions = new LinkedList<>();
254        while (reader.hasNextElement())
255        {
256          attributeOptions.add(reader.readOctetStringAsString());
257        }
258        reader.readEndSequence();
259        loadAttribute(encodedAttribute, attributeName, attributeOptions);
260        status = adCursor.getNext(keyEntry, valueEntry, READ_UNCOMMITTED);
261      }
262    }
263    catch (final IOException e)
264    {
265      logger.traceException(e);
266      throw new InitializationException(
267          ERR_COMPSCHEMA_CANNOT_DECODE_AD_TOKEN.get(e.getMessage()), e);
268    }
269    finally
270    {
271      adCursor.close();
272    }
273  }
274
275
276
277  private void store(final Database database, final byte[] key, final ByteStringBuilder value) throws DirectoryException
278  {
279    if (!putNoOverwrite(database, key, value))
280    {
281      final LocalizableMessage m = ERR_JEB_COMPSCHEMA_CANNOT_STORE_MULTIPLE_FAILURES.get();
282      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), m);
283    }
284  }
285
286  private boolean putNoOverwrite(final Database database, final byte[] key, final ByteStringBuilder value)
287      throws DirectoryException
288  {
289    final DatabaseEntry keyEntry = new DatabaseEntry(key);
290    final DatabaseEntry valueEntry = new DatabaseEntry(value.getBackingArray(), 0, value.length());
291    for (int i = 0; i < 3; i++)
292    {
293      try
294      {
295        final OperationStatus status = database.putNoOverwrite(null, keyEntry, valueEntry);
296        if (status != SUCCESS)
297        {
298          final LocalizableMessage m = ERR_JEB_COMPSCHEMA_CANNOT_STORE_STATUS.get(status);
299          throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), m);
300        }
301        return true;
302      }
303      catch (final LockConflictException ce)
304      {
305        continue;
306      }
307      catch (final DatabaseException de)
308      {
309        throw new DirectoryException(
310            DirectoryServer.getServerErrorResultCode(), ERR_COMPSCHEMA_CANNOT_STORE_EX.get(de.getMessage()), de);
311      }
312    }
313    return false;
314  }
315
316}