/*
* 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
* trunk/opends/resource/legal-notices/OpenDS.LICENSE
* or https://OpenDS.dev.java.net/OpenDS.LICENSE.
* 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
* trunk/opends/resource/legal-notices/OpenDS.LICENSE. 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 2008-2010 Sun Microsystems, Inc.
* Portions Copyright 2011-2013 ForgeRock AS
*/
package org.opends.server.workflowelement.localbackend;
import java.util.ArrayList;
import java.util.List;
import java.util.TreeMap;
import java.util.concurrent.CopyOnWriteArrayList;
import org.opends.messages.Message;
import org.opends.server.admin.server.ConfigurationChangeListener;
import org.opends.server.admin.server.ServerManagementContext;
import org.opends.server.admin.std.server.BackendCfg;
import org.opends.server.admin.std.server.LocalBackendWorkflowElementCfg;
import org.opends.server.admin.std.server.RootCfg;
import org.opends.server.api.Backend;
import org.opends.server.config.ConfigException;
import org.opends.server.controls.LDAPPostReadRequestControl;
import org.opends.server.controls.LDAPPostReadResponseControl;
import org.opends.server.controls.LDAPPreReadRequestControl;
import org.opends.server.controls.LDAPPreReadResponseControl;
import org.opends.server.core.*;
import org.opends.server.types.*;
import org.opends.server.workflowelement.LeafWorkflowElement;
import static org.opends.messages.CoreMessages.*;
import static org.opends.server.config.ConfigConstants.*;
/**
* This class defines a local backend workflow element; e-g an entity that
* handle the processing of an operation against a local backend.
*/
public class LocalBackendWorkflowElement extends
LeafWorkflowElement<LocalBackendWorkflowElementCfg>
implements ConfigurationChangeListener<LocalBackendWorkflowElementCfg>
{
// the backend associated with the local workflow element
private Backend backend;
// the set of local backend workflow elements registered with the server
private static TreeMap<String, LocalBackendWorkflowElement>
registeredLocalBackends =
new TreeMap<String, LocalBackendWorkflowElement>();
// The set of persistent searches registered with this work flow
// element.
private final List<PersistentSearch> persistentSearches =
new CopyOnWriteArrayList<PersistentSearch>();
// a lock to guarantee safe concurrent access to the registeredLocalBackends
// variable
private static final Object registeredLocalBackendsLock = new Object();
// A string indicating the type of the workflow element.
private static final String BACKEND_WORKFLOW_ELEMENT = "Backend";
/**
* Creates a new instance of the local backend workflow element.
*/
public LocalBackendWorkflowElement()
{
// There is nothing to do in this constructor.
}
/**
* Initializes a new instance of the local backend workflow element.
* This method is intended to be called by DirectoryServer when
* workflow configuration mode is auto as opposed to
* initializeWorkflowElement which is invoked when workflow
* configuration mode is manual.
*
* @param workflowElementID the workflow element identifier
* @param backend the backend associated to that workflow element
*/
private void initialize(String workflowElementID, Backend backend)
{
// Initialize the workflow ID
super.initialize(workflowElementID, BACKEND_WORKFLOW_ELEMENT);
this.backend = backend;
if (this.backend != null)
{
setPrivate(this.backend.isPrivateBackend());
}
}
/**
* Initializes a new instance of the local backend workflow element.
* This method is intended to be called by DirectoryServer when
* workflow configuration mode is manual as opposed to
* initialize(String,Backend) which is invoked when workflow
* configuration mode is auto.
*
* @param configuration The configuration for this local backend
* workflow element.
*
* @throws ConfigException If there is a problem with the provided
* configuration.
*
* @throws InitializationException If an error occurs while trying
* to initialize this workflow
* element that is not related to
* the provided configuration.
*/
public void initializeWorkflowElement(
LocalBackendWorkflowElementCfg configuration
) throws ConfigException, InitializationException
{
configuration.addLocalBackendChangeListener(this);
// Read configuration and apply changes.
processWorkflowElementConfig(configuration, true);
}
/**
* {@inheritDoc}
*/
@Override
public void finalizeWorkflowElement()
{
// null all fields so that any use of the finalized object will raise
// an NPE
super.initialize(null, null);
backend = null;
// Cancel all persistent searches.
for (PersistentSearch psearch : persistentSearches) {
psearch.cancel();
}
persistentSearches.clear();
}
/**
* {@inheritDoc}
*/
@Override
public boolean isConfigurationChangeAcceptable(
LocalBackendWorkflowElementCfg configuration,
List<Message> unacceptableReasons
)
{
boolean isAcceptable =
processWorkflowElementConfig(configuration, false);
return isAcceptable;
}
/**
* {@inheritDoc}
*/
@Override
public ConfigChangeResult applyConfigurationChange(
LocalBackendWorkflowElementCfg configuration
)
{
// Returned result.
ConfigChangeResult changeResult = new ConfigChangeResult(
ResultCode.SUCCESS, false, new ArrayList<Message>()
);
processWorkflowElementConfig(configuration, true);
return changeResult;
}
/**
* Parses the provided configuration and configure the workflow element.
*
* @param configuration The new configuration containing the changes.
* @param applyChanges If true then take into account the new configuration.
*
* @return <code>true</code> if the configuration is acceptable.
*/
private boolean processWorkflowElementConfig(
LocalBackendWorkflowElementCfg configuration,
boolean applyChanges
)
{
// returned status
boolean isAcceptable = true;
// If the workflow element is disabled then do nothing. Note that the
// configuration manager could have finalized the object right before.
if (configuration.isEnabled())
{
// Read configuration.
String newBackendID = configuration.getBackend();
Backend newBackend = DirectoryServer.getBackend(newBackendID);
// If the backend is null (i.e. not found in the list of
// registered backends, this is probably because we are looking
// for the config backend
if (newBackend == null) {
ServerManagementContext context = ServerManagementContext.getInstance();
RootCfg root = context.getRootConfiguration();
try {
BackendCfg backendCfg = root.getBackend(newBackendID);
if (backendCfg.getBaseDN().contains(DN.decode(DN_CONFIG_ROOT))) {
newBackend = DirectoryServer.getConfigHandler();
}
} catch (Exception ex) {
// Unable to find the backend
newBackend = null;
}
}
// Get the new configuration
if (applyChanges)
{
super.initialize(
configuration.dn().getRDN().getAttributeValue(0).toString(),
BACKEND_WORKFLOW_ELEMENT);
backend = newBackend;
if (backend != null)
{
setPrivate(backend.isPrivateBackend());
}
}
}
return isAcceptable;
}
/**
* Creates and registers a local backend with the server.
*
* @param workflowElementID the identifier of the workflow element to create
* @param backend the backend to associate with the local backend
* workflow element
*
* @return the existing local backend workflow element if it was
* already created or a newly created local backend workflow
* element.
*/
public static LocalBackendWorkflowElement createAndRegister(
String workflowElementID,
Backend backend)
{
// If the requested workflow element does not exist then create one.
LocalBackendWorkflowElement localBackend =
registeredLocalBackends.get(workflowElementID);
if (localBackend == null)
{
localBackend = new LocalBackendWorkflowElement();
localBackend.initialize(workflowElementID, backend);
// store the new local backend in the list of registered backends
registerLocalBackend(localBackend);
}
return localBackend;
}
/**
* Removes a local backend that was registered with the server.
*
* @param workflowElementID the identifier of the workflow element to remove
*/
public static void remove(String workflowElementID)
{
deregisterLocalBackend(workflowElementID);
}
/**
* Removes all the local backends that were registered with the server.
* This function is intended to be called when the server is shutting down.
*/
public static void removeAll()
{
synchronized (registeredLocalBackendsLock)
{
for (LocalBackendWorkflowElement localBackend:
registeredLocalBackends.values())
{
deregisterLocalBackend(localBackend.getWorkflowElementID());
}
}
}
/**
* Determine whether or not the provided request control is permitted by the
* access control policy. If it is not allowed, then abort the operation if
* the control was critical, otherwise ignore it.
*
* @param targetDN
* The operation target DN.
* @param op
* The operation.
* @param control
* The request control.
* @return {@code true} if access is allowed, or {@code false} if access is
* not allowed, but the control is non-critical and should be ignored.
* @throws DirectoryException
* If access is not allowed and the control is critical.
*/
static boolean isControlAllowed(DN targetDN, Operation op, Control control)
throws DirectoryException
{
if (!AccessControlConfigManager.getInstance().getAccessControlHandler()
.isAllowed(targetDN, op, control))
{
// As per RFC 4511 4.1.11.
if (control.isCritical())
{
throw new DirectoryException(ResultCode.UNAVAILABLE_CRITICAL_EXTENSION,
ERR_CONTROL_INSUFFICIENT_ACCESS_RIGHTS.get(control.getOID()));
}
else
{
// We don't want the backend to process this non-critical control, so
// remove it.
op.removeRequestControl(control);
return false;
}
}
return true;
}
/**
* Adds the post-read response control to the response if requested.
*
* @param operation
* The update operation.
* @param postReadRequest
* The request control, if present.
* @param entry
* The post-update entry.
*/
static void addPostReadResponse(final Operation operation,
final LDAPPostReadRequestControl postReadRequest, final Entry entry)
{
if (postReadRequest == null)
{
return;
}
/*
* Virtual and collective attributes are only added to an entry when it is
* read from the backend, not before it is written, so we need to add them
* ourself.
*/
final Entry fullEntry = entry.duplicate(true);
/*
* Even though the associated update succeeded, we should still check
* whether or not we should return the entry.
*/
final SearchResultEntry unfilteredSearchEntry = new SearchResultEntry(
fullEntry, null);
if (AccessControlConfigManager.getInstance().getAccessControlHandler()
.maySend(operation, unfilteredSearchEntry))
{
// Filter the entry based on the control's attribute list.
final Entry filteredEntry = fullEntry.filterEntry(
postReadRequest.getRequestedAttributes(), false, false, false);
final SearchResultEntry filteredSearchEntry = new SearchResultEntry(
filteredEntry, null);
// Strip out any attributes which access control denies access to.
AccessControlConfigManager.getInstance().getAccessControlHandler()
.filterEntry(operation, unfilteredSearchEntry, filteredSearchEntry);
final LDAPPostReadResponseControl responseControl =
new LDAPPostReadResponseControl(filteredSearchEntry);
operation.addResponseControl(responseControl);
}
}
/**
* Adds the pre-read response control to the response if requested.
*
* @param operation
* The update operation.
* @param preReadRequest
* The request control, if present.
* @param entry
* The pre-update entry.
*/
static void addPreReadResponse(final Operation operation,
final LDAPPreReadRequestControl preReadRequest, final Entry entry)
{
if (preReadRequest == null)
{
return;
}
/*
* Even though the associated update succeeded, we should still check
* whether or not we should return the entry.
*/
final SearchResultEntry unfilteredSearchEntry = new SearchResultEntry(
entry, null);
if (AccessControlConfigManager.getInstance().getAccessControlHandler()
.maySend(operation, unfilteredSearchEntry))
{
// Filter the entry based on the control's attribute list.
final Entry filteredEntry = entry.filterEntry(
preReadRequest.getRequestedAttributes(), false, false, false);
final SearchResultEntry filteredSearchEntry = new SearchResultEntry(
filteredEntry, null);
// Strip out any attributes which access control denies access to.
AccessControlConfigManager.getInstance().getAccessControlHandler()
.filterEntry(operation, unfilteredSearchEntry, filteredSearchEntry);
final LDAPPreReadResponseControl responseControl =
new LDAPPreReadResponseControl(filteredSearchEntry);
operation.addResponseControl(responseControl);
}
}
/**
* Registers a local backend with the server.
*
* @param localBackend the local backend to register with the server
*/
private static void registerLocalBackend(
LocalBackendWorkflowElement localBackend)
{
synchronized (registeredLocalBackendsLock)
{
String localBackendID = localBackend.getWorkflowElementID();
LocalBackendWorkflowElement existingLocalBackend =
registeredLocalBackends.get(localBackendID);
if (existingLocalBackend == null)
{
TreeMap<String, LocalBackendWorkflowElement> newLocalBackends =
new TreeMap
<String, LocalBackendWorkflowElement>(registeredLocalBackends);
newLocalBackends.put(localBackendID, localBackend);
registeredLocalBackends = newLocalBackends;
}
}
}
/**
* Deregisters a local backend with the server.
*
* @param workflowElementID the identifier of the workflow element to remove
*/
private static void deregisterLocalBackend(String workflowElementID)
{
synchronized (registeredLocalBackendsLock)
{
LocalBackendWorkflowElement existingLocalBackend =
registeredLocalBackends.get(workflowElementID);
if (existingLocalBackend != null)
{
TreeMap<String, LocalBackendWorkflowElement> newLocalBackends =
new TreeMap<String, LocalBackendWorkflowElement>(
registeredLocalBackends);
newLocalBackends.remove(workflowElementID);
registeredLocalBackends = newLocalBackends;
}
}
}
/**
* {@inheritDoc}
*/
@Override
public void execute(Operation operation) throws CanceledOperationException {
switch (operation.getOperationType())
{
case BIND:
LocalBackendBindOperation bindOperation =
new LocalBackendBindOperation((BindOperation) operation);
bindOperation.processLocalBind(this);
break;
case SEARCH:
LocalBackendSearchOperation searchOperation =
new LocalBackendSearchOperation((SearchOperation) operation);
searchOperation.processLocalSearch(this);
break;
case ADD:
LocalBackendAddOperation addOperation =
new LocalBackendAddOperation((AddOperation) operation);
addOperation.processLocalAdd(this);
break;
case DELETE:
LocalBackendDeleteOperation deleteOperation =
new LocalBackendDeleteOperation((DeleteOperation) operation);
deleteOperation.processLocalDelete(this);
break;
case MODIFY:
LocalBackendModifyOperation modifyOperation =
new LocalBackendModifyOperation((ModifyOperation) operation);
modifyOperation.processLocalModify(this);
break;
case MODIFY_DN:
LocalBackendModifyDNOperation modifyDNOperation =
new LocalBackendModifyDNOperation((ModifyDNOperation) operation);
modifyDNOperation.processLocalModifyDN(this);
break;
case COMPARE:
LocalBackendCompareOperation compareOperation =
new LocalBackendCompareOperation((CompareOperation) operation);
compareOperation.processLocalCompare(this);
break;
case ABANDON:
// There is no processing for an abandon operation.
break;
default:
throw new AssertionError("Attempted to execute an invalid operation " +
"type: " + operation.getOperationType() +
" (" + operation + ")");
}
}
/**
* Attaches the current local operation to the global operation so that
* operation runner can execute local operation post response later on.
*
* @param <O> subtype of Operation
* @param <L> subtype of LocalBackendOperation
* @param globalOperation the global operation to which local operation
* should be attached to
* @param currentLocalOperation the local operation to attach to the global
* operation
*/
@SuppressWarnings("unchecked")
public static <O extends Operation,L> void
attachLocalOperation (O globalOperation, L currentLocalOperation)
{
List<?> existingAttachment =
(List<?>) globalOperation.getAttachment(Operation.LOCALBACKENDOPERATIONS);
List<L> newAttachment = new ArrayList<L>();
if (existingAttachment != null)
{
// This line raises an unchecked conversion warning.
// There is nothing we can do to prevent this warning
// so let's get rid of it since we know the cast is safe.
newAttachment.addAll ((List<L>) existingAttachment);
}
newAttachment.add (currentLocalOperation);
globalOperation.setAttachment(Operation.LOCALBACKENDOPERATIONS,
newAttachment);
}
/**
* Gets the backend associated with this local backend workflow
* element.
*
* @return The backend associated with this local backend workflow
* element.
*/
public Backend getBackend()
{
return backend;
}
/**
* Registers the provided persistent search operation with this
* local backend workflow element so that it will be notified of any
* add, delete, modify, or modify DN operations that are performed.
*
* @param persistentSearch
* The persistent search operation to register with this
* local backend workflow element.
*/
void registerPersistentSearch(PersistentSearch persistentSearch)
{
PersistentSearch.CancellationCallback callback =
new PersistentSearch.CancellationCallback()
{
@Override
public void persistentSearchCancelled(PersistentSearch psearch)
{
persistentSearches.remove(psearch);
}
};
persistentSearches.add(persistentSearch);
persistentSearch.registerCancellationCallback(callback);
}
/**
* Gets the list of persistent searches currently active against
* this local backend workflow element.
*
* @return The list of persistent searches currently active against
* this local backend workflow element.
*/
List<PersistentSearch> getPersistentSearches()
{
return persistentSearches;
}
}