Package org.apache.tapestry.internal.structure

Source Code of org.apache.tapestry.internal.structure.ComponentPageElementImpl$RenderPhaseEventHandler

// Copyright 2006, 2007, 2008 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.structure;

import org.apache.tapestry.*;
import org.apache.tapestry.dom.Element;
import org.apache.tapestry.internal.InternalComponentResources;
import org.apache.tapestry.internal.TapestryInternalUtils;
import org.apache.tapestry.internal.services.ComponentEventImpl;
import org.apache.tapestry.internal.services.EventImpl;
import org.apache.tapestry.internal.services.Instantiator;
import org.apache.tapestry.internal.util.NotificationEventCallback;
import org.apache.tapestry.ioc.BaseLocatable;
import org.apache.tapestry.ioc.Location;
import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newCaseInsensitiveMap;
import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newList;
import org.apache.tapestry.ioc.internal.util.Defense;
import static org.apache.tapestry.ioc.internal.util.Defense.notBlank;
import org.apache.tapestry.ioc.internal.util.InternalUtils;
import org.apache.tapestry.ioc.internal.util.TapestryException;
import org.apache.tapestry.model.ComponentModel;
import org.apache.tapestry.model.ParameterModel;
import org.apache.tapestry.runtime.*;
import org.slf4j.Logger;

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

/**
* Implements {@link org.apache.tapestry.internal.structure.PageElement} and represents a component within an overall
* page. Much of a component page element's behavior is delegated to user code, via a {@link
* org.apache.tapestry.runtime.Component} instance.
* <p/>
* Once instantiated, a ComponentPageElement should be registered as a {@linkplain
* org.apache.tapestry.internal.structure.Page#addLifecycleListener(org.apache.tapestry.runtime.PageLifecycleListener)
* lifecycle listener}. This could be done inside the constructors, but that tends to complicate unit tests, so its done
* by {@link org.apache.tapestry.internal.services.PageElementFactoryImpl}.
* <p/>
*/
public class ComponentPageElementImpl extends BaseLocatable implements ComponentPageElement, PageLifecycleListener
{
    /**
     * @see #render(org.apache.tapestry.MarkupWriter, org.apache.tapestry.runtime.RenderQueue)
     */
    private static final RenderCommand POP_COMPONENT_ID = new RenderCommand()
    {
        public void render(MarkupWriter writer, RenderQueue queue)
        {
            queue.endComponent();
        }
    };

    private static final ComponentCallback CONTAINING_PAGE_DID_ATTACH = new ComponentCallback()
    {
        public void run(Component component)
        {
            component.containingPageDidAttach();
        }
    };

    private static final ComponentCallback CONTAINING_PAGE_DID_DETACH = new ComponentCallback()
    {
        public void run(Component component)
        {
            component.containingPageDidDetach();
        }
    };

    private static final ComponentCallback CONTAINING_PAGE_DID_LOAD = new ComponentCallback()
    {
        public void run(Component component)
        {
            component.containingPageDidLoad();
        }
    };

    private static final ComponentCallback POST_RENDER_CLEANUP = new ComponentCallback()
    {
        public void run(Component component)
        {
            component.postRenderCleanup();
        }
    };

    // For the moment, every component will have a template, even if it consists of
    // just a page element to queue up a BeforeRenderBody phase.

    private static void pushElements(RenderQueue queue, List<PageElement> list)
    {
        int count = size(list);
        for (int i = count - 1; i >= 0; i--)
            queue.push(list.get(i));
    }

    private static int size(List<?> list)
    {
        return list == null ? 0 : list.size();
    }

    private static class RenderPhaseEventHandler implements ComponentEventCallback
    {
        private boolean _result = true;

        private List<RenderCommand> _commands;

        boolean getResult()
        {
            return _result;
        }

