mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

Yannick Lecaillez
03.45.2015 005e0af2b1779bdc5c2074c7fad78158c58cce2e
opendj-server-legacy/src/main/java/org/opends/server/backends/pdb/PDBStorage.java
@@ -32,6 +32,7 @@
import static org.opends.messages.UtilityMessages.*;
import static org.opends.server.util.StaticUtils.*;
import java.io.Closeable;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
@@ -63,6 +64,7 @@
import org.opends.server.api.DiskSpaceMonitorHandler;
import org.opends.server.backends.pluggable.spi.Cursor;
import org.opends.server.backends.pluggable.spi.Importer;
import org.opends.server.backends.pluggable.spi.ReadOnlyStorageException;
import org.opends.server.backends.pluggable.spi.ReadOperation;
import org.opends.server.backends.pluggable.spi.Storage;
import org.opends.server.backends.pluggable.spi.StorageInUseException;
@@ -96,6 +98,7 @@
import com.persistit.exception.InUseException;
import com.persistit.exception.PersistitException;
import com.persistit.exception.RollbackException;
import com.persistit.exception.TreeNotFoundException;
/** PersistIt database implementation of the {@link Storage} engine. */
@SuppressWarnings("javadoc")
@@ -110,7 +113,7 @@
  private static final int BUFFER_SIZE = 16 * 1024;
  /** PersistIt implementation of the {@link Cursor} interface. */
  private final class CursorImpl implements Cursor<ByteString, ByteString>
  private static final class CursorImpl implements Cursor<ByteString, ByteString>
  {
    private ByteString currentKey;
    private ByteString currentValue;
@@ -125,7 +128,7 @@
    public void close()
    {
      // Release immediately because this exchange did not come from the txn cache
      db.releaseExchange(exchange);
      exchange.getPersistitInstance().releaseExchange(exchange);
    }
    @Override
@@ -255,7 +258,7 @@
  /** PersistIt implementation of the {@link Importer} interface. */
  private final class ImporterImpl implements Importer
  {
    private final Map<TreeName, Tree> trees = new HashMap<TreeName, Tree>();
    private final Map<TreeName, Tree> trees = new HashMap<>();
    private final Queue<Map<TreeName, Exchange>> allExchanges = new ConcurrentLinkedDeque<>();
    private final ThreadLocal<Map<TreeName, Exchange>> exchanges = new ThreadLocal<Map<TreeName, Exchange>>()
    {
@@ -356,10 +359,14 @@
    }
  }
  /** Common interface for internal WriteableTransaction implementations. */
  private interface StorageImpl extends WriteableTransaction, Closeable {
  }
  /** PersistIt implementation of the {@link WriteableTransaction} interface. */
  private final class StorageImpl implements WriteableTransaction
  private final class WriteableStorageImpl implements StorageImpl
  {
    private final Map<TreeName, Exchange> exchanges = new HashMap<TreeName, Exchange>();
    private final Map<TreeName, Exchange> exchanges = new HashMap<>();
    @Override
    public void put(final TreeName treeName, final ByteSequence key, final ByteSequence value)
@@ -541,7 +548,8 @@
      return exchange;
    }
    private void release()
    @Override
    public void close()
    {
      for (final Exchange ex : exchanges.values())
      {
@@ -551,14 +559,107 @@
    }
  }
  /** PersistIt read-only implementation of {@link StorageImpl} interface. */
  private final class ReadOnlyStorageImpl implements StorageImpl {
    private final WriteableStorageImpl delegate;
    ReadOnlyStorageImpl(WriteableStorageImpl delegate)
    {
      this.delegate = delegate;
    }
    @Override
    public ByteString read(TreeName treeName, ByteSequence key)
    {
      return delegate.read(treeName, key);
    }
    @Override
    public Cursor<ByteString, ByteString> openCursor(TreeName treeName)
    {
      return delegate.openCursor(treeName);
    }
    @Override
    public long getRecordCount(TreeName treeName)
    {
      return delegate.getRecordCount(treeName);
    }
    @Override
    public void openTree(TreeName treeName)
    {
      Exchange ex = null;
      try
      {
        ex = getNewExchange(treeName, false);
      }
      catch (final TreeNotFoundException e)
      {
        throw new ReadOnlyStorageException();
      }
      catch (final PersistitException e)
      {
        throw new StorageRuntimeException(e);
      }
      finally
      {
        db.releaseExchange(ex);
      }
    }
    @Override
    public void close()
    {
      delegate.close();
    }
    @Override
    public void renameTree(TreeName oldName, TreeName newName)
    {
      throw new ReadOnlyStorageException();
    }
    @Override
    public void deleteTree(TreeName name)
    {
      throw new ReadOnlyStorageException();
    }
    @Override
    public void put(TreeName treeName, ByteSequence key, ByteSequence value)
    {
      throw new ReadOnlyStorageException();
    }
    @Override
    public boolean update(TreeName treeName, ByteSequence key, UpdateFunction f)
    {
      throw new ReadOnlyStorageException();
    }
    @Override
    public boolean delete(TreeName treeName, ByteSequence key)
    {
      throw new ReadOnlyStorageException();
    }
  }
  private Exchange getNewExchange(final TreeName treeName, final boolean create) throws PersistitException
  {
    return db.getExchange(volume, mangleTreeName(treeName), create);
  }
  private StorageImpl newStorageImpl() {
    final WriteableStorageImpl writeableStorage = new WriteableStorageImpl();
    return accessMode.equals(AccessMode.READ_ONLY) ? new ReadOnlyStorageImpl(writeableStorage) : writeableStorage;
  }
  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
  private final ServerContext serverContext;
  private final File backendDirectory;
  private AccessMode accessMode;
  private Persistit db;
  private Volume volume;
  private PDBBackendCfg config;
@@ -586,11 +687,13 @@
  private Configuration buildConfiguration(AccessMode accessMode)
  {
    this.accessMode = accessMode;
    final Configuration dbCfg = new Configuration();
    dbCfg.setLogFile(new File(backendDirectory, VOLUME_NAME + ".log").getPath());
    dbCfg.setJournalPath(new File(backendDirectory, JOURNAL_NAME).getPath());
    dbCfg.setVolumeList(asList(new VolumeSpecification(new File(backendDirectory, VOLUME_NAME).getPath(), null,
        BUFFER_SIZE, 4096, Long.MAX_VALUE / BUFFER_SIZE, 2048, true, false, accessMode.equals(AccessMode.READ_ONLY))));
        BUFFER_SIZE, 4096, Long.MAX_VALUE / BUFFER_SIZE, 2048, true, false, false)));
    final BufferPoolConfiguration bufferPoolCfg = getBufferPoolCfg(dbCfg);
    bufferPoolCfg.setMaximumCount(Integer.MAX_VALUE);
