ch.entwine.weblounge.common.impl.content.page.PageTemplateImpl.java Source code

Java tutorial

Introduction

Here is the source code for ch.entwine.weblounge.common.impl.content.page.PageTemplateImpl.java

Source

/*
 *  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.content.page;

import static ch.entwine.weblounge.common.site.Environment.Any;

import ch.entwine.weblounge.common.content.RenderException;
import ch.entwine.weblounge.common.content.page.HTMLHeadElement;
import ch.entwine.weblounge.common.content.page.Link;
import ch.entwine.weblounge.common.content.page.PageTemplate;
import ch.entwine.weblounge.common.content.page.Script;
import ch.entwine.weblounge.common.impl.site.SiteImpl;
import ch.entwine.weblounge.common.impl.util.config.ConfigurationUtils;
import ch.entwine.weblounge.common.impl.util.xml.XPathHelper;
import ch.entwine.weblounge.common.request.CacheTag;
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.Environment;
import ch.entwine.weblounge.common.site.Site;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.Iterator;
import java.util.Map;

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

/**
 * This renderer implements a page template that is backed by a Java Server
 * Page.
 */
public class PageTemplateImpl extends AbstractRenderer implements PageTemplate {

    /** The logging facility */
    private final Logger logger = LoggerFactory.getLogger(PageTemplateImpl.class);

    /** Default composer for action output */
    protected String stage = DEFAULT_STAGE;

    /** Default page layout */
    protected String layout = null;

    /** Is this the default template? */
    protected boolean isDefault = false;

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

    /**
     * Creates a new page template.
     */
    public PageTemplateImpl() {
        addFlavor(RequestFlavor.HTML);
    }

