Package ch.entwine.weblounge.common.impl.site

Source Code of ch.entwine.weblounge.common.impl.site.ActionSupport

/*
*  Weblounge: Web Content Management System
*  Copyright (c) 2003 - 2011 The Weblounge Team
*  http://entwinemedia.com/weblounge
*
*  This program is free software; you can redistribute it and/or
*  modify it under the terms of the GNU Lesser General Public License
*  as published by the Free Software Foundation; either version 2
*  of the License, or (at your option) any later version.
*
*  This program is distributed in the hope that it will be useful,
*  but WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*  GNU Lesser General Public License for more details.
*
*  You should have received a copy of the GNU Lesser General Public License
*  along with this program; if not, write to the Free Software Foundation
*  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

package ch.entwine.weblounge.common.impl.site;

import ch.entwine.weblounge.common.content.page.HTMLHeadElement;
import ch.entwine.weblounge.common.content.page.HTMLInclude;
import ch.entwine.weblounge.common.content.page.Link;
import ch.entwine.weblounge.common.content.page.PageTemplate;
import ch.entwine.weblounge.common.content.page.Pagelet;
import ch.entwine.weblounge.common.content.page.PageletRenderer;
import ch.entwine.weblounge.common.content.page.Script;
import ch.entwine.weblounge.common.impl.content.GeneralComposeable;
import ch.entwine.weblounge.common.impl.content.page.LinkImpl;
import ch.entwine.weblounge.common.impl.content.page.ScriptImpl;
import ch.entwine.weblounge.common.impl.request.RequestUtils;
import ch.entwine.weblounge.common.impl.url.WebUrlImpl;
import ch.entwine.weblounge.common.impl.util.config.ConfigurationUtils;
import ch.entwine.weblounge.common.impl.util.config.OptionsHelper;
import ch.entwine.weblounge.common.impl.util.xml.XPathHelper;
import ch.entwine.weblounge.common.request.RequestFlavor;
import ch.entwine.weblounge.common.request.WebloungeRequest;
import ch.entwine.weblounge.common.request.WebloungeResponse;
import ch.entwine.weblounge.common.site.Action;
import ch.entwine.weblounge.common.site.ActionException;
import ch.entwine.weblounge.common.site.Environment;
import ch.entwine.weblounge.common.site.Module;
import ch.entwine.weblounge.common.site.Site;
import ch.entwine.weblounge.common.url.UrlUtils;
import ch.entwine.weblounge.common.url.WebUrl;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.lang.StringUtils;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathFactory;

/**
* This class is the default implementation for an <code>Action</code>. Its main
* two methods
* {@link #configure(WebloungeRequest, WebloungeResponse, RequestFlavor)} and
* {@link #startResponse(WebloungeRequest, WebloungeResponse)}
* <p>
* <b>Note:</b> Be aware of the fact that actions are pooled, so make sure to
* implement the <code>activate()</code> and <code>passivate()</code> method
* accordingly and of course to include the respective super implementations.
*/
public abstract class ActionSupport extends GeneralComposeable implements Action {

  /** Logging facility */
  private static final Logger logger = LoggerFactory.getLogger(ActionSupport.class);

  /** The action mountpoint */
  protected String mountpoint = null;

  /** The list of flavors */
  protected Set<RequestFlavor> flavors = new HashSet<RequestFlavor>();

  /** Options support */
  protected OptionsHelper options = new OptionsHelper();

  /** The requested output flavor */
  protected RequestFlavor flavor = null;

  /** The parent site */
  protected Site site = null;

  /** The parent module */
  protected Module module = null;

  /** List of supported methods */
  private Set<String> verbs = null;

  /** Map containing uploaded files */
  protected List<FileItem> files = null;

  /** The number of includes */
  protected int includeCount = 0;

  /** The current request object */
  protected WebloungeRequest request = null;

  /** The current response object */
  protected WebloungeResponse response = null;

  /** The site's bundle context */
  protected BundleContext bundleContext = null;

  /**
   * Default constructor.
   */
  public ActionSupport() {
    verbs = new HashSet<String>();
  }

  /**
   * {@inheritDoc}
   *
   * @see ch.entwine.weblounge.common.site.Action#startResponse(ch.entwine.weblounge.common.request.WebloungeRequest,
   *      ch.entwine.weblounge.common.request.WebloungeResponse)
   */
  public abstract int startResponse(WebloungeRequest request,
      WebloungeResponse response) throws ActionException;

