org.apache.tiles.impl.BasicTilesContainer.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.tiles.impl.BasicTilesContainer.java

Source

/*
 * $Id: BasicTilesContainer.java 736275 2009-01-21 09:58:20Z apetrelli $
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.tiles.impl;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.tiles.Attribute;
import org.apache.tiles.AttributeContext;
import org.apache.tiles.BasicAttributeContext;
import org.apache.tiles.Definition;
import org.apache.tiles.TilesApplicationContext;
import org.apache.tiles.TilesContainer;
import org.apache.tiles.context.TilesRequestContext;
import org.apache.tiles.context.TilesRequestContextFactory;
import org.apache.tiles.definition.DefinitionsFactory;
import org.apache.tiles.definition.DefinitionsFactoryException;
import org.apache.tiles.definition.NoSuchDefinitionException;
import org.apache.tiles.evaluator.AttributeEvaluator;
import org.apache.tiles.preparer.NoSuchPreparerException;
import org.apache.tiles.preparer.PreparerFactory;
import org.apache.tiles.preparer.ViewPreparer;
import org.apache.tiles.renderer.AttributeRenderer;
import org.apache.tiles.renderer.RendererFactory;

import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import java.util.StringTokenizer;

/**
 * Basic implementation of the tiles container interface.
 * In most cases, this container will be customized by
 * injecting customized services, not necessarily by
 * override the container
 *
 * @since 2.0
 * @version $Rev: 736275 $ $Date: 2009-01-21 10:58:20 +0100 (Wed, 21 Jan 2009) $
 */
public class BasicTilesContainer implements TilesContainer {

    /**
     * Constant representing the configuration parameter used to define the
     * tiles definition resources.
     *
     * @deprecated Use
     * {@link org.apache.tiles.definition.DefinitionsFactory#DEFINITIONS_CONFIG}.
     */
    public static final String DEFINITIONS_CONFIG = "org.apache.tiles.impl.BasicTilesContainer.DEFINITIONS_CONFIG";

    /**
     * Compatibility constant.
     *
     * @deprecated use {@link #DEFINITIONS_CONFIG} to avoid namespace collisions.
     */
    private static final String LEGACY_DEFINITIONS_CONFIG = "definitions-config";

    /**
     * Name used to store attribute context stack.
     */
    private static final String ATTRIBUTE_CONTEXT_STACK = "org.apache.tiles.AttributeContext.STACK";

    /**
     * Log instance for all BasicTilesContainer
     * instances.
     */
    private final Log log = LogFactory.getLog(BasicTilesContainer.class);

    /**
     * The Tiles application context object.
     */
    private TilesApplicationContext context;

    /**
     * The definitions factory.
     */
    private DefinitionsFactory definitionsFactory;

    /**
     * The preparer factory.
     */
    private PreparerFactory preparerFactory;

    /**
     * The renderer factory.
     */
    private RendererFactory rendererFactory;

    /**
     * The attribute evaluator.
     */
    private AttributeEvaluator evaluator;

    /**
     * The Tiles request context factory.
     */
    private TilesRequestContextFactory contextFactory;

    /**
     * Initialization flag. If set, this container cannot be changed.
     */
    private boolean initialized = false;

    /**
     * Initialize the Container with the given configuration.
     *
     * @param initParameters application context for this container
     * @throws IllegalStateException If the container has been already
     * initialized.
     * @throws DefinitionsFactoryException If something goes wrong during
     * initialization.
     */
    public void init(Map<String, String> initParameters) {
        checkInit();
        initialized = true;

        if (rendererFactory == null) {
            throw new IllegalStateException("RendererFactory not specified");
        }
        if (preparerFactory == null) {
            throw new IllegalStateException("PreparerFactory not specified");
        }
        if (definitionsFactory == null) {
            throw new IllegalStateException("DefinitionsFactory not specified");
        }
        if (evaluator == null) {
            throw new IllegalStateException("AttributeEvaluator not specified");
        }
        if (contextFactory == null) {
            throw new IllegalStateException("TilesContextFactory not specified");
        }
        if (context == null) {
            throw new IllegalStateException("TilesApplicationContext not specified");
        }
    }