    /**
     * Creates a new page template that is backed by a Java Server Page located at
     * <code>url</code>.
     * 
     * @param identifier
     *          the template identifier
     * @param url
     *          the renderer url
     */
    public PageTemplateImpl(String identifier, URL url) {
        super(identifier, url);
        addFlavor(RequestFlavor.HTML);
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.content.page.PageTemplate#setSite(ch.entwine.weblounge.common.site.Site)
     */
    public void setSite(Site site) {
        if (site == null)
            throw new IllegalArgumentException("Site must not be null");

        this.site = site;
        for (HTMLHeadElement htmlHead : headers) {
            htmlHead.setSite(site);
        }

        if (!Any.equals(environment)) {
            processURLTemplates(environment);
        }
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.content.page.PageTemplate#setStage(java.lang.String)
     */
    public void setStage(String stage) {
        this.stage = stage;
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.content.page.PageTemplate#getStage()
     */
    public String getStage() {
        return stage;
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.content.page.PageTemplate#getDefaultLayout()
     */
    public String getDefaultLayout() {
        return layout;
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.content.page.PageTemplate#setDefaultLayout(java.lang.String)
     */
    public void setDefaultLayout(String layout) {
        this.layout = layout;
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.content.page.PageTemplate#setDefault(boolean)
     */
    public void setDefault(boolean v) {
        isDefault = v;
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.content.page.PageTemplate#isDefault()
     */
    public boolean isDefault() {
        return isDefault;
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.content.page.PageTemplate#render(ch.entwine.weblounge.common.request.WebloungeRequest,
     *      ch.entwine.weblounge.common.request.WebloungeResponse)
     */
    public void render(WebloungeRequest request, WebloungeResponse response) throws RenderException {

        // Adjust revalidation and expiration time
        response.setClientRevalidationTime(getClientRevalidationTime());
        response.setCacheExpirationTime(getCacheExpirationTime());

        // Add cache support
        response.addTag(CacheTag.Renderer, getIdentifier());

        URL renderer = renderers.get(RendererType.Page.toString().toLowerCase());
        includeJSP(request, response, renderer);
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.impl.content.GeneralComposeable#hashCode()
     */
    @Override
    public int hashCode() {
        return super.hashCode();
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.impl.content.GeneralComposeable#equals(java.lang.Object)
     */
    @Override
    public boolean equals(Object o) {
        // This is to indicate that using the super implementation is sufficient
        return super.equals(o);
    }

    /**
     * Initializes this page template 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 node
     *          the page template node
     * @throws IllegalStateException
     *           if the page template cannot be parsed
     * @see #fromXml(Node, XPath)
     * @see #toXml()
     */
    public static PageTemplate fromXml(Node node) throws IllegalStateException {
        XPath xpath = XPathFactory.newInstance().newXPath();

        // Define the xml namespace
        xpath.setNamespaceContext(new NamespaceContext() {
            public String getNamespaceURI(String prefix) {
                return "ns".equals(prefix) ? SiteImpl.SITE_XMLNS : null;
            }

            public String getPrefix(String namespaceURI) {
                return null;
            }

            public Iterator<?> getPrefixes(String namespaceURI) {
                return null;
            }
        });

        return fromXml(node, xpath);
    }

    /**
     * Initializes this page template from an XML node that was generated using
     * {@link #toXml()}.
     * 
     * @param node
     *          the page template node
     * @param xpath
     *          the xpath processor
     * @throws IllegalStateException
     *           if the page template cannot be parsed
     * @see #fromXml(Node)
     * @see #toXml()
     */
    public static PageTemplate fromXml(Node node, XPath xpath) throws IllegalStateException {

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

        // Identifier
        String id = XPathHelper.valueOf(node, "@id", xpath);
        if (id == null)
            throw new IllegalStateException("Missing id in page template definition");

        // Class
        String className = XPathHelper.valueOf(node, "class", xpath);

        // Renderer url
        URL rendererUrl = null;
        String rendererUrlNode = XPathHelper.valueOf(node, "ns:renderer", xpath);
        if (rendererUrlNode == null)
            throw new IllegalStateException("Missing renderer in page template definition");
        try {
            rendererUrl = new URL(rendererUrlNode);
        } catch (MalformedURLException e) {
            throw new IllegalStateException(
                    "Malformed renderer url in page template definition: " + rendererUrlNode);
        }

        // Create the page template
        PageTemplate template = null;
        if (className != null) {
            Class<? extends PageTemplate> c = null;
            try {
                c = (Class<? extends PageTemplate>) classLoader.loadClass(className);
                template = c.newInstance();
                template.setIdentifier(id);
                template.setRenderer(rendererUrl);
            } catch (ClassNotFoundException e) {
                throw new IllegalStateException(
                        "Implementation " + className + " for page template '" + id + "' not found", e);
            } catch (InstantiationException e) {
                throw new IllegalStateException(
                        "Error instantiating impelementation " + className + " for page template '" + id + "'", e);
            } catch (IllegalAccessException e) {
                throw new IllegalStateException("Access violation instantiating implementation " + className
                        + " for page template '" + id + "'", e);
            } catch (Throwable t) {
                throw new IllegalStateException(
                        "Error loading implementation " + className + " for page template '" + id + "'", t);
            }
        } else {
            template = new PageTemplateImpl(id, rendererUrl);
        }

        // Composeable
        template.setComposeable(ConfigurationUtils.isTrue(XPathHelper.valueOf(node, "@composeable", xpath)));

        // Default
        template.setDefault(ConfigurationUtils.isTrue(XPathHelper.valueOf(node, "@default", xpath)));

        // Stage
        String stage = XPathHelper.valueOf(node, "ns:stage", xpath);
        if (stage != null)
            template.setStage(stage);

        // Layout
        String layout = XPathHelper.valueOf(node, "ns:layout", xpath);
        if (layout != null)
            template.setDefaultLayout(layout);

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

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

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

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

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

        return template;
    }

    /**
     * Returns an XML representation of this renderer.
     * 
     * @return the xml representation
     */
    public String toXml() {
        StringBuffer buf = new StringBuffer();
        buf.append("<template");
        buf.append(" id=\"").append(identifier).append("\"");
        buf.append(" composeable=\"").append(composeable).append("\"");
        if (isDefault)
            buf.append(" default=\"true\"");
        buf.append(">");

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

        // Renderer class
        if (!this.getClass().equals(PageTemplateImpl.class))
            buf.append("<class>").append(getClass().getName()).append("</class>");

        // Renderer url
        for (Map.Entry<String, URL> entry : renderers.entrySet()) {
            if (renderers.size() > 1)
                buf.append("<renderer type=\"").append(entry.getKey()).append("\">");
            else
                buf.append("<renderer>");
            buf.append(entry.getValue().toExternalForm()).append("</renderer>");
        }

        // Stage name
        if (stage != null && !DEFAULT_STAGE.equals(stage))
            buf.append("<stage>").append(stage).append("</stage>");

        // Default page layout
        if (layout != null)
            buf.append("<layout>").append(layout).append("</layout>");

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

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

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

        buf.append("</template>");
        return buf.toString();
    }

    /**
     * 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");

        // Process the renderer URL
        for (Map.Entry<String, URL> entry : renderers.entrySet()) {
            URL renderer = entry.getValue();
            String rendererURL = ConfigurationUtils.processTemplate(renderer.toExternalForm(), site, environment);
            try {
                renderer = new URL(rendererURL);
                renderers.put(entry.getKey(), renderer);
            } catch (MalformedURLException e) {
                logger.error("Renderer url {} of pagelet {} is malformed", rendererURL, this);
            }
        }

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

        return true;
    }

    /**
     * {@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 cannot be null");

        // Is there anything we need to be doing?
        if (!environment.equals(this.environment) && site != null) {
            logger.debug("Processing url templates of {} with environment {}", this, environment);
            processURLTemplates(environment);
        }

        super.setEnvironment(environment);
    }

}