Package org.apache.tapestry.internal.services

Source Code of org.apache.tapestry.internal.services.PageLoaderProcessor

// Copyright 2007 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.internal.services;

import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newStack;
import static org.apache.tapestry.ioc.internal.util.InternalUtils.isBlank;
import static org.apache.tapestry.ioc.internal.util.InternalUtils.isNonBlank;

import java.util.Locale;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.tapestry.Binding;
import org.apache.tapestry.ComponentResources;
import org.apache.tapestry.TapestryConstants;
import org.apache.tapestry.internal.bindings.LiteralBinding;
import org.apache.tapestry.internal.parser.AttributeToken;
import org.apache.tapestry.internal.parser.BlockToken;
import org.apache.tapestry.internal.parser.BodyToken;
import org.apache.tapestry.internal.parser.CommentToken;
import org.apache.tapestry.internal.parser.ComponentTemplate;
import org.apache.tapestry.internal.parser.EndElementToken;
import org.apache.tapestry.internal.parser.ExpansionToken;
import org.apache.tapestry.internal.parser.ParameterToken;
import org.apache.tapestry.internal.parser.StartComponentToken;
import org.apache.tapestry.internal.parser.StartElementToken;
import org.apache.tapestry.internal.parser.TemplateToken;
import org.apache.tapestry.internal.parser.TextToken;
import org.apache.tapestry.internal.structure.BlockImpl;
import org.apache.tapestry.internal.structure.BodyPageElement;
import org.apache.tapestry.internal.structure.ComponentPageElement;
import org.apache.tapestry.internal.structure.Page;
import org.apache.tapestry.internal.structure.PageElement;
import org.apache.tapestry.internal.structure.PageImpl;
import org.apache.tapestry.ioc.internal.util.CollectionFactory;
import org.apache.tapestry.ioc.internal.util.IdAllocator;
import org.apache.tapestry.ioc.internal.util.OneShotLock;
import org.apache.tapestry.ioc.internal.util.TapestryException;
import org.apache.tapestry.ioc.util.Stack;
import org.apache.tapestry.model.ComponentModel;
import org.apache.tapestry.model.EmbeddedComponentModel;
import org.apache.tapestry.services.BindingSource;
import org.apache.tapestry.services.PersistentFieldManager;

/**
* Contains all the work-state related to the {@link PageLoaderImpl}.
*/
class PageLoaderProcessor
{
    private static Runnable NO_OP = new Runnable()
    {
        public void run()
        {
            // Do nothing.
        }
    };

    private Stack<ComponentPageElement> _activeElementStack = newStack();

    private boolean _addAttributesAsComponentBindings = false;

    private final BindingSource _bindingSource;

    private final Stack<BodyPageElement> _bodyPageElementStack = newStack();

    // You can use a stack as a queue
    private final Stack<ComponentPageElement> _componentQueue = newStack();

    private final Stack<Boolean> _discardEndTagStack = newStack();

    private final Stack<Runnable> _endElementCommandStack = newStack();

    private final IdAllocator _idAllocator = new IdAllocator();

    private final LinkFactory _linkFactory;

    private ComponentModel _loadingComponentModel;

    private ComponentPageElement _loadingElement;

    private Locale _locale;

    private final OneShotLock _lock = new OneShotLock();

    private Page _page;

    private final PageElementFactory _pageElementFactory;

    private final PersistentFieldManager _persistentFieldManager;

    private final ComponentTemplateSource _templateSource;

    public PageLoaderProcessor(ComponentTemplateSource templateSource,
            PageElementFactory pageElementFactory, BindingSource bindingSource,
            LinkFactory linkFactory, PersistentFieldManager persistentFieldManager)
    {
        _templateSource = templateSource;
        _pageElementFactory = pageElementFactory;
        _bindingSource = bindingSource;
        _linkFactory = linkFactory;
        _persistentFieldManager = persistentFieldManager;
    }

    private void bindParameterFromTemplate(ComponentPageElement component, AttributeToken token)
    {
        String name = token.getName();
        ComponentResources resources = component.getComponentResources();

        // If already bound (i.e., from the component class, via @Component), then
        // ignore the value in the template. This may need improving to just ignore
        // the value if it is an unprefixed literal string.

        if (resources.isBound(name))
            return;

        // Meta default of literal for the template.

        String defaultBindingPrefix = determineDefaultBindingPrefix(
                component,
                name,
                TapestryConstants.LITERAL_BINDING_PREFIX);

        Binding binding = _bindingSource.newBinding(
                "parameter " + name,
                _loadingElement.getComponentResources(),
                component.getComponentResources(),
                defaultBindingPrefix,
                token.getValue(),
                token.getLocation());

        component.bindParameter(name, binding);
    }