    /** {@inheritDoc} */
    public AttributeContext startContext(Object... requestItems) {
        TilesRequestContext tilesContext = getRequestContext(requestItems);
        return startContext(tilesContext);
    }

    /** {@inheritDoc} */
    public void endContext(Object... requestItems) {
        TilesRequestContext tilesContext = getRequestContext(requestItems);
        endContext(tilesContext);
    }

    /** {@inheritDoc} */
    public void renderContext(Object... requestItems) {
        TilesRequestContext request = getRequestContext(requestItems);
        AttributeContext attributeContext = getAttributeContext(request);

        render(request, attributeContext);
    }

    /**
     * Returns the Tiles application context used by this container.
     *
     * @return the application context for this container.
     */
    public TilesApplicationContext getApplicationContext() {
        return context;
    }

    /**
     * Sets the Tiles application context to use.
     *
     * @param context The Tiles application context.
     */
    public void setApplicationContext(TilesApplicationContext context) {
        this.context = context;
    }

    /** {@inheritDoc} */
    public AttributeContext getAttributeContext(Object... requestItems) {
        TilesRequestContext tilesContext = getRequestContext(requestItems);
        return getAttributeContext(tilesContext);

    }

    /**
     * Returns the context factory.
     *
     * @return Always <code>null</code>.
     * @deprecated Do not use it, it returns <code>null</code>. Use
     * {@link #getRequestContextFactory()}.
     */
    @Deprecated
    public org.apache.tiles.context.TilesContextFactory getContextFactory() {
        return null;
    }

    /**
     * Returns the request context factory.
     *
     * @return The request context factory.
     * @since 2.1.1
     */
    protected TilesRequestContextFactory getRequestContextFactory() {
        return contextFactory;
    }

    /**
     * Sets the context factory.
     *
     * @param contextFactory The context factory.
     * @deprecated Use
     * {@link #setRequestContextFactory(TilesRequestContextFactory)}.
     */
    public void setContextFactory(org.apache.tiles.context.TilesContextFactory contextFactory) {
        // Does nothing
    }

    /**
     * Sets the request context factory.
     *
     * @param contextFactory The context factory.
     * @since 2.1.1
     */
    public void setRequestContextFactory(TilesRequestContextFactory contextFactory) {
        checkInit();
        this.contextFactory = contextFactory;
    }

    /**
     * Returns the definitions factory.
     *
     * @return The definitions factory used by this container.
     */
    public DefinitionsFactory getDefinitionsFactory() {
        return definitionsFactory;
    }

    /**
     * Set the definitions factory. This method first ensures
     * that the container has not yet been initialized.
     *
     * @param definitionsFactory the definitions factory for this instance.
     */
    public void setDefinitionsFactory(DefinitionsFactory definitionsFactory) {
        checkInit();
        this.definitionsFactory = definitionsFactory;
    }

    /**
     * Returns the preparer factory used by this container.
     *
     * @return return the preparerInstance factory used by this container.
     */
    public PreparerFactory getPreparerFactory() {
        return preparerFactory;
    }

    /**
     * Set the preparerInstance factory.  This method first ensures
     * that the container has not yet been initialized.
     *
     * @param preparerFactory the preparerInstance factory for this conainer.
     */
    public void setPreparerFactory(PreparerFactory preparerFactory) {
        this.preparerFactory = preparerFactory;
    }

    /**
     * Sets the renderer instance factory.
     *
     * @param rendererFactory the renderer instance factory for this container.
     * @since 2.1.0
     */
    public void setRendererFactory(RendererFactory rendererFactory) {
        this.rendererFactory = rendererFactory;
    }

    /**
     * Sets the evaluator to use.
     *
     * @param evaluator The evaluator to use.
     * @since 2.1.0
     */
    public void setEvaluator(AttributeEvaluator evaluator) {
        this.evaluator = evaluator;
    }

