/*
* 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 2014-2015 ForgeRock AS
*/
package org.opends.server.tasks;
import static org.opends.messages.TaskMessages.*;
import static org.opends.messages.ToolMessages.*;
import static org.opends.server.config.ConfigConstants.*;
import static org.opends.server.core.DirectoryServer.*;
import static org.opends.server.util.StaticUtils.*;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.forgerock.opendj.ldap.ResultCode;
import org.opends.messages.Severity;
import org.opends.messages.TaskMessages;
import org.opends.server.api.Backend;
import org.opends.server.api.Backend.BackendOperation;
import org.opends.server.api.ClientConnection;
import org.opends.server.backends.task.Task;
import org.opends.server.backends.task.TaskState;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.LockFileManager;
import org.opends.server.types.Attribute;
import org.opends.server.types.AttributeType;
import org.opends.server.types.DN;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.Entry;
import org.opends.server.types.ExistingFileBehavior;
import org.opends.server.types.LDIFExportConfig;
import org.opends.server.types.Operation;
import org.opends.server.types.Privilege;
import org.opends.server.types.SearchFilter;
/**
* This class provides an implementation of a Directory Server task that can
* be used to export the contents of a Directory Server backend to an LDIF file.
*/
public class ExportTask extends Task
{
private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
/** Stores mapping between configuration attribute name and its label. */
private static Map<String,LocalizableMessage> argDisplayMap = new HashMap<>();
static {
argDisplayMap.put(ATTR_TASK_EXPORT_LDIF_FILE, INFO_EXPORT_ARG_LDIF_FILE.get());
argDisplayMap.put(ATTR_TASK_EXPORT_BACKEND_ID, INFO_EXPORT_ARG_BACKEND_ID.get());
argDisplayMap.put(ATTR_TASK_EXPORT_APPEND_TO_LDIF, INFO_EXPORT_ARG_APPEND_TO_LDIF.get());
argDisplayMap.put(ATTR_TASK_EXPORT_COMPRESS_LDIF, INFO_EXPORT_ARG_COMPRESS_LDIF.get());
argDisplayMap.put(ATTR_TASK_EXPORT_ENCRYPT_LDIF, INFO_EXPORT_ARG_ENCRYPT_LDIF.get());
argDisplayMap.put(ATTR_TASK_EXPORT_SIGN_HASH, INFO_EXPORT_ARG_SIGN_HASH.get());
argDisplayMap.put(ATTR_TASK_EXPORT_INCLUDE_ATTRIBUTE, INFO_EXPORT_ARG_INCL_ATTR.get());
argDisplayMap.put(ATTR_TASK_EXPORT_EXCLUDE_ATTRIBUTE, INFO_EXPORT_ARG_EXCL_ATTR.get());
argDisplayMap.put(ATTR_TASK_EXPORT_INCLUDE_FILTER, INFO_EXPORT_ARG_INCL_FILTER.get());
argDisplayMap.put(ATTR_TASK_EXPORT_EXCLUDE_FILTER, INFO_EXPORT_ARG_EXCL_FILTER.get());
argDisplayMap.put(ATTR_TASK_EXPORT_INCLUDE_BRANCH, INFO_EXPORT_ARG_INCL_BRANCH.get());
argDisplayMap.put(ATTR_TASK_EXPORT_EXCLUDE_BRANCH, INFO_EXPORT_ARG_EXCL_BRANCH.get());
argDisplayMap.put(ATTR_TASK_EXPORT_WRAP_COLUMN, INFO_EXPORT_ARG_WRAP_COLUMN.get());
}
private String ldifFile;
private String backendID;
private int wrapColumn;
private boolean appendToLDIF;
private boolean compressLDIF;
private boolean encryptLDIF;
private boolean signHash;
private boolean includeOperationalAttributes;
private ArrayList<String> includeAttributeStrings;
private ArrayList<String> excludeAttributeStrings;
private ArrayList<String> includeFilterStrings;
private ArrayList<String> excludeFilterStrings;
private ArrayList<String> includeBranchStrings;
private ArrayList<String> excludeBranchStrings;
private LDIFExportConfig exportConfig;
/** {@inheritDoc} */
@Override
public LocalizableMessage getDisplayName() {
return INFO_TASK_EXPORT_NAME.get();
}
/** {@inheritDoc} */
@Override
public LocalizableMessage getAttributeDisplayName(String name) {
return argDisplayMap.get(name);
}
/** {@inheritDoc} */
@Override
public void initializeTask() throws DirectoryException
{
// If the client connection is available, then make sure the associated
// client has the LDIF_EXPORT privilege.
Operation operation = getOperation();
if (operation != null)
{
ClientConnection clientConnection = operation.getClientConnection();
if (! clientConnection.hasPrivilege(Privilege.LDIF_EXPORT, operation))
{
LocalizableMessage message = ERR_TASK_LDIFEXPORT_INSUFFICIENT_PRIVILEGES.get();
throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
message);
}
}
Entry taskEntry = getTaskEntry();
AttributeType typeWrapColumn = getAttributeTypeOrDefault(ATTR_TASK_EXPORT_WRAP_COLUMN);
ldifFile = toString(taskEntry, ATTR_TASK_EXPORT_LDIF_FILE);
File f = new File (ldifFile);
if (! f.isAbsolute())
{
f = new File(DirectoryServer.getInstanceRoot(), ldifFile);
try
{
ldifFile = f.getCanonicalPath();
}
catch (Exception ex)
{
ldifFile = f.getAbsolutePath();
}
}
backendID = toString(taskEntry, ATTR_TASK_EXPORT_BACKEND_ID);
appendToLDIF = toBoolean(taskEntry, false, ATTR_TASK_EXPORT_APPEND_TO_LDIF);
compressLDIF = toBoolean(taskEntry, false, ATTR_TASK_EXPORT_COMPRESS_LDIF);
encryptLDIF = toBoolean(taskEntry, false, ATTR_TASK_EXPORT_ENCRYPT_LDIF);
signHash = toBoolean(taskEntry, false, ATTR_TASK_EXPORT_SIGN_HASH);
includeAttributeStrings = toListOfString(taskEntry, ATTR_TASK_EXPORT_INCLUDE_ATTRIBUTE);
excludeAttributeStrings = toListOfString(taskEntry, ATTR_TASK_EXPORT_EXCLUDE_ATTRIBUTE);
includeFilterStrings = toListOfString(taskEntry, ATTR_TASK_EXPORT_INCLUDE_FILTER);
excludeFilterStrings = toListOfString(taskEntry, ATTR_TASK_EXPORT_EXCLUDE_FILTER);
includeBranchStrings = toListOfString(taskEntry, ATTR_TASK_EXPORT_INCLUDE_BRANCH);
excludeBranchStrings = toListOfString(taskEntry, ATTR_TASK_EXPORT_EXCLUDE_BRANCH);
List<Attribute> attrList = taskEntry.getAttribute(typeWrapColumn);
wrapColumn = TaskUtils.getSingleValueInteger(attrList, 0);
includeOperationalAttributes = toBoolean(taskEntry, true, ATTR_TASK_EXPORT_INCLUDE_OPERATIONAL_ATTRIBUTES);
}
private boolean toBoolean(Entry entry, boolean defaultValue, String attrName)
{
final AttributeType attrType = getAttributeTypeOrDefault(attrName);
final List<Attribute> attrs = entry.getAttribute(attrType);
return TaskUtils.getBoolean(attrs, defaultValue);
}
private ArrayList<String> toListOfString(Entry entry, String attrName)
{
final AttributeType attrType = getAttributeTypeOrDefault(attrName);
final List<Attribute> attrs = entry.getAttribute(attrType);
return TaskUtils.getMultiValueString(attrs);
}
private String toString(Entry entry, String attrName)
{
final AttributeType attrType = getAttributeTypeOrDefault(attrName);
final List<Attribute> attrs = entry.getAttribute(attrType);
return TaskUtils.getSingleValueString(attrs);
}
/** {@inheritDoc} */
@Override
public void interruptTask(TaskState interruptState, LocalizableMessage interruptReason)
{
if (TaskState.STOPPED_BY_ADMINISTRATOR.equals(interruptState) &&
exportConfig != null)
{
addLogMessage(Severity.INFORMATION, TaskMessages.INFO_TASK_STOPPED_BY_ADMIN.get(
interruptReason));
setTaskInterruptState(interruptState);
exportConfig.cancel();
}
}
/** {@inheritDoc} */
@Override
public boolean isInterruptable() {
return true;
}
/** {@inheritDoc} */
@Override
protected TaskState runTask()
{
// See if there were any user-defined sets of include/exclude attributes or
// filters. If so, then process them.
HashSet<AttributeType> excludeAttributes = toAttributeTypes(excludeAttributeStrings);
HashSet<AttributeType> includeAttributes = toAttributeTypes(includeAttributeStrings);
ArrayList<SearchFilter> excludeFilters;
if (excludeFilterStrings == null)
{
excludeFilters = null;
}
else
{
excludeFilters = new ArrayList<>();
for (String filterString : excludeFilterStrings)
{
try
{
excludeFilters.add(SearchFilter.createFilterFromString(filterString));
}
catch (DirectoryException de)
{
logger.error(ERR_LDIFEXPORT_CANNOT_PARSE_EXCLUDE_FILTER, filterString, de.getMessageObject());
return TaskState.STOPPED_BY_ERROR;
}
catch (Exception e)
{
logger.error(ERR_LDIFEXPORT_CANNOT_PARSE_EXCLUDE_FILTER, filterString, getExceptionMessage(e));
return TaskState.STOPPED_BY_ERROR;
}
}
}
ArrayList<SearchFilter> includeFilters;
if (includeFilterStrings == null)
{
includeFilters = null;
}
else
{
includeFilters = new ArrayList<>();
for (String filterString : includeFilterStrings)
{
try
{
includeFilters.add(SearchFilter.createFilterFromString(filterString));
}
catch (DirectoryException de)
{
logger.error(ERR_LDIFEXPORT_CANNOT_PARSE_INCLUDE_FILTER, filterString, de.getMessageObject());
return TaskState.STOPPED_BY_ERROR;
}
catch (Exception e)
{
logger.error(ERR_LDIFEXPORT_CANNOT_PARSE_INCLUDE_FILTER, filterString, getExceptionMessage(e));
return TaskState.STOPPED_BY_ERROR;
}
}
}
// Get the backend into which the LDIF should be imported.
Backend<?> backend = DirectoryServer.getBackend(backendID);
if (backend == null)
{
logger.error(ERR_LDIFEXPORT_NO_BACKENDS_FOR_ID, backendID);
return TaskState.STOPPED_BY_ERROR;
}
else if (!backend.supports(BackendOperation.LDIF_EXPORT))
{
logger.error(ERR_LDIFEXPORT_CANNOT_EXPORT_BACKEND, backendID);
return TaskState.STOPPED_BY_ERROR;
}
ArrayList<DN> defaultIncludeBranches = new ArrayList<>(backend.getBaseDNs().length);
Collections.addAll(defaultIncludeBranches, backend.getBaseDNs());
ArrayList<DN> excludeBranches = new ArrayList<>();
if (excludeBranchStrings != null)
{
for (String s : excludeBranchStrings)
{
DN excludeBranch;
try
{
excludeBranch = DN.valueOf(s);
}
catch (DirectoryException de)
{
logger.error(ERR_LDIFEXPORT_CANNOT_DECODE_EXCLUDE_BASE, s, de.getMessageObject());
return TaskState.STOPPED_BY_ERROR;
}
catch (Exception e)
{
logger.error(ERR_LDIFEXPORT_CANNOT_DECODE_EXCLUDE_BASE, s, getExceptionMessage(e));
return TaskState.STOPPED_BY_ERROR;
}
if (! excludeBranches.contains(excludeBranch))
{
excludeBranches.add(excludeBranch);
}
}
}
ArrayList<DN> includeBranches;
if (!includeBranchStrings.isEmpty())
{
includeBranches = new ArrayList<>();
for (String s : includeBranchStrings)
{
DN includeBranch;
try
{
includeBranch = DN.valueOf(s);
}
catch (DirectoryException de)
{
logger.error(ERR_LDIFIMPORT_CANNOT_DECODE_INCLUDE_BASE, s, de.getMessageObject());
return TaskState.STOPPED_BY_ERROR;
}
catch (Exception e)
{
logger.error(ERR_LDIFIMPORT_CANNOT_DECODE_INCLUDE_BASE, s, getExceptionMessage(e));
return TaskState.STOPPED_BY_ERROR;
}
if (! Backend.handlesEntry(includeBranch, defaultIncludeBranches,
excludeBranches))
{
logger.error(ERR_LDIFEXPORT_INVALID_INCLUDE_BASE, s, backendID);
return TaskState.STOPPED_BY_ERROR;
}
includeBranches.add(includeBranch);
}
}
else
{
includeBranches = defaultIncludeBranches;
}
// Create the LDIF export configuration to use when reading the LDIF.
ExistingFileBehavior existingBehavior;
if (appendToLDIF)
{
existingBehavior = ExistingFileBehavior.APPEND;
}
else
{
existingBehavior = ExistingFileBehavior.OVERWRITE;
}
exportConfig = new LDIFExportConfig(ldifFile, existingBehavior);
exportConfig.setCompressData(compressLDIF);
exportConfig.setEncryptData(encryptLDIF);
exportConfig.setExcludeAttributes(excludeAttributes);
exportConfig.setExcludeBranches(excludeBranches);
exportConfig.setExcludeFilters(excludeFilters);
exportConfig.setIncludeAttributes(includeAttributes);
exportConfig.setIncludeBranches(includeBranches);
exportConfig.setIncludeFilters(includeFilters);
exportConfig.setSignHash(signHash);
exportConfig.setWrapColumn(wrapColumn);
exportConfig.setIncludeOperationalAttributes(includeOperationalAttributes);
// FIXME -- Should this be conditional?
exportConfig.setInvokeExportPlugins(true);
// Get the set of base DNs for the backend as an array.
DN[] baseDNs = new DN[defaultIncludeBranches.size()];
defaultIncludeBranches.toArray(baseDNs);
// From here we must make sure we close the export config.
try
{
// Acquire a shared lock for the backend.
try
{
String lockFile = LockFileManager.getBackendLockFileName(backend);
StringBuilder failureReason = new StringBuilder();
if (! LockFileManager.acquireSharedLock(lockFile, failureReason))
{
logger.error(ERR_LDIFEXPORT_CANNOT_LOCK_BACKEND, backend.getBackendID(), failureReason);
return TaskState.STOPPED_BY_ERROR;
}
}
catch (Exception e)
{
logger.error(ERR_LDIFEXPORT_CANNOT_LOCK_BACKEND, backend.getBackendID(), getExceptionMessage(e));
return TaskState.STOPPED_BY_ERROR;
}
// From here we must make sure we release the shared backend lock.
try
{
// Launch the export.
try
{
DirectoryServer.notifyExportBeginning(backend, exportConfig);
addLogMessage(Severity.INFORMATION, INFO_LDIFEXPORT_PATH_TO_LDIF_FILE.get(ldifFile));
backend.exportLDIF(exportConfig);
DirectoryServer.notifyExportEnded(backend, exportConfig, true);
}
catch (DirectoryException de)
{
DirectoryServer.notifyExportEnded(backend, exportConfig, false);
logger.error(ERR_LDIFEXPORT_ERROR_DURING_EXPORT, de.getMessageObject());
return TaskState.STOPPED_BY_ERROR;
}
catch (Exception e)
{
DirectoryServer.notifyExportEnded(backend, exportConfig, false);
logger.error(ERR_LDIFEXPORT_ERROR_DURING_EXPORT, getExceptionMessage(e));
return TaskState.STOPPED_BY_ERROR;
}
}
finally
{
// Release the shared lock on the backend.
try
{
String lockFile = LockFileManager.getBackendLockFileName(backend);
StringBuilder failureReason = new StringBuilder();
if (! LockFileManager.releaseLock(lockFile, failureReason))
{
logger.warn(WARN_LDIFEXPORT_CANNOT_UNLOCK_BACKEND, backend.getBackendID(), failureReason);
return TaskState.COMPLETED_WITH_ERRORS;
}
}
catch (Exception e)
{
logger.warn(WARN_LDIFEXPORT_CANNOT_UNLOCK_BACKEND, backend.getBackendID(), getExceptionMessage(e));
return TaskState.COMPLETED_WITH_ERRORS;
}
}
}
finally
{
// Clean up after the export by closing the export config.
exportConfig.close();
}
// If the operation was cancelled delete the export file since
// if will be incomplete.
if (exportConfig.isCancelled())
{
File f = new File(ldifFile);
if (f.exists())
{
f.delete();
}
}
// If we got here the task either completed successfully or was interrupted
return getFinalTaskState();
}
private HashSet<AttributeType> toAttributeTypes(ArrayList<String> attributeStrings)
{
if (attributeStrings == null)
{
return null;
}
HashSet<AttributeType> attributes = new HashSet<>();
for (String attrName : attributeStrings)
{
attributes.add(DirectoryServer.getAttributeTypeOrDefault(attrName.toLowerCase(), attrName));
}
return attributes;
}
}