Package org.apache.tapestry.pageload

Source Code of org.apache.tapestry.pageload.PageLoader

// Copyright 2004, 2005 The Apache Software Foundation
//
// Licensed 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.tapestry.pageload;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;

import org.apache.commons.logging.Log;
import org.apache.hivemind.ApplicationRuntimeException;
import org.apache.hivemind.ClassResolver;
import org.apache.hivemind.HiveMind;
import org.apache.hivemind.Location;
import org.apache.hivemind.Resource;
import org.apache.hivemind.service.ThreadLocale;
import org.apache.hivemind.util.ContextResource;
import org.apache.tapestry.BaseComponent;
import org.apache.tapestry.IAsset;
import org.apache.tapestry.IBinding;
import org.apache.tapestry.IComponent;
import org.apache.tapestry.INamespace;
import org.apache.tapestry.IPage;
import org.apache.tapestry.IRequestCycle;
import org.apache.tapestry.ITemplateComponent;
import org.apache.tapestry.TapestryConstants;
import org.apache.tapestry.asset.AssetSource;
import org.apache.tapestry.binding.BindingSource;
import org.apache.tapestry.engine.IPageLoader;
import org.apache.tapestry.resolver.ComponentSpecificationResolver;
import org.apache.tapestry.services.ComponentConstructor;
import org.apache.tapestry.services.ComponentConstructorFactory;
import org.apache.tapestry.services.ComponentPropertySource;
import org.apache.tapestry.services.ComponentTemplateLoader;
import org.apache.tapestry.spec.BindingType;
import org.apache.tapestry.spec.ContainedComponent;
import org.apache.tapestry.spec.IAssetSpecification;
import org.apache.tapestry.spec.IBindingSpecification;
import org.apache.tapestry.spec.IComponentSpecification;
import org.apache.tapestry.spec.IContainedComponent;
import org.apache.tapestry.spec.IParameterSpecification;
import org.apache.tapestry.web.WebContextResource;

/**
* Implementation of tapestry.page.PageLoader. Runs the process of building the
* component hierarchy for an entire page.
* <p>
* This implementation is not threadsafe, therefore the pooled service model
* must be used.
*
* @author Howard Lewis Ship
*/

public class PageLoader implements IPageLoader
{

    private Log _log;

    /** @since 4.0 */

    private ComponentSpecificationResolver _componentResolver;

    /** @since 4.0 */

    private BindingSource _bindingSource;

    /** @since 4.0 */

    private ComponentTemplateLoader _componentTemplateLoader;

    private List _inheritedBindingQueue = new ArrayList();

    /** @since 4.0 */
    private IComponentVisitor _establishDefaultParameterValuesVisitor;

    private ComponentTreeWalker _establishDefaultParameterValuesWalker;

    private ComponentTreeWalker _verifyRequiredParametersWalker;

    /** @since 4.0 */

    private ComponentConstructorFactory _componentConstructorFactory;

    /** @since 4.0 */

    private AssetSource _assetSource;

    /**
     * Used to find the correct Java component class for a page.
     *
     * @since 4.0
     */

    private ComponentClassProvider _pageClassProvider;

    /**
     * Used to find the correct Java component class for a component (a similar
     * process to resolving a page, but with slightly differen steps and
     * defaults).
     *
     * @since 4.0
     */

    private ComponentClassProvider _componentClassProvider;

    /**
     * Used to resolve meta-data properties related to a component.
     *
     * @since 4.0
     */

    private ComponentPropertySource _componentPropertySource;

    /**
     * Tracks the current locale into which pages are loaded.
     *
     * @since 4.0
     */

    private ThreadLocale _threadLocale;

    /**
     * The locale of the application, which is also the locale of the page being
     * loaded.
     */

    private Locale _locale;

    /**
     * Number of components instantiated, excluding the page itself.
     */

    private int _count;

    /**
     * The recursion depth. A page with no components is zero. A component on a
     * page is one.
     */

    private int _depth;

    /**
     * The maximum depth reached while building the page.
     */

    private int _maxDepth;

    /** @since 4.0 */

    private ClassResolver _classResolver;

