package example;

import java.util.ArrayList;
import java.util.List;

import javax.xml.namespace.QName;

import org.apache.axis.AxisEngine;
import org.apache.axis.AxisFault;
import org.apache.axis.Chain;
import org.apache.axis.ConfigurationException;
import org.apache.axis.EngineConfiguration;
import org.apache.axis.Handler;
import org.apache.axis.MessageContext;
import org.apache.axis.components.logger.LogFactory;
import org.apache.axis.handlers.BasicHandler;
import org.apache.commons.logging.Log;

/**
 * Chain that can enable/disable its contained handlers in a per request
 * basis.
 * <p>
 * The handlers to chain must have been previously defined with a unique name.
 * <p>
 * It must be deployed into <code>client-config.wsdd</code> as a regular
 * handler, specifying singleton scope, and a space-separated list of handler
 * names to chain through a parameter called "handlers", as follows:
 *
 * <pre>
 *   &lt;handler type="java:example.DynamicChain"&gt;
 *     &lt;parameter name="scope" value="singleton"/&gt;
 *     &lt;parameter name="handlers" value="name1 name2 ..."/&gt;
 *   &lt;/handler&gt;
 * </pre>
 *
 * It should be deployed on both the request and response flows.
 *
 * This is a template, and lacks the logic for:
 *
 * <ol>
 *   <li>Obtain the list of "active" handlers from the MessageContext,
 *       or any other channel.</li>
 * </ol>
 *
 * @author <a href="mailto:rruiz@gridsystems.com">Rodrigo Ruiz Aguayo</a>
 */
public class DynamicChain extends BasicHandler implements Chain {

  /**
   * <code>serialVersionUID</code> attribute.
   */
  private static final long serialVersionUID = 4204047633340209266L;

  /**
   * Class logger.
   */
  private static Log log = LogFactory.getLog(DynamicChain.class.getName());

  /**
   * Contains the list of "enabled" handlers.
   */
  private static final ThreadLocal<List<Handler>> ACTIVE
    = new ThreadLocal<List<Handler>>();

  /**
   * Name of the configuration file path option.
   */
  public static final String OPTION_NAMES = "handlers";

  /**
   * List of chained handlers.
   */
  private final List<Handler> handlers = new ArrayList<Handler>();

  /**
   * {@inheritDoc}
   */
  public void addHandler(final Handler handler) {
    handlers.add(handler);
  }

  /**
   * {@inheritDoc}
   */
  public boolean contains(final Handler handler) {
    return handlers.contains(handler);
  }

  /**
   * {@inheritDoc}
   */
  public Handler[] getHandlers() {
    final Handler[] array = new Handler[handlers.size()];
    return handlers.toArray(array);
  }

  /**
   * Finds a handler by its name.
   *
   * @param name The handler name
   * @return The handler if found
   */
  private Handler findHandler(final String name) {
    MessageContext ctx = MessageContext.getCurrentContext();
    if (ctx != null) {
      AxisEngine engine = ctx.getAxisEngine();
      if (engine != null) {
        EngineConfiguration config = engine.getConfig();
        if (config != null) {
          try {
            final QName qname = new QName("", name);
            return config.getHandler(qname);
          } catch (ConfigurationException e) {
            log.warn("Could not find handler " + name, e);
          }
        }
      }
    }

    return null;
  }

  /**
   * {@inheritDoc}
   */
  public void init() {
    // Parse the list of handler names and setup the chain
    final String opt = (String)getOption(OPTION_NAMES);
    final String[] names = opt.split("\\s+");
    for (final String name : names) {
      final Handler h = findHandler(name);
      if (h != null) {
        this.handlers.add(h);
      }
    }

    // Initialise ALL handlers
    for (final Handler h : handlers) {
      h.init();
    }
  }

  /**
   * Builds the list of active handlers, and put it into the ACTIVE thread
   * local field.
   *
   * @param ctx The context used to find out which are the active handlers
   *            It may be null
   * @return A list containing the active handlers
   */
  private List<Handler> getActiveHandlers(final MessageContext ctx) {
    List<Handler> active = ACTIVE.get();
    if (active == null) {
      active = new ArrayList<Handler>();

      final MessageContext mc;
      mc = (ctx == null) ? MessageContext.getCurrentContext() : ctx;

      // @todo Process the message context and get the list of active handlers

      ACTIVE.set(active);
    }

    return active;
  }

  /**
   * {@inheritDoc}
   */
  public void cleanup() {
    for (final Handler h : getActiveHandlers(null)) {
      h.cleanup();
    }

    // Prepares the chain for the next request
    ACTIVE.remove();
  }

  /**
   * {@inheritDoc}
   */
  public void invoke(final MessageContext ctx) throws AxisFault {

    for (final Handler h : getActiveHandlers(ctx)) {
      h.invoke(ctx);
    }
  }

  /**
   * {@inheritDoc}
   */
  public void onFault(final MessageContext ctx) {
    final List<Handler> active = getActiveHandlers(ctx);

    // The fault is processed in reverse order
    final int count = active.size();
    for (int i = count - 1; i >= 0; i--) {
      active.get(i).onFault(ctx);
    }
  }

  /**
   * {@inheritDoc}
   */
  public boolean canHandleBlock(final QName qname) {
    for (final Handler h : getActiveHandlers(null)) {
      if (h.canHandleBlock(qname)) {
        return true;
      }
    }
    return false;
  }

  /**
   * {@inheritDoc}
   */
  @SuppressWarnings("unchecked")
  public List getUnderstoodHeaders() {
    final List qnames = new ArrayList();

    for (final Handler h : getActiveHandlers(null)) {
      final List list = h.getUnderstoodHeaders();
      if (list != null) {
        qnames.addAll(list);
      }
    }
    return qnames;
  }

  /**
   * {@inheritDoc}
   */
  public void generateWSDL(final MessageContext ctx) throws AxisFault {
    for (final Handler h : getActiveHandlers(ctx)) {
      h.generateWSDL(ctx);
    }
  }

}