  /**
   * Enables support for the given HTTP verb.
   *
   * @param method
   *          the method
   * @throws IllegalArgumentException
   *           if <code>method</code> is <code>null</code> or empty
   * @see Action#supportsMethod(String)
   */
  protected void enableMethod(String method) {
    if (StringUtils.isBlank(method))
      throw new IllegalArgumentException("Method must not be blank");
    if (verbs == null)
      verbs = new HashSet<String>();
    verbs.add(method.toUpperCase());
  }

  /**
   * Enables support for the given HTTP verbs.
   *
   * @param methods
   *          the HTTP verbs to support
   * @throws IllegalArgumentException
   *           if <code>methods</code> is <code>null</code>
   * @see Action#supportsMethod(String)
   */
  protected void enableMethods(String... methods) {
    if (methods == null)
      throw new IllegalArgumentException("Methods must not be blank");
    for (String method : methods) {
      enableMethod(method);
    }
  }

  /**
   * Disables support for the given HTTP verb.
   *
   * @param method
   *          the method
   * @throws IllegalArgumentException
   *           if <code>method</code> is <code>null</code> or empty
   * @see Action#supportsMethod(String)
   */
  protected void disableMethod(String method) {
    if (StringUtils.isBlank(method))
      throw new IllegalArgumentException("Method must not be blank");
    if (verbs != null)
      verbs.remove(method.toUpperCase());
  }

  /**
   * Disables support for the given HTTP verbs.
   *
   * @param methods
   *          the methods
   * @throws IllegalArgumentException
   *           if <code>methods</code> is <code>null</code>
   * @see Action#supportsMethod(String)
   */
  protected void disableMethods(String... methods) {
    if (methods == null)
      throw new IllegalArgumentException("Methods must not be blank");
    for (String method : methods) {
      disableMethod(method);
    }
  }

  /**
   * {@inheritDoc}
   * <p>
   * Note that this default implementation enables support for <code>GET</code>
   * requests only.
   *
   * @throws IllegalArgumentException
   *           if <code>method</code> is <code>null</code>
   * @see ch.entwine.weblounge.common.site.Action#supportsMethod(java.lang.String)
   */
  public boolean supportsMethod(String method) {
    if (StringUtils.isBlank(method))
      throw new IllegalArgumentException("Method must not be blank");
    method = method.toUpperCase();
    // TODO Remove after all actions provide their own verbs
    if (verbs == null || verbs.size() == 0)
      return "GET".equals(method) || "POST".equals(method) || "PUT".equals(method);
    return verbs.contains(method.toUpperCase());
  }

  /**
   * {@inheritDoc}
   *
   * @see ch.entwine.weblounge.common.site.Action#getMethods()
   */
  @Override
  public String[] getMethods() {
    if (verbs == null || verbs.size() == 0) {
      return new String[] { "GET", "POST", "PUT" };
    } else {
      return verbs.toArray(new String[verbs.size()]);
    }
  }

  /**
   * Returns the site's OSGi bundle context if available, <code>null</code>
   * otherwise.
   *
   * @return the bundle context
   */
  protected BundleContext getBundleContext() {
    return bundleContext;
  }

  /**
   * Sets the parent module.
   *
   * @param module
   *          the parent module
   */
  public void setModule(Module module) {
    this.module = module;
    for (HTMLHeadElement headElement : headers) {
      headElement.setModule(module);
    }
  }

  /**
   * Returns the parent module.
   *
   * @return the module
   */
  public Module getModule() {
    return module;
  }

  /**
   * Sets the associated site.
   *
   * @param site
   *          the associated site
   */
  public void setSite(Site site) {
    this.site = site;
    for (HTMLHeadElement headElement : headers) {
      headElement.setSite(site);
    }

    // Store the site's bundle context
    if (site != null && site instanceof SiteImpl) {
      bundleContext = ((SiteImpl) site).getBundleContext();
    }
  }

  /**
   * Returns the associated site.
   *
   * @return the site
   */
  public Site getSite() {
    return site;
  }