    public void initializeService()
    {

        // Create the mechanisms for walking the component tree when it is
        // complete
        IComponentVisitor verifyRequiredParametersVisitor = new VerifyRequiredParametersVisitor();

        _verifyRequiredParametersWalker = new ComponentTreeWalker(
                new IComponentVisitor[] { verifyRequiredParametersVisitor });

        _establishDefaultParameterValuesWalker = new ComponentTreeWalker(
                new IComponentVisitor[] { _establishDefaultParameterValuesVisitor });
    }

    /**
     * Binds properties of the component as defined by the container's
     * specification.
     * <p>
     * This implementation is very simple, we will need a lot more sanity
     * checking and eror checking in the final version.
     *
     * @param container
     *            The containing component. For a dynamic binding ({@link ExpressionBinding})
     *            the property name is evaluated with the container as the root.
     * @param component
     *            The contained component being bound.
     * @param spec
     *            The specification of the contained component.
     * @param contained
     *            The contained component specification (from the container's
     *            {@link IComponentSpecification}).
     */

    void bind(IComponent container, IComponent component,
            IContainedComponent contained, String defaultBindingPrefix)
    {
        IComponentSpecification spec = component.getSpecification();
        boolean formalOnly = !spec.getAllowInformalParameters();

        if (contained.getInheritInformalParameters())
        {
            if (formalOnly)
                throw new ApplicationRuntimeException(PageloadMessages
                        .inheritInformalInvalidComponentFormalOnly(component),
                        component, contained.getLocation(), null);

            IComponentSpecification containerSpec = container
                    .getSpecification();

            if (!containerSpec.getAllowInformalParameters())
                throw new ApplicationRuntimeException(PageloadMessages
                        .inheritInformalInvalidContainerFormalOnly(container,
                                component), component, contained.getLocation(),
                        null);

            IQueuedInheritedBinding queued = new QueuedInheritInformalBindings(
                    component);
            _inheritedBindingQueue.add(queued);
        }

        Iterator i = contained.getBindingNames().iterator();

        while(i.hasNext())
        {
            String name = (String) i.next();

            IParameterSpecification pspec = spec.getParameter(name);

            boolean isFormal = pspec != null;

            String parameterName = isFormal ? pspec.getParameterName() : name;

            IBindingSpecification bspec = contained.getBinding(name);

            // If not allowing informal parameters, check that each binding
            // matches
            // a formal parameter.

            if (formalOnly && !isFormal)
                throw new ApplicationRuntimeException(PageloadMessages
                        .formalParametersOnly(component, name), component,
                        bspec.getLocation(), null);

            // If an informal parameter that conflicts with a reserved name,
            // then skip it.

            if (!isFormal && spec.isReservedParameterName(name)) continue;

            if (isFormal)
            {
                if (!name.equals(parameterName))
                {
                    _log.warn(PageloadMessages.usedParameterAlias(contained,
                            name, parameterName, bspec.getLocation()));
                }
                else if (pspec.isDeprecated())
                    _log.warn(PageloadMessages.deprecatedParameter(name, bspec
                            .getLocation(), contained.getType()));
            }

            // The type determines how to interpret the value:
            // As a simple static String
            // As a nested property name (relative to the component)
            // As the name of a binding inherited from the containing component.
            // As the name of a public field
            // As a script for a listener

            BindingType type = bspec.getType();

            // For inherited bindings, defer until later. This gives components
            // a chance to setup bindings from static values and expressions in
            // the template. The order of operations is tricky, template
            // bindings
            // come later. Note that this is a hold over from the Tapestry 3.0
            // DTD
            // and will some day no longer be supported.

            if (type == BindingType.INHERITED)
            {
                QueuedInheritedBinding queued = new QueuedInheritedBinding(
                        component, bspec.getValue(), parameterName);
                _inheritedBindingQueue.add(queued);
                continue;
            }

            String description = PageloadMessages.parameterName(name);

            IBinding binding = convert(container, description,
                    defaultBindingPrefix, bspec);

            addBindingToComponent(component, parameterName, binding);
        }
    }

    /**
     * Adds a binding to the component, checking to see if there's a name
     * conflict (an existing binding for the same parameter ... possibly because
     * parameter names can be aliased).
     *
     * @param component
     *            to which the binding should be added
     * @param parameterName
     *            the name of the parameter to bind, which should be a true
     *            name, not an alias
     * @param binding
     *            the binding to add
     * @throws ApplicationRuntimeException
     *             if a binding already exists
     * @since 4.0
     */