    // As element, components, parameters or blocks are started, they push an element onto this
    // stack. Whenever an end element token is reached, the top value is popped off and executed,
    // to return state to where it should be.

    private void addMixinsToComponent(ComponentPageElement component, EmbeddedComponentModel model,
            String mixins)
    {
        if (model != null)
        {
            for (String mixinClassName : model.getMixinClassNames())
                _pageElementFactory.addMixinByClassName(component, mixinClassName);
        }

        if (mixins != null)
        {
            for (String type : mixins.split(","))
                _pageElementFactory.addMixinByTypeName(component, type);
        }
    }

    private void bindParametersFromModel(EmbeddedComponentModel model,
            ComponentPageElement loadingComponent, ComponentPageElement component)
    {
        for (String name : model.getParameterNames())
        {
            String value = model.getParameterValue(name);

            String defaultBindingPrefix = determineDefaultBindingPrefix(
                    component,
                    name,
                    TapestryConstants.PROP_BINDING_PREFIX);

            // At some point we may add meta data to control what the default prefix is within a
            // component.

            Binding binding = _bindingSource.newBinding(
                    "parameter " + name,
                    loadingComponent.getComponentResources(),
                    component.getComponentResources(),
                    defaultBindingPrefix,
                    value,
                    null);

            component.bindParameter(name, binding);
        }
    }

    /**
     * Determines the default binding prefix for a particular parameter.
     *
     * @param component
     *            the component which will have a parameter bound
     * @param parameterName
     *            the name of the parameter
     * @param informalParameterBindingPrefix
     *            the default to use for informal parameters
     * @return the binding prefix
     */
    private String determineDefaultBindingPrefix(ComponentPageElement component,
            String parameterName, String informalParameterBindingPrefix)
    {
        String defaultBindingPrefix = component.getDefaultBindingPrefix(parameterName);

        return defaultBindingPrefix != null ? defaultBindingPrefix : informalParameterBindingPrefix;
    }

    private PageElement newRenderBodyElement()
    {
        return _pageElementFactory.newRenderBodyElement(_loadingElement);
    }

    private void addToBody(PageElement element)
    {
        _bodyPageElementStack.peek().addToBody(element);
    }

    private void attribute(AttributeToken token)
    {
        // This kind of bookkeeping is ugly, we probably should have distinct (if very similar)
        // tokens for attributes and for parameter bindings.

        if (_addAttributesAsComponentBindings)
        {
            ComponentPageElement activeElement = _activeElementStack.peek();

            bindParameterFromTemplate(activeElement, token);
            return;
        }

        PageElement element = _pageElementFactory.newAttributeElement(token);

        addToBody(element);
    }

    private void body(BodyToken token)
    {
        addToBody(newRenderBodyElement());

        // BODY tokens are *not* matched by END_ELEMENT tokens. Nor will there be
        // text or comment content "inside" the BODY.
    }

    private void comment(CommentToken token)
    {
        PageElement commentElement = _pageElementFactory.newCommentElement(token);

        addToBody(commentElement);
    }

    /**
     * Invoked whenever a token (start, startComponent, etc.) is encountered that will eventually
     * have a matching end token. Sets up the behavior for the end token.
     *
     * @param discard
     *            if true, the end is discarded (if false the end token is added to the active body
     *            element)
     * @param command
     *            command to execute to return processor state back to what it was before the
     *            command executed
     */
    private void configureEnd(boolean discard, Runnable command)
    {
        _discardEndTagStack.push(discard);
        _endElementCommandStack.push(command);
    }

    private void endElement(EndElementToken token)
    {
        // discard will be false if the matching start token was for a static element, and will be
        // true otherwise (component, block, parameter).

        boolean discard = _discardEndTagStack.pop();

        if (!discard)
        {
            PageElement element = _pageElementFactory.newEndElement();

            addToBody(element);
        }

        Runnable command = _endElementCommandStack.pop();

        // Used to return environment to prior state.

        command.run();
    }

    private void expansion(ExpansionToken token)
    {
        PageElement element = _pageElementFactory.newExpansionElement(_loadingElement
                .getComponentResources(), token);

        addToBody(element);
    }

