/* * 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 Sun Microsystems, Inc. */ package org.opends.server.core; import static org.opends.messages.CoreMessages.*; import static org.opends.server.util.Validator.ensureNotNull; import java.util.Collection; import java.util.Observable; import java.util.Observer; import java.util.TreeMap; import org.opends.messages.MessageBuilder; import org.opends.server.admin.std.server.WorkflowCfg; import org.opends.server.types.*; import org.opends.server.workflowelement.WorkflowElement; import org.opends.server.workflowelement.ObservableWorkflowElementState; /** * This class implements the workflow interface. Each task in the workflow * is implemented by a WorkflowElement. All the tasks in the workflow are * structured in a tree of tasks and the root node of the task tree is * stored in the Workflow class itself. To execute a workflow, one just need * to call the execute method on the root node of the task tree. Then each * task in turn will execute its subordinate nodes and synchronizes them * as needed. */ public class WorkflowImpl implements Workflow, Observer { // The workflow identifier used by the configuration. private final String workflowID; // The root of the workflow task tree. private WorkflowElement<?> rootWorkflowElement = null; // The root workflow element identifier. private String rootWorkflowElementID = null; // The base DN of the data handled by the workflow. private final DN baseDN; // Flag indicating whether the workflow root node of the task tree is // handling a private local backend. // // A private local backend is used by the server to store "private data" // such as schemas, tasks, monitoring data, configuration data... Such // private data are not returned upon a subtree search on the root DSE. // Also it is not planned to have anything but a single node task tree // to handle private local backend. So workflows used for proxy and // virtual will always be made public (ie. not private). So, unless the // rootWorkflowElement is handling a private local backend, the isPrivate // flag will always return false. private boolean isPrivate = false; // The set of workflows registered with the server. private static TreeMap<String, Workflow> registeredWorkflows = new TreeMap<String, Workflow>(); // A lock to protect concurrent access to the registeredWorkflows. private static Object registeredWorkflowsLock = new Object(); // A reference counter used to count the number of workflow nodes that // were registered with a network group. A workflow can be disabled or // deleted only when its reference counter value is 0. private int referenceCounter = 0; private Object referenceCounterLock = new Object(); /** * Creates a new instance of a workflow implementation. To define a workflow * one needs to provide a task tree root node (the rootWorkflowElement) and * a base DN to identify the data set upon which the tasks can be applied. * * The rootWorkflowElement must not be null. * * @param workflowId workflow internal identifier * @param baseDN identifies the data handled by the workflow * @param rootWorkflowElementID the identifier of the root workflow element * @param rootWorkflowElement the root node of the workflow task tree */ public WorkflowImpl( String workflowId, DN baseDN, String rootWorkflowElementID, WorkflowElement<?> rootWorkflowElement ) { this.workflowID = workflowId; this.baseDN = baseDN; this.rootWorkflowElement = rootWorkflowElement; if (this.rootWorkflowElement != null) { this.isPrivate = rootWorkflowElement.isPrivate(); this.rootWorkflowElementID = rootWorkflowElementID; // The workflow wants to be notified when the workflow element state // is changing from enabled to disabled and vice versa. WorkflowElement.registereForStateUpdate( rootWorkflowElement, null, this); } else { // The root workflow element has not been loaded, let's register // the workflow with the list of objects that want to be notify // when the workflow element is created. WorkflowElement.registereForStateUpdate( null, rootWorkflowElementID, this); } } /** * Performs any finalization that might be required when this * workflow is unloaded. No action is taken in the default * implementation. */ public void finalizeWorkflow() { // No action is required by default. } /** * Gets the base DN of the data set being handled by the workflow. * * @return the workflow base DN */ public DN getBaseDN() { return baseDN; } /** * Gets the workflow internal identifier. * * @return the workflow internal identifier */ public String getWorkflowId() { return workflowID; } /** * Indicates whether the root node of the workflow task tree is * handling a private local backend. * * @return <code>true</code> if the workflow encapsulates a private local * backend */ public boolean isPrivate() { return isPrivate; } /** * Executes all the tasks defined by the workflow task tree for a given * operation. * * @param operation the operation to execute * * @throws CanceledOperationException if this operation should * be canceled. */ public void execute(Operation operation) throws CanceledOperationException { if (rootWorkflowElement != null) { rootWorkflowElement.execute(operation); } else { // No root workflow element? It's a configuration error. operation.setResultCode(ResultCode.OPERATIONS_ERROR); MessageBuilder message = new MessageBuilder( ERR_ROOT_WORKFLOW_ELEMENT_NOT_DEFINED.get(workflowID)); operation.setErrorMessage(message); } } /** * Registers the current workflow (this) with the server. * * @throws DirectoryException If the workflow ID for the provided workflow * conflicts with the workflow ID of an existing * workflow. */ public void register() throws DirectoryException { ensureNotNull(workflowID); synchronized (registeredWorkflowsLock) { // The workflow must not be already registered if (registeredWorkflows.containsKey(workflowID)) { throw new DirectoryException( ResultCode.UNWILLING_TO_PERFORM, ERR_REGISTER_WORKFLOW_ALREADY_EXISTS.get(workflowID)); } TreeMap<String, Workflow> newRegisteredWorkflows = new TreeMap<String, Workflow>(registeredWorkflows); newRegisteredWorkflows.put(workflowID, this); registeredWorkflows = newRegisteredWorkflows; } } /** * Deregisters the current workflow (this) with the server. */ public void deregister() { ensureNotNull(workflowID); // Deregister the workflow with the list of objects to notify when // a workflow element is created or deleted. WorkflowElement.deregistereForStateUpdate( null, rootWorkflowElementID, this); // Deregister the workflow with the list of registered workflows. synchronized (registeredWorkflowsLock) { TreeMap<String, Workflow> newWorkflows = new TreeMap<String, Workflow>(registeredWorkflows); newWorkflows.remove(workflowID); registeredWorkflows = newWorkflows; } } /** * Deregisters a workflow with the server. The workflow to deregister * is identified with its identifier. * * @param workflowID the identifier of the workflow to deregister * * @return the workflow that has been deregistered, * <code>null</code> if no workflow has been found. */ public WorkflowImpl deregister(String workflowID) { WorkflowImpl workflowToDeregister = null; synchronized (registeredWorkflowsLock) { if (registeredWorkflows.containsKey(workflowID)) { workflowToDeregister = (WorkflowImpl) registeredWorkflows.get(workflowID); workflowToDeregister.deregister(); } } return workflowToDeregister; } /** * Deregisters all Workflows that have been registered. This should be * called when the server is shutting down. */ public static void deregisterAllOnShutdown() { synchronized (registeredWorkflowsLock) { registeredWorkflows = new TreeMap<String, Workflow>(); } } /** * Gets a workflow that was registered with the server. * * @param workflowID the ID of the workflow to get * @return the requested workflow */ public static Workflow getWorkflow( String workflowID) { return registeredWorkflows.get(workflowID); } /** * Gets all the workflows that were registered with the server. * * @return the list of registered workflows */ public static Collection<Workflow> getWorkflows() { return registeredWorkflows.values(); } /** * Gets the root workflow element for test purpose only. * * @return the root workflow element. */ WorkflowElement<?> getRootWorkflowElement() { return rootWorkflowElement; } /** * Resets all the registered workflows. */ public static void resetConfig() { synchronized (registeredWorkflowsLock) { registeredWorkflows = new TreeMap<String, Workflow>(); } } /** * Updates the workflow configuration. This method should be invoked * whenever an existing workflow is modified. * * @param configuration the new workflow configuration */ public void updateConfig(WorkflowCfg configuration) { // The only parameter that can be changed is the root workflow element. String rootWorkflowElementID = configuration.getWorkflowElement(); WorkflowElement<?> rootWorkflowElement = DirectoryServer.getWorkflowElement(rootWorkflowElementID); // Update the ID of the new root workflow element // and deregister the workflow with the list of objects to notify // when the former root workflow element is created String previousRootWorkflowElement = this.rootWorkflowElementID; WorkflowElement.deregistereForStateUpdate( null, previousRootWorkflowElement, this); this.rootWorkflowElementID = rootWorkflowElementID; // Does the new root workflow element exist? if (rootWorkflowElement == null) { // The new root workflow element does not exist yet then do nothing // but register with the list of object to notify when the workflow // element is created (and deregister first in case the workflow // was already registered) WorkflowElement.registereForStateUpdate( null, rootWorkflowElementID, this); rootWorkflowElement = null; } else { // The new root workflow element exists, let's use it and don't forget // to register with the list of objects to notify when the workflow // element is deleted. this.rootWorkflowElement = rootWorkflowElement; WorkflowElement.registereForStateUpdate( rootWorkflowElement, null, this); } } /** * {@inheritDoc} */ public void update(Observable observable, Object arg) { if (observable instanceof ObservableWorkflowElementState) { ObservableWorkflowElementState weState = (ObservableWorkflowElementState) observable; updateRootWorkflowElementState(weState); } } /** * Display the workflow object. * @return a string identifying the workflow. */ public String toString() { String id = "Workflow " + workflowID; return id; } /** * Takes into account an update of the root workflow element. * <p> * This method is called when a workflow element is created or * deleted. If the workflow element is the root workflow element * then the workflow processes it as follow: * <br> * If the workflow element is disabled then the workflow reset * its root workflow element (ie. the workflow has no more workflow * element, hence the workflow cannot process requests anymore). * <br> * If the workflow element is enabled then the workflow adopts the * workflow element as its new root workflow element. The workflow * then can process requests again. * * @param weState the new state of the root workflow element */ private void updateRootWorkflowElementState( ObservableWorkflowElementState weState) { // Check that the workflow element maps the root workflow element. // If not then ignore the workflow element. WorkflowElement<?> we = weState.getObservedWorkflowElement(); String newWorkflowElementID = we.getWorkflowElementID(); if (! rootWorkflowElementID.equalsIgnoreCase(newWorkflowElementID)) { return; } // The workflow element maps the root workflow element, let's process it. if (weState.workflowElementIsEnabled()) { // The root workflow element is enabled, let's use it // and don't forget to register the workflow with the list // of objects to notify when the root workflow element // is disabled... rootWorkflowElement = weState.getObservedWorkflowElement(); WorkflowElement.registereForStateUpdate( rootWorkflowElement, null, this); WorkflowElement.deregistereForStateUpdate( null, rootWorkflowElementID, this); } else { // The root workflow element has been disabled. Reset the current // reference to the root workflow element and register the workflow // with the list of objects to notify when new workflow elements // are created. WorkflowElement.registereForStateUpdate( null, rootWorkflowElement.getWorkflowElementID(), this); rootWorkflowElement = null; } } /** * Increments the workflow reference counter. * <p> * As long as the counter value is not 0 the workflow cannot be * disabled nor deleted. */ public void incrementReferenceCounter() { synchronized (referenceCounterLock) { referenceCounter++; } } /** * Decrements the workflow reference counter. * <p> * As long as the counter value is not 0 the workflow cannot be * disabled nor deleted. */ public void decrementReferenceCounter() { synchronized (referenceCounterLock) { if (referenceCounter == 0) { // the counter value is 0, we should not need to decrement anymore throw new AssertionError( "Reference counter of the workflow " + workflowID + " is already set to 0, cannot decrement it anymore" ); } referenceCounter--; } } /** * Gets the value of the reference counter of the workflow. * * @return the reference counter of the workflow */ public int getReferenceCounter() { return referenceCounter; } }