From 74fea9c73aa679eebe68f78d34ae80fa0f263c24 Mon Sep 17 00:00:00 2001
From: Fabio Pistolesi <fabio.pistolesi@forgerock.com>
Date: Fri, 21 Oct 2016 09:41:24 +0000
Subject: [PATCH] OPENDJ-3400 Fake a no-operation storage for offline commands when a suffix has no files

---
 opendj-server-legacy/src/main/java/org/opends/server/backends/jeb/JEStorage.java |  117 ++++++++++++++++++++++++++++++++++++++++++++++++++++++----
 1 files changed, 108 insertions(+), 9 deletions(-)

diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/jeb/JEStorage.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/jeb/JEStorage.java
index e6115f3..210a267 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/jeb/JEStorage.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/jeb/JEStorage.java
@@ -56,6 +56,7 @@
 import org.forgerock.opendj.server.config.server.JEBackendCfg;
 import org.opends.server.api.Backupable;
 import org.opends.server.api.DiskSpaceMonitorHandler;
+import org.opends.server.backends.pluggable.spi.EmptyCursor;
 import org.opends.server.backends.pluggable.spi.AccessMode;
 import org.opends.server.backends.pluggable.spi.Cursor;
 import org.opends.server.backends.pluggable.spi.Importer;
@@ -352,7 +353,7 @@
      * <ol>
      * <li>Opening the EntryContainer calls {@link #openTree(TreeName, boolean)} for each index</li>
      * <li>Then the underlying storage is closed</li>
-     * <li>Then {@link Importer#startImport()} is called</li>
+     * <li>Then {@link #startImport()} is called</li>
      * <li>Then ID2Entry#put() is called</li>
      * <li>Which in turn calls ID2Entry#encodeEntry()</li>
      * <li>Which in turn finally calls PersistentCompressedSchema#store()</li>
@@ -520,7 +521,7 @@
     }
   }
 
-  /** JE read-only implementation of {@link StorageImpl} interface. */
+  /** JE read-only implementation of {@link WriteableTransaction} interface. */
   private final class ReadOnlyTransactionImpl implements WriteableTransaction
   {
     private final WriteableTransactionImpl delegate;
@@ -583,8 +584,69 @@
     }
   }
 
+  /** No operation storage transaction faking database files are present and empty. */
+  private final class ReadOnlyEmptyTransactionImpl implements WriteableTransaction
+  {
+    @Override
+    public void openTree(TreeName name, boolean createOnDemand)
+    {
+      if (createOnDemand)
+      {
+        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();
+    }
+
+    @Override
+    public ByteString read(TreeName treeName, ByteSequence key)
+    {
+      return null;
+    }
+
+    @Override
+    public Cursor<ByteString, ByteString> openCursor(TreeName treeName)
+    {
+      return new EmptyCursor<>();
+    }
+
+    @Override
+    public long getRecordCount(TreeName treeName)
+    {
+      return 0;
+    }
+  }
+
   private WriteableTransaction newWriteableTransaction(Transaction txn)
   {
+    // If no database files have been created yet and we're opening READ-ONLY
+    // there is no db to use, since open was not called. Fake it.
+    if (env == null)
+    {
+      return new ReadOnlyEmptyTransactionImpl();
+    }
     final WriteableTransactionImpl writeableStorage = new WriteableTransactionImpl(txn);
     return accessMode.isWriteable() ? writeableStorage : new ReadOnlyTransactionImpl(writeableStorage);
   }
@@ -601,6 +663,7 @@
   private JEBackendCfg config;
   private AccessMode accessMode;
 
+  /** It is NULL when opening the storage READ-ONLY and no files have been created yet. */
   private Environment env;
   private EnvironmentConfig envConfig;
   private MemoryQuota memQuota;
@@ -713,26 +776,58 @@
       }
     }
 
-    if (config.getDBCacheSize() > 0)
+    if (memQuota != null)
     {
-      memQuota.releaseMemory(config.getDBCacheSize());
-    }
-    else
-    {
-      memQuota.releaseMemory(memQuota.memPercentToBytes(config.getDBCachePercent()));
+      if (config.getDBCacheSize() > 0)
+      {
+        memQuota.releaseMemory(config.getDBCacheSize());
+      }
+      else
+      {
+        memQuota.releaseMemory(memQuota.memPercentToBytes(config.getDBCachePercent()));
+      }
     }
     config.removeJEChangeListener(this);
-    diskMonitor.deregisterMonitoredDirectory(getDirectory(), this);
+    envConfig = null;
+    if (diskMonitor != null)
+    {
+      diskMonitor.deregisterMonitoredDirectory(getDirectory(), this);
+    }
   }
 
   @Override
   public void open(AccessMode accessMode) throws ConfigException, StorageRuntimeException
   {
     Reject.ifNull(accessMode, "accessMode must not be null");
+    if (isBackendIncomplete(accessMode))
+    {
+      envConfig = new EnvironmentConfig();
+      envConfig.setAllowCreate(false).setTransactional(false);
+      // Do not open files on disk
+      return;
+    }
     buildConfiguration(accessMode, false);
     open0();
   }
 
+  private boolean isBackendIncomplete(AccessMode accessMode)
+  {
+    return !accessMode.isWriteable() && (!backendDirectory.exists() || backendDirectoryIncomplete());
+  }
+
+  // TODO: it belongs to disk-based Storage Interface.
+  private boolean backendDirectoryIncomplete()
+  {
+    try
+    {
+      return !getFilesToBackup().hasNext();
+    }
+    catch (DirectoryException ignored)
+    {
+      return true;
+    }
+  }
+
   private void open0() throws ConfigException
   {
     setupStorageFiles(backendDirectory, config.getDBDirectoryPermissions(), config.dn());
@@ -1073,6 +1168,10 @@
   @Override
   public Set<TreeName> listTrees()
   {
+    if (env == null)
+    {
+      return Collections.<TreeName>emptySet();
+    }
     try
     {
       List<String> treeNames = env.getDatabaseNames();

--
Gitblit v1.10.0