        public boolean handleResult(Object result)
        {
            if (result instanceof Boolean)
            {
                _result = (Boolean) result;
                return true; // abort other handler methods
            }

            if (result instanceof RenderCommand)
            {
                RenderCommand command = (RenderCommand) result;

                add(command);

                return false; // do not abort!
            }

            if (result instanceof Renderable)
            {
                final Renderable renderable = (Renderable) result;

                RenderCommand wrapper = new RenderCommand()
                {
                    public void render(MarkupWriter writer, RenderQueue queue)
                    {
                        renderable.render(writer);
                    }
                };

                add(wrapper);

                return false;
            }

            throw new RuntimeException(StructureMessages.wrongPhaseResultType(Boolean.class));
        }

        private void add(RenderCommand command)
        {
            if (_commands == null) _commands = newList();

            _commands.add(command);
        }

        public void queueCommands(RenderQueue queue)
        {
            if (_commands == null) return;

            for (RenderCommand command : _commands)
                queue.push(command);
        }
    }

    private final RenderCommand _afterRender = new RenderCommand()
    {
        public void render(final MarkupWriter writer, RenderQueue queue)
        {
            RenderPhaseEventHandler handler = new RenderPhaseEventHandler();
            final Event event = new EventImpl(handler);

            ComponentCallback callback = new ComponentCallback()
            {
                public void run(Component component)
                {
                    component.afterRender(writer, event);
                }
            };

            invoke(true, callback);

            if (!handler.getResult()) queue.push(_beginRender);

            handler.queueCommands(queue);
        }

        @Override
        public String toString()
        {
            return phaseToString("AfterRender");
        }
    };

    private final RenderCommand _afterRenderBody = new RenderCommand()
    {
        public void render(final MarkupWriter writer, RenderQueue queue)
        {
            RenderPhaseEventHandler handler = new RenderPhaseEventHandler();
            final Event event = new EventImpl(handler);

            ComponentCallback callback = new ComponentCallback()
            {
                public void run(Component component)
                {
                    component.afterRenderBody(writer, event);
                }
            };

            invoke(true, callback);

            if (!handler.getResult()) queue.push(_beforeRenderBody);

            handler.queueCommands(queue);
        }

        @Override
        public String toString()
        {
            return phaseToString("AfterRenderBody");
        }
    };

    private final RenderCommand _afterRenderTemplate = new RenderCommand()
    {
        public void render(final MarkupWriter writer, final RenderQueue queue)
        {
            RenderPhaseEventHandler handler = new RenderPhaseEventHandler();
            final Event event = new EventImpl(handler);

            ComponentCallback callback = new ComponentCallback()
            {
                public void run(Component component)
                {
                    component.afterRenderTemplate(writer, event);
                }
            };

            invoke(true, callback);

            if (!handler.getResult()) queue.push(_beforeRenderTemplate);

            handler.queueCommands(queue);
        }

        @Override
        public String toString()
        {
            return phaseToString("AfterRenderTemplate");
        }
    };

    private final RenderCommand _beforeRenderBody = new RenderCommand()
    {
        public void render(final MarkupWriter writer, RenderQueue queue)
        {
            RenderPhaseEventHandler handler = new RenderPhaseEventHandler();
            final Event event = new EventImpl(handler);

            ComponentCallback callback = new ComponentCallback()
            {
                public void run(Component component)
                {
                    component.beforeRenderBody(writer, event);
                }
            };

            invoke(false, callback);

            queue.push(_afterRenderBody);

            if (handler.getResult()) pushElements(queue, _body);

            handler.queueCommands(queue);
        }

        @Override
        public String toString()
        {
            return phaseToString("BeforeRenderBody");
        }
    };

    private final RenderCommand _beforeRenderTemplate = new RenderCommand()
    {
        public void render(final MarkupWriter writer, final RenderQueue queue)
        {
            final RenderPhaseEventHandler handler = new RenderPhaseEventHandler();
            final Event event = new EventImpl(handler);

            ComponentCallback callback = new ComponentCallback()
            {
                public void run(Component component)
                {
                    component.beforeRenderTemplate(writer, event);
                }
            };

            invoke(false, callback);

            queue.push(_afterRenderTemplate);

            if (handler.getResult()) pushElements(queue, _template);

            handler.queueCommands(queue);
        }

        @Override
        public String toString()
        {
            return phaseToString("BeforeRenderTemplate");
        }
    };