    static void addBindingToComponent(IComponent component,
            String parameterName, IBinding binding)
    {
        IBinding existing = component.getBinding(parameterName);

        if (existing != null)
            throw new ApplicationRuntimeException(PageloadMessages
                    .duplicateParameter(parameterName, existing), component,
                    binding.getLocation(), null);

        component.setBinding(parameterName, binding);
    }

    private IBinding convert(IComponent container, String description,
            String defaultBindingType, IBindingSpecification spec)
    {
        Location location = spec.getLocation();
        String bindingReference = spec.getValue();

        return _bindingSource.createBinding(container, description,
                bindingReference, defaultBindingType, location);
    }

    /**
     * Sets up a component. This involves:
     * <ul>
     * <li>Instantiating any contained components.
     * <li>Add the contained components to the container.
     * <li>Setting up bindings between container and containees.
     * <li>Construct the containees recursively.
     * <li>Invoking
     * {@link IComponent#finishLoad(IRequestCycle, IPageLoader, IComponentSpecification)}
     * </ul>
     *
     * @param cycle
     *            the request cycle for which the page is being (initially)
     *            constructed
     * @param page
     *            The page on which the container exists.
     * @param container
     *            The component to be set up.
     * @param containerSpec
     *            The specification for the container.
     * @param the
     *            namespace of the container
     */

    private void constructComponent(IRequestCycle cycle, IPage page,
            IComponent container, IComponentSpecification containerSpec,
            INamespace namespace)
    {
        _depth++;
        if (_depth > _maxDepth) _maxDepth = _depth;

        String defaultBindingPrefix = _componentPropertySource
                .getComponentProperty(container,
                        TapestryConstants.DEFAULT_BINDING_PREFIX_NAME);

        List ids = new ArrayList(containerSpec.getComponentIds());
        int count = ids.size();

        try
        {
            for(int i = 0; i < count; i++)
            {
                String id = (String) ids.get(i);

                // Get the sub-component specification from the
                // container's specification.

                IContainedComponent contained = containerSpec.getComponent(id);

                String type = contained.getType();
                Location location = contained.getLocation();

                _componentResolver.resolve(cycle, namespace, type, location);

                IComponentSpecification componentSpecification = _componentResolver
                        .getSpecification();
                INamespace componentNamespace = _componentResolver
                        .getNamespace();

                // Instantiate the contained component.

                IComponent component = instantiateComponent(page, container,
                        id, componentSpecification, _componentResolver
                                .getType(), componentNamespace, contained);

                // Add it, by name, to the container.

                container.addComponent(component);

                // Set up any bindings in the IContainedComponent specification

                bind(container, component, contained, defaultBindingPrefix);

                // Now construct the component recusively; it gets its chance
                // to create its subcomponents and set their bindings.

                constructComponent(cycle, page, component,
                        componentSpecification, componentNamespace);
            }

            addAssets(container, containerSpec);

            // Finish the load of the component; most components (which
            // subclass BaseComponent) load their templates here.
            // Properties with initial values will be set here (or the
            // initial value will be recorded for later use in pageDetach().
            // That may cause yet more components to be created, and more
            // bindings to be set, so we defer some checking until
            // later.

            container.finishLoad(cycle, this, containerSpec);

            // Have the component switch over to its active state.

            container.enterActiveState();
        }
        catch (ApplicationRuntimeException ex)
        {
            throw ex;
        }
        catch (RuntimeException ex)
        {
            throw new ApplicationRuntimeException(PageloadMessages
                    .unableToInstantiateComponent(container, ex), container,
                    null, ex);
        }

        _depth--;
    }

    /**
     * Invoked to create an implicit component (one which is defined in the
     * containing component's template, rather that in the containing
     * component's specification).
     *
     * @see org.apache.tapestry.services.impl.ComponentTemplateLoaderImpl
     * @since 3.0
     */