    /** {@inheritDoc} */
    public void prepare(String preparer, Object... requestItems) {
        TilesRequestContext requestContext = getRequestContextFactory()
                .createRequestContext(getApplicationContext(), requestItems);
        prepare(requestContext, preparer, false);
    }

    /** {@inheritDoc} */
    public void render(String definitionName, Object... requestItems) {
        TilesRequestContext requestContext = getRequestContextFactory()
                .createRequestContext(getApplicationContext(), requestItems);
        render(requestContext, definitionName);
    }

    /** {@inheritDoc} */
    @Deprecated
    public void render(Attribute attr, Writer writer, Object... requestItems) throws IOException {
        render(attr, requestItems);
    }

    /** {@inheritDoc} */
    public void render(Attribute attr, Object... requestItems) throws IOException {
        TilesRequestContext requestContext = getRequestContextFactory()
                .createRequestContext(getApplicationContext(), requestItems);
        render(attr, requestContext);
    }

    /** {@inheritDoc} */
    public Object evaluate(Attribute attribute, Object... requestItems) {
        TilesRequestContext request = getRequestContextFactory().createRequestContext(context, requestItems);
        return evaluator.evaluate(attribute, request);
    }

    /** {@inheritDoc} */
    public boolean isValidDefinition(String definitionName, Object... requestItems) {
        return isValidDefinition(getRequestContext(requestItems), definitionName);
    }

    /**
     * Returns a definition specifying its name.
     *
     * @param definitionName The name of the definition to find.
     * @param request The request context.
     * @return The definition, if found.
     * @throws DefinitionsFactoryException If the definitions factory throws an
     * exception.
     */
    protected Definition getDefinition(String definitionName, TilesRequestContext request) {
        Definition definition = definitionsFactory.getDefinition(definitionName, request);
        return definition;
    }

    /**
     * Derive the resource string from the initialization parameters.
     * If no parameter {@link #DEFINITIONS_CONFIG} is available, attempts
     * to retrieve {@link #LEGACY_DEFINITIONS_CONFIG}.  If niether are
     * available, returns "/WEB-INF/tiles.xml".
     *
     * @return resource string to be parsed.
     */
    protected String getResourceString() {
        return getResourceString(context.getInitParams());
    }

    /**
     * Derive the resource string from the initialization parameters.
     * If no parameter {@link #DEFINITIONS_CONFIG} is available, attempts
     * to retrieve {@link #LEGACY_DEFINITIONS_CONFIG}.  If niether are
     * available, returns "/WEB-INF/tiles.xml".
     *
     * @param parms The initialization parameters.
     * @return resource string to be parsed.
     */
    protected String getResourceString(Map<String, String> parms) {
        String resourceStr = parms.get(DEFINITIONS_CONFIG);
        if (resourceStr == null) {
            resourceStr = parms.get(LEGACY_DEFINITIONS_CONFIG);
        }
        if (resourceStr == null) {
            resourceStr = "/WEB-INF/tiles.xml";
        }
        return resourceStr;
    }

    /**
     * Parse the resourceString into a list of resource paths
     * which can be loaded by the application context.
     *
     * @param resourceString comma seperated resources
     * @return parsed resources
     */
    protected List<String> getResourceNames(String resourceString) {
        StringTokenizer tokenizer = new StringTokenizer(resourceString, ",");
        List<String> filenames = new ArrayList<String>(tokenizer.countTokens());
        while (tokenizer.hasMoreTokens()) {
            filenames.add(tokenizer.nextToken().trim());
        }
        return filenames;
    }

    /**
     * Determine whether or not the container has been
     * initialized. Utility method used for methods which
     * can not be invoked after the container has been
     * started.
     *
     * @throws IllegalStateException if the container has already been initialized.
     */
    protected void checkInit() {
        if (initialized) {
            throw new IllegalStateException("Container allready initialized");
        }
    }