    private final RenderCommand _beginRender = new RenderCommand()
    {
        public void render(final MarkupWriter writer, final RenderQueue queue)
        {
            RenderPhaseEventHandler handler = new RenderPhaseEventHandler();
            final Event event = new EventImpl(handler);

            ComponentCallback callback = new ComponentCallback()
            {
                public void run(Component component)
                {
                    component.beginRender(writer, event);
                }
            };

            invoke(false, callback);

            queue.push(_afterRender);

            // If the component has no template whatsoever, then a
            // renderBody element is added as the lone element of the component's template.
            // So every component will have a non-empty template.

            if (handler.getResult()) queue.push(_beforeRenderTemplate);

            handler.queueCommands(queue);
        }

        @Override
        public String toString()
        {
            return phaseToString("BeginRender");
        }
    };

    private Map<String, Block> _blocks;

    private List<PageElement> _body;

    private Map<String, ComponentPageElement> _children;

    private final String _elementName;

    private final PageResources _pageResources;

    private final RenderCommand _cleanupRender = new RenderCommand()
    {
        public void render(final MarkupWriter writer, RenderQueue queue)
        {
            RenderPhaseEventHandler handler = new RenderPhaseEventHandler();
            final Event event = new EventImpl(handler);

            ComponentCallback callback = new ComponentCallback()
            {
                public void run(Component component)
                {
                    component.cleanupRender(writer, event);
                }
            };

            invoke(true, callback);

            if (handler.getResult())
            {
                _rendering = false;

                Element current = writer.getElement();

                if (current != _elementAtSetup)
                    throw new TapestryException(StructureMessages.unbalancedElements(_completeId), getLocation(), null);

                _elementAtSetup = null;

                invoke(false, POST_RENDER_CLEANUP);

                // NOW and only now the component is done rendering and fully cleaned up. Decrement
                // the page's dirty count. If the entire render goes well, then the page will be
                // clean and can be stored into the pool for later reuse.

                _page.decrementDirtyCount();
            }
            else
            {
                queue.push(_setupRender);
            }

            handler.queueCommands(queue);
        }

        @Override
        public String toString()
        {
            return phaseToString("CleanupRender");
        }
    };

    private final String _completeId;

    // The user-provided class, with runtime code enhancements. In a component with mixins, this
    // is the component to which the mixins are attached.
    private final Component _coreComponent;

    /**
     * Component lifecycle instances for all mixins; the core component is added to this list during page load. This is
     * only used in the case that a component has mixins (in which case, the core component is listed last).
     */
    private List<Component> _components = null;

    private final ComponentPageElement _container;

    private final InternalComponentResources _coreResources;

    private final String _id;

    private boolean _loaded;

    /**
     * Map from mixin name to resources for the mixin. Created when first mixin is added.
     */
    private Map<String, InternalComponentResources> _mixinsByShortName;

    private final String _nestedId;

    private final Page _page;

    private boolean _rendering;

    /**
     * Used to detect mismatches calls to {@link MarkupWriter#element(String, Object[])} } and {@link
     * org.apache.tapestry.MarkupWriter#end()}.  The expectation is that any element(s) begun by this component during
     * rendering will be balanced by end() calls, resulting in the current element reverting to its initial value.
     */
    private Element _elementAtSetup;

    private final RenderCommand _setupRender = new RenderCommand()
    {
        public void render(final MarkupWriter writer, RenderQueue queue)
        {
            // TODO: Check for recursive rendering.

            _rendering = true;

            _elementAtSetup = writer.getElement();

            RenderPhaseEventHandler handler = new RenderPhaseEventHandler();
            final Event event = new EventImpl(handler);

            ComponentCallback callback = new ComponentCallback()
            {
                public void run(Component component)
                {
                    component.setupRender(writer, event);
                }
            };

            invoke(false, callback);

            queue.push(_cleanupRender);

            if (handler.getResult()) queue.push(_beginRender);

            handler.queueCommands(queue);
        }

        @Override
        public String toString()
        {
            return phaseToString("SetupRender");
        }
    };

    // We know that, at the very least, there will be an element to force the component to render
    // its body, so there's no reason to wait to initialize the list.

    private final List<PageElement> _template = newList();