@@ -696,8 +799,7 @@
      txn.begin();
      try
      {
        final StorageImpl storageImpl = new StorageImpl();
        try
        try (final StorageImpl storageImpl = newStorageImpl())
        {
          final T result = operation.run(storageImpl);
          txn.commit();
@@ -711,10 +813,6 @@
          }
          throw e;
        }
        finally
        {
          storageImpl.release();
        }
      }
      catch (final RollbackException e)
      {
@@ -767,8 +865,7 @@
      txn.begin();
      try
      {
        final StorageImpl storageImpl = new StorageImpl();
        try
        try (final StorageImpl storageImpl = newStorageImpl())
        {
          operation.run(storageImpl);
          txn.commit();
@@ -778,14 +875,10 @@
        {
          if (e.getCause() != null)
          {
              throw (Exception) e.getCause();
            throw (Exception) e.getCause();
          }
          throw e;
        }
        finally
        {
          storageImpl.release();
        }
      }
      catch (final RollbackException e)
      {
opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/spi/ReadOnlyStorageException.java
New file
@@ -0,0 +1,76 @@
/*
 * 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 2015 ForgeRock AS
 */
package org.opends.server.backends.pluggable.spi;
/**
 * Thrown when the server or a tool attempts to access the storage while it is read-only.
 */
@SuppressWarnings("serial")
public final class ReadOnlyStorageException extends StorageRuntimeException
{
  /** Constructor with default error message. */
  public ReadOnlyStorageException()
  {
    this("This storage is read-only.");
  }
  /**
   * Constructor with a message and a cause.
   *
   * @param message
   *          the exception message
   * @param cause
   *          the cause of the exception
   */
  public ReadOnlyStorageException(String message, Throwable cause)
  {
    super(message, cause);
  }
  /**
   * Constructor with a message.
   *
   * @param message
   *          the exception message
   */
  public ReadOnlyStorageException(String message)
  {
    super(message);
  }
  /**
   * Constructor with a cause.
   *
   * @param cause
   *          the cause of the exception
   */
  public ReadOnlyStorageException(Throwable cause)
  {
    super(cause);
  }
}
opendj-server-legacy/src/test/java/org/opends/server/backends/pluggable/PluggableBackendImplTestCase.java
@@ -57,6 +57,12 @@
import org.opends.server.admin.std.server.PluggableBackendCfg;
import org.opends.server.api.Backend.BackendOperation;
import org.opends.server.api.ClientConnection;
import org.opends.server.backends.pluggable.spi.ReadOnlyStorageException;
import org.opends.server.backends.pluggable.spi.Storage;
import org.opends.server.backends.pluggable.spi.Storage.AccessMode;
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.protocols.internal.InternalClientConnection;
import org.opends.server.protocols.internal.InternalSearchOperation;
@@ -922,4 +928,38 @@
    assertEquals(ldifString.contains(testBaseDN.toString()), true, "Export without rootDN");
    assertEquals(ldifString.contains(searchDN.toString()), true, "Export without rootDN");
  }
  @Test(expectedExceptions=ReadOnlyStorageException.class)
  public void testReadOnly() throws Exception
  {
    C backendCfg = createBackendCfg();
    when(backendCfg.dn()).thenReturn(testBaseDN);
    when(backendCfg.getBackendId()).thenReturn(backendTestName);
    when(backendCfg.getBaseDN()).thenReturn(newSortedSet(testBaseDN));
    when(backendCfg.listBackendIndexes()).thenReturn(new String[0]);
    when(backendCfg.listBackendVLVIndexes()).thenReturn(new String[0]);
    final Storage storage = backend.configureStorage(backendCfg, DirectoryServer.getInstance().getServerContext());
    final RootContainer readOnlyContainer = new RootContainer(backend.getBackendID(), storage, backendCfg);
    // Put backend offline so that export LDIF open read-only container
    backend.finalizeBackend();
    try
    {
      readOnlyContainer.open(AccessMode.READ_ONLY);
      readOnlyContainer.getStorage().write(new WriteOperation()
      {
        @Override
        public void run(WriteableTransaction txn) throws Exception
        {
          txn.put(new TreeName("dc=test,dc=com", "id2entry"), ByteString.valueOf("key"), ByteString.valueOf("value"));
        }
      });
    }
    finally
    {
      readOnlyContainer.close();
      backend.openBackend();
    }
  }
}