    /**
     * Initializes a definitions factory.
     *
     * @param definitionsFactory The factory to initialize.
     * @param resourceString The string containing a comma-separated-list of
     * resources.
     * @param initParameters A map containing the initialization parameters.
     * @throws DefinitionsFactoryException If something goes wrong.
     * @deprecated Do not use, the Definitions Factory should be initialized by
     * the Tiles Container Factory.
     */
    @Deprecated
    protected void initializeDefinitionsFactory(DefinitionsFactory definitionsFactory, String resourceString,
            Map<String, String> initParameters) {
        if (rendererFactory == null) {
            throw new IllegalStateException("No RendererFactory found");
        }

        definitionsFactory.init(initParameters);

        if (log.isInfoEnabled()) {
            log.info("Tiles2 container initialization complete.");
        }
    }

    /**
     * Returns the context stack.
     *
     * @param tilesContext The Tiles context object to use.
     * @return The needed stack of contexts.
     * @since 2.0.6
     */
    @SuppressWarnings("unchecked")
    protected Stack<AttributeContext> getContextStack(TilesRequestContext tilesContext) {
        Stack<AttributeContext> contextStack = (Stack<AttributeContext>) tilesContext.getRequestScope()
                .get(ATTRIBUTE_CONTEXT_STACK);
        if (contextStack == null) {
            contextStack = new Stack<AttributeContext>();
            tilesContext.getRequestScope().put(ATTRIBUTE_CONTEXT_STACK, contextStack);
        }

        return contextStack;
    }

    /**
     * Pushes a context object in the stack.
     *
     * @param context The context to push.
     * @param tilesContext The Tiles context object to use.
     * @since 2.0.6
     */
    protected void pushContext(AttributeContext context, TilesRequestContext tilesContext) {
        Stack<AttributeContext> contextStack = getContextStack(tilesContext);
        contextStack.push(context);
    }

    /**
     * Pops a context object out of the stack.
     *
     * @param tilesContext The Tiles context object to use.
     * @return The popped context object.
     * @since 2.0.6
     */
    protected AttributeContext popContext(TilesRequestContext tilesContext) {
        Stack<AttributeContext> contextStack = getContextStack(tilesContext);
        return contextStack.pop();
    }

    /**
     * Get attribute context from request.
     *
     * @param tilesContext current Tiles application context.
     * @return BasicAttributeContext or null if context is not found.
     * @since 2.0.6
     */
    protected AttributeContext getContext(TilesRequestContext tilesContext) {
        Stack<AttributeContext> contextStack = getContextStack(tilesContext);
        if (!contextStack.isEmpty()) {
            return contextStack.peek();
        } else {
            return null;
        }
    }

    /**
     * Returns the current attribute context.
     *
     * @param tilesContext The request context to use.
     * @return The current attribute context.
     */
    private AttributeContext getAttributeContext(TilesRequestContext tilesContext) {
        AttributeContext context = getContext(tilesContext);
        if (context == null) {
            context = new BasicAttributeContext();
            pushContext(context, tilesContext);
        }
        return context;
    }

    /**
     * Creates a Tiles request context from request items.
     *
     * @param requestItems The request items.
     * @return The created Tiles request context.
     */
    private TilesRequestContext getRequestContext(Object... requestItems) {
        return getRequestContextFactory().createRequestContext(getApplicationContext(), requestItems);
    }

    /**
     * Starts an attribute context inside the container.
     *
     * @param tilesContext The request context to use.
     * @return The newly created attribute context.
     */
    private AttributeContext startContext(TilesRequestContext tilesContext) {
        AttributeContext context = new BasicAttributeContext();
        Stack<AttributeContext> stack = getContextStack(tilesContext);
        if (!stack.isEmpty()) {
            AttributeContext parent = stack.peek();
            context.inheritCascadedAttributes(parent);
        }
        stack.push(context);
        return context;
    }