  /**
   * {@inheritDoc}
   *
   * @see ch.entwine.weblounge.common.impl.content.GeneralComposeable#setEnvironment(ch.entwine.weblounge.common.site.Environment)
   */
  @Override
  public void setEnvironment(Environment environment) {
    if (environment == null)
      throw new IllegalArgumentException("Environment must not be null");

    if (!this.environment.equals(environment) && module != null) {
      processURLTemplates(environment);
      options.setEnvironment(environment);
    }

    super.setEnvironment(environment);
  }

  /**
   * Processes both renderer and editor url by replacing templates in their
   * paths with real values from the actual module.
   *
   * @param environment
   *          the environment
   *
   * @return <code>false</code> if the paths don't end up being real urls,
   *         <code>true</code> otherwise
   */
  private boolean processURLTemplates(Environment environment) {
    if (site == null)
      throw new IllegalStateException("Site cannot be null");
    if (module == null)
      throw new IllegalStateException("Module cannot be null");

    // Process the head elements (scripts and style sheet includes)
    for (HTMLHeadElement headElement : headers) {
      headElement.setEnvironment(environment);
    }

    return true;
  }

  /**
   * Returns the absolute link pointing to this action.
   *
   * @return the action's link
   */
  public WebUrl getUrl() {
    return new WebUrlImpl(site, mountpoint);
  }

  /**
   * {@inheritDoc}
   *
   * @see ch.entwine.weblounge.common.site.Action#setPath(java.lang.String)
   */
  public void setPath(String path) {
    if (StringUtils.isBlank(path))
      throw new IllegalArgumentException("Path cannot be blank");
    if (!path.startsWith("/"))
      throw new IllegalArgumentException("Action mountpoint '" + path + "' must be absolute");
    this.mountpoint = UrlUtils.trim(path);
  }

  /**
   * {@inheritDoc}
   *
   * @see ch.entwine.weblounge.common.site.Action#getPath()
   */
  public String getPath() {
    return mountpoint;
  }

  /**
   * Returns the requested output flavor.
   *
   * @return the output flavor
   */
  protected RequestFlavor getFlavor() {
    return flavor;
  }

  /**
   * Returns <code>true</code> if <code>composer</code> equals the stage of the
   * current renderer.
   *
   * @param composer
   *          the composer to test
   * @param request
   *          the request
   * @return <code>true</code> if <code>composer</code> is the main stage
   */
  protected boolean isStage(String composer, WebloungeRequest request) {
    if (composer == null)
      throw new IllegalArgumentException("Composer may not be null!");

    String stage = PageTemplate.DEFAULT_STAGE;
    PageTemplate template = (PageTemplate) request.getAttribute(WebloungeRequest.TEMPLATE);
    if (template != null)
      stage = template.getStage();
    return composer.equalsIgnoreCase(stage);
  }

  /**
   * {@inheritDoc}
   *
   * @see ch.entwine.weblounge.common.site.Action#addFlavor(ch.entwine.weblounge.common.request.RequestFlavor)
   */
  public void addFlavor(RequestFlavor flavor) {
    flavors.add(flavor);
  }

  /**
   * {@inheritDoc}
   *
   * @see ch.entwine.weblounge.common.site.Action#removeFlavor(ch.entwine.weblounge.common.request.RequestFlavor)
   */
  public void removeFlavor(RequestFlavor flavor) {
    flavors.remove(flavor);
  }

  /**
   * {@inheritDoc}
   *
   * @see ch.entwine.weblounge.common.site.Action#getFlavors()
   */
  public RequestFlavor[] getFlavors() {
    return flavors.toArray(new RequestFlavor[flavors.size()]);
  }

  /**
   * {@inheritDoc}
   *
   * @see ch.entwine.weblounge.common.site.Action#supportsFlavor(java.lang.String)
   */
  public boolean supportsFlavor(RequestFlavor flavor) {
    return flavors.contains(flavor);
  }

  /**
   * Removes all flavors.
   */
  protected void clearFlavors() {
    flavors.clear();
  }

  /**
   * {@inheritDoc}
   *
   * @see ch.entwine.weblounge.common.site.Action#setOption(java.lang.String,
   *      java.lang.String)
   */
  public void setOption(String key, String value) {
    options.setOption(key, value);
  }

  /**
   * {@inheritDoc}
   *
   * @see ch.entwine.weblounge.common.Customizable#setOption(java.lang.String,
   *      java.lang.String, ch.entwine.weblounge.common.site.Environment)
   */
  public void setOption(String name, String value, Environment environment) {
    options.setOption(name, value, environment);
  }