    public IComponent createImplicitComponent(IRequestCycle cycle,
            IComponent container, String componentId, String componentType,
            Location location)
    {
        IPage page = container.getPage();

        _componentResolver.resolve(cycle, container.getNamespace(),
                componentType, location);

        INamespace componentNamespace = _componentResolver.getNamespace();
        IComponentSpecification spec = _componentResolver.getSpecification();

        IContainedComponent contained = new ContainedComponent();
        contained.setLocation(location);
        contained.setType(componentType);

        IComponent result = instantiateComponent(page, container, componentId,
                spec, _componentResolver.getType(), componentNamespace,
                contained);

        container.addComponent(result);

        // Recusively build the component.

        constructComponent(cycle, page, result, spec, componentNamespace);

        return result;
    }

    /**
     * Instantiates a component from its specification. We instantiate the
     * component object, then set its specification, page, container and id.
     *
     * @see AbstractComponent
     */

    private IComponent instantiateComponent(IPage page, IComponent container,
            String id, IComponentSpecification spec, String type,
            INamespace namespace, IContainedComponent containedComponent)
    {
        ComponentClassProviderContext context = new ComponentClassProviderContext(
                type, spec, namespace);
        String className = _componentClassProvider
                .provideComponentClassName(context);

        if (HiveMind.isBlank(className))
            className = BaseComponent.class.getName();
        else
        {
            Class componentClass = _classResolver.findClass(className);

            if (!IComponent.class.isAssignableFrom(componentClass))
                throw new ApplicationRuntimeException(PageloadMessages
                        .classNotComponent(componentClass), container, spec
                        .getLocation(), null);

            if (IPage.class.isAssignableFrom(componentClass))
                throw new ApplicationRuntimeException(PageloadMessages
                        .pageNotAllowed(id), container, spec.getLocation(),
                        null);
        }

        ComponentConstructor cc = _componentConstructorFactory
                .getComponentConstructor(spec, className);

        IComponent result = (IComponent) cc.newInstance();

        result.setNamespace(namespace);
        result.setPage(page);
        result.setContainer(container);
        result.setId(id);
        result.setContainedComponent(containedComponent);
        result.setLocation(containedComponent.getLocation());

        _count++;

        return result;
    }

    /**
     * Instantitates a page from its specification.
     *
     * @param name
     *            the unqualified, simple, name for the page
     * @param namespace
     *            the namespace containing the page's specification
     * @param spec
     *            the page's specification We instantiate the page object, then
     *            set its specification, names and locale.
     * @see IEngine
     * @see ChangeObserver
     */

    private IPage instantiatePage(String name, INamespace namespace,
            IComponentSpecification spec)
    {
        Location location = spec.getLocation();
        ComponentClassProviderContext context = new ComponentClassProviderContext(
                name, spec, namespace);
        String className = _pageClassProvider
                .provideComponentClassName(context);

        Class pageClass = _classResolver.findClass(className);

        if (!IPage.class.isAssignableFrom(pageClass))
            throw new ApplicationRuntimeException(PageloadMessages
                    .classNotPage(pageClass), location, null);

        String pageName = namespace.constructQualifiedName(name);

        ComponentConstructor cc = _componentConstructorFactory
                .getComponentConstructor(spec, className);

        IPage result = (IPage) cc.newInstance();

        result.setNamespace(namespace);
        result.setPageName(pageName);
        result.setPage(result);
        result.setLocale(_locale);
        result.setLocation(location);

        return result;
    }

    public IPage loadPage(String name, INamespace namespace,
            IRequestCycle cycle, IComponentSpecification specification)
    {
        IPage page = null;

        _count = 0;
        _depth = 0;
        _maxDepth = 0;

        _locale = _threadLocale.getLocale();

        try
        {
            page = instantiatePage(name, namespace, specification);

            // The page is now attached to the engine and request cycle; some
            // code
            // inside the page's finishLoad() method may require this.
            // TAPESTRY-763

            page.attach(cycle.getEngine(), cycle);

            constructComponent(cycle, page, page, specification, namespace);

            // Walk through the complete component tree to set up the default
            // parameter values.
            _establishDefaultParameterValuesWalker.walkComponentTree(page);

            establishInheritedBindings();

            // Walk through the complete component tree to ensure that required
            // parameters are bound
            _verifyRequiredParametersWalker.walkComponentTree(page);

            // Now that the page has been properly constructed, the page
            // or any components on the page will have been registered as
            // page attach listeners.

            page.firePageAttached();
        }
        finally
        {
            _locale = null;
            _inheritedBindingQueue.clear();
        }

        if (_log.isDebugEnabled())
            _log.debug("Loaded page " + page + " with " + _count
                    + " components (maximum depth " + _maxDepth + ")");

        return page;
    }