    /**
     * Releases and removes a previously created attribute context.
     *
     * @param tilesContext The request context to use.
     */
    private void endContext(TilesRequestContext tilesContext) {
        popContext(tilesContext);
    }

    /**
     * Execute a preparer.
     *
     * @param context The request context.
     * @param preparerName The name of the preparer.
     * @param ignoreMissing If <code>true</code> if the preparer is not found,
     * it ignores the problem.
     * @throws NoSuchPreparerException If the preparer is not found (and
     * <code>ignoreMissing</code> is not set) or if the preparer itself threw an
     * exception.
     */
    private void prepare(TilesRequestContext context, String preparerName, boolean ignoreMissing) {

        if (log.isDebugEnabled()) {
            log.debug("Prepare request received for '" + preparerName);
        }

        ViewPreparer preparer = preparerFactory.getPreparer(preparerName, context);
        if (preparer == null && ignoreMissing) {
            return;
        }

        if (preparer == null) {
            throw new NoSuchPreparerException("Preparer '" + preparerName + " not found");
        }

        AttributeContext attributeContext = getContext(context);

        preparer.execute(context, attributeContext);
    }

    /**
     * Renders the specified definition.
     *
     * @param request The request context.
     * @param definitionName The name of the definition to render.
     * @throws NoSuchDefinitionException If the definition has not been found.
     * @throws DefinitionsFactoryException If something goes wrong when
     * obtaining the definition.
     */
    private void render(TilesRequestContext request, String definitionName) {

        if (log.isDebugEnabled()) {
            log.debug("Render request recieved for definition '" + definitionName + "'");
        }

        Definition definition = getDefinition(definitionName, request);

        if (definition == null) {
            if (log.isWarnEnabled()) {
                String message = "Unable to find the definition '" + definitionName + "'";
                log.warn(message);
            }
            throw new NoSuchDefinitionException(definitionName);
        }

        AttributeContext originalContext = getAttributeContext(request);
        BasicAttributeContext subContext = new BasicAttributeContext(originalContext);
        subContext.inherit(definition);

        pushContext(subContext, request);

        try {
            render(request, subContext);
        } finally {
            popContext(request);
        }
    }

    /**
     * Renders an attribute.
     *
     * @param attr The attribute to render.
     * @param requestContext The Tiles request context.
     * @throws IOException If something goes wrong during rendering.
     */
    private void render(Attribute attr, TilesRequestContext requestContext) throws IOException {
        if (attr == null) {
            throw new CannotRenderException("Cannot render a null attribute");
        }

        AttributeRenderer renderer = rendererFactory.getRenderer(attr.getRenderer());
        if (renderer == null) {
            throw new CannotRenderException("Cannot render an attribute with renderer name " + attr.getRenderer());
        }
        renderer.render(attr, requestContext);
    }

    /**
     * Renders the specified attribute context.
     *
     * @param request The request context.
     * @param attributeContext The context to render.
     * @throws InvalidTemplateException If the template is not valid.
     * @throws CannotRenderException If something goes wrong during rendering.
     */
    private void render(TilesRequestContext request, AttributeContext attributeContext) {

        try {
            if (attributeContext.getPreparer() != null) {
                prepare(request, attributeContext.getPreparer(), true);
            }

            render(attributeContext.getTemplateAttribute(), request);
        } catch (IOException e) {
            throw new CannotRenderException(e.getMessage(), e);
        }
    }

    /**
     * Checks if a string is a valid definition name.
     *
     * @param context The request context.
     * @param definitionName The name of the definition to find.
     * @return <code>true</code> if <code>definitionName</code> is a valid
     * definition name.
     */
    private boolean isValidDefinition(TilesRequestContext context, String definitionName) {
        try {
            Definition definition = getDefinition(definitionName, context);
            return definition != null;
        } catch (NoSuchDefinitionException nsde) {
            return false;
        } catch (DefinitionsFactoryException e) {
            // TODO, is this the right thing to do?
            return false;
        }
    }
}