  /**
   * {@inheritDoc}
   *
   * @see ch.entwine.weblounge.common.Customizable#getOptionValue(java.lang.String)
   */
  public String getOptionValue(String name) {
    return options.getOptionValue(name);
  }

  /**
   * {@inheritDoc}
   *
   * @see ch.entwine.weblounge.common.Customizable#getOptionValue(java.lang.String,
   *      java.lang.String)
   */
  public String getOptionValue(String name, String defaultValue) {
    return options.getOptionValue(name, defaultValue);
  }

  /**
   * {@inheritDoc}
   *
   * @see ch.entwine.weblounge.common.Customizable#getOptionValues(java.lang.String)
   */
  public String[] getOptionValues(String name) {
    return options.getOptionValues(name);
  }

  /**
   * {@inheritDoc}
   *
   * @see ch.entwine.weblounge.common.Customizable#getOptions()
   */
  public Map<String, Map<Environment, List<String>>> getOptions() {
    return options.getOptions();
  }

  /**
   * {@inheritDoc}
   *
   * @see ch.entwine.weblounge.common.Customizable#hasOption(java.lang.String)
   */
  public boolean hasOption(String name) {
    return options.hasOption(name);
  }

  /**
   * {@inheritDoc}
   *
   * @see ch.entwine.weblounge.common.Customizable#getOptionNames()
   */
  public String[] getOptionNames() {
    return options.getOptionNames();
  }

  /**
   * {@inheritDoc}
   *
   * @see ch.entwine.weblounge.common.Customizable#removeOption(java.lang.String)
   */
  public void removeOption(String name) {
    options.removeOption(name);
  }

  /**
   * @see ch.entwine.weblounge.common.site.Action.module.ActionHandler#configure(ch.entwine.weblounge.api.request.WebloungeRequest,
   *      ch.entwine.weblounge.api.request.WebloungeResponse, java.lang.String)
   */
  public void configure(WebloungeRequest request, WebloungeResponse response,
      RequestFlavor flavor) throws ActionException {

    this.includeCount = 0;
    this.request = request;
    this.response = response;
    this.flavor = flavor;

    // Check if we have a file upload request
    if (ServletFileUpload.isMultipartContent(request)) {

      // Create a factory for disk-based file items
      DiskFileItemFactory factory = new DiskFileItemFactory();
      // TODO: Configure factory
      // factory.setSizeThreshold(yourMaxMemorySize);
      // factory.setRepository(yourTempDirectory);

      // Create a new file upload handler
      ServletFileUpload upload = new ServletFileUpload(factory);

      // Set overall request size constraint
      // TODO: Configure uploader
      // upload.setSizeMax(yourMaxRequestSize);

      // Parse the request
      try {
        files = upload.parseRequest(request);
      } catch (FileUploadException e) {
        logger.error("Error parsing uploads: {}", e.getMessage(), e);
      }
    }

  }

  /**
   * Returns an iteration of the files that have been uploaded in the current
   * step. Note that this iterator may be empty if no files are present, since
   * the files collection is cleared if the wizard moves on. <br>
   * The iterator returns elements of type <code>UploadedFile</code>.
   *
   * @return an iteration of uploaded files
   */
  protected Iterator<FileItem> files() {
    if (files != null)
      return files.iterator();
    return (new ArrayList<FileItem>()).iterator();
  }

  /**
   * includes the given renderer with the request.
   *
   * @param request
   *          the request
   * @param response
   *          the response
   * @param renderer
   *          the renderer to include
   * @param data
   *          is passed to the renderer
   * @throws ActionException
   *           if the passed renderer is <code>null</code>
   */
  protected void include(WebloungeRequest request, WebloungeResponse response,
      PageletRenderer renderer, Pagelet data) throws ActionException {
    if (renderer == null) {
      String msg = "The renderer passed to include in action '" + this + "' was <null>!";
      throw new ActionException(new IllegalArgumentException(msg));
    }

    if (data != null)
      request.setAttribute(WebloungeRequest.PAGELET, data);

    // Include renderer in response
    try {
      renderer.render(request, response);
    } catch (Throwable t) {
      String params = RequestUtils.dumpParameters(request);
      String msg = "Error including '" + renderer + "' in action '" + this + "' on " + request.getUrl() + " " + params;
      Throwable o = t.getCause();
      if (o != null) {
        msg += ": " + o.getMessage();
        logger.error(msg, o);
      } else {
        logger.error(msg, t);
      }
      response.invalidate();
    }

    request.removeAttribute(WebloungeRequest.PAGELET);
    includeCount++;
  }