    public ComponentPageElement newChild(String id, String elementName, Instantiator instantiator, Location location)
    {
        ComponentPageElementImpl child = new ComponentPageElementImpl(_page, this, id, elementName, instantiator,
                                                                      location, _pageResources);

        addEmbeddedElement(child);

        return child;
    }

    /**
     * Constructor for other components embedded within the root component or at deeper levels of the hierarchy.
     *
     * @param page          ultimately containing this component
     * @param container     component immediately containing this component (may be null for a root component)
     * @param id            unique (within the container) id for this component (may be null for a root component)
     * @param elementName   the name of the element which represents this component in the template, or null for
     *                      &lt;comp&gt; element or a page component
     * @param instantiator  used to create the new component instance and access the component's model
     * @param location      location of the element (within a template), used as part of exception reporting
     * @param pageResources Provides access to common methods of various services
     */

    ComponentPageElementImpl(Page page, ComponentPageElement container, String id, String elementName,
                             Instantiator instantiator, Location location, PageResources pageResources)
    {
        super(location);

        _page = page;
        _container = container;
        _id = id;
        _elementName = elementName;
        _pageResources = pageResources;

        ComponentResources containerResources = container == null ? null : container
                .getComponentResources();

        String pageName = _page.getLogicalName();

        // A page (really, the root component of a page) does not have a container.

        if (container == null)
        {
            _completeId = pageName;
            _nestedId = null;
        }
        else
        {
            String caselessId = id.toLowerCase();

            String parentNestedId = container.getNestedId();

            // The root element has no nested id.
            // The children of the root element have an id.

            if (parentNestedId == null)
            {
                _nestedId = caselessId;
                _completeId = pageName + ":" + caselessId;
            }
            else
            {
                _nestedId = parentNestedId + "." + caselessId;
                _completeId = container.getCompleteId() + "." + caselessId;
            }
        }

        _coreResources = new InternalComponentResourcesImpl(_page, this, containerResources, instantiator,
                                                            _pageResources);

        _coreComponent = _coreResources.getComponent();
    }

    /**
     * Constructor for the root component of a page.
     */
    public ComponentPageElementImpl(Page page, Instantiator instantiator, PageResources pageResources)
    {
        this(page, null, null, null, instantiator, null, pageResources);
    }

    public void addEmbeddedElement(ComponentPageElement child)
    {
        if (_children == null) _children = newCaseInsensitiveMap();

        String childId = child.getId();

        ComponentPageElement existing = _children.get(childId);
        if (existing != null)
            throw new TapestryException(StructureMessages.duplicateChildComponent(this, childId), child, null);

        _children.put(childId, child);
    }

    public void addMixin(Instantiator instantiator)
    {
        if (_mixinsByShortName == null)
        {
            _mixinsByShortName = newCaseInsensitiveMap();
            _components = newList();
        }

        String mixinClassName = instantiator.getModel().getComponentClassName();
        String mixinName = TapestryInternalUtils.lastTerm(mixinClassName);

        InternalComponentResourcesImpl resources = new InternalComponentResourcesImpl(_page, this, _coreResources,
                                                                                      instantiator, _pageResources);

        // TODO: Check for name collision?

        _mixinsByShortName.put(mixinName, resources);

        _components.add(resources.getComponent());
    }

