Java tutorial
/* * 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(); } }