  /**
   * Requests the renderer with the given id from the current module and
   * includes it in the request.
   *
   * @param request
   *          the request
   * @param response
   *          the response
   * @param renderer
   *          the renderer to include
   * @throws ActionException
   *           if the passed renderer cannot be found.
   */
  protected void include(WebloungeRequest request, WebloungeResponse response,
      String renderer) throws ActionException {
    include(request, response, getModule(), renderer, null);
  }

  /**
   * Requests the renderer with the given id from the current module and
   * includes it in the request.
   *
   * @param request
   *          the request
   * @param response
   *          the response
   * @param renderer
   *          the renderer to include
   * @param data
   *          is passed to the renderer
   * @throws ActionException
   *           if the passed renderer cannot be found.
   */
  protected void include(WebloungeRequest request, WebloungeResponse response,
      String renderer, Pagelet data) throws ActionException {
    include(request, response, getModule(), renderer, data);
  }
 
  /**
   * Requests the renderer with the given id from the current module and
   * includes it in the request.
   *
   * @param request
   *          the request
   * @param response
   *          the response
   * @param renderer
   *          the renderer to include
   * @throws ActionException
   *           if the passed renderer cannot be found.
   */
  protected void include(WebloungeRequest request, WebloungeResponse response,
      PageletRenderer renderer) throws ActionException {
    include(request, response, renderer, null);
  }

  /**
   * Requests the renderer with the given id from module <code>module</code> and
   * includes it in the request.
   *
   * @param request
   *          the request
   * @param response
   *          the response
   * @param module
   *          the module identifier
   * @param renderer
   *          the renderer to include
   * @param data
   *          is passed to the renderer
   * @throws ActionException
   *           if the passed renderer cannot be found.
   */
  protected void include(WebloungeRequest request, WebloungeResponse response,
      String module, String renderer, Pagelet data) throws ActionException {
    if (module == null)
      throw new ActionException(new IllegalArgumentException("Module is null!"));
    if (renderer == null)
      throw new ActionException(new IllegalArgumentException("Renderer is null!"));
    Module m = getSite().getModule(module);
    if (m == null) {
      String msg = "Trying to include renderer from unknown module '" + module + "'";
      throw new ActionException(new IllegalArgumentException(msg));
    }
    include(request, response, m, renderer, data);
  }

  /**
   * Requests the renderer with the given id from module <code>module</code> and
   * includes it in the request.
   *
   * @param request
   *          the request
   * @param response
   *          the response
   * @param module
   *          the module
   * @param renderer
   *          the renderer to include
   * @param data
   *          is passed to the renderer
   * @throws ActionException
   *           if the passed renderer cannot be found.
   */
  protected void include(WebloungeRequest request, WebloungeResponse response,
      Module module, String renderer, Pagelet data) throws ActionException {
    if (module == null)
      throw new ActionException(new IllegalArgumentException("Module is null!"));
    if (renderer == null)
      throw new ActionException(new IllegalArgumentException("Renderer is null!"));
    PageletRenderer r = module.getRenderer(renderer);
    if (r == null) {
      String msg = "Trying to include unknown renderer '" + renderer + "'";
      throw new ActionException(new IllegalArgumentException(msg));
    }
    logger.debug("Including renderer {}", renderer);

    // Add the pagelet's header elements to the response
    for (HTMLHeadElement header : r.getHTMLHeaders()) {
      if (!HTMLInclude.Use.Editor.equals(header.getUse()))
        response.addHTMLHeader(header);
    }

    include(request, response, r, data);
  }

  /**
   * Finds the first service in the service registry and returns it. If not such
   * service is available, <code>null</code> is returned.
   *
   * @param c
   *          the class of the service to look for
   * @return the service
   */
  @SuppressWarnings("unchecked")
  protected <S extends Object> S getService(Class<S> c) {
    String className = c.getName();
    BundleContext ctx = getBundleContext();
    if (ctx == null)
      return null;
    try {
      ServiceReference serviceRef = ctx.getServiceReference(className);
      if (serviceRef == null) {
        logger.debug("No service for class {} found", className);
        return null;
      }
      S service = (S) ctx.getService(serviceRef);
      return service;
    } catch (IllegalStateException e) {
      logger.debug("Service of type {} cannot be received through deactivating bundle {}", className, ctx);
      return null;
    }
  }

