/*
* 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 2006-2009 Sun Microsystems, Inc.
* Portions Copyright 2011-2015 ForgeRock AS
*/
package org.opends.server.backends.task;
import static org.forgerock.util.Reject.*;
import static org.opends.messages.BackendMessages.*;
import static org.opends.server.config.ConfigConstants.*;
import static org.opends.server.util.StaticUtils.*;
import java.io.File;
import java.io.FileFilter;
import java.net.InetAddress;
import java.nio.file.Path;
import java.util.Collections;
import java.util.GregorianCalendar;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.forgerock.opendj.config.server.ConfigChangeResult;
import org.forgerock.opendj.config.server.ConfigException;
import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.opendj.ldap.ConditionResult;
import org.forgerock.opendj.ldap.ModificationType;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.SearchScope;
import org.forgerock.util.Reject;
import org.opends.server.admin.server.ConfigurationChangeListener;
import org.opends.server.admin.std.server.TaskBackendCfg;
import org.opends.server.api.Backend;
import org.opends.server.api.Backupable;
import org.opends.server.config.ConfigEntry;
import org.opends.server.core.*;
import org.opends.server.types.*;
import org.opends.server.types.LockManager.DNLock;
import org.opends.server.util.BackupManager;
import org.opends.server.util.LDIFException;
import org.opends.server.util.LDIFReader;
import org.opends.server.util.LDIFWriter;
import org.opends.server.util.StaticUtils;
/**
* 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>, Backupable
{
private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
/** 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. */
private DN[] baseDNs;
/**
* 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;
private ServerContext serverContext;
/**
* 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} */
@Override
public void configureBackend(TaskBackendCfg cfg, ServerContext serverContext) throws ConfigException
{
Reject.ifNull(cfg);
this.serverContext = serverContext;
final DN[] baseDNs = new DN[cfg.getBaseDN().size()];
cfg.getBaseDN().toArray(baseDNs);
ConfigEntry configEntry = DirectoryServer.getConfigEntry(cfg.dn());
configEntryDN = configEntry.getDN();
// Make sure that the provided set of base DNs contains exactly one value.
// We will only allow one base for task entries.
if (baseDNs.length == 0)
{
throw new ConfigException(ERR_TASKBE_NO_BASE_DNS.get());
}
else if (baseDNs.length > 1)
{
LocalizableMessage message = ERR_TASKBE_MULTIPLE_BASE_DNS.get();
throw new ConfigException(message);
}
else
{
this.baseDNs = baseDNs;
taskRootDN = baseDNs[0];
String recurringTaskBaseString = RECURRING_TASK_BASE_RDN + "," +
taskRootDN;
try
{
recurringTaskParentDN = DN.valueOf(recurringTaskBaseString);
}
catch (Exception e)
{
logger.traceException(e);
// This should never happen.
LocalizableMessage message = ERR_TASKBE_CANNOT_DECODE_RECURRING_TASK_BASE_DN.get(
recurringTaskBaseString, getExceptionMessage(e));
throw new ConfigException(message, e);
}
String scheduledTaskBaseString = SCHEDULED_TASK_BASE_RDN + "," +
taskRootDN;
try
{
scheduledTaskParentDN = DN.valueOf(scheduledTaskBaseString);
}
catch (Exception e)
{
logger.traceException(e);
// This should never happen.
LocalizableMessage message = ERR_TASKBE_CANNOT_DECODE_SCHEDULED_TASK_BASE_DN.get(
scheduledTaskBaseString, getExceptionMessage(e));
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.
retentionTime = cfg.getTaskRetentionTime();
// Get the notification sender address.
notificationSenderAddress = cfg.getNotificationSenderAddress();
if (notificationSenderAddress == null)
{
try
{
notificationSenderAddress = "opendj-task-notification@" +
InetAddress.getLocalHost().getCanonicalHostName();
}
catch (Exception e)
{
notificationSenderAddress = "opendj-task-notification@opendj.org";
}
}
// Get the path to the task data backing file.
taskBackingFile = cfg.getTaskBackingFile();
currentConfig = cfg;
}
/** {@inheritDoc} */
@Override
public void openBackend()
throws ConfigException, InitializationException
{
// Create the scheduler and initialize it from the backing file.
taskScheduler = new TaskScheduler(serverContext, this);
taskScheduler.start();
// Register with the Directory Server as a configurable component.
currentConfig.addTaskChangeListener(this);
// Register the task base as a private suffix.
try
{
DirectoryServer.registerBaseDN(taskRootDN, this, true);
}
catch (Exception e)
{
logger.traceException(e);
LocalizableMessage message = ERR_BACKEND_CANNOT_REGISTER_BASEDN.get(
taskRootDN, getExceptionMessage(e));
throw new InitializationException(message, e);
}
}
/** {@inheritDoc} */
@Override
public void closeBackend()
{
currentConfig.removeTaskChangeListener(this);
try
{
taskScheduler.stopScheduler();
}
catch (Exception e)
{
logger.traceException(e);
}
try
{
LocalizableMessage message = INFO_TASKBE_INTERRUPTED_BY_SHUTDOWN.get();
taskScheduler.interruptRunningTasks(TaskState.STOPPED_BY_SHUTDOWN,
message, true);
}
catch (Exception e)
{
logger.traceException(e);
}
try
{
DirectoryServer.deregisterBaseDN(taskRootDN);
}
catch (Exception e)
{
logger.traceException(e);
}
}
/** {@inheritDoc} */
@Override
public DN[] getBaseDNs()
{
return baseDNs;
}
/** {@inheritDoc} */
@Override
public long getEntryCount()
{
if (taskScheduler != null)
{
return taskScheduler.getEntryCount();
}
return -1;
}
/** {@inheritDoc} */
@Override
public boolean isIndexed(AttributeType attributeType, IndexType indexType)
{
// All searches in this backend will always be considered indexed.
return true;
}
/** {@inheritDoc} */
@Override
public ConditionResult hasSubordinates(DN entryDN)
throws DirectoryException
{
long ret = numSubordinates(entryDN, false);
if(ret < 0)
{
return ConditionResult.UNDEFINED;
}
return ConditionResult.valueOf(ret != 0);
}
/** {@inheritDoc} */
@Override
public long getNumberOfEntriesInBaseDN(DN baseDN) throws DirectoryException {
checkNotNull(baseDN, "baseDN must not be null");
return numSubordinates(baseDN, true) + 1;
}
/** {@inheritDoc} */
@Override
public long getNumberOfChildren(DN parentDN) throws DirectoryException {
checkNotNull(parentDN, "parentDN must not be null");
return numSubordinates(parentDN, false);
}
private long numSubordinates(DN entryDN, boolean subtree) throws DirectoryException
{
if (entryDN == null)
{
return -1;
}
if (entryDN.equals(taskRootDN))
{
// scheduled and recurring parents.
if(!subtree)
{
return 2;
}
else
{
return taskScheduler.getScheduledTaskCount() +
taskScheduler.getRecurringTaskCount() + 2;
}
}
else if (entryDN.equals(scheduledTaskParentDN))
{
return taskScheduler.getScheduledTaskCount();
}
else if (entryDN.equals(recurringTaskParentDN))
{
return taskScheduler.getRecurringTaskCount();
}
DN parentDN = entryDN.getParentDNInSuffix();
if (parentDN == null)
{
return -1;
}
if (parentDN.equals(scheduledTaskParentDN) &&
taskScheduler.getScheduledTask(entryDN) != null)
{
return 0;
}
else if (parentDN.equals(recurringTaskParentDN) &&
taskScheduler.getRecurringTask(entryDN) != null)
{
return 0;
}
else
{
return -1;
}
}
/** {@inheritDoc} */
@Override
public Entry getEntry(DN entryDN)
throws DirectoryException
{
if (entryDN == null)
{
return null;
}
DNLock lock = taskScheduler.readLockEntry(entryDN);
try
{
if (entryDN.equals(taskRootDN))
{
return taskScheduler.getTaskRootEntry();
}
else if (entryDN.equals(scheduledTaskParentDN))
{
return taskScheduler.getScheduledTaskParentEntry();
}
else if (entryDN.equals(recurringTaskParentDN))
{
return taskScheduler.getRecurringTaskParentEntry();
}
DN parentDN = entryDN.getParentDNInSuffix();
if (parentDN == null)
{
return null;
}
if (parentDN.equals(scheduledTaskParentDN))
{
return taskScheduler.getScheduledTaskEntry(entryDN);
}
else if (parentDN.equals(recurringTaskParentDN))
{
return taskScheduler.getRecurringTaskEntry(entryDN);
}
else
{
// If we've gotten here then this is not an entry
// that should exist in the task backend.
return null;
}
}
finally
{
lock.unlock();
}
}
/** {@inheritDoc} */
@Override
public void addEntry(Entry entry, AddOperation addOperation)
throws DirectoryException
{
Entry e = entry.duplicate(false);
// Get the DN for the entry and then get its parent.
DN entryDN = e.getName();
DN parentDN = entryDN.getParentDNInSuffix();
if (parentDN == null)
{
LocalizableMessage message = ERR_TASKBE_ADD_DISALLOWED_DN.
get(scheduledTaskParentDN, recurringTaskParentDN);
throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
}
// If the parent DN is equal to the parent for scheduled tasks, then try to
// treat the provided entry like a scheduled task.
if (parentDN.equals(scheduledTaskParentDN))
{
Task task = taskScheduler.entryToScheduledTask(e, addOperation);
taskScheduler.scheduleTask(task, true);
return;
}
// If the parent DN is equal to the parent for recurring tasks, then try to
// treat the provided entry like a recurring task.
if (parentDN.equals(recurringTaskParentDN))
{
RecurringTask recurringTask = taskScheduler.entryToRecurringTask(e);
taskScheduler.addRecurringTask(recurringTask, true);
return;
}
// We won't allow the entry to be added.
LocalizableMessage message = ERR_TASKBE_ADD_DISALLOWED_DN.
get(scheduledTaskParentDN, recurringTaskParentDN);
throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
}
/** {@inheritDoc} */
@Override
public void deleteEntry(DN entryDN, DeleteOperation deleteOperation)
throws DirectoryException
{
// Get the parent for the provided entry DN. It must be either the
// scheduled or recurring task parent DN.
DN parentDN = entryDN.getParentDNInSuffix();
if (parentDN == null)
{
LocalizableMessage message = ERR_TASKBE_DELETE_INVALID_ENTRY.get(entryDN);
throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
}
else if (parentDN.equals(scheduledTaskParentDN))
{
// It's a scheduled task. Make sure that it exists.
Task t = taskScheduler.getScheduledTask(entryDN);
if (t == null)
{
LocalizableMessage message = ERR_TASKBE_DELETE_NO_SUCH_TASK.get(entryDN);
throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message);
}
// Look at the state of the task. We will allow pending and completed
// tasks to be removed, but not running tasks.
TaskState state = t.getTaskState();
if (TaskState.isPending(state))
{
if (t.isRecurring()) {
taskScheduler.removePendingTask(t.getTaskID());
long scheduledStartTime = t.getScheduledStartTime();
long currentSystemTime = System.currentTimeMillis();
if (scheduledStartTime < currentSystemTime) {
scheduledStartTime = currentSystemTime;
}
GregorianCalendar calendar = new GregorianCalendar();
calendar.setTimeInMillis(scheduledStartTime);
taskScheduler.scheduleNextRecurringTaskIteration(t,
calendar);
} else {
taskScheduler.removePendingTask(t.getTaskID());
}
}
else if (TaskState.isDone(t.getTaskState()))
{
taskScheduler.removeCompletedTask(t.getTaskID());
}
else
{
LocalizableMessage message = ERR_TASKBE_DELETE_RUNNING.get(entryDN);
throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
}
}
else if (parentDN.equals(recurringTaskParentDN))
{
// It's a recurring task. Make sure that it exists.
RecurringTask rt = taskScheduler.getRecurringTask(entryDN);
if (rt == null)
{
LocalizableMessage message = ERR_TASKBE_DELETE_NO_SUCH_RECURRING_TASK.get(entryDN);
throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message);
}
taskScheduler.removeRecurringTask(rt.getRecurringTaskID());
}
else
{
LocalizableMessage message = ERR_TASKBE_DELETE_INVALID_ENTRY.get(entryDN);
throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
}
}
/** {@inheritDoc} */
@Override
public void replaceEntry(Entry oldEntry, Entry newEntry,
ModifyOperation modifyOperation) throws DirectoryException
{
DN entryDN = newEntry.getName();
DNLock entryLock = null;
if (! taskScheduler.holdsSchedulerLock())
{
entryLock = DirectoryServer.getLockManager().tryWriteLockEntry(entryDN);
if (entryLock == null)
{
throw new DirectoryException(ResultCode.BUSY,
ERR_TASKBE_MODIFY_CANNOT_LOCK_ENTRY.get(entryDN));
}
}
try
{
// Get the parent for the provided entry DN. It must be either the
// scheduled or recurring task parent DN.
DN parentDN = entryDN.getParentDNInSuffix();
if (parentDN == null)
{
LocalizableMessage message = ERR_TASKBE_MODIFY_INVALID_ENTRY.get(entryDN);
throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
}
else if (parentDN.equals(scheduledTaskParentDN))
{
// It's a scheduled task. Make sure that it exists.
Task t = taskScheduler.getScheduledTask(entryDN);
if (t == null)
{
LocalizableMessage message = ERR_TASKBE_MODIFY_NO_SUCH_TASK.get(entryDN);
throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message);
}
// 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.
TaskState state = t.getTaskState();
if (TaskState.isPending(state) && !t.isRecurring())
{
Task newTask = taskScheduler.entryToScheduledTask(newEntry,
modifyOperation);
taskScheduler.removePendingTask(t.getTaskID());
taskScheduler.scheduleTask(newTask, true);
return;
}
else if (TaskState.isRunning(state))
{
// 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.
boolean acceptable = isReplaceEntryAcceptable(modifyOperation);
if (acceptable)
{
LocalizableMessage message = INFO_TASKBE_RUNNING_TASK_CANCELLED.get();
t.interruptTask(TaskState.STOPPED_BY_ADMINISTRATOR, message);
return;
}
else
{
LocalizableMessage message = ERR_TASKBE_MODIFY_RUNNING.get(entryDN);
throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
}
}
else if (TaskState.isPending(state) && t.isRecurring())
{
// Pending recurring task iterations can only be canceled.
boolean acceptable = isReplaceEntryAcceptable(modifyOperation);
if (acceptable)
{
Task newTask = taskScheduler.entryToScheduledTask(newEntry,
modifyOperation);
if (newTask.getTaskState() ==
TaskState.CANCELED_BEFORE_STARTING)
{
taskScheduler.removePendingTask(t.getTaskID());
long scheduledStartTime = t.getScheduledStartTime();
long currentSystemTime = System.currentTimeMillis();
if (scheduledStartTime < currentSystemTime) {
scheduledStartTime = currentSystemTime;
}
GregorianCalendar calendar = new GregorianCalendar();
calendar.setTimeInMillis(scheduledStartTime);
taskScheduler.scheduleNextRecurringTaskIteration(
newTask, calendar);
}
else if (newTask.getTaskState() ==
TaskState.STOPPED_BY_ADMINISTRATOR)
{
LocalizableMessage message = INFO_TASKBE_RUNNING_TASK_CANCELLED.get();
t.interruptTask(TaskState.STOPPED_BY_ADMINISTRATOR, message);
}
return;
}
else
{
LocalizableMessage message = ERR_TASKBE_MODIFY_RECURRING.get(entryDN);
throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
}
}
else
{
LocalizableMessage message = ERR_TASKBE_MODIFY_COMPLETED.get(entryDN);
throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
}
}
else if (parentDN.equals(recurringTaskParentDN))
{
// We don't currently support altering recurring tasks.
LocalizableMessage message = ERR_TASKBE_MODIFY_RECURRING.get(entryDN);
throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
}
else
{
LocalizableMessage message = ERR_TASKBE_MODIFY_INVALID_ENTRY.get(entryDN);
throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
}
}
finally
{
if (entryLock != null)
{
entryLock.unlock();
}
}
}
/**
* 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.
*/
private boolean isReplaceEntryAcceptable(ModifyOperation modifyOperation)
{
for (Modification m : modifyOperation.getModifications()) {
if (m.isInternal()) {
continue;
}
if (m.getModificationType() != ModificationType.REPLACE) {
return false;
}
Attribute a = m.getAttribute();
AttributeType at = a.getAttributeType();
if (!at.hasName(ATTR_TASK_STATE)) {
return false;
}
Iterator<ByteString> iterator = a.iterator();
if (!iterator.hasNext()) {
return false;
}
ByteString v = iterator.next();
if (iterator.hasNext()) {
return false;
}
String valueString = toLowerCase(v.toString());
if (!valueString.startsWith("cancel")
&& !valueString.startsWith("stop")) {
return false;
}
}
return true;
}
/** {@inheritDoc} */
@Override
public void renameEntry(DN currentDN, Entry entry,
ModifyDNOperation modifyDNOperation)
throws DirectoryException
{
throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
ERR_BACKEND_MODIFY_DN_NOT_SUPPORTED.get(currentDN, getBackendID()));
}
/** {@inheritDoc} */
@Override
public void search(SearchOperation searchOperation)
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;
DN baseDN = searchOperation.getBaseDN();
SearchScope searchScope = searchOperation.getScope();
SearchFilter searchFilter = searchOperation.getFilter();
if (baseDN.equals(taskRootDN))
{
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;
}
}
else if (baseDN.equals(scheduledTaskParentDN))
{
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;
}
}
else if (baseDN.equals(recurringTaskParentDN))
{
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
{
DN parentDN = baseDN.getParentDNInSuffix();
if (parentDN == null)
{
LocalizableMessage message = ERR_TASKBE_SEARCH_INVALID_BASE.get(baseDN);
throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message);
}
else if (parentDN.equals(scheduledTaskParentDN))
{
DNLock lock = taskScheduler.readLockEntry(baseDN);
try
{
Entry e = taskScheduler.getScheduledTaskEntry(baseDN);
if (e == null)
{
LocalizableMessage message = ERR_TASKBE_SEARCH_NO_SUCH_TASK.get(baseDN);
throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message,
scheduledTaskParentDN, null);
}
if ((searchScope == SearchScope.BASE_OBJECT || searchScope == SearchScope.WHOLE_SUBTREE)
&& searchFilter.matchesEntry(e))
{
searchOperation.returnEntry(e, null);
}
return;
}
finally
{
lock.unlock();
}
}
else if (parentDN.equals(recurringTaskParentDN))
{
DNLock lock = taskScheduler.readLockEntry(baseDN);
try
{
Entry e = taskScheduler.getRecurringTaskEntry(baseDN);
if (e == null)
{
LocalizableMessage message = ERR_TASKBE_SEARCH_NO_SUCH_RECURRING_TASK.get(baseDN);
throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message,
recurringTaskParentDN, null);
}
if ((searchScope == SearchScope.BASE_OBJECT || searchScope == SearchScope.WHOLE_SUBTREE)
&& searchFilter.matchesEntry(e))
{
searchOperation.returnEntry(e, null);
}
return;
}
finally
{
lock.unlock();
}
}
else
{
LocalizableMessage message = ERR_TASKBE_SEARCH_INVALID_BASE.get(baseDN);
throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message);
}
}
if (searchRoot)
{
Entry e = taskScheduler.getTaskRootEntry();
if (searchFilter.matchesEntry(e) && !searchOperation.returnEntry(e, null))
{
return;
}
}
if (searchScheduledParent)
{
Entry e = taskScheduler.getScheduledTaskParentEntry();
if (searchFilter.matchesEntry(e) && !searchOperation.returnEntry(e, null))
{
return;
}
}
if (searchScheduledTasks
&& !taskScheduler.searchScheduledTasks(searchOperation))
{
return;
}
if (searchRecurringParent)
{
Entry e = taskScheduler.getRecurringTaskParentEntry();
if (searchFilter.matchesEntry(e) && !searchOperation.returnEntry(e, null))
{
return;
}
}
if (searchRecurringTasks
&& !taskScheduler.searchRecurringTasks(searchOperation))
{
return;
}
}
/** {@inheritDoc} */
@Override
public Set<String> getSupportedControls()
{
return Collections.emptySet();
}
/** {@inheritDoc} */
@Override
public Set<String> getSupportedFeatures()
{
return Collections.emptySet();
}
/** {@inheritDoc} */
@Override
public boolean supports(BackendOperation backendOperation)
{
switch (backendOperation)
{
case LDIF_EXPORT:
case BACKUP:
case RESTORE:
return true;
default:
return false;
}
}
/** {@inheritDoc} */
@Override
public void exportLDIF(LDIFExportConfig exportConfig)
throws DirectoryException
{
File taskFile = getFileForPath(taskBackingFile);
// Read from.
LDIFReader ldifReader;
try
{
ldifReader = new LDIFReader(new LDIFImportConfig(taskFile.getPath()));
}
catch (Exception e)
{
LocalizableMessage message = ERR_TASKS_CANNOT_EXPORT_TO_FILE.get(e);
throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message, e);
}
// Write to.
LDIFWriter ldifWriter;
try
{
ldifWriter = new LDIFWriter(exportConfig);
}
catch (Exception e)
{
logger.traceException(e);
LocalizableMessage message = ERR_TASKS_CANNOT_EXPORT_TO_FILE.get(
stackTraceToSingleLineString(e));
throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
message);
}
// Copy record by record.
try
{
while (true)
{
Entry e = null;
try
{
e = ldifReader.readEntry();
if (e == null)
{
break;
}
}
catch (LDIFException le)
{
if (! le.canContinueReading())
{
LocalizableMessage message = ERR_TASKS_CANNOT_EXPORT_TO_FILE.get(e);
throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message, le);
}
continue;
}
ldifWriter.writeEntry(e);
}
}
catch (Exception e)
{
logger.traceException(e);
}
finally
{
close(ldifWriter, ldifReader);
}
}
/** {@inheritDoc} */
@Override
public LDIFImportResult importLDIF(LDIFImportConfig importConfig, ServerContext sContext) throws DirectoryException
{
throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
ERR_BACKEND_IMPORT_NOT_SUPPORTED.get(getBackendID()));
}
/** {@inheritDoc} */
@Override
public void createBackup(BackupConfig backupConfig) throws DirectoryException
{
new BackupManager(getBackendID()).createBackup(this, backupConfig);
}
/** {@inheritDoc} */
@Override
public void removeBackup(BackupDirectory backupDirectory, String backupID) throws DirectoryException
{
new BackupManager(getBackendID()).removeBackup(backupDirectory, backupID);
}
/** {@inheritDoc} */
@Override
public void restoreBackup(RestoreConfig restoreConfig) throws DirectoryException
{
new BackupManager(getBackendID()).restoreBackup(this, restoreConfig);
}
/** {@inheritDoc} */
@Override
public boolean isConfigurationAcceptable(TaskBackendCfg config,
List<LocalizableMessage> unacceptableReasons,
ServerContext serverContext)
{
return isConfigAcceptable(config, unacceptableReasons, null);
}
/** {@inheritDoc} */
@Override
public boolean isConfigurationChangeAcceptable(TaskBackendCfg configEntry,
List<LocalizableMessage> unacceptableReasons)
{
return isConfigAcceptable(configEntry, unacceptableReasons,
taskBackingFile);
}
/**
* 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.
*/
private static boolean isConfigAcceptable(TaskBackendCfg config,
List<LocalizableMessage> unacceptableReasons,
String taskBackingFile)
{
boolean configIsAcceptable = true;
try
{
String tmpBackingFile = config.getTaskBackingFile();
if (taskBackingFile == null ||
!taskBackingFile.equals(tmpBackingFile))
{
File f = getFileForPath(tmpBackingFile);
if (f.exists())
{
// This is only a problem if it's different from the active one.
if (taskBackingFile != null)
{
unacceptableReasons.add(
ERR_TASKBE_BACKING_FILE_EXISTS.get(tmpBackingFile));
configIsAcceptable = false;
}
}
else
{
File p = f.getParentFile();
if (p == null)
{
unacceptableReasons.add(ERR_TASKBE_INVALID_BACKING_FILE_PATH.get(
tmpBackingFile));
configIsAcceptable = false;
}
else if (! p.exists())
{
unacceptableReasons.add(ERR_TASKBE_BACKING_FILE_MISSING_PARENT.get(
p.getPath(),
tmpBackingFile));
configIsAcceptable = false;
}
else if (! p.isDirectory())
{
unacceptableReasons.add(
ERR_TASKBE_BACKING_FILE_PARENT_NOT_DIRECTORY.get(
p.getPath(),
tmpBackingFile));
configIsAcceptable = false;
}
}
}
}
catch (Exception e)
{
logger.traceException(e);
unacceptableReasons.add(ERR_TASKBE_ERROR_GETTING_BACKING_FILE.get(
getExceptionMessage(e)));
configIsAcceptable = false;
}
return configIsAcceptable;
}
/** {@inheritDoc} */
@Override
public ConfigChangeResult applyConfigurationChange(TaskBackendCfg configEntry)
{
final ConfigChangeResult ccr = new ConfigChangeResult();
String tmpBackingFile = taskBackingFile;
try
{
{
tmpBackingFile = configEntry.getTaskBackingFile();
if (! taskBackingFile.equals(tmpBackingFile))
{
File f = getFileForPath(tmpBackingFile);
if (f.exists())
{
ccr.addMessage(ERR_TASKBE_BACKING_FILE_EXISTS.get(tmpBackingFile));
ccr.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
}
else
{
File p = f.getParentFile();
if (p == null)
{
ccr.addMessage(ERR_TASKBE_INVALID_BACKING_FILE_PATH.get(tmpBackingFile));
ccr.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
}
else if (! p.exists())
{
ccr.addMessage(ERR_TASKBE_BACKING_FILE_MISSING_PARENT.get(p, tmpBackingFile));
ccr.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
}
else if (! p.isDirectory())
{
ccr.addMessage(ERR_TASKBE_BACKING_FILE_PARENT_NOT_DIRECTORY.get(p, tmpBackingFile));
ccr.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
}
}
}
}
}
catch (Exception e)
{
logger.traceException(e);
ccr.addMessage(ERR_TASKBE_ERROR_GETTING_BACKING_FILE.get(getExceptionMessage(e)));
ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
}
long tmpRetentionTime = configEntry.getTaskRetentionTime();
if (ccr.getResultCode() == ResultCode.SUCCESS)
{
// Everything looks OK, so apply the changes.
if (retentionTime != tmpRetentionTime)
{
retentionTime = tmpRetentionTime;
ccr.addMessage(INFO_TASKBE_UPDATED_RETENTION_TIME.get(retentionTime));
}
if (! taskBackingFile.equals(tmpBackingFile))
{
taskBackingFile = tmpBackingFile;
taskScheduler.writeState();
ccr.addMessage(INFO_TASKBE_UPDATED_BACKING_FILE.get(taskBackingFile));
}
}
String tmpNotificationAddress = configEntry.getNotificationSenderAddress();
if (tmpNotificationAddress == null)
{
try
{
tmpNotificationAddress = "opendj-task-notification@" +
InetAddress.getLocalHost().getCanonicalHostName();
}
catch (Exception e)
{
tmpNotificationAddress = "opendj-task-notification@opendj.org";
}
}
notificationSenderAddress = tmpNotificationAddress;
currentConfig = configEntry;
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()
{
File f = getFileForPath(taskBackingFile);
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.
*/
public Task getScheduledTask(DN taskEntryDN)
{
return taskScheduler.getScheduledTask(taskEntryDN);
}
/**
* 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.
*/
public RecurringTask getRecurringTask(DN taskEntryDN)
{
return taskScheduler.getRecurringTask(taskEntryDN);
}
/** {@inheritDoc} */
@Override
public File getDirectory()
{
return getFileForPath(taskBackingFile).getParentFile();
}
private FileFilter getFilesToBackupFilter()
{
return new FileFilter()
{
@Override
public boolean accept(File file)
{
return file.getName().equals(getFileForPath(taskBackingFile).getName());
}
};
}
/** {@inheritDoc} */
@Override
public ListIterator<Path> getFilesToBackup() throws DirectoryException
{
return BackupManager.getFiles(getDirectory(), getFilesToBackupFilter(), getBackendID()).listIterator();
}
/** {@inheritDoc} */
@Override
public boolean isDirectRestore()
{
return true;
}
/** {@inheritDoc} */
@Override
public Path beforeRestore() throws DirectoryException
{
// save current files
return BackupManager.saveCurrentFilesToDirectory(this, getBackendID());
}
/** {@inheritDoc} */
@Override
public void afterRestore(Path restoreDirectory, Path saveDirectory) throws DirectoryException
{
// restore was successful, delete the save directory
StaticUtils.recursiveDelete(saveDirectory.toFile());
}
}