/*
 * 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.
 */
package org.opends.server.workflowelement;


import java.util.List;
import java.util.Observable;
import java.util.Observer;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;
import org.opends.server.admin.std.server.MonitorProviderCfg;

import org.opends.server.admin.std.server.WorkflowElementCfg;
import org.opends.server.api.MonitorProvider;
import org.opends.server.core.DirectoryServer;
import org.opends.server.types.Operation;
import org.opends.server.types.CanceledOperationException;


/**
 * This class defines the super class for all the workflow elements. A workflow
 * element is a task in a workflow. A workflow element can wrap a physical
 * repository such as a local backend, a remote LDAP server or a local LDIF
 * file. A workflow element can also be used to route operations. This is the
 * case for load balancing and distribution. And workflow element can be used
 * in a virtual environment to transform data (DN and attribute renaming,
 * attribute value renaming...).
 *
 * @param  <T>  The type of configuration handled by this workflow element.
 */
public abstract class WorkflowElement <T extends WorkflowElementCfg>
    implements Observer
{
  // Indicates whether the workflow element encapsulates a private local
  // backend.
  private boolean isPrivate = false;


  // An information indicating the type of the current workflow element.
  // This information is for debug and tooling purpose only.
  private String workflowElementTypeInfo = "not defined";


  // The workflow element identifier.
  private String workflowElementID = null;


  // The observable state of the workflow element.
  private ObservableWorkflowElementState observableState =
    new ObservableWorkflowElementState(this);


  // The list of observers who want to be notified when a workflow element
  // required by the observer is created. The key of the map is a string
  // that identifies the newly created workflow element.
  private static ConcurrentMap<String, List<Observer>>
    newWorkflowElementNotificationList =
      new ConcurrentHashMap<String, List<Observer>>();


  // The observable status of the workflow element.
  // The status contains the health indicator (aka saturation index)
  // of the workflow element.
  private ObservableWorkflowElementStatus observableStatus =
    new ObservableWorkflowElementStatus(this);

  // The statistics exported by the workflow element
  private MonitorProvider<MonitorProviderCfg> statistics;

  /**
   * Provides the observable state of the workflow element.
   * This method is intended to be called by the WorkflowElementConfigManager
   * that wants to notify observers that the workflow element state has
   * changed (in particular when a workflow element has been disabled).
   *
   * @return the observable state of the workflow element
   */
  protected ObservableWorkflowElementState getObservableState()
  {
    return observableState;
  }


  /**
   * Provides the observable status of the workflow element.
   *
   * @return the observable status of the workflow element.
   */
  protected ObservableWorkflowElementStatus getObservableStatus()
  {
    return observableStatus;
  }


  /**
   * Registers with a specific workflow element to be notified when the
   * workflow element state has changed. This notification system is
   * mainly used to be warned when a workflow element is enabled or
   * disabled.
   * <p>
   * If the workflow element <code>we</code> is not <code>null</code>
   * then the <code>observer</code> is registered with the list of objects
   * to notify when <code>we</code> has changed.
   * <p>
   * If the workflow element <code>we</code> is <code>null</code> then
   * the <code>observer</code> is registered with a static list of objects
   * to notify when a workflow element named <code>weid</code> is created.
   *
   * @param we        the workflow element. If <code>null</code> then observer
   *                  is registered with a list of workflow element
   *                  identifiers.
   * @param weid      the identifier of the workflow element. This parameter
   *                  is useless when <code>we</code> is not <code>null</code>
   * @param observer  the observer to notify when the workflow element state
   *                  has been modified
   */
  public static void registereForStateUpdate(
      WorkflowElement<?> we,
      String weid,
      Observer observer
      )
  {
    // If the workflow element "we" exists then register the observer with "we"
    // else register the observer with a static list of workflow element
    // identifiers
    if (we != null)
    {
      ObservableWorkflowElementState westate = we.getObservableState();
      westate.addObserver(observer);
    }
    else
    {
      if (weid == null)
      {
        return;
      }

      List<Observer> observers = newWorkflowElementNotificationList.get(weid);
      if (observers == null)
      {
        // create the list of observers
        observers = new CopyOnWriteArrayList<Observer>();
        observers.add(observer);
        newWorkflowElementNotificationList.put(weid, observers);
      }
      else
      {
        // update the observer list
        observers.add(observer);
      }
    }
  }


  /**
   * Deregisters an observer that was registered with a specific workflow
   * element.
   * <p>
   * If the workflow element <code>we</code> is not <code>null</code>
   * then the <code>observer</code> is deregistered with the list of objects
   * to notify when <code>we</code> has changed.
   * <p>
   * If the workflow element <code>we</code> is <code>null</code> then
   * the <code>observer</code> is deregistered with a static list of objects
   * to notify when a workflow element named <code>weid</code> is created.
   *
   * @param we        the workflow element. If <code>null</code> then observer
   *                  is deregistered with a list of workflow element
   *                  identifiers.
   * @param weid      the identifier of the workflow element. This parameter
   *                  is useless when <code>we</code> is not <code>null</code>
   * @param observer  the observer to deregister
   */
  public static void deregistereForStateUpdate(
      WorkflowElement<?> we,
      String weid,
      Observer observer
      )
  {
    // If the workflow element "we" exists then deregister the observer
    // with "we" else deregister the observer with a static list of
    // workflow element identifiers
    if (we != null)
    {
      ObservableWorkflowElementState westate = we.getObservableState();
      westate.deleteObserver(observer);
    }

    if (weid != null)
    {
      List<Observer> observers = newWorkflowElementNotificationList.get(weid);
      if (observers != null)
      {
        observers.remove(observer);
      }
    }
  }


  /**
   * Notifies all the observers who want to be warn when a workflow element
   * is created.
   *
   * @param workflowElement  the newly created workflow element
   */
  public static void notifyStateUpdate(
      WorkflowElement<?> workflowElement)
  {
    // Go through the list of observers and notify them all
    String weID = workflowElement.getWorkflowElementID();

    List<Observer> observers = newWorkflowElementNotificationList.get(weID);
    if (observers != null)
    {
      for (Observer observer: observers)
      {
        // The update might fail because an observer could have been
        // terminated. In this case, just ignore the failure and remove
        // the observer from the list of objects to notify.
        try
        {
          observer.update(workflowElement.getObservableState(), null);
        }
        catch(Exception e)
        {
          observers.remove(observer);
        }
      }
    }
  }


  /**
   * Creates a new instance of the workflow element.
   */
  public WorkflowElement()
  {
    // There is nothing to do in the constructor.
  }


  /**
   * Initializes the instance of the workflow element.
   *
   * @param workflowElementID  the workflow element identifier as defined
   *                           in the configuration.
   * @param workflowElementTypeInfo  an information to indicate the type of
   *                                 the current workflow element. For example
   *                                 "Backend" if the current workflow element
   *                                 is a local backend workflow element.
   */
  public void initialize(
      String workflowElementID,
      String workflowElementTypeInfo)
  {
    this.workflowElementID = workflowElementID;
    this.workflowElementTypeInfo = workflowElementTypeInfo;
    this.statistics = this.createStatistics();
    if (this.statistics != null) {
      DirectoryServer.registerMonitorProvider(this.statistics);
    }
  }


  /**
   * {@inheritDoc}
   */
  public void update(Observable o, Object arg)
  {
    // By default, do nothing when notification hits the workflow element.
  }


  /**
   * Get the type of the workflow element. The type is a string information
   * indicating which type is the current workflow element. This information
   * is intended to be used by tools for trace and debug purpose.
   *
   * @return the type of the workflow element.
   */
  public String getWorkflowElementTypeInfo()
  {
    return this.workflowElementTypeInfo;
  }


  /**
   * Indicates whether the provided configuration is acceptable for
   * this workflow element.
   *
   * @param  configuration        The workflow element configuration for
   *                              which to make the determination.
   * @param  unacceptableReasons  A list that may be used to hold the
   *                              reasons that the provided
   *                              configuration is not acceptable.
   *
   * @return  {@code true} if the provided configuration is acceptable
   *          for this workflow element, or {@code false} if not.
   */
  public boolean isConfigurationAcceptable(
      T configuration,
      List<String> unacceptableReasons)
  {
    // This default implementation does not perform any special
    // validation.  It should be overridden by workflow element
    // implementations that wish to perform more detailed validation.
    return true;
  }


  /**
   * Performs any finalization that might be required when this
   * workflow element is unloaded.  No action is taken in the default
   * implementation.
   */
  public void finalizeWorkflowElement()
  {
    // Deregister the monitor provider.
    if (this.statistics != null) {
      DirectoryServer.deregisterMonitorProvider(
          this.statistics.getMonitorInstanceName());
    }
  }

  /**
   * Executes the workflow element for an operation.
   *
   * @param operation the operation to execute
   *
   * @throws CanceledOperationException if this operation should be
   * canceled
   */
  public abstract void execute(Operation operation)
      throws CanceledOperationException;


  /**
   * Indicates whether the workflow element encapsulates a private
   * local backend.
   *
   * @return <code>true</code> if the workflow element encapsulates a private
   *         local backend, <code>false</code> otherwise
   */
  public boolean isPrivate()
  {
    return isPrivate;
  }


  /**
   * Specifies whether the workflow element encapsulates a private local
   * backend.
   *
   * @param  isPrivate  Indicates whether the workflow element encapsulates a
   *                    private local backend.
   */
  protected void setPrivate(boolean isPrivate)
  {
    this.isPrivate = isPrivate;
  }


  /**
   * Provides the workflow element identifier.
   *
   * @return the workflow element identifier
   */
  public String getWorkflowElementID()
  {
    return workflowElementID;
  }


  /**
   * Modifies the saturation index of the workflow element.
   *
   * @param  newValue
   *         The new value of the saturation index of the workflow element.
   */
  public void setSaturationIndex(int newValue)
  {
    observableStatus.setSaturationIndex(newValue);
  }


  /**
   * Gets the saturation index of the workflow element.
   *
   * @return  the value of the saturation index of the workflow element.
   */
  public int getSaturationIndex()
  {
    return observableStatus.getSaturationIndex();
  }


  /**
   * Registers an observer with the saturation index of the workflow
   * element. The observer will be notified when the saturation index
   * is updated.
   *
   * @param  observer
   *         The observer to notify when the saturation index is modified.
   */
  public void registerForSaturationIndexUpdate(Observer observer)
  {
    observableStatus.addObserver(observer);
  }


  /**
   * Deregisters an observer with the saturation index of the workflow
   * element.
   *
   * @param  observer
   *         The observer to deregister.
   */
  public void deregisterForSaturationIndexUpdate(Observer observer)
  {
    observableStatus.deleteObserver(observer);
  }

  /**
   * Retrieves the list of child workflow elements, ie the
   * WorkflowElements below this one in the topology tree.
   *
   * @return child workflow elements
   */
  public abstract List<WorkflowElement<?>> getChildWorkflowElements();

  /**
   * Checks whether the tree of workflow elements below this one
   * contains the provided workflow element.
   *
   * @param element The WorkflowElement we are looking for in the topology
   *        below this object.
   * @return boolean
   */
  public boolean hasChildWorkflowElement(WorkflowElement element) {
    if (this.getChildWorkflowElements().size() == 0) {
      return (this.equals(element));
    }

    for (WorkflowElement subElement:this.getChildWorkflowElements()) {
      if (subElement.equals(element) ||
          subElement.hasChildWorkflowElement(element)) {
        return true;
      }
    }
    return false;
  }

  /**
   * Creates the statistics exposed by the workflow element. By default,
   * workflow elements do not expose anything but specific implementations
   * can override this method and provide their own stats.
   * @return the statistics exposed by the workflow element.
   */
  public MonitorProvider<MonitorProviderCfg> createStatistics() {
    // by default, no stats are created;
    // This method should be overriden if necessary
    return null;
  }
}