    public void bindParameter(String parameterName, Binding binding)
    {
        // Maybe should use colon here? Depends on what works best in the template,
        // don't want to lock this out as just
        int dotx = parameterName.lastIndexOf('.');

        if (dotx > 0)
        {
            String mixinName = parameterName.substring(0, dotx);
            InternalComponentResources mixinResources = InternalUtils.get(_mixinsByShortName, mixinName);

            if (mixinResources == null) throw new TapestryException(
                    StructureMessages.missingMixinForParameter(_completeId, mixinName, parameterName), binding, null);

            String simpleName = parameterName.substring(dotx + 1);

            mixinResources.bindParameter(simpleName, binding);
            return;
        }

        InternalComponentResources informalParameterResources = null;

        // Does it match a formal parameter name of the core component? That takes precedence

        if (_coreResources.getComponentModel().getParameterModel(parameterName) != null)
        {
            _coreResources.bindParameter(parameterName, binding);
            return;
        }

        for (String mixinName : InternalUtils.sortedKeys(_mixinsByShortName))
        {
            InternalComponentResources resources = _mixinsByShortName.get(mixinName);
            if (resources.getComponentModel().getParameterModel(parameterName) != null)
            {
                resources.bindParameter(parameterName, binding);
                return;
            }

            if (informalParameterResources == null && resources.getComponentModel().getSupportsInformalParameters())
                informalParameterResources = resources;
        }

        // An informal parameter

        if (informalParameterResources == null && _coreResources.getComponentModel().getSupportsInformalParameters())
            informalParameterResources = _coreResources;

        // For the moment, informal parameters accumulate in the core component's resources, but
        // that will likely change.

        if (informalParameterResources != null) informalParameterResources.bindParameter(parameterName, binding);
    }

    public void addToBody(PageElement element)
    {
        if (_body == null) _body = newList();

        _body.add(element);
    }

    public void addToTemplate(PageElement element)
    {
        _template.add(element);
    }

    private void addUnboundParameterNames(String prefix, List<String> unbound, InternalComponentResources resource)
    {
        ComponentModel model = resource.getComponentModel();

        for (String name : model.getParameterNames())
        {
            if (resource.isBound(name)) continue;

            ParameterModel parameterModel = model.getParameterModel(name);

            if (parameterModel.isRequired())
            {
                String fullName = prefix == null ? name : prefix + "." + name;

                unbound.add(fullName);
            }
        }
    }

    public void containingPageDidAttach()
    {
        invoke(false, CONTAINING_PAGE_DID_ATTACH);
    }

    public void containingPageDidDetach()
    {
        invoke(false, CONTAINING_PAGE_DID_DETACH);
    }

    public void containingPageDidLoad()
    {
        // If this component has mixins, add the core component to the end of the list, after the
        // mixins.

        if (_components != null)
        {
            List<Component> ordered = newList();

            Iterator<Component> i = _components.iterator();

            // Add all the normal components to the final list.

            while (i.hasNext())
            {
                Component mixin = i.next();

                if (mixin.getComponentResources().getComponentModel().isMixinAfter()) continue;

                ordered.add(mixin);

                // Remove from list, leaving just the late executing mixins

                i.remove();
            }

            ordered.add(_coreComponent);

            // Add the remaining, late executing mixins

            ordered.addAll(_components);

            _components = ordered;
        }

        _loaded = true;

        // For some parameters, bindings (from defaults) are provided inside the callback method, so
        // that is invoked first, before we check for unbound parameters.

        invoke(false, CONTAINING_PAGE_DID_LOAD);

        verifyRequiredParametersAreBound();
    }


    public void enqueueBeforeRenderBody(RenderQueue queue)
    {
        // If no body, then no beforeRenderBody or afterRenderBody

        if (_body != null) queue.push(_beforeRenderBody);
    }

    public String getCompleteId()
    {
        return _completeId;
    }

    public Component getComponent()
    {
        return _coreComponent;
    }

    public InternalComponentResources getComponentResources()
    {
        return _coreResources;
    }

    public ComponentPageElement getContainerElement()
    {
        return _container;
    }

    public Page getContainingPage()
    {
        return _page;
    }

    public ComponentPageElement getEmbeddedElement(String embeddedId)
    {
        ComponentPageElement embeddedElement = InternalUtils.get(_children, embeddedId
                .toLowerCase());

        if (embeddedElement == null)
            throw new TapestryException(StructureMessages.noSuchComponent(this, embeddedId), this, null);

        return embeddedElement;
    }


    public String getId()
    {
        return _id;
    }

    public Logger getLogger()
    {
        return _coreResources.getLogger();
    }

    public Component getMixinByClassName(String mixinClassName)
    {
        Component result = null;

        if (_mixinsByShortName != null)
        {
            for (InternalComponentResources resources : _mixinsByShortName.values())
            {
                if (resources.getComponentModel().getComponentClassName().equals(mixinClassName))
                {
                    result = resources.getComponent();
                    break;
                }
            }
        }

        if (result == null) throw new TapestryException(StructureMessages.unknownMixin(_completeId, mixinClassName),
                                                        getLocation(), null);

        return result;
    }

