TaskBackend.java revision 763a75aeed1a7731ddb95b99496aa7c1bf206ed0
/*
* 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 2006-2009 Sun Microsystems, Inc.
* Portions Copyright 2011-2015 ForgeRock AS
*/
/**
* This class provides an implementation of a Directory Server backend that may
* be used to execute various kinds of administrative tasks on a one-time or
* recurring basis.
*/
public class TaskBackend
extends Backend<TaskBackendCfg>
implements ConfigurationChangeListener<TaskBackendCfg>
{
/** The current configuration state. */
private TaskBackendCfg currentConfig;
/** The DN of the configuration entry for this backend. */
private DN configEntryDN;
/**
* The DN of the entry that will serve as the parent for all recurring task
* entries.
*/
private DN recurringTaskParentDN;
/**
* The DN of the entry that will serve as the parent for all scheduled task
* entries.
*/
private DN scheduledTaskParentDN;
/** The DN of the entry that will serve as the root for all task entries. */
private DN taskRootDN;
/** The set of base DNs defined for this backend. */
/**
* The length of time in seconds after a task is completed that it should be
* removed from the set of scheduled tasks.
*/
private long retentionTime;
/** The e-mail address to use for the sender from notification messages. */
private String notificationSenderAddress;
/** The path to the task backing file. */
private String taskBackingFile;
/**
* The task scheduler that will be responsible for actually invoking scheduled
* tasks.
*/
private TaskScheduler taskScheduler;
/**
* Creates a new backend with the provided information. All backend
* implementations must implement a default constructor that use
* <CODE>super()</CODE> to invoke this constructor.
*/
public TaskBackend()
{
super();
// Perform all initialization in initializeBackend.
}
/** {@inheritDoc} */
{
// Make sure that the provided set of base DNs contains exactly one value.
// We will only allow one base for task entries.
{
}
{
throw new ConfigException(message);
}
else
{
try
{
}
catch (Exception e)
{
logger.traceException(e);
// This should never happen.
throw new ConfigException(message, e);
}
try
{
}
catch (Exception e)
{
logger.traceException(e);
// This should never happen.
throw new ConfigException(message, e);
}
}
// Get the retention time that will be used to determine how long task
// information stays around once the associated task is completed.
// Get the notification sender address.
if (notificationSenderAddress == null)
{
try
{
notificationSenderAddress = "opendj-task-notification@" +
}
catch (Exception e)
{
notificationSenderAddress = "opendj-task-notification@opendj.org";
}
}
// Get the path to the task data backing file.
currentConfig = cfg;
}
/** {@inheritDoc} */
public void initializeBackend()
{
// Create the scheduler and initialize it from the backing file.
taskScheduler = new TaskScheduler(this);
// Register with the Directory Server as a configurable component.
// Register the task base as a private suffix.
try
{
}
catch (Exception e)
{
logger.traceException(e);
throw new InitializationException(message, e);
}
}
/** {@inheritDoc} */
public void finalizeBackend()
{
super.finalizeBackend();
try
{
}
catch (Exception e)
{
logger.traceException(e);
}
try
{
message, true);
}
catch (Exception e)
{
logger.traceException(e);
}
try
{
}
catch (Exception e)
{
logger.traceException(e);
}
}
/** {@inheritDoc} */
public DN[] getBaseDNs()
{
return baseDNs;
}
/** {@inheritDoc} */
public long getEntryCount()
{
if (taskScheduler != null)
{
return taskScheduler.getEntryCount();
}
return -1;
}
/** {@inheritDoc} */
{
// All searches in this backend will always be considered indexed.
return true;
}
/** {@inheritDoc} */
throws DirectoryException
{
if(ret < 0)
{
return ConditionResult.UNDEFINED;
}
}
/** {@inheritDoc} */
throws DirectoryException
{
{
return -1;
}
{
// scheduled and recurring parents.
if(!subtree)
{
return 2;
}
else
{
return taskScheduler.getScheduledTaskCount() +
}
}
{
return taskScheduler.getScheduledTaskCount();
}
{
return taskScheduler.getRecurringTaskCount();
}
{
return -1;
}
{
return 0;
}
{
return 0;
}
else
{
return -1;
}
}
/** {@inheritDoc} */
throws DirectoryException
{
{
return null;
}
try
{
{
return taskScheduler.getTaskRootEntry();
}
{
return taskScheduler.getScheduledTaskParentEntry();
}
{
return taskScheduler.getRecurringTaskParentEntry();
}
{
return null;
}
{
}
{
}
else
{
// If we've gotten here then this is not an entry
// that should exist in the task backend.
return null;
}
}
finally
{
}
}
/** {@inheritDoc} */
throws DirectoryException
{
// Get the DN for the entry and then get its parent.
{
}
// If the parent DN is equal to the parent for scheduled tasks, then try to
// treat the provided entry like a scheduled task.
{
return;
}
// If the parent DN is equal to the parent for recurring tasks, then try to
// treat the provided entry like a recurring task.
{
return;
}
// We won't allow the entry to be added.
}
/** {@inheritDoc} */
throws DirectoryException
{
// Get the parent for the provided entry DN. It must be either the
// scheduled or recurring task parent DN.
{
}
{
// It's a scheduled task. Make sure that it exists.
if (t == null)
{
}
// Look at the state of the task. We will allow pending and completed
// tasks to be removed, but not running tasks.
{
if (t.isRecurring()) {
long scheduledStartTime = t.getScheduledStartTime();
if (scheduledStartTime < currentSystemTime) {
}
calendar);
} else {
}
}
{
}
else
{
}
}
{
// It's a recurring task. Make sure that it exists.
{
}
}
else
{
}
}
/** {@inheritDoc} */
{
if (! taskScheduler.holdsSchedulerLock())
{
{
}
}
try
{
// Get the parent for the provided entry DN. It must be either the
// scheduled or recurring task parent DN.
{
}
{
// It's a scheduled task. Make sure that it exists.
if (t == null)
{
}
// Look at the state of the task. We will allow anything to be altered
// for a pending task. For a running task, we will only allow the state
// to be altered in order to cancel it. We will not allow any
// modifications for completed tasks.
{
return;
}
{
// If the task is running, we will only allow it to be cancelled.
// This will only be allowed using the replace modification type on
// the ds-task-state attribute if the value starts with "cancel" or
// "stop". In that case, we'll cancel the task.
if (acceptable)
{
return;
}
else
{
}
}
{
// Pending recurring task iterations can only be canceled.
if (acceptable)
{
if (newTask.getTaskState() ==
{
long scheduledStartTime = t.getScheduledStartTime();
if (scheduledStartTime < currentSystemTime) {
}
}
else if (newTask.getTaskState() ==
{
}
return;
}
else
{
}
}
else
{
}
}
{
// We don't currently support altering recurring tasks.
}
else
{
}
}
finally
{
{
}
}
}
/**
* Helper to determine if requested modifications are acceptable.
* @param modifyOperation associated with requested modifications.
* @return <CODE>true</CODE> if requested modifications are
* acceptable, <CODE>false</CODE> otherwise.
*/
{
boolean acceptable = true;
if (m.isInternal()) {
continue;
}
acceptable = false;
break;
}
Attribute a = m.getAttribute();
acceptable = false;
break;
}
acceptable = false;
break;
}
acceptable = false;
break;
}
acceptable = false;
break;
}
}
return acceptable;
}
/** {@inheritDoc} */
throws DirectoryException
{
}
/** {@inheritDoc} */
throws DirectoryException, CanceledOperationException {
// Look at the base DN and scope for the search operation to decide which
// entries we need to look at.
boolean searchRoot = false;
boolean searchScheduledParent = false;
boolean searchScheduledTasks = false;
boolean searchRecurringParent = false;
boolean searchRecurringTasks = false;
{
switch (searchScope.asEnum())
{
case BASE_OBJECT:
searchRoot = true;
break;
case SINGLE_LEVEL:
searchScheduledParent = true;
searchRecurringParent = true;
break;
case WHOLE_SUBTREE:
searchRoot = true;
searchScheduledParent = true;
searchRecurringParent = true;
searchScheduledTasks = true;
searchRecurringTasks = true;
break;
case SUBORDINATES:
searchScheduledParent = true;
searchRecurringParent = true;
searchScheduledTasks = true;
searchRecurringTasks = true;
break;
}
}
{
switch (searchScope.asEnum())
{
case BASE_OBJECT:
searchScheduledParent = true;
break;
case SINGLE_LEVEL:
searchScheduledTasks = true;
break;
case WHOLE_SUBTREE:
searchScheduledParent = true;
searchScheduledTasks = true;
break;
case SUBORDINATES:
searchScheduledTasks = true;
break;
}
}
{
switch (searchScope.asEnum())
{
case BASE_OBJECT:
searchRecurringParent = true;
break;
case SINGLE_LEVEL:
searchRecurringTasks = true;
break;
case WHOLE_SUBTREE:
searchRecurringParent = true;
searchRecurringTasks = true;
break;
case SUBORDINATES:
searchRecurringTasks = true;
break;
}
}
else
{
{
}
{
try
{
if (e == null)
{
}
&& searchFilter.matchesEntry(e))
{
}
return;
}
finally
{
}
}
{
try
{
if (e == null)
{
}
&& searchFilter.matchesEntry(e))
{
}
return;
}
finally
{
}
}
else
{
}
}
if (searchRoot)
{
{
return;
}
}
{
{
return;
}
}
{
return;
}
{
{
return;
}
}
{
return;
}
}
/** {@inheritDoc} */
{
return Collections.emptySet();
}
/** {@inheritDoc} */
{
return Collections.emptySet();
}
/** {@inheritDoc} */
{
switch (backendOperation)
{
case LDIF_EXPORT:
case BACKUP:
case RESTORE:
return true;
default:
return false;
}
}
/** {@inheritDoc} */
throws DirectoryException
{
// Read from.
try
{
}
catch (Exception e)
{
}
// Write to.
try
{
}
catch (Exception e)
{
logger.traceException(e);
message);
}
// Copy record by record.
try
{
while (true)
{
try
{
e = ldifReader.readEntry();
if (e == null)
{
break;
}
}
catch (LDIFException le)
{
if (! le.canContinueReading())
{
}
else
{
continue;
}
}
ldifWriter.writeEntry(e);
}
}
catch (Exception e)
{
logger.traceException(e);
}
finally
{
}
}
/** {@inheritDoc} */
throws DirectoryException
{
}
/** {@inheritDoc} */
throws DirectoryException
{
// Get the properties to use for the backup. We don't care whether or not
// it's incremental, so there's no need to get that.
// Create a hash map that will hold the extra backup property information
// for this backup.
// Get the crypto manager and use it to obtain references to the message
if (hash)
{
if (signHash)
{
try
{
}
catch (Exception e)
{
logger.traceException(e);
throw new DirectoryException(
e);
}
}
else
{
try
{
}
catch (Exception e)
{
logger.traceException(e);
throw new DirectoryException(
e);
}
}
}
// Create an output stream that will be used to write the archive file. At
// its core, it will be a file output stream to put a file on the disk. If
// we are to encrypt the data, then that file output stream will be wrapped
// in a cipher output stream. The resulting output stream will then be
// wrapped by a zip output stream (which may or may not actually use
// compression).
try
{
filename);
if (archiveFile.exists())
{
int i=1;
while (true)
{
filename + "." + i);
if (archiveFile.exists())
{
i++;
}
else
{
break;
}
}
}
}
catch (Exception e)
{
logger.traceException(e);
}
// If we should encrypt the data, then wrap the output stream in a cipher
// output stream.
if (encrypt)
{
try
{
}
catch (CryptoManagerException e)
{
logger.traceException(e);
message, e);
}
}
// Wrap the file output stream in a zip output stream.
backupID);
if (compress)
{
}
else
{
}
// Take tasks file and write it to the zip stream. If we
// are using a hash or MAC, then calculate that as well.
byte[] buffer = new byte[8192];
// We'll put the name in the hash, too.
if (hash) {
if (signHash) {
} else {
}
}
try {
while (true) {
break;
}
if (hash) {
if (signHash) {
} else {
}
}
}
inputStream.close();
} catch (Exception e) {
logger.traceException(e);
throw new DirectoryException(
message, e);
}
// We're done writing the file, so close the zip stream (which should also
// close the underlying stream).
try
{
}
catch (Exception e)
{
logger.traceException(e);
message, e);
}
// Get the digest or MAC bytes if appropriate.
byte[] digestBytes = null;
if (hash)
{
if (signHash)
{
}
else
{
}
}
// Create the backup info structure for this backup and add it to the backup
// directory.
// FIXME -- Should I use the date from when I started or finished?
try
{
}
catch (Exception e)
{
logger.traceException(e);
message, e);
}
}
/** {@inheritDoc} */
throws DirectoryException
{
if (backupInfo == null)
{
message);
}
try
{
}
catch (ConfigException e)
{
logger.traceException(e);
e.getMessageObject());
}
try
{
}
catch (Exception e)
{
logger.traceException(e);
message, e);
}
// Remove the archive file.
}
/** {@inheritDoc} */
throws DirectoryException
{
// First, make sure that the requested backup exists.
if (backupInfo == null)
{
message);
}
// Read the backup info structure to determine the name of the file that
// contains the archive. Then make sure that file exists.
if (backupFilename == null)
{
message);
}
try
{
if (! backupFile.exists())
{
message);
}
}
catch (DirectoryException de)
{
throw de;
}
catch (Exception e)
{
message, e);
}
// If the backup is hashed, then we need to get the message digest to use
// to verify it.
if (unsignedHash != null)
{
if (digestAlgorithm == null)
{
message);
}
try
{
}
catch (Exception e)
{
message, e);
}
}
// If the backup is signed, then we need to get the MAC to use to verify it.
if (signedHash != null)
{
{
message);
}
try
{
}
catch (Exception e)
{
message, e);
}
}
// Create the input stream that will be used to read the backup file. At
// its core, it will be a file input stream.
try
{
}
catch (Exception e)
{
message, e);
}
// If the backup is encrypted, then we need to wrap the file input stream
// in a cipher input stream.
if (backupInfo.isEncrypted())
{
try
{
}
catch (CryptoManagerException e)
{
message, e);
}
}
// Now wrap the resulting input stream in a zip stream so that we can read
// its contents. We don't need to worry about whether to use compression or
// not because it will be handled automatically.
// Read through the archive file an entry at a time. For each entry, update
// the digest or MAC if necessary.
byte[] buffer = new byte[8192];
while (true)
{
try
{
}
catch (Exception e)
{
message, e);
}
{
break;
}
// Get the filename for the zip entry and update the digest or MAC as
// necessary.
{
}
{
}
// If we're doing the restore, then create the output stream to write the
// file.
if (!verifyOnly)
{
try
{
}
catch (Exception e)
{
throw new DirectoryException(
e);
}
}
// Read the contents of the file and update the digest or MAC as
// necessary.
try
{
while (true)
{
if (bytesRead < 0)
{
// We've reached the end of the entry.
break;
}
// Update the digest or MAC if appropriate.
{
}
{
}
// Write the data to the output stream if appropriate.
if (outputStream != null)
{
}
}
// We're at the end of the file so close the output stream if we're
// writing it.
if (outputStream != null)
{
}
}
catch (Exception e)
{
message, e);
}
}
// Close the zip stream since we don't need it anymore.
try
{
}
catch (Exception e)
{
message, e);
}
// At this point, we should be done with the contents of the ZIP file and
// the restore should be complete. If we were generating a digest or MAC,
// then make sure it checks out.
{
{
}
else
{
message);
}
}
{
{
}
else
{
message);
}
}
// If we are just verifying the archive, then we're done.
if (verifyOnly)
{
return;
}
// If we've gotten here, then the archive was restored successfully.
}
/** {@inheritDoc} */
{
}
/** {@inheritDoc} */
{
}
/**
* Indicates whether the provided configuration is acceptable for this task
* backend.
*
* @param config The configuration for which to make the
* determination.
* @param unacceptableReasons A list into which the unacceptable reasons
* should be placed.
* @param taskBackingFile The currently-configured task backing file, or
* {@code null} if it should not be taken into
* account.
*
* @return {@code true} if the configuration is acceptable, or {@code false}
* if not.
*/
{
boolean configIsAcceptable = true;
try
{
if (taskBackingFile == null ||
{
if (f.exists())
{
// This is only a problem if it's different from the active one.
if (taskBackingFile != null)
{
configIsAcceptable = false;
}
}
else
{
File p = f.getParentFile();
if (p == null)
{
configIsAcceptable = false;
}
else if (! p.exists())
{
p.getPath(),
configIsAcceptable = false;
}
else if (! p.isDirectory())
{
p.getPath(),
configIsAcceptable = false;
}
}
}
}
catch (Exception e)
{
logger.traceException(e);
getExceptionMessage(e)));
configIsAcceptable = false;
}
return configIsAcceptable;
}
/** {@inheritDoc} */
{
try
{
{
{
if (f.exists())
{
}
else
{
File p = f.getParentFile();
if (p == null)
{
}
else if (! p.exists())
{
}
else if (! p.isDirectory())
{
}
}
}
}
}
catch (Exception e)
{
logger.traceException(e);
}
{
// Everything looks OK, so apply the changes.
if (retentionTime != tmpRetentionTime)
{
}
{
}
}
if (tmpNotificationAddress == null)
{
try
{
tmpNotificationAddress = "opendj-task-notification@" +
}
catch (Exception e)
{
tmpNotificationAddress = "opendj-task-notification@opendj.org";
}
}
return ccr;
}
/**
* Retrieves the DN of the configuration entry for this task backend.
*
* @return The DN of the configuration entry for this task backend.
*/
public DN getConfigEntryDN()
{
return configEntryDN;
}
/**
* Retrieves the path to the backing file that will hold the scheduled and
* recurring task definitions.
*
* @return The path to the backing file that will hold the scheduled and
* recurring task definitions.
*/
public String getTaskBackingFile()
{
return f.getPath();
}
/**
* Retrieves the sender address that should be used for e-mail notifications
* of task completion.
*
* @return The sender address that should be used for e-mail notifications of
* task completion.
*/
public String getNotificationSenderAddress()
{
return notificationSenderAddress;
}
/**
* Retrieves the length of time in seconds that information for a task should
* be retained after processing on it has completed.
*
* @return The length of time in seconds that information for a task should
* be retained after processing on it has completed.
*/
public long getRetentionTime()
{
return retentionTime;
}
/**
* Retrieves the DN of the entry that is the root for all task information in
* the Directory Server.
*
* @return The DN of the entry that is the root for all task information in
* the Directory Server.
*/
public DN getTaskRootDN()
{
return taskRootDN;
}
/**
* Retrieves the DN of the entry that is the immediate parent for all
* recurring task information in the Directory Server.
*
* @return The DN of the entry that is the immediate parent for all recurring
* task information in the Directory Server.
*/
public DN getRecurringTasksParentDN()
{
return recurringTaskParentDN;
}
/**
* Retrieves the DN of the entry that is the immediate parent for all
* scheduled task information in the Directory Server.
*
* @return The DN of the entry that is the immediate parent for all scheduled
* task information in the Directory Server.
*/
public DN getScheduledTasksParentDN()
{
return scheduledTaskParentDN;
}
/**
* Retrieves the scheduled task for the entry with the provided DN.
*
* @param taskEntryDN The DN of the entry for the task to retrieve.
*
* @return The requested task, or {@code null} if there is no task with the
* specified entry DN.
*/
{
}
/**
* Retrieves the recurring task for the entry with the provided DN.
*
* @param taskEntryDN The DN of the entry for the recurring task to
* retrieve.
*
* @return The requested recurring task, or {@code null} if there is no task
* with the specified entry DN.
*/
{
}
/** {@inheritDoc} */
public void preloadEntryCache() throws UnsupportedOperationException {
throw new UnsupportedOperationException("Operation not supported.");
}
}