    /** @since 4.0 */

    public void loadTemplateForComponent(IRequestCycle cycle,
            ITemplateComponent component)
    {
        _componentTemplateLoader.loadTemplate(cycle, component);
    }

    private void establishInheritedBindings()
    {
        _log.debug("Establishing inherited bindings");

        int count = _inheritedBindingQueue.size();

        for(int i = 0; i < count; i++)
        {
            IQueuedInheritedBinding queued = (IQueuedInheritedBinding) _inheritedBindingQueue
                    .get(i);

            queued.connect();
        }
    }

    private void addAssets(IComponent component,
            IComponentSpecification specification)
    {
        List names = specification.getAssetNames();

        if (names.isEmpty()) return;

        Iterator i = names.iterator();

        while(i.hasNext())
        {
            String name = (String) i.next();

            IAssetSpecification assetSpec = specification.getAsset(name);

            IAsset asset = convertAsset(assetSpec);

            component.addAsset(name, asset);
        }
    }

    /**
     * Builds an instance of {@link IAsset} from the specification.
     */

    private IAsset convertAsset(IAssetSpecification spec)
    {
        // AssetType type = spec.getType();
        String path = spec.getPath();
        Location location = spec.getLocation();

        Resource specResource = location.getResource();

        // And ugly, ugly kludge. For page and component specifications in the
        // context (typically, somewhere under WEB-INF), we evaluate them
        // relative the web application root.

        if (isContextResource(specResource))
            specResource = specResource.getRelativeResource("/");

        return _assetSource.findAsset(specResource, path, _locale, location);
    }

    private boolean isContextResource(Resource resource)
    {
        return (resource instanceof WebContextResource)
                || (resource instanceof ContextResource);
    }

    /** @since 4.0 */

    public void setLog(Log log)
    {
        _log = log;
    }

    /** @since 4.0 */

    public void setComponentResolver(ComponentSpecificationResolver resolver)
    {
        _componentResolver = resolver;
    }

    /** @since 4.0 */

    public void setBindingSource(BindingSource bindingSource)
    {
        _bindingSource = bindingSource;
    }

    /**
     * @since 4.0
     */
    public void setComponentTemplateLoader(
            ComponentTemplateLoader componentTemplateLoader)
    {
        _componentTemplateLoader = componentTemplateLoader;
    }

    /** @since 4.0 */
    public void setEstablishDefaultParameterValuesVisitor(
            IComponentVisitor establishDefaultParameterValuesVisitor)
    {
        _establishDefaultParameterValuesVisitor = establishDefaultParameterValuesVisitor;
    }

    /** @since 4.0 */
    public void setComponentConstructorFactory(
            ComponentConstructorFactory componentConstructorFactory)
    {
        _componentConstructorFactory = componentConstructorFactory;
    }

    /** @since 4.0 */
    public void setAssetSource(AssetSource assetSource)
    {
        _assetSource = assetSource;
    }

    /** @since 4.0 */
    public void setPageClassProvider(ComponentClassProvider pageClassProvider)
    {
        _pageClassProvider = pageClassProvider;
    }

    /** @since 4.0 */
    public void setClassResolver(ClassResolver classResolver)
    {
        _classResolver = classResolver;
    }

    /**
     * @since 4.0
     */
    public void setComponentClassProvider(
            ComponentClassProvider componentClassProvider)
    {
        _componentClassProvider = componentClassProvider;
    }

    /** @since 4.0 */
    public void setThreadLocale(ThreadLocale threadLocale)
    {
        _threadLocale = threadLocale;
    }

    /** @since 4.0 */
    public void setComponentPropertySource(
            ComponentPropertySource componentPropertySource)
    {
        _componentPropertySource = componentPropertySource;
    }
}
TOP

Related Classes of org.apache.tapestry.pageload.PageLoader

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.