    private String generateEmbeddedId(String embeddedType, IdAllocator idAllocator)
    {
        // Component types may be in folders; strip off the folder part for starters.

        int slashx = embeddedType.lastIndexOf("/");

        String baseId = embeddedType.substring(slashx + 1).toLowerCase();

        // The idAllocator is pre-loaded with all the component ids from the template, so even
        // if the lower-case type matches the id of an existing component, there won't be a name
        // collision.

        return idAllocator.allocateId(baseId);
    }

    /**
     * As currently implemented, this should be invoked just once and then the instance should be
     * discarded.
     */
    public Page loadPage(String pageClassName, Locale locale)
    {
        // Ensure that loadPage() may only be invoked once.

        _lock.lock();

        _locale = locale;

        _page = new PageImpl(pageClassName, _locale, _linkFactory, _persistentFieldManager);

        loadRootComponent(pageClassName);

        workComponentQueue();

        // The page is *loaded* before it is attached to the request.
        // This is to help ensure that no client-specific information leaks
        // into the page.

        _page.loaded();

        return _page;
    }

    private void loadRootComponent(String className)
    {
        ComponentPageElement rootComponent = _pageElementFactory.newRootComponentElement(
                _page,
                className);

        _page.setRootElement(rootComponent);

        _componentQueue.push(rootComponent);
    }

    /**
     * Do you smell something? I'm smelling that this class needs to be redesigned to not need a
     * central method this large and hard to test. I think a lot of instance and local variables
     * need to be bundled up into some kind of process object. This code is effectively too big to
     * be tested except through integration testing.
     */
    private void loadTemplateForComponent(ComponentPageElement loadingElement)
    {
        _loadingElement = loadingElement;
        _loadingComponentModel = loadingElement.getComponentResources().getComponentModel();

        String componentClassName = _loadingComponentModel.getComponentClassName();
        ComponentTemplate template = _templateSource.getTemplate(_loadingComponentModel, _locale);

        // When the template for a component is missing, we pretend it consists of just a RenderBody
        // phase. Missing is not an error ... many component simply do not have a template.

        if (template.isMissing())
        {
            _loadingElement.addToTemplate(newRenderBodyElement());
            return;
        }

        // Pre-allocate ids to avoid later name collisions.

        Log log = _loadingComponentModel.getLog();

        Set<String> embeddedIds = CollectionFactory.newSet(_loadingComponentModel
                .getEmbeddedComponentIds());

        _idAllocator.clear();

        for (String id : template.getComponentIds())
        {
            _idAllocator.allocateId(id);
            embeddedIds.remove(id);
        }

        if (!embeddedIds.isEmpty())
            log.error(ServicesMessages.embeddedComponentsNotInTemplate(
                    embeddedIds,
                    componentClassName));

        _addAttributesAsComponentBindings = false;

        // The outermost elements of the template belong in the loading component's template list,
        // not its body list. This shunt allows everyone else to not have to make that decision,
        // they can add to the "body" and (if there isn't an active component), the shunt will
        // add the element to the component's template.

        BodyPageElement shunt = new BodyPageElement()
        {
            public void addToBody(PageElement element)
            {
                _loadingElement.addToTemplate(element);
            }
        };

        _bodyPageElementStack.push(shunt);

        for (TemplateToken token : template.getTokens())
        {
            switch (token.getTokenType())
            {
                case TEXT:
                    text((TextToken) token);
                    break;

                case EXPANSION:
                    expansion((ExpansionToken) token);
                    break;

                case BODY:
                    body((BodyToken) token);
                    break;

                case START_ELEMENT:
                    startElement((StartElementToken) token);
                    break;

                case START_COMPONENT:
                    startComponent((StartComponentToken) token);
                    break;

                case ATTRIBUTE:
                    attribute((AttributeToken) token);
                    break;

                case END_ELEMENT:
                    endElement((EndElementToken) token);
                    break;

                case COMMENT:
                    comment((CommentToken) token);
                    break;

                case BLOCK:
                    block((BlockToken) token);
                    break;

                case PARAMETER:
                    parameter((ParameterToken) token);
                    break;

                default:
                    throw new IllegalStateException("Not implemented yet: " + token);
            }
        }

        // For neatness / symmetry:

        _bodyPageElementStack.pop(); // the shunt

        // TODO: Check that all stacks are empty. That should never happen, as long
        // as the ComponentTemplate is valid.
    }