  /**
   * {@inheritDoc}
   * <p>
   * When overwriting this method, please make sure to call
   * <code>super.activate()</code> as well.
   *
   * @see ch.entwine.weblounge.common.site.Action#activate()
   */
  public void activate() {
    logger.trace("Activating action {}", this);
  }

  /**
   * {@inheritDoc}
   * <p>
   * When overwriting this method, please make sure to call
   * <code>super.passivate()</code> as well.
   *
   * @see ch.entwine.weblounge.common.site.Action#passivate()
   */
  public void passivate() {
    logger.trace("Passivating action {}", this);
    files = null;
    includeCount = 0;
    request = null;
    response = null;
  }

  /**
   * {@inheritDoc}
   *
   * @see java.lang.Object#hashCode()
   */
  @Override
  public int hashCode() {
    return identifier.hashCode();
  }

  /**
   * Returns <code>true</code> if <code>o</code> equals this action handler.
   *
   * @param o
   *          the object to test for equality
   * @see java.lang.Object#equals(java.lang.Object)
   */
  @Override
  public boolean equals(Object o) {
    if (o != null && o instanceof ActionSupport) {
      ActionSupport h = (ActionSupport) o;
      if (module == null && h.getModule() != null)
        return false;
      if (module != null && !module.equals(h.getModule()))
        return false;
      return identifier.equals(h.identifier);
    }
    return false;
  }

  /**
   * Initializes this action from an XML node that was generated using
   * {@link #toXml()}.
   * <p>
   * To speed things up, you might consider using the second signature that uses
   * an existing <code>XPath</code> instance instead of creating a new one.
   *
   * @param config
   *          the action node
   * @throws IllegalStateException
   *           if the configuration cannot be parsed
   * @see #fromXml(Node, XPath)
   * @see #toXml()
   */
  public static Action fromXml(Node config) throws IllegalStateException {
    XPath xpath = XPathFactory.newInstance().newXPath();
    return fromXml(config, xpath);
  }

