001// Copyright 2006-2014  The Apache Software Foundation
002//
003// Licensed under the Apache License, Version 2.0 (the "License");
004// you may not use this file except in compliance with the License.
005// You may obtain a copy of the License at
006//
007// http://www.apache.org/licenses/LICENSE-2.0
008//
009// Unless required by applicable law or agreed to in writing, software
010// distributed under the License is distributed on an "AS IS" BASIS,
011// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012// See the License for the specific language governing permissions and
013// limitations under the License.
014
015package org.apache.tapestry5.internal.structure;
016
017import org.apache.tapestry5.*;
018import org.apache.tapestry5.annotations.*;
019import org.apache.tapestry5.dom.Element;
020import org.apache.tapestry5.internal.AbstractEventContext;
021import org.apache.tapestry5.internal.InternalComponentResources;
022import org.apache.tapestry5.internal.InternalConstants;
023import org.apache.tapestry5.internal.services.ComponentEventImpl;
024import org.apache.tapestry5.internal.services.Instantiator;
025import org.apache.tapestry5.internal.util.NamedSet;
026import org.apache.tapestry5.internal.util.NotificationEventCallback;
027import org.apache.tapestry5.ioc.BaseLocatable;
028import org.apache.tapestry5.ioc.Invokable;
029import org.apache.tapestry5.ioc.Location;
030import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
031import org.apache.tapestry5.ioc.internal.util.InternalUtils;
032import org.apache.tapestry5.ioc.internal.util.Orderer;
033import org.apache.tapestry5.ioc.internal.util.TapestryException;
034import org.apache.tapestry5.ioc.services.PerThreadValue;
035import org.apache.tapestry5.ioc.util.AvailableValues;
036import org.apache.tapestry5.ioc.util.UnknownValueException;
037import org.apache.tapestry5.model.ComponentModel;
038import org.apache.tapestry5.model.ParameterModel;
039import org.apache.tapestry5.runtime.Component;
040import org.apache.tapestry5.runtime.*;
041import org.apache.tapestry5.services.pageload.ComponentResourceSelector;
042import org.slf4j.Logger;
043
044import java.util.*;
045
046/**
047 * Implements {@link RenderCommand} and represents a component within an overall page. Much of a
048 * component page
049 * element's behavior is delegated to user code, via a {@link org.apache.tapestry5.runtime.Component} instance.
050 * <p/>
051 * Once instantiated, a ComponentPageElement should be registered as a
052 * {@linkplain org.apache.tapestry5.internal.structure.Page#addLifecycleListener(org.apache.tapestry5.runtime.PageLifecycleListener)
053 * lifecycle listener}. This could be done inside the constructors, but that tends to complicate unit tests, so its done
054 * by {@link org.apache.tapestry5.internal.services.PageElementFactoryImpl}. There's still a bit of refactoring in this
055 * class (and its many inner classes) that can improve overall efficiency.
056 * <p/>
057 * Modified for Tapestry 5.2 to adjust for the no-pooling approach (shared instances with externalized mutable state).
058 */
059public class ComponentPageElementImpl extends BaseLocatable implements ComponentPageElement
060{
061    /**
062     * Placeholder for the body used when the component has no real content.
063     */
064    private static class PlaceholderBlock implements Block, Renderable, RenderCommand
065    {
066        public void render(MarkupWriter writer)
067        {
068        }
069
070        public void render(MarkupWriter writer, RenderQueue queue)
071        {
072        }
073
074        @Override
075        public String toString()
076        {
077            return "<PlaceholderBlock>";
078        }
079    }
080
081    private static final Block PLACEHOLDER_BLOCK = new PlaceholderBlock();
082
083    private static final ComponentCallback POST_RENDER_CLEANUP = new LifecycleNotificationComponentCallback()
084    {
085        public void run(Component component)
086        {
087            component.postRenderCleanup();
088        }
089    };
090
091    // For the moment, every component will have a template, even if it consists of
092    // just a page element to queue up a BeforeRenderBody phase.
093
094    private static void pushElements(RenderQueue queue, List<RenderCommand> list)
095    {
096        int count = size(list);
097        for (int i = count - 1; i >= 0; i--)
098            queue.push(list.get(i));
099    }
100
101    private static int size(List<?> list)
102    {
103        return list == null ? 0 : list.size();
104    }
105
106    private abstract class AbstractPhase implements RenderCommand
107    {
108        private final String name;
109
110        private final boolean reverse;
111
112        AbstractPhase(String name)
113        {
114            this(name, false);
115        }
116
117        AbstractPhase(String name, boolean reverse)
118        {
119            this.name = name;
120            this.reverse = reverse;
121        }
122
123        @Override
124        public String toString()
125        {
126            return phaseToString(name);
127        }
128
129        void invoke(MarkupWriter writer, Event event)
130        {
131            try
132            {
133                if (components == null)
134                {
135                    invokeComponent(coreComponent, writer, event);
136                    return;
137                }
138
139                // Multiple components (i.e., some mixins).
140
141                Iterator<Component> i = reverse ? InternalUtils.reverseIterator(components) : components.iterator();
142
143                while (i.hasNext())
144                {
145                    invokeComponent(i.next(), writer, event);
146
147                    if (event.isAborted())
148                        break;
149                }
150            }
151            // This used to be RuntimeException, but with TAP5-1508 changes to RenderPhaseMethodWorker, we now
152            // let ordinary exceptions bubble up as well.
153            catch (Exception ex)
154            {
155                throw new TapestryException(ex.getMessage(), getLocation(), ex);
156            }
157
158        }
159
160        /**
161         * Each concrete class implements this method to branch to the corresponding method
162         * of {@link Component}.
163         */
164        protected abstract void invokeComponent(Component component, MarkupWriter writer, Event event);
165    }
166
167    private class SetupRenderPhase extends AbstractPhase
168    {
169        public SetupRenderPhase()
170        {
171            super("SetupRender");
172        }
173
174        protected void invokeComponent(Component component, MarkupWriter writer, Event event)
175        {
176            component.setupRender(writer, event);
177        }
178
179        public void render(MarkupWriter writer, RenderQueue queue)
180        {
181            RenderPhaseEvent event = createRenderEvent(queue);
182
183            invoke(writer, event);
184
185            push(queue, event.getResult(), beginRenderPhase, cleanupRenderPhase);
186
187            event.enqueueSavedRenderCommands();
188        }
189    }
190
191    private class BeginRenderPhase extends AbstractPhase
192    {
193        private BeginRenderPhase()
194        {
195            super("BeginRender");
196        }
197
198        protected void invokeComponent(Component component, MarkupWriter writer, Event event)
199        {
200            if (isRenderTracingEnabled())
201                writer.comment("BEGIN " + component.getComponentResources().getCompleteId() + " (" + getLocation()
202                        + ")");
203
204            component.beginRender(writer, event);
205        }
206
207        public void render(final MarkupWriter writer, final RenderQueue queue)
208        {
209            RenderPhaseEvent event = createRenderEvent(queue);
210
211            invoke(writer, event);
212
213            push(queue, afterRenderPhase);
214            push(queue, event.getResult(), beforeRenderTemplatePhase, null);
215
216            event.enqueueSavedRenderCommands();
217        }
218    }
219
220    /**
221     * Replaces {@link org.apache.tapestry5.internal.structure.ComponentPageElementImpl.BeginRenderPhase} when there is
222     * a handler for AfterRender but not BeginRender.
223     */
224    private class OptimizedBeginRenderPhase implements RenderCommand
225    {
226        public void render(MarkupWriter writer, RenderQueue queue)
227        {
228            push(queue, afterRenderPhase);
229            push(queue, beforeRenderTemplatePhase);
230        }
231
232        @Override
233        public String toString()
234        {
235            return phaseToString("OptimizedBeginRenderPhase");
236        }
237    }
238
239    /**
240     * Reponsible for rendering the component's template. Even a component that doesn't have a
241     * template goes through
242     * this phase, as a synthetic template (used to trigger the rendering of the component's body)
243     * will be supplied.
244     */
245    private class BeforeRenderTemplatePhase extends AbstractPhase
246    {
247        private BeforeRenderTemplatePhase()
248        {
249            super("BeforeRenderTemplate");
250        }
251
252        protected void invokeComponent(Component component, MarkupWriter writer, Event event)
253        {
254            component.beforeRenderTemplate(writer, event);
255        }
256
257        public void render(final MarkupWriter writer, final RenderQueue queue)
258        {
259            RenderPhaseEvent event = createRenderEvent(queue);
260
261            invoke(writer, event);
262
263            push(queue, afterRenderTemplatePhase);
264
265            if (event.getResult())
266                pushElements(queue, template);
267
268            event.enqueueSavedRenderCommands();
269        }
270    }
271
272    /**
273     * Alternative version of BeforeRenderTemplatePhase used when the BeforeRenderTemplate render
274     * phase is not handled.
275     */
276    private class RenderTemplatePhase implements RenderCommand
277    {
278        public void render(MarkupWriter writer, RenderQueue queue)
279        {
280            push(queue, afterRenderTemplatePhase);
281
282            pushElements(queue, template);
283        }
284
285        @Override
286        public String toString()
287        {
288            return phaseToString("RenderTemplate");
289        }
290    }
291
292    private class BeforeRenderBodyPhase extends AbstractPhase
293    {
294        private BeforeRenderBodyPhase()
295        {
296            super("BeforeRenderBody");
297        }
298
299        protected void invokeComponent(Component component, MarkupWriter writer, Event event)
300        {
301            component.beforeRenderBody(writer, event);
302        }
303
304        public void render(final MarkupWriter writer, RenderQueue queue)
305        {
306            RenderPhaseEvent event = createRenderEvent(queue);
307
308            invoke(writer, event);
309
310            push(queue, afterRenderBodyPhase);
311
312            if (event.getResult() && bodyBlock != null)
313                queue.push(bodyBlock);
314
315            event.enqueueSavedRenderCommands();
316        }
317    }
318
319    private class AfterRenderBodyPhase extends AbstractPhase
320    {
321
322        private AfterRenderBodyPhase()
323        {
324            super("AfterRenderBody", true);
325        }
326
327        protected void invokeComponent(Component component, MarkupWriter writer, Event event)
328        {
329            component.afterRenderBody(writer, event);
330        }
331
332        public void render(final MarkupWriter writer, RenderQueue queue)
333        {
334            RenderPhaseEvent event = createRenderEvent(queue);
335
336            invoke(writer, event);
337
338            push(queue, event.getResult(), null, beforeRenderBodyPhase);
339
340            event.enqueueSavedRenderCommands();
341        }
342    }
343
344    private class AfterRenderTemplatePhase extends AbstractPhase
345    {
346        private AfterRenderTemplatePhase()
347        {
348            super("AfterRenderTemplate", true);
349        }
350
351        protected void invokeComponent(Component component, MarkupWriter writer, Event event)
352        {
353            component.afterRenderTemplate(writer, event);
354        }
355
356        public void render(final MarkupWriter writer, final RenderQueue queue)
357        {
358            RenderPhaseEvent event = createRenderEvent(queue);
359
360            invoke(writer, event);
361
362            push(queue, event.getResult(), null, beforeRenderTemplatePhase);
363
364            event.enqueueSavedRenderCommands();
365        }
366    }
367
368    private class AfterRenderPhase extends AbstractPhase
369    {
370        private AfterRenderPhase()
371        {
372            super("AfterRender", true);
373        }
374
375        protected void invokeComponent(Component component, MarkupWriter writer, Event event)
376        {
377            component.afterRender(writer, event);
378
379            if (isRenderTracingEnabled())
380                writer.comment("END " + component.getComponentResources().getCompleteId());
381        }
382
383        public void render(final MarkupWriter writer, RenderQueue queue)
384        {
385            RenderPhaseEvent event = createRenderEvent(queue);
386
387            invoke(writer, event);
388
389            push(queue, event.getResult(), cleanupRenderPhase, beginRenderPhase);
390
391            event.enqueueSavedRenderCommands();
392        }
393    }
394
395    private class CleanupRenderPhase extends AbstractPhase
396    {
397        private CleanupRenderPhase()
398        {
399            super("CleanupRender", true);
400        }
401
402        protected void invokeComponent(Component component, MarkupWriter writer, Event event)
403        {
404            component.cleanupRender(writer, event);
405        }
406
407        public void render(final MarkupWriter writer, RenderQueue queue)
408        {
409            RenderPhaseEvent event = createRenderEvent(queue);
410
411            invoke(writer, event);
412
413            push(queue, event.getResult(), null, setupRenderPhase);
414
415            event.enqueueSavedRenderCommands();
416        }
417    }
418
419    private class PostRenderCleanupPhase implements RenderCommand
420    {
421        /**
422         * Used to detect mismatches calls to {@link MarkupWriter#element(String, Object[])} and
423         * {@link org.apache.tapestry5.MarkupWriter#end()}. The expectation is that any element(s)
424         * begun by this component
425         * during rendering will be balanced by end() calls, resulting in the current element
426         * reverting to its initial
427         * value.
428         */
429        private final Element expectedElementAtCompletion;
430
431        PostRenderCleanupPhase(Element expectedElementAtCompletion)
432        {
433            this.expectedElementAtCompletion = expectedElementAtCompletion;
434        }
435
436        public void render(MarkupWriter writer, RenderQueue queue)
437        {
438            renderingValue.set(false);
439
440            Element current = writer.getElement();
441
442            if (current != expectedElementAtCompletion)
443                throw new TapestryException(StructureMessages.unbalancedElements(completeId), getLocation(), null);
444
445            invoke(false, POST_RENDER_CLEANUP);
446
447            queue.endComponent();
448        }
449
450        @Override
451        public String toString()
452        {
453            return phaseToString("PostRenderCleanup");
454        }
455    }
456
457    private NamedSet<Block> blocks;
458
459    private BlockImpl bodyBlock;
460
461    private List<ComponentPageElement> children;
462
463    private final String elementName;
464
465    private final Logger eventLogger;
466
467    private final String completeId;
468
469    // The user-provided class, with runtime code enhancements. In a component with mixins, this
470    // is the component to which the mixins are attached.
471    private final Component coreComponent;
472
473    /**
474     * Component lifecycle instances for all mixins; the core component is added to this list during
475     * page load. This is only used in the case that a component has mixins (in which case, the core component is
476     * listed last).
477     */
478    private List<Component> components = null;
479
480    private final ComponentPageElementResources elementResources;
481
482    private final ComponentPageElement container;
483
484    private final InternalComponentResources coreResources;
485
486    private final String id;
487
488    private Orderer<Component> mixinBeforeOrderer;
489
490    private Orderer<Component> mixinAfterOrderer;
491
492    private boolean loaded;
493
494    /**
495     * Map from mixin id (the simple name of the mixin class) to resources for the mixin. Created
496     * when first mixin is added.
497     */
498    private NamedSet<InternalComponentResources> mixinIdToComponentResources;
499
500    private final String nestedId;
501
502    private final Page page;
503
504    private final PerThreadValue<Boolean> renderingValue;
505
506    private final boolean exactParameterCountMatch;
507
508    // We know that, at the very least, there will be an element to force the component to render
509    // its body, so there's no reason to wait to initialize the list.
510
511    private final List<RenderCommand> template = CollectionFactory.newList();
512
513    private RenderCommand setupRenderPhase, beginRenderPhase, beforeRenderTemplatePhase, beforeRenderBodyPhase,
514            afterRenderBodyPhase, afterRenderTemplatePhase, afterRenderPhase, cleanupRenderPhase;
515
516    /**
517     * Constructor for other components embedded within the root component or at deeper levels of
518     * the hierarchy.
519     *
520     * @param page
521     *         ultimately containing this component
522     * @param container
523     *         component immediately containing this component (may be null for a root component)
524     * @param id
525     *         unique (within the container) id for this component (may be null for a root
526     *         component)
527     * @param elementName
528     *         the name of the element which represents this component in the template, or null
529     *         for
530     *         &lt;comp&gt; element or a page component
531     * @param instantiator
532     *         used to create the new component instance and access the component's model
533     * @param location
534     *         location of the element (within a template), used as part of exception reporting
535     * @param elementResources
536     */
537    ComponentPageElementImpl(Page page, ComponentPageElement container, String id, String nestedId, String completeId,
538                             String elementName, Instantiator instantiator, Location location,
539                             ComponentPageElementResources elementResources)
540    {
541        super(location);
542
543        this.page = page;
544        this.container = container;
545        this.id = id;
546        this.nestedId = nestedId;
547        this.completeId = completeId;
548        this.elementName = elementName;
549        this.elementResources = elementResources;
550
551        this.exactParameterCountMatch = page.isExactParameterCountMatch();
552
553        ComponentResources containerResources = container == null ? null : container.getComponentResources();
554
555        coreResources = new InternalComponentResourcesImpl(this.page, this, containerResources, this.elementResources,
556                completeId, nestedId, instantiator, false);
557
558        coreComponent = coreResources.getComponent();
559
560        eventLogger = elementResources.getEventLogger(coreResources.getLogger());
561
562        renderingValue = elementResources.createPerThreadValue();
563
564        page.addPageLoadedCallback(new Runnable()
565        {
566            public void run()
567            {
568                pageLoaded();
569            }
570        });
571    }
572
573    /**
574     * Constructor for the root component of a page.
575     */
576    public ComponentPageElementImpl(Page page, Instantiator instantiator,
577                                    ComponentPageElementResources elementResources)
578    {
579        this(page, null, null, null, page.getName(), null, instantiator, null, elementResources);
580    }
581
582    private void initializeRenderPhases()
583    {
584        setupRenderPhase = new SetupRenderPhase();
585        beginRenderPhase = new BeginRenderPhase();
586        beforeRenderTemplatePhase = new BeforeRenderTemplatePhase();
587        beforeRenderBodyPhase = new BeforeRenderBodyPhase();
588        afterRenderBodyPhase = new AfterRenderBodyPhase();
589        afterRenderTemplatePhase = new AfterRenderTemplatePhase();
590        afterRenderPhase = new AfterRenderPhase();
591        cleanupRenderPhase = new CleanupRenderPhase();
592
593        // Now the optimization, where we remove, replace and collapse unused phases. We use
594        // the component models to determine which phases have handler methods for the
595        // render phases.
596
597        Set<Class> handled = coreResources.getComponentModel().getHandledRenderPhases();
598
599        for (ComponentResources r : NamedSet.getValues(mixinIdToComponentResources))
600        {
601            handled.addAll(r.getComponentModel().getHandledRenderPhases());
602        }
603
604        if (!handled.contains(CleanupRender.class))
605            cleanupRenderPhase = null;
606
607        // Now, work back to front.
608
609        if (!handled.contains(AfterRender.class))
610            afterRenderPhase = cleanupRenderPhase;
611
612        if (!handled.contains(AfterRenderTemplate.class))
613            afterRenderTemplatePhase = null;
614
615        if (!handled.contains(AfterRenderBody.class))
616            afterRenderBodyPhase = null;
617
618        if (!handled.contains(BeforeRenderTemplate.class))
619            beforeRenderTemplatePhase = new RenderTemplatePhase();
620
621        if (!handled.contains(BeginRender.class))
622        {
623            RenderCommand replacement = afterRenderPhase != null ? new OptimizedBeginRenderPhase()
624                    : beforeRenderTemplatePhase;
625
626            beginRenderPhase = replacement;
627        }
628
629        if (!handled.contains(SetupRender.class))
630            setupRenderPhase = beginRenderPhase;
631    }
632
633    public ComponentPageElement newChild(String id, String nestedId, String completeId, String elementName,
634                                         Instantiator instantiator, Location location)
635    {
636        ComponentPageElementImpl child = new ComponentPageElementImpl(page, this, id, nestedId, completeId,
637                elementName, instantiator, location, elementResources);
638
639        addEmbeddedElement(child);
640
641        return child;
642    }
643
644    void push(RenderQueue queue, boolean forward, RenderCommand forwardPhase, RenderCommand backwardPhase)
645    {
646        push(queue, forward ? forwardPhase : backwardPhase);
647    }
648
649    void push(RenderQueue queue, RenderCommand nextPhase)
650    {
651        if (nextPhase != null)
652            queue.push(nextPhase);
653    }
654
655    void addEmbeddedElement(ComponentPageElement child)
656    {
657        if (children == null)
658            children = CollectionFactory.newList();
659
660        String childId = child.getId();
661
662        for (ComponentPageElement existing : children)
663        {
664            if (existing.getId().equalsIgnoreCase(childId))
665                throw new TapestryException(StructureMessages.duplicateChildComponent(this, childId), child,
666                        new TapestryException(StructureMessages.originalChildComponent(this, childId,
667                                existing.getLocation()), existing, null));
668        }
669
670        children.add(child);
671    }
672
673    public void addMixin(String mixinId, Instantiator instantiator, String... order)
674    {
675        if (mixinIdToComponentResources == null)
676        {
677            mixinIdToComponentResources = NamedSet.create();
678            components = CollectionFactory.newList();
679        }
680
681        String mixinExtension = "$" + mixinId.toLowerCase();
682
683        InternalComponentResourcesImpl resources = new InternalComponentResourcesImpl(page, this, coreResources,
684                elementResources, completeId + mixinExtension, nestedId + mixinExtension, instantiator, true);
685
686        mixinIdToComponentResources.put(mixinId, resources);
687        // note that since we're using explicit ordering now,
688        // we don't add anything to components until we page load; instead, we add
689        // to the orderers.
690        if (order == null)
691            order = InternalConstants.EMPTY_STRING_ARRAY;
692
693        if (resources.getComponentModel().isMixinAfter())
694        {
695            if (mixinAfterOrderer == null)
696                mixinAfterOrderer = new Orderer<Component>(getLogger());
697            mixinAfterOrderer.add(mixinId, resources.getComponent(), order);
698        } else
699        {
700            if (mixinBeforeOrderer == null)
701                mixinBeforeOrderer = new Orderer<Component>(getLogger());
702            mixinBeforeOrderer.add(mixinId, resources.getComponent(), order);
703        }
704    }
705
706    public void bindMixinParameter(String mixinId, String parameterName, Binding binding)
707    {
708        InternalComponentResources mixinResources = NamedSet.get(mixinIdToComponentResources, mixinId);
709
710        mixinResources.bindParameter(parameterName, binding);
711    }
712
713    public Binding getBinding(String parameterName)
714    {
715        return coreResources.getBinding(parameterName);
716    }
717
718    public void bindParameter(String parameterName, Binding binding)
719    {
720        coreResources.bindParameter(parameterName, binding);
721    }
722
723    public void addToBody(RenderCommand element)
724    {
725        if (bodyBlock == null)
726            bodyBlock = new BlockImpl(getLocation(), "Body of " + getCompleteId());
727
728        bodyBlock.addToBody(element);
729    }
730
731    public void addToTemplate(RenderCommand element)
732    {
733        template.add(element);
734    }
735
736    private void addUnboundParameterNames(String prefix, List<String> unbound, InternalComponentResources resource)
737    {
738        ComponentModel model = resource.getComponentModel();
739
740        for (String name : model.getParameterNames())
741        {
742            if (resource.isBound(name))
743                continue;
744
745            ParameterModel parameterModel = model.getParameterModel(name);
746
747            if (parameterModel.isRequired())
748            {
749                String fullName = prefix == null ? name : prefix + "." + name;
750
751                unbound.add(fullName);
752            }
753        }
754    }
755
756    private void pageLoaded()
757    {
758        // If this component has mixins, order them according to:
759        // mixins.
760
761        if (components != null)
762        {
763            List<Component> ordered = CollectionFactory.newList();
764
765            if (mixinBeforeOrderer != null)
766                ordered.addAll(mixinBeforeOrderer.getOrdered());
767
768            ordered.add(coreComponent);
769
770            // Add the remaining, late executing mixins
771            if (mixinAfterOrderer != null)
772                ordered.addAll(mixinAfterOrderer.getOrdered());
773
774            components = ordered;
775            // no need to keep the orderers around.
776            mixinBeforeOrderer = null;
777            mixinAfterOrderer = null;
778        }
779
780        initializeRenderPhases();
781
782        page.addVerifyCallback(new Runnable()
783        {
784            public void run()
785            {
786                // For some parameters, bindings (from defaults) are provided inside the callback method, so
787                // that is invoked first, before we check for unbound parameters.
788
789                verifyRequiredParametersAreBound();
790            }
791        });
792
793
794        loaded = true;
795    }
796
797    public void enqueueBeforeRenderBody(RenderQueue queue)
798    {
799        if (bodyBlock != null)
800            push(queue, beforeRenderBodyPhase);
801    }
802
803    public String getCompleteId()
804    {
805        return completeId;
806    }
807
808    public Component getComponent()
809    {
810        return coreComponent;
811    }
812
813    public InternalComponentResources getComponentResources()
814    {
815        return coreResources;
816    }
817
818    public ComponentPageElement getContainerElement()
819    {
820        return container;
821    }
822
823    public Page getContainingPage()
824    {
825        return page;
826    }
827
828    public ComponentPageElement getEmbeddedElement(String embeddedId)
829    {
830        ComponentPageElement embeddedElement = null;
831
832        if (children != null)
833        {
834            for (ComponentPageElement child : children)
835            {
836                if (child.getId().equalsIgnoreCase(embeddedId))
837                {
838                    embeddedElement = child;
839                    break;
840                }
841            }
842        }
843
844        if (embeddedElement == null)
845        {
846            Set<String> ids = CollectionFactory.newSet();
847
848            if (children != null)
849            {
850                for (ComponentPageElement child : children)
851                {
852                    ids.add(child.getId());
853                }
854            }
855
856            throw new UnknownValueException(String.format("Component %s does not contain embedded component '%s'.",
857                    getCompleteId(), embeddedId), new AvailableValues("Embedded components", ids));
858        }
859
860        return embeddedElement;
861    }
862
863    public String getId()
864    {
865        return id;
866    }
867
868    public Logger getLogger()
869    {
870        return coreResources.getLogger();
871    }
872
873    public Component getMixinByClassName(String mixinClassName)
874    {
875        Component result = mixinForClassName(mixinClassName);
876
877        if (result == null)
878            throw new TapestryException(StructureMessages.unknownMixin(completeId, mixinClassName), getLocation(), null);
879
880        return result;
881    }
882
883    private Component mixinForClassName(String mixinClassName)
884    {
885        if (mixinIdToComponentResources == null)
886            return null;
887
888        for (InternalComponentResources resources : NamedSet.getValues(mixinIdToComponentResources))
889        {
890            if (resources.getComponentModel().getComponentClassName().equals(mixinClassName))
891            {
892                return resources
893                        .getComponent();
894            }
895        }
896
897        return null;
898    }
899
900    public ComponentResources getMixinResources(String mixinId)
901    {
902        ComponentResources result = NamedSet.get(mixinIdToComponentResources, mixinId);
903
904        if (result == null)
905            throw new IllegalArgumentException(String.format("Unable to locate mixin '%s' for component '%s'.",
906                    mixinId, completeId));
907
908        return result;
909    }
910
911    public String getNestedId()
912    {
913        return nestedId;
914    }
915
916    public boolean dispatchEvent(ComponentEvent event)
917    {
918        if (components == null)
919            return coreComponent.dispatchComponentEvent(event);
920
921        // Otherwise, iterate over mixins + core component
922
923        boolean result = false;
924
925        for (Component component : components)
926        {
927            result |= component.dispatchComponentEvent(event);
928
929            if (event.isAborted())
930                break;
931        }
932
933        return result;
934    }
935
936    /**
937     * Invokes a callback on the component instances (the core component plus any mixins).
938     *
939     * @param reverse
940     *         if true, the callbacks are in the reverse of the normal order (this is associated
941     *         with AfterXXX
942     *         phases)
943     * @param callback
944     *         the object to receive each component instance
945     */
946    private void invoke(boolean reverse, ComponentCallback callback)
947    {
948        try
949        { // Optimization: In the most general case (just the one component, no mixins)
950            // invoke the callback on the component and be done ... no iterators, no nothing.
951
952            if (components == null)
953            {
954                callback.run(coreComponent);
955                return;
956            }
957
958            Iterator<Component> i = reverse ? InternalUtils.reverseIterator(components) : components.iterator();
959
960            while (i.hasNext())
961            {
962                callback.run(i.next());
963
964                if (callback.isEventAborted())
965                    return;
966            }
967        } catch (RuntimeException ex)
968        {
969            throw new TapestryException(ex.getMessage(), getLocation(), ex);
970        }
971    }
972
973    public boolean isLoaded()
974    {
975        return loaded;
976    }
977
978    public boolean isRendering()
979    {
980        return renderingValue.get(false);
981    }
982
983    /**
984     * Generate a toString() for the inner classes that represent render phases.
985     */
986    private String phaseToString(String phaseName)
987    {
988        return String.format("%s[%s]", phaseName, completeId);
989    }
990
991    /**
992     * Pushes the SetupRender phase state onto the queue.
993     */
994    public final void render(MarkupWriter writer, RenderQueue queue)
995    {
996        // TODO: An error if the render flag is already set (recursive rendering not
997        // allowed or advisable).
998
999        // TODO: Check for recursive rendering.
1000
1001        renderingValue.set(true);
1002
1003        queue.startComponent(coreResources);
1004
1005        queue.push(new PostRenderCleanupPhase(writer.getElement()));
1006
1007        push(queue, setupRenderPhase);
1008    }
1009
1010    @Override
1011    public String toString()
1012    {
1013        return String.format("ComponentPageElement[%s]", completeId);
1014    }
1015
1016    public boolean triggerEvent(String eventType, Object[] contextValues, ComponentEventCallback callback)
1017    {
1018        return triggerContextEvent(eventType, createParameterContext(contextValues == null ? new Object[0]
1019                : contextValues), callback);
1020    }
1021
1022    private EventContext createParameterContext(final Object... values)
1023    {
1024        return new AbstractEventContext()
1025        {
1026            public int getCount()
1027            {
1028                return values.length;
1029            }
1030
1031            public <T> T get(Class<T> desiredType, int index)
1032            {
1033                return elementResources.coerce(values[index], desiredType);
1034            }
1035        };
1036    }
1037
1038    public boolean triggerContextEvent(final String eventType, final EventContext context,
1039                                       final ComponentEventCallback callback)
1040    {
1041        assert InternalUtils.isNonBlank(eventType);
1042        assert context != null;
1043        String description = String.format("Triggering event '%s' on %s", eventType, completeId);
1044
1045        return elementResources.invoke(description, new Invokable<Boolean>()
1046        {
1047            public Boolean invoke()
1048            {
1049                return processEventTriggering(eventType, context, callback);
1050            }
1051        });
1052    }
1053
1054    @SuppressWarnings("all")
1055    private boolean processEventTriggering(String eventType, EventContext context, ComponentEventCallback callback)
1056    {
1057        boolean result = false;
1058
1059        ComponentPageElement component = this;
1060        String componentId = "";
1061
1062        // Provide a default handler for when the provided handler is null.
1063        final ComponentEventCallback providedHandler = callback == null ? new NotificationEventCallback(eventType,
1064                completeId) : callback;
1065
1066        ComponentEventCallback wrapped = new ComponentEventCallback()
1067        {
1068            public boolean handleResult(Object result)
1069            {
1070                // Boolean value is not passed to the handler; it will be true (abort event)
1071                // or false (continue looking for event handlers).
1072
1073                if (result instanceof Boolean)
1074                    return (Boolean) result;
1075
1076                return providedHandler.handleResult(result);
1077            }
1078        };
1079
1080        RuntimeException rootException = null;
1081
1082        // Because I don't like to reassign parameters.
1083
1084        String currentEventType = eventType;
1085        EventContext currentContext = context;
1086
1087        // Track the location of the original component for the event, even as we work our way up
1088        // the hierarchy. This may not be ideal if we trigger an "exception" event ... or maybe
1089        // it's right (it's the location of the originally thrown exception).
1090
1091        Location location = component.getComponentResources().getLocation();
1092
1093        while (component != null)
1094        {
1095            try
1096            {
1097                Logger logger = component.getEventLogger();
1098
1099                ComponentEvent event = new ComponentEventImpl(currentEventType, componentId, currentContext, wrapped,
1100                        elementResources, exactParameterCountMatch, coreResources.getComponentModel(), logger);
1101
1102                logger.debug(TapestryMarkers.EVENT_DISPATCH, "Dispatch event: {}", event);
1103
1104                result |= component.dispatchEvent(event);
1105
1106                if (event.isAborted())
1107                    return result;
1108            }
1109
1110            // As with render phase methods, dispatchEvent() can now simply throw arbitrary exceptions
1111            // (the distinction between RuntimeException and checked Exception is entirely in the compiler,
1112            // not the JVM).
1113            catch (Exception ex)
1114            {
1115                // An exception in an event handler method
1116                // while we're trying to handle a previous exception!
1117
1118                if (rootException != null)
1119                    throw rootException;
1120
1121                // We know component is not null and therefore has a component resources that
1122                // should have a location.
1123
1124                // Wrap it up to help ensure that a location is available to the event handler
1125                // method or,
1126                // more likely, to the exception report page.
1127
1128                rootException = new ComponentEventException(ex.getMessage(), eventType, context, location, ex);
1129
1130                // Switch over to triggering an "exception" event, starting in the component that
1131                // threw the exception.
1132
1133                currentEventType = "exception";
1134                currentContext = createParameterContext(rootException);
1135
1136                continue;
1137            }
1138
1139            // On each bubble up, make the event appear to come from the previous component
1140            // in which the event was triggered.
1141
1142            componentId = component.getId();
1143
1144            component = component.getContainerElement();
1145        }
1146
1147        // If there was a handler for the exception event, it is required to return a non-null (and
1148        // non-boolean) value
1149        // to tell Tapestry what to do. Since that didn't happen, we have no choice but to rethrow
1150        // the (wrapped)
1151        // exception.
1152
1153        if (rootException != null)
1154            throw rootException;
1155
1156        return result;
1157    }
1158
1159    private void verifyRequiredParametersAreBound()
1160    {
1161        List<String> unbound = CollectionFactory.newList();
1162
1163        addUnboundParameterNames(null, unbound, coreResources);
1164
1165        List<String> sortedNames = CollectionFactory.newList(NamedSet.getNames(mixinIdToComponentResources));
1166
1167        Collections.sort(sortedNames);
1168
1169        for (String name : sortedNames)
1170        {
1171            addUnboundParameterNames(name, unbound, mixinIdToComponentResources.get(name));
1172        }
1173
1174        if (!unbound.isEmpty())
1175        {
1176            throw new TapestryException(StructureMessages.missingParameters(unbound, this), this, null);
1177        }
1178    }
1179
1180    public Locale getLocale()
1181    {
1182        return page.getSelector().locale;
1183    }
1184
1185    public String getElementName(String defaultElementName)
1186    {
1187        return elementName != null ? elementName : defaultElementName;
1188    }
1189
1190    public Block getBlock(String id)
1191    {
1192        Block result = findBlock(id);
1193
1194        if (result == null)
1195            throw new BlockNotFoundException(StructureMessages.blockNotFound(completeId, id), getLocation());
1196
1197        return result;
1198    }
1199
1200    public Block findBlock(String id)
1201    {
1202        assert InternalUtils.isNonBlank(id);
1203
1204        return NamedSet.get(blocks, id);
1205    }
1206
1207    public void addBlock(String blockId, Block block)
1208    {
1209        if (blocks == null)
1210            blocks = NamedSet.create();
1211
1212        if (!blocks.putIfNew(blockId, block))
1213            throw new TapestryException(StructureMessages.duplicateBlock(this, blockId), block, null);
1214    }
1215
1216    public String getPageName()
1217    {
1218        return page.getName();
1219    }
1220
1221    public boolean hasBody()
1222    {
1223        return bodyBlock != null;
1224    }
1225
1226    public Block getBody()
1227    {
1228        return bodyBlock == null ? PLACEHOLDER_BLOCK : bodyBlock;
1229    }
1230
1231    public Map<String, Binding> getInformalParameterBindings()
1232    {
1233        return coreResources.getInformalParameterBindings();
1234    }
1235
1236    public Logger getEventLogger()
1237    {
1238        return eventLogger;
1239    }
1240
1241    public Link createEventLink(String eventType, Object... context)
1242    {
1243        return elementResources.createComponentEventLink(coreResources, eventType, false, context);
1244    }
1245
1246    public Link createActionLink(String eventType, boolean forForm, Object... context)
1247    {
1248        return elementResources.createComponentEventLink(coreResources, eventType, forForm, context);
1249    }
1250
1251    public Link createFormEventLink(String eventType, Object... context)
1252    {
1253        return elementResources.createComponentEventLink(coreResources, eventType, true, context);
1254    }
1255
1256    public Link createPageLink(String pageName, boolean override, Object... context)
1257    {
1258        return elementResources.createPageRenderLink(pageName, override, context);
1259    }
1260
1261    public Link createPageLink(Class pageClass, boolean override, Object... context)
1262    {
1263        return elementResources.createPageRenderLink(pageClass, override, context);
1264    }
1265
1266    protected RenderPhaseEvent createRenderEvent(RenderQueue queue)
1267    {
1268        return new RenderPhaseEvent(new RenderPhaseEventHandler(queue), eventLogger, elementResources);
1269    }
1270
1271    boolean isRenderTracingEnabled()
1272    {
1273        return elementResources.isRenderTracingEnabled();
1274    }
1275
1276    public ComponentResourceSelector getResourceSelector()
1277    {
1278        return page.getSelector();
1279    }
1280}