PDBStorage.java revision a069e2643d6be5b34309179220b2777d42df13fc
/*
* 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
* 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 2014-2015 ForgeRock AS
*/
/** PersistIt database implementation of the {@link Storage} engine. */
public final class PDBStorage implements Storage, Backupable, ConfigurationChangeListener<PDBBackendCfg>,
{
private static final double MAX_SLEEP_ON_RETRY_MS = 50.0;
/** The buffer / page size used by the PersistIt storage. */
/** PersistIt implementation of the {@link Cursor} interface. */
{
private ByteString currentKey;
private ByteString currentValue;
{
}
public void close()
{
// Release immediately because this exchange did not come from the txn cache
}
public boolean isDefined() {
}
public ByteString getKey()
{
if (currentKey == null)
{
}
return currentKey;
}
public ByteString getValue()
{
if (currentValue == null)
{
}
return currentValue;
}
public boolean next()
{
try
{
}
catch (final PersistitException e)
{
throw new StorageRuntimeException(e);
}
}
{
try
{
}
catch (final PersistitException e)
{
throw new StorageRuntimeException(e);
}
}
{
try
{
}
catch (final PersistitException e)
{
throw new StorageRuntimeException(e);
}
}
public boolean positionToIndex(int index)
{
// There doesn't seem to be a way to optimize this using Persistit.
try
{
for (int i = 0; i <= index; i++)
{
{
return false;
}
}
return true;
}
catch (final PersistitException e)
{
throw new StorageRuntimeException(e);
}
}
public boolean positionToLastKey()
{
try
{
}
catch (final PersistitException e)
{
throw new StorageRuntimeException(e);
}
}
private void clearCurrentKeyAndValue()
{
currentKey = null;
currentValue = null;
}
private void throwIfUndefined() {
if (!isDefined()) {
throw new NoSuchElementException();
}
}
}
/** PersistIt implementation of the {@link Importer} interface. */
private final class ImporterImpl implements Importer
{
private final ThreadLocal<Map<TreeName, Exchange>> exchanges = new ThreadLocal<Map<TreeName, Exchange>>()
{
{
return value;
}
};
public void close()
{
{
{
}
}
PDBStorage.this.close();
}
{
}
{
try
{
}
catch (PersistitException e)
{
throw new StorageRuntimeException(e);
}
finally
{
}
}
{
try
{
ex.removeTree();
}
catch (PersistitException e)
{
throw new StorageRuntimeException(e);
}
finally
{
}
}
{
{
}
}
{
try
{
}
catch (final Exception e)
{
throw new StorageRuntimeException(e);
}
}
{
try
{
}
catch (final PersistitException e)
{
throw new StorageRuntimeException(e);
}
}
{
{
}
return exchange;
}
{
try
{
}
catch (PersistitException e)
{
throw new StorageRuntimeException(e);
}
}
}
/** Common interface for internal WriteableTransaction implementations. */
}
/** PersistIt implementation of the {@link WriteableTransaction} interface. */
private final class WriteableStorageImpl implements StorageImpl
{
{
try
{
}
catch (final Exception e)
{
throw new StorageRuntimeException(e);
}
}
{
try
{
}
catch (final PersistitException e)
{
throw new StorageRuntimeException(e);
}
}
{
try
{
ex.removeTree();
}
catch (final PersistitException e)
{
throw new StorageRuntimeException(e);
}
finally
{
}
}
{
{
long count = 0;
{
count++;
}
return count;
}
}
{
try
{
/*
* Acquire a new exchange for the cursor rather than using a cached
* exchange in order to avoid reentrant accesses to the same tree
* interfering with the cursor position.
*/
}
catch (final PersistitException e)
{
throw new StorageRuntimeException(e);
}
}
{
if (createOnDemand)
{
}
else
{
try
{
}
catch (final PersistitException e)
{
throw new StorageRuntimeException(e);
}
}
}
{
try
{
}
catch (final PersistitException e)
{
throw new StorageRuntimeException(e);
}
}
{
try
{
{
{
}
else
{
}
return true;
}
return false;
}
catch (final Exception e)
{
throw new StorageRuntimeException(e);
}
}
{
try
{
// Work around a problem with forced shutdown right after tree creation.
// Tree operations are not part of the journal, so force a couple operations to be able to recover.
}
catch (final PersistitException e)
{
throw new StorageRuntimeException(e);
}
finally
{
}
}
{
{
}
return exchange;
}
public void close()
{
{
}
}
}
/** PersistIt read-only implementation of {@link StorageImpl} interface. */
private final class ReadOnlyStorageImpl implements StorageImpl {
private final WriteableStorageImpl delegate;
{
}
{
}
{
}
{
}
{
if (createOnDemand)
{
throw new ReadOnlyStorageException();
}
try
{
}
catch (final TreeNotFoundException e)
{
// ignore missing trees.
}
catch (final PersistitException e)
{
throw new StorageRuntimeException(e);
}
finally
{
}
}
public void close()
{
}
{
throw new ReadOnlyStorageException();
}
{
throw new ReadOnlyStorageException();
}
{
throw new ReadOnlyStorageException();
}
{
throw new ReadOnlyStorageException();
}
}
private Exchange getNewExchange(final TreeName treeName, final boolean create) throws PersistitException
{
}
private StorageImpl newStorageImpl() {
}
private final ServerContext serverContext;
private final File backendDirectory;
private AccessMode accessMode;
private PDBBackendCfg config;
private DiskSpaceMonitor diskMonitor;
private PDBMonitor pdbMonitor;
private MemoryQuota memQuota;
/**
* Creates a new persistit storage with the provided configuration.
*
* @param cfg
* The configuration.
* @param serverContext
* This server instance context
* @throws ConfigException if memory cannot be reserved
*/
// FIXME: should be package private once importer is decoupled.
{
this.serverContext = serverContext;
cfg.addPDBChangeListener(this);
}
private Configuration buildImportConfiguration()
{
return dbCfg;
}
{
this.accessMode = accessMode;
// Volume is opened read write because recovery will fail if opened read-only
dbCfg.setVolumeList(asList(new VolumeSpecification(new File(backendDirectory, VOLUME_NAME).getPath(), null,
{
}
else
{
}
dbCfg.setJmxEnabled(false);
return dbCfg;
}
public void close()
{
{
pdbMonitor = null;
try
{
}
catch (final PersistitException e)
{
throw new IllegalStateException(e);
}
}
{
}
else
{
}
config.removePDBChangeListener(this);
}
{
}
{
}
{
try
{
{
throw new IllegalStateException(
"Database is already open, either the backend is enabled or an import is currently running.");
}
db.initialize();
}
catch(final InUseException e) {
throw new StorageInUseException(e);
}
catch (final PersistitException e)
{
throw new StorageRuntimeException(e);
}
getDirectory(),
this);
}
{
for (;;)
{
try
{
{
return result;
}
catch (final StorageRuntimeException e)
{
{
}
throw e;
}
}
catch (final RollbackException e)
{
// retry
}
catch (final Exception e)
{
throw e;
}
finally
{
}
}
}
{
return new ImporterImpl();
}
{
{
{
ch = '_';
}
}
}
{
for (;;)
{
try
{
{
return;
}
catch (final StorageRuntimeException e)
{
{
}
throw e;
}
}
catch (final RollbackException e)
{
// retry after random sleep (reduces transactions collision. Drawback: increased latency)
}
catch (final Exception e)
{
throw e;
}
finally
{
}
}
}
public boolean supportsBackupAndRestore()
{
return true;
}
public File getDirectory()
{
}
{
try
{
{
return getFilesToBackupWhenOffline();
}
// FIXME: use full programmatic way of retrieving backup file once available in persistIt
// When requesting files to backup, append only mode must also be set (-a) otherwise it will be ended
// by PersistIt and performing backup may corrupt the DB.
{
}
return files.listIterator();
}
catch (Exception e)
{
}
}
/** Filter to retrieve the database files to backup. */
{
{
}
};
/**
* Returns the list of files to backup when there is no open database.
* <p>
* It is not possible to rely on the database returning the files, so the files must be retrieved
* from a file filter.
*/
{
return BackupManager.getFiles(getDirectory(), BACKUP_FILES_FILTER, config.getBackendId()).listIterator();
}
{
return null;
}
public boolean isDirectRestore()
{
// restore is done in an intermediate directory
return false;
}
{
// intermediate directory content is moved to database directory
try
{
}
catch(IOException e)
{
LocalizableMessage msg = ERR_CANNOT_RENAME_RESTORE_DIRECTORY.get(restoreDirectory, targetDirectory.getPath());
}
}
/**
* Switch the database in append only mode.
* <p>
* This is a mandatory operation before performing a backup.
*/
private void switchToAppendOnlyMode() throws DirectoryException
{
try
{
// FIXME: use full programmatic way of switching to this mode once available in persistIt
}
catch (RemoteException e)
{
}
}
/**
* Terminate the append only mode of the database.
* <p>
* This should be called only when database was previously switched to append only mode.
*/
private void endAppendOnlyMode() throws DirectoryException
{
try
{
// FIXME: use full programmatic way of ending append mode once available in persistIt
}
catch (RemoteException e)
{
}
}
{
{
}
try
{
}
finally
{
{
}
}
}
public void removeBackup(BackupDirectory backupDirectory, String backupID) throws DirectoryException
{
}
{
}
{
try
{
{
}
return results;
}
catch (PersistitException e)
{
throw new StorageRuntimeException(e);
}
}
/**
* inefficient at the moment for simple byte arrays.
*/
{
}
{
return value;
}
{
{
}
return null;
}
{
}
{
return cfg.getDBCacheSize() > 0 ? cfg.getDBCacheSize() : memQuota.memPercentToBytes(cfg.getDBCachePercent());
}
/**
* Checks newly created backend has a valid configuration.
* @param cfg the new configuration
* @param unacceptableReasons the list of accumulated errors and their messages
* @param context TODO
* @return true if newly created backend has a valid configuration
*/
static boolean isConfigurationAcceptable(PDBBackendCfg cfg, List<LocalizableMessage> unacceptableReasons,
{
{
{
return false;
}
{
return false;
}
}
}
{
{
return false;
}
return true;
}
/**
* Checks a directory exists or can actually be created.
*
* @param backendDir the directory to check for
* @param ccr the list of reasons to return upstream or null if called from setupStorage()
* @param cleanup true if the directory should be deleted after creation
*/
private static void checkDBDirExistsOrCanCreate(File backendDir, ConfigChangeResult ccr, boolean cleanup)
{
if (!backendDir.exists())
{
if(!backendDir.mkdirs())
{
}
if (cleanup)
{
backendDir.delete();
}
}
else if (!backendDir.isDirectory())
{
}
}
/**
* Returns false if directory permissions in the configuration are invalid. Otherwise returns the
* same value as it was passed in.
*
* @param cfg a (possibly new) backend configuration
* @param ccr the current list of change results
* @throws forwards a file exception
*/
{
try
{
// Make sure the mode will allow the server itself access to the database
if(!backendPermission.isOwnerWritable() ||
{
}
}
catch(ConfigException ce)
{
}
}
/**
* Sets files permissions on the backend directory.
*
* @param backendDir the directory to setup
* @param curCfg a backend configuration
*/
{
// Get the backend database backendDirectory permissions and apply
try
{
{
}
}
catch(Exception e)
{
// Log an warning that the permissions were not set.
}
}
{
try
{
}
catch (Exception e)
{
}
}
{
try
{
// Create the directory if it doesn't exist.
{
{
return ccr;
}
ccr.setAdminActionRequired(true);
ccr.addMessage(NOTE_CONFIG_DB_DIR_REQUIRES_RESTART.get(config.getDBDirectory(), cfg.getDBDirectory()));
}
{
{
return ccr;
}
}
getDirectory(),
this);
}
catch (Exception e)
{
}
return ccr;
}
{
}
private void setupStorageFiles() throws ConfigException
{
{
}
{
}
}
public void removeStorageFiles() throws StorageRuntimeException
{
if (!backendDirectory.exists())
{
return;
}
if (!backendDirectory.isDirectory())
{
throw new StorageRuntimeException(ERR_DIRECTORY_INVALID.get(backendDirectory.getPath()).toString());
}
try
{
{
f.delete();
}
}
catch (Exception e)
{
logger.traceException(e);
}
}
public StorageStatus getStorageStatus()
{
return storageStatus;
}
}
}
public void diskSpaceRestored(File directory, long lowThresholdInBytes, long fullThresholdInBytes) {
}
}