  /**
   * Initializes this action from an XML node that was generated using
   * {@link #toXml()}.
   *
   * @param config
   *          the action node
   * @param xpath
   *          xpath processor to use
   * @throws IllegalStateException
   *           if the configuration cannot be parsed
   * @see #toXml()
   */
  @SuppressWarnings("unchecked")
  public static Action fromXml(Node config, XPath xpath)
      throws IllegalStateException {

    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();

    // identifier
    String identifier = XPathHelper.valueOf(config, "@id", xpath);
    if (identifier == null)
      throw new IllegalStateException("Unable to create actions without identifier");

    // class
    Action action = null;
    String className = XPathHelper.valueOf(config, "m:class", xpath);
    if (className != null) {
      try {
        Class<? extends Action> c = (Class<? extends Action>) classLoader.loadClass(className);
        action = c.newInstance();
        action.setIdentifier(identifier);
      } catch (ClassNotFoundException e) {
        throw new IllegalStateException("Implementation " + className + " for action handler '" + identifier + "' not found", e);
      } catch (InstantiationException e) {
        throw new IllegalStateException("Error instantiating impelementation " + className + " for action handler '" + identifier + "'", e);
      } catch (IllegalAccessException e) {
        throw new IllegalStateException("Access violation instantiating implementation " + className + " for action handler '" + identifier + "'", e);
      } catch (Throwable t) {
        throw new IllegalStateException("Error loading implementation " + className + " for action handler '" + identifier + "'", t);
      }
    } else {
      action = new HTMLActionSupport();
      action.setIdentifier(identifier);
    }

    // mountpoint
    String mountpoint = XPathHelper.valueOf(config, "m:mountpoint", xpath);
    if (mountpoint == null)
      throw new IllegalStateException("Action '" + identifier + " has no mountpoint");
    action.setPath(mountpoint);
    // TODO: handle /, /*

    // content url
    String targetUrl = XPathHelper.valueOf(config, "m:page", xpath);
    if (StringUtils.isNotBlank(targetUrl)) {
      if (!(action instanceof HTMLActionSupport))
        throw new IllegalStateException("Target page configuration for '" + action.getIdentifier() + "' requires subclassing HTMLActionSupport");
      ((HTMLActionSupport) action).setPageURI(targetUrl);
    }

    // template
    String targetTemplate = XPathHelper.valueOf(config, "m:template", xpath);
    if (StringUtils.isNotBlank(targetTemplate)) {
      if (!(action instanceof HTMLActionSupport))
        throw new IllegalStateException("Target template configuration for '" + action.getIdentifier() + "' requires subclassing HTMLActionSupport");
      ((HTMLActionSupport) action).setDefaultTemplate(targetTemplate);
    }

    // client revalidation time
    String recheck = XPathHelper.valueOf(config, "m:recheck", xpath);
    if (recheck != null) {
      try {
        action.setClientRevalidationTime(ConfigurationUtils.parseDuration(recheck));
      } catch (NumberFormatException e) {
        throw new IllegalStateException("The action revalidation time is malformed: '" + recheck + "'");
      } catch (IllegalArgumentException e) {
        throw new IllegalStateException("The action revalidation time is malformed: '" + recheck + "'");
      }
    }

    // cache expiration time
    String valid = XPathHelper.valueOf(config, "m:valid", xpath);
    if (valid != null) {
      try {
        action.setCacheExpirationTime(ConfigurationUtils.parseDuration(valid));
      } catch (NumberFormatException e) {
        throw new IllegalStateException("The action valid time is malformed: '" + valid + "'", e);
      } catch (IllegalArgumentException e) {
        throw new IllegalStateException("The action valid time is malformed: '" + valid + "'", e);
      }
    }

    // scripts
    NodeList scripts = XPathHelper.selectList(config, "m:includes/m:script", xpath);
    for (int i = 0; i < scripts.getLength(); i++) {
      action.addHTMLHeader(ScriptImpl.fromXml(scripts.item(i)));
    }

    // links
    NodeList includes = XPathHelper.selectList(config, "m:includes/m:link", xpath);
    for (int i = 0; i < includes.getLength(); i++) {
      action.addHTMLHeader(LinkImpl.fromXml(includes.item(i)));
    }

    // name
    String name = XPathHelper.valueOf(config, "m:name", xpath);
    action.setName(name);

    // options
    Node optionsNode = XPathHelper.select(config, "m:options", xpath);
    OptionsHelper.fromXml(optionsNode, action, xpath);

    return action;
  }

  /**
   * {@inheritDoc}
   *
   * @see ch.entwine.weblounge.common.site.Action#toXml()
   */
  public String toXml() {
    StringBuffer b = new StringBuffer();
    b.append("<action id=\"");
    b.append(identifier);
    b.append("\">");

    // class
    b.append("<class>").append(getClass().getName()).append("</class>");

    // mountpoint
    b.append("<mountpoint>").append(mountpoint).append("</mountpoint>");

    // Recheck time
    if (clientRevalidationTime >= 0) {
      b.append("<recheck>");
      b.append(ConfigurationUtils.toDuration(clientRevalidationTime));
      b.append("</recheck>");
    }

    // Valid time
    if (cacheExpirationTime >= 0) {
      b.append("<valid>");
      b.append(ConfigurationUtils.toDuration(cacheExpirationTime));
      b.append("</valid>");
    }

    // Name
    if (StringUtils.isNotBlank(name)) {
      b.append("<name><![CDATA[");
      b.append(name);
      b.append("]]></name>");
    }

    // Includes
    if (headers.size() > 0) {
      b.append("<includes>");
      for (HTMLHeadElement header : getHTMLHeaders()) {
        if (header instanceof Link)
          b.append(header.toXml());
      }
      for (HTMLHeadElement header : getHTMLHeaders()) {
        if (header instanceof Script)
          b.append(header.toXml());
      }
      b.append("</includes>");
    }

    // Options
    b.append(options.toXml());

    b.append("</action>");
    return b.toString();
  }

  /**
   * Returns a string representation of this action, which consists of the
   * action identifier and the configured method.
   *
   * @see java.lang.Object#toString()
   */
  @Override
  public String toString() {
    StringBuffer buf = new StringBuffer();
    if (module != null)
      buf.append(module.getIdentifier()).append("/");
    buf.append(identifier);
    return buf.toString();
  }

}
TOP

Related Classes of ch.entwine.weblounge.common.impl.site.ActionSupport

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.