    public String getNestedId()
    {
        return _nestedId;
    }

    public boolean dispatchEvent(ComponentEvent event)
    {
        if (_components == null) return _coreComponent.dispatchComponentEvent(event);

        // Otherwise, iterate over mixins + core component

        boolean result = false;

        for (Component component : _components)
        {
            result |= component.dispatchComponentEvent(event);

            if (event.isAborted()) break;
        }

        return result;
    }

    /**
     * Invokes a callback on the component instances (the core component plus any mixins).
     *
     * @param reverse  if true, the callbacks are in the reverse of the normal order (this is associated with AfterXXX
     *                 phases)
     * @param callback the object to receive each component instance
     */
    private void invoke(boolean reverse, ComponentCallback callback)
    {
        try
        { // Optimization: In the most general case (just the one component, no mixins)
            // invoke the callback on the component and be done ... no iterators, no nothing.

            if (_components == null)
            {
                callback.run(_coreComponent);
                return;
            }

            Iterator<Component> i = reverse ? InternalUtils.reverseIterator(_components) : _components.iterator();

            while (i.hasNext()) callback.run(i.next());
        }
        catch (RuntimeException ex)
        {
            throw new TapestryException(ex.getMessage(), getLocation(), ex);
        }
    }

    public boolean isLoaded()
    {
        return _loaded;
    }

    public boolean isRendering()
    {
        return _rendering;
    }

    /**
     * Generate a toString() for the inner classes that represent render phases.
     */
    private String phaseToString(String phaseName)
    {
        return String.format("%s[%s]", phaseName, _completeId);
    }


    /**
     * Pushes the SetupRender phase state onto the queue.
     */
    public final void render(MarkupWriter writer, RenderQueue queue)
    {
        // TODO: An error if the _render flag is already set (recursive rendering not
        // allowed or advisable).

        // Once we start rendering, the page is considered dirty, until we cleanup post render.

        _page.incrementDirtyCount();

        queue.startComponent(_coreResources);

        queue.push(POP_COMPONENT_ID);

        queue.push(_setupRender);
    }

    @Override
    public String toString()
    {
        return String.format("ComponentPageElement[%s]", _completeId);
    }

    public boolean triggerEvent(String eventType, Object[] contextValues, ComponentEventCallback callback)
    {
        return triggerContextEvent(eventType,
                                   createParameterContext(contextValues == null ? new Object[0] : contextValues),
                                   callback);
    }

    private EventContext createParameterContext(final Object... values)
    {

        return new EventContext()
        {
            public int getCount()
            {
                return values.length;
            }

            public <T> T get(Class<T> desiredType, int index)
            {
                return _pageResources.coerce(values[index], desiredType);
            }
        };
    }


    public boolean triggerContextEvent(String eventType, EventContext context, ComponentEventCallback callback)
    {
        Defense.notBlank(eventType, "eventType");
        Defense.notNull(context, "context");

        boolean result = false;

        ComponentPageElement component = this;
        String componentId = "";

        // Provide a default handler for when the provided handler is null.
        final ComponentEventCallback providedHandler = callback == null ? new NotificationEventCallback(eventType,
                                                                                                        _completeId) : callback;

        ComponentEventCallback wrapped = new ComponentEventCallback()
        {
            public boolean handleResult(Object result)
            {
                // Boolean value is not passed to the handler; it will be true (abort event)
                // or false (continue looking for event handlers).

                if (result instanceof Boolean) return (Boolean) result;

                return providedHandler.handleResult(result);
            }
        };

        RuntimeException rootException = null;

        // Because I don't like to reassign parameters.

        String currentEventType = eventType;
        EventContext currentContext = context;

        // Track the location of the original component for the event, even as we work our way up
        // the hierarchy. This may not be ideal if we trigger an "exception" event ... or maybe
        // it's right (it's the location of the originally thrown exception).

        Location location = component.getComponentResources().getLocation();

        while (component != null)
        {
            try
            {
                ComponentEvent event = new ComponentEventImpl(currentEventType, componentId, currentContext, wrapped,
                                                              _pageResources);

                result |= component.dispatchEvent(event);

                if (event.isAborted()) return result;
            }
            catch (RuntimeException ex)
            {
                // An exception in an event handler method
                // while we're trying to handle a previous exception!

                if (rootException != null) throw rootException;

                // We know component is not null and therefore has a component resources that
                // should have a location.

                // Wrap it up to help ensure that a location is available to the event handler method or,
                // more likely, to the exception report page.

                rootException = new ComponentEventException(ex.getMessage(), eventType, context, location, ex);

                // Switch over to triggering an "exception" event, starting in the component that
                // threw the exception.

                currentEventType = "exception";
                currentContext = createParameterContext(rootException);

                continue;
            }

            // On each bubble up, make the event appear to come from the previous component
            // in which the event was triggered.

            componentId = component.getId();

            component = component.getContainerElement();
        }

        // If there was a handler for the exception event, it is required to return a non-null (and non-boolean) value
        // to tell Tapestry what to do.  Since that didn't happen, we have no choice but to rethrow the (wrapped)
        // exception.

        if (rootException != null) throw rootException;

        return result;
    }