    private void parameter(ParameterToken token)
    {
        BlockImpl block = new BlockImpl(token.getLocation());
        String name = token.getName();

        Binding binding = new LiteralBinding("block parameter " + name, block, token.getLocation());

        // TODO: Check that the t:parameter doesn't appear outside of an embedded component.

        _activeElementStack.peek().bindParameter(name, binding);

        setupBlock(block);
    }

    private void setupBlock(BodyPageElement block)
    {
        _bodyPageElementStack.push(block);

        Runnable cleanup = new Runnable()
        {
            public void run()
            {
                _bodyPageElementStack.pop();
            }
        };

        configureEnd(true, cleanup);
    }

    private void block(BlockToken token)
    {
        // Don't use the page element factory here becauses we need something that is both Block and
        // BodyPageElement and don't want to use casts.

        BlockImpl block = new BlockImpl(token.getLocation());

        String id = token.getId();

        if (id != null)
            _loadingElement.addBlock(id, block);

        setupBlock(block);
    }

    private void startComponent(StartComponentToken token)
    {
        String elementName = token.getElementName();

        // Initial guess: the type from the token (but this may be null in many cases).
        String embeddedType = token.getComponentType();

        String embeddedId = token.getId();

        String embeddedComponentClassName = null;

        // We know that if embeddedId is null, embeddedType is not.

        if (embeddedId == null)
            embeddedId = generateEmbeddedId(embeddedType, _idAllocator);

        EmbeddedComponentModel embeddedModel = _loadingComponentModel
                .getEmbeddedComponentModel(embeddedId);

        if (embeddedModel != null)
        {
            String modelType = embeddedModel.getComponentType();

            if (isNonBlank(modelType) && embeddedType != null)
            {
                Log log = _loadingComponentModel.getLog();
                log.error(ServicesMessages.compTypeConflict(embeddedId, embeddedType, modelType));
            }

            embeddedType = modelType;
            embeddedComponentClassName = embeddedModel.getComponentClassName();
        }

        if (isBlank(embeddedType) && isBlank(embeddedComponentClassName))
        {
            // non-null means its invisible instrumentation; the Any component
            // will mimic the actual element, w/ body and informal parameters.

            if (elementName != null)
                embeddedType = "Any";
            else
                throw new TapestryException(ServicesMessages.noTypeForEmbeddedComponent(
                        embeddedId,
                        _loadingComponentModel.getComponentClassName()), token, null);
        }

        ComponentPageElement newComponent = _pageElementFactory.newComponentElement(
                _page,
                _loadingElement,
                embeddedId,
                embeddedType,
                embeddedComponentClassName,
                elementName,
                token.getLocation());

        addMixinsToComponent(newComponent, embeddedModel, token.getMixins());

        if (embeddedModel != null)
            bindParametersFromModel(embeddedModel, _loadingElement, newComponent);

        addToBody(newComponent);

        // Remember to load the template for this new component
        _componentQueue.push(newComponent);

        // Any attribute tokens that immediately follow should be
        // used to bind parameters.

        _addAttributesAsComponentBindings = true;

        // Any attributes (including component parameters) that come up belong on this component.

        _activeElementStack.push(newComponent);

        // Set things up so that content inside the component is added to the component's body.

        _bodyPageElementStack.push(newComponent);

        // And clean that up when the end element is reached.

        Runnable cleanup = new Runnable()
        {
            public void run()
            {
                _activeElementStack.pop();
                _bodyPageElementStack.pop();
            }
        };

        // The start tag is not added to the body of the component, so neither should
        // the end tag.
        configureEnd(true, cleanup);
    }

    private void startElement(StartElementToken token)
    {
        PageElement element = _pageElementFactory.newStartElement(token);

        addToBody(element);

        // Controls how attributes are interpretted.
        _addAttributesAsComponentBindings = false;

        // Start will be matched by end:

        // Do NOT discard the end tag; add it to the body.

        configureEnd(false, NO_OP);
    }

    private void text(TextToken token)
    {
        PageElement element = _pageElementFactory.newTextElement(token);

        addToBody(element);
    }

    /** Works the component queue, until exausted. */
    private void workComponentQueue()
    {
        while (!_componentQueue.isEmpty())
        {
            ComponentPageElement componentElement = _componentQueue.pop();

            loadTemplateForComponent(componentElement);
        }
    }
}
TOP

Related Classes of org.apache.tapestry.internal.services.PageLoaderProcessor

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.