    private void verifyRequiredParametersAreBound()
    {
        List<String> unbound = newList();

        addUnboundParameterNames(null, unbound, _coreResources);

        for (String name : InternalUtils.sortedKeys(_mixinsByShortName))
            addUnboundParameterNames(name, unbound, _mixinsByShortName.get(name));

        if (unbound.isEmpty()) return;

        throw new TapestryException(StructureMessages.missingParameters(unbound, this), this, null);
    }

    public Locale getLocale()
    {
        return _page.getLocale();
    }

    public String getElementName(String defaultElementName)
    {
        return _elementName != null ? _elementName : defaultElementName;
    }

    public Block getBlock(String id)
    {
        Block result = findBlock(id);

        if (result == null)
            throw new BlockNotFoundException(StructureMessages.blockNotFound(_completeId, id), getLocation());

        return result;
    }

    public Block findBlock(String id)
    {
        notBlank(id, "id");

        return InternalUtils.get(_blocks, id);
    }

    public void addBlock(String blockId, Block block)
    {
        if (_blocks == null) _blocks = newCaseInsensitiveMap();

        if (_blocks.containsKey(blockId))
            throw new TapestryException(StructureMessages.duplicateBlock(this, blockId), block, null);

        _blocks.put(blockId, block);
    }

    public String getDefaultBindingPrefix(String parameterName)
    {
        int dotx = parameterName.lastIndexOf('.');

        if (dotx > 0)
        {
            String mixinName = parameterName.substring(0, dotx);
            InternalComponentResources mixinResources = InternalUtils.get(_mixinsByShortName, mixinName);

            if (mixinResources == null) throw new TapestryException(
                    StructureMessages.missingMixinForParameter(_completeId, mixinName, parameterName), null, null);

            String simpleName = parameterName.substring(dotx + 1);

            ParameterModel pm = mixinResources.getComponentModel().getParameterModel(simpleName);

            return pm != null ? pm.getDefaultBindingPrefix() : null;
        }

        // A formal parameter of the core component?

        ParameterModel pm = _coreResources.getComponentModel().getParameterModel(parameterName);

        if (pm != null) return pm.getDefaultBindingPrefix();

        // Search for mixin that it is a formal parameter of

        for (String mixinName : InternalUtils.sortedKeys(_mixinsByShortName))
        {
            InternalComponentResources resources = _mixinsByShortName.get(mixinName);

            pm = resources.getComponentModel().getParameterModel(parameterName);

            if (pm != null) return pm.getDefaultBindingPrefix();
        }

        // Not a formal parameter of the core component or any mixin.

        return null;
    }

    public String getPageName()
    {
        return _page.getLogicalName();
    }

    public Map<String, Binding> getInformalParameterBindings()
    {
        return _coreResources.getInformalParameterBindings();
    }
}
TOP

Related Classes of org.apache.tapestry.internal.structure.ComponentPageElementImpl$RenderPhaseEventHandler

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.