001// Licensed under the Apache License, Version 2.0 (the "License");
002// you may not use this file except in compliance with the License.
003// You may obtain a copy of the License at
004//
005// http://www.apache.org/licenses/LICENSE-2.0
006//
007// Unless required by applicable law or agreed to in writing, software
008// distributed under the License is distributed on an "AS IS" BASIS,
009// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
010// See the License for the specific language governing permissions and
011// limitations under the License.
012
013package org.apache.tapestry5.internal.pageload;
014
015import org.apache.tapestry5.Binding;
016import org.apache.tapestry5.BindingConstants;
017import org.apache.tapestry5.ComponentResources;
018import org.apache.tapestry5.MarkupWriter;
019import org.apache.tapestry5.internal.InternalComponentResources;
020import org.apache.tapestry5.internal.InternalConstants;
021import org.apache.tapestry5.internal.bindings.LiteralBinding;
022import org.apache.tapestry5.internal.parser.*;
023import org.apache.tapestry5.internal.services.*;
024import org.apache.tapestry5.internal.structure.*;
025import org.apache.tapestry5.ioc.Invokable;
026import org.apache.tapestry5.ioc.Location;
027import org.apache.tapestry5.ioc.OperationTracker;
028import org.apache.tapestry5.ioc.annotations.PostInjection;
029import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
030import org.apache.tapestry5.ioc.internal.util.InternalUtils;
031import org.apache.tapestry5.ioc.internal.util.TapestryException;
032import org.apache.tapestry5.ioc.services.PerthreadManager;
033import org.apache.tapestry5.ioc.util.AvailableValues;
034import org.apache.tapestry5.ioc.util.Stack;
035import org.apache.tapestry5.ioc.util.UnknownValueException;
036import org.apache.tapestry5.model.ComponentModel;
037import org.apache.tapestry5.model.EmbeddedComponentModel;
038import org.apache.tapestry5.runtime.RenderCommand;
039import org.apache.tapestry5.runtime.RenderQueue;
040import org.apache.tapestry5.services.*;
041import org.apache.tapestry5.services.pageload.ComponentResourceSelector;
042import org.slf4j.Logger;
043
044import java.util.Collections;
045import java.util.List;
046import java.util.Map;
047
048/**
049 * There's still a lot of room to beef up {@link org.apache.tapestry5.internal.pageload.ComponentAssembler} and
050 * {@link org.apache.tapestry5.internal.pageload.EmbeddedComponentAssembler} to perform more static analysis, but
051 * that may no longer be necessary, given the switch to shared (non-pooled) pages in 5.2.
052 *
053 * Loading a page involves a recursive process of creating
054 * {@link org.apache.tapestry5.internal.pageload.ComponentAssembler}s: for the root component, then down the tree for
055 * each embedded component. A ComponentAssembler is largely a collection of
056 * {@link org.apache.tapestry5.internal.pageload.PageAssemblyAction}s. Once created, a ComponentAssembler can quickly
057 * assemble any number of component instances. All of the expensive logic, such as fitting template tokens together and
058 * matching parameters to bindings, is done as part of the one-time construction of the ComponentAssembler. The end
059 * result removes a huge amount of computational redundancy that was present in Tapestry 5.0, but to understand this,
060 * you need to split your mind into two phases: construction (of the ComponentAssemblers) and assembly.
061 *
062 * And truly, <em>This is the Tapestry Heart, This is the Tapestry Soul...</em>
063 */
064public class PageLoaderImpl implements PageLoader, ComponentAssemblerSource
065{
066    private static final class Key
067    {
068        private final String className;
069
070        private final ComponentResourceSelector selector;
071
072        private Key(String className, ComponentResourceSelector selector)
073        {
074            this.className = className;
075            this.selector = selector;
076        }
077
078        @Override
079        public boolean equals(Object o)
080        {
081            if (this == o)
082                return true;
083            if (o == null || getClass() != o.getClass())
084                return false;
085
086            Key key = (Key) o;
087
088            return className.equals(key.className) && selector.equals(key.selector);
089        }
090
091        @Override
092        public int hashCode()
093        {
094            return 31 * className.hashCode() + selector.hashCode();
095        }
096    }
097
098    private static final PageAssemblyAction POP_EMBEDDED_COMPONENT_ACTION = new PageAssemblyAction()
099    {
100        public void execute(PageAssembly pageAssembly)
101        {
102            pageAssembly.createdElement.pop();
103            pageAssembly.bodyElement.pop();
104            pageAssembly.embeddedAssembler.pop();
105        }
106    };
107
108    private static final RenderCommand END_ELEMENT = new RenderCommand()
109    {
110        public void render(MarkupWriter writer, RenderQueue queue)
111        {
112            writer.end();
113        }
114
115        @Override
116        public String toString()
117        {
118            return "End";
119        }
120    };
121
122    private final Map<Key, ComponentAssembler> cache = CollectionFactory.newConcurrentMap();
123
124    private final ComponentInstantiatorSource instantiatorSource;
125
126    private final ComponentTemplateSource templateSource;
127
128    private final PageElementFactory elementFactory;
129
130    private final ComponentPageElementResourcesSource resourcesSource;
131
132    private final ComponentClassResolver componentClassResolver;
133
134    private final PersistentFieldManager persistentFieldManager;
135
136    private final StringInterner interner;
137
138    private final OperationTracker tracker;
139
140    private final PerthreadManager perThreadManager;
141
142    private final Logger logger;
143
144    private final MetaDataLocator metaDataLocator;
145
146    private final RequestGlobals requestGlobals;
147
148    public PageLoaderImpl(ComponentInstantiatorSource instantiatorSource, ComponentTemplateSource templateSource,
149                          PageElementFactory elementFactory, ComponentPageElementResourcesSource resourcesSource,
150                          ComponentClassResolver componentClassResolver, PersistentFieldManager persistentFieldManager,
151                          StringInterner interner, OperationTracker tracker, PerthreadManager perThreadManager,
152                          Logger logger, MetaDataLocator metaDataLocator, RequestGlobals requestGlobals)
153    {
154        this.instantiatorSource = instantiatorSource;
155        this.templateSource = templateSource;
156        this.elementFactory = elementFactory;
157        this.resourcesSource = resourcesSource;
158        this.componentClassResolver = componentClassResolver;
159        this.persistentFieldManager = persistentFieldManager;
160        this.interner = interner;
161        this.tracker = tracker;
162        this.perThreadManager = perThreadManager;
163        this.logger = logger;
164        this.metaDataLocator = metaDataLocator;
165        this.requestGlobals = requestGlobals;
166    }
167
168    @PostInjection
169    public void setupInvalidation(@ComponentClasses InvalidationEventHub classesHub,
170                                  @ComponentTemplates InvalidationEventHub templatesHub,
171                                  @ComponentMessages InvalidationEventHub messagesHub)
172    {
173        classesHub.clearOnInvalidation(cache);
174        templatesHub.clearOnInvalidation(cache);
175        messagesHub.clearOnInvalidation(cache);
176    }
177
178    public void clearCache()
179    {
180        cache.clear();
181    }
182
183    public Page loadPage(final String logicalPageName, final ComponentResourceSelector selector)
184    {
185        final String pageClassName = componentClassResolver.resolvePageNameToClassName(logicalPageName);
186
187        final long startTime = System.nanoTime();
188
189        return tracker.invoke("Constructing instance of page class " + pageClassName, new Invokable<Page>()
190        {
191            public Page invoke()
192            {
193                Page page = new PageImpl(logicalPageName, selector, persistentFieldManager, perThreadManager, metaDataLocator);
194
195                ComponentAssembler assembler = getAssembler(pageClassName, selector);
196
197                ComponentPageElement rootElement = assembler.assembleRootComponent(page);
198
199                page.setRootElement(rootElement);
200
201                // The page is *loaded* before it is attached to the request.
202                // This is to help ensure that no client-specific information leaks
203                // into the page's default state.
204
205                page.loaded();
206
207                long elapsedTime = System.nanoTime() - startTime;
208
209                double elapsedMS = elapsedTime * 10E-7d;
210
211                if (logger.isInfoEnabled())
212                {
213                    logger.info(String.format("Loaded page '%s' (%s) in %.3f ms",
214                            logicalPageName, selector.toShortString(), elapsedMS));
215                }
216
217                // The rough stats are set by the assembler, and don't include the page load time;
218                // so we update them to match.
219
220                Page.Stats roughStats = page.getStats();
221
222                page.setStats(new Page.Stats(elapsedMS, roughStats.componentCount, roughStats.weight));
223
224                return page;
225            }
226        });
227    }
228
229    public ComponentAssembler getAssembler(String className, ComponentResourceSelector selector)
230    {
231        Key key = new Key(className, selector);
232
233        ComponentAssembler result = cache.get(key);
234
235        if (result == null)
236        {
237            // There's a window here where two threads may create the same assembler simultaneously;
238            // the extra assembler will be discarded.
239
240            result = createAssembler(className, selector);
241
242            cache.put(key, result);
243        }
244
245        return result;
246    }
247
248    private ComponentAssembler createAssembler(final String className, final ComponentResourceSelector selector)
249    {
250        return tracker.invoke("Creating ComponentAssembler for " + className, new Invokable<ComponentAssembler>()
251        {
252            public ComponentAssembler invoke()
253            {
254                Instantiator instantiator = instantiatorSource.getInstantiator(className);
255
256                ComponentModel componentModel = instantiator.getModel();
257
258                ComponentTemplate template = templateSource.getTemplate(componentModel, selector);
259
260                ComponentPageElementResources resources = resourcesSource.get(selector);
261
262                ComponentAssembler assembler = new ComponentAssemblerImpl(PageLoaderImpl.this, instantiatorSource,
263                        componentClassResolver, instantiator, resources, tracker, template.usesStrictMixinParameters());
264
265                // "Program" the assembler by adding actions to it. The actions interact with a
266                // PageAssembly object (a fresh one for each new page being created).
267
268                programAssembler(assembler, template);
269
270                return assembler;
271            }
272        });
273    }
274
275    /**
276     * "Programs" the assembler by analyzing the component, its mixins and its embedded components (both in the template
277     * and in the Java class), adding new PageAssemblyActions.
278     */
279    private void programAssembler(ComponentAssembler assembler, ComponentTemplate template)
280    {
281        TokenStream stream = createTokenStream(assembler, template);
282
283        AssemblerContext context = new AssemblerContext(assembler, stream, template.usesStrictMixinParameters());
284
285        if (template.isMissing())
286        {
287            // Pretend the template has a single <t:body> element.
288
289            body(context);
290
291            return;
292        }
293
294        while (context.more())
295        {
296            processTemplateToken(context);
297        }
298
299        context.flushComposable();
300    }
301
302    /**
303     * Creates the TokenStream by pre-processing the templates, looking for
304     * {@link org.apache.tapestry5.internal.parser.ExtensionPointToken}s
305     * and replacing them with appropriate overrides. Also validates that all embedded ids are accounted for.
306     */
307    private TokenStream createTokenStream(ComponentAssembler assembler, ComponentTemplate template)
308    {
309        List<TemplateToken> tokens = CollectionFactory.newList();
310
311        Stack<TemplateToken> queue = CollectionFactory.newStack();
312
313        List<ComponentTemplate> overrideSearch = buildOverrideSearch(assembler, template);
314
315        // The base template is the first non-extension template upwards in the hierarchy
316        // from this component.
317
318        ComponentTemplate baseTemplate = getLast(overrideSearch);
319
320        pushAll(queue, baseTemplate.getTokens());
321
322        while (!queue.isEmpty())
323        {
324            TemplateToken token = queue.pop();
325
326            // When an ExtensionPoint is found, it is replaced with the tokens of its override.
327
328            if (token.getTokenType().equals(TokenType.EXTENSION_POINT))
329            {
330                ExtensionPointToken extensionPointToken = (ExtensionPointToken) token;
331
332                queueOverrideTokensForExtensionPoint(extensionPointToken, queue, overrideSearch);
333
334            } else
335            {
336                tokens.add(token);
337            }
338        }
339
340        // Build up a map of component ids to locations
341
342        Collections.reverse(overrideSearch);
343
344        Map<String, Location> componentIds = CollectionFactory.newCaseInsensitiveMap();
345
346        for (ComponentTemplate ct : overrideSearch)
347        {
348            componentIds.putAll(ct.getComponentIds());
349        }
350
351        // Validate that every emebedded component id in the template (or inherited from an extended template)
352        // is accounted for.
353
354        assembler.validateEmbeddedIds(componentIds, template.getResource());
355
356        return new TokenStreamImpl(tokens);
357    }
358
359    private static <T> T getLast(List<T> list)
360    {
361        int count = list.size();
362
363        return list.get(count - 1);
364    }
365
366    private void queueOverrideTokensForExtensionPoint(ExtensionPointToken extensionPointToken,
367                                                      Stack<TemplateToken> queue, List<ComponentTemplate> overrideSearch)
368    {
369        String extensionPointId = extensionPointToken.getExtensionPointId();
370
371        // Work up from the component, through its base classes, towards the last non-extension template.
372
373        for (ComponentTemplate t : overrideSearch)
374        {
375            List<TemplateToken> tokens = t.getExtensionPointTokens(extensionPointId);
376
377            if (tokens != null)
378            {
379                pushAll(queue, tokens);
380                return;
381            }
382        }
383
384        // Sanity check: since an extension point defines its own default, it's going to be hard to
385        // not find an override, somewhere, for it.
386
387        throw new TapestryException(String.format("Could not find an override for extension point '%s'.", extensionPointId),
388                extensionPointToken.getLocation(), null);
389    }
390
391    private List<ComponentTemplate> buildOverrideSearch(ComponentAssembler assembler, ComponentTemplate template)
392    {
393        List<ComponentTemplate> result = CollectionFactory.newList();
394        result.add(template);
395
396        ComponentModel model = assembler.getModel();
397
398        ComponentTemplate lastTemplate = template;
399
400        while (lastTemplate.isExtension())
401        {
402            ComponentModel parentModel = model.getParentModel();
403
404            if (parentModel == null)
405            {
406                throw new RuntimeException(String.format("Component %s uses an extension template, but does not have a parent component.", model.getComponentClassName()));
407            }
408
409            ComponentTemplate parentTemplate = templateSource.getTemplate(parentModel, assembler.getSelector());
410
411            result.add(parentTemplate);
412
413            lastTemplate = parentTemplate;
414
415            model = parentModel;
416        }
417
418        return result;
419    }
420
421    /**
422     * Push all the tokens onto the stack, in reverse order, so that the last token is deepest and the first token is
423     * most shallow (first to come off the queue).
424     */
425    private void pushAll(Stack<TemplateToken> queue, List<TemplateToken> tokens)
426    {
427        for (int i = tokens.size() - 1; i >= 0; i--)
428            queue.push(tokens.get(i));
429    }
430
431    private void processTemplateToken(AssemblerContext context)
432    {
433        // These tokens can appear at the top level, or at lower levels (this method is invoked
434        // from token-processing loops inside element(), component(), etc.
435
436        switch (context.peekType())
437        {
438            case TEXT:
439
440                text(context);
441                break;
442
443            case EXPANSION:
444                expansion(context);
445                break;
446
447            case BODY:
448                context.next();
449
450                body(context);
451                break;
452
453            case START_ELEMENT:
454                // Will consume past matching end token
455                element(context);
456                break;
457
458            case START_COMPONENT:
459                // Will consume past matching end token
460                component(context);
461                break;
462
463            // ATTRIBUTE and END_ELEMENT can't happen at the top level, they're
464            // handled at a lower level. (inside element(), component(), etc.)
465
466            case COMMENT:
467                comment(context);
468                break;
469
470            case BLOCK:
471                // Will consume past matching end token
472                block(context);
473                break;
474
475            case PARAMETER:
476                // Will consume past the matching end token
477                parameter(context);
478                break;
479
480            case DTD:
481                dtd(context);
482                break;
483
484            case DEFINE_NAMESPACE_PREFIX:
485
486                defineNamespacePrefix(context);
487                break;
488
489            case CDATA:
490                cdata(context);
491                break;
492
493            default:
494                throw new IllegalStateException(String.format("Not yet implemented: %s", context.peekType().toString()));
495        }
496    }
497
498    private void cdata(AssemblerContext context)
499    {
500        CDATAToken token = context.next(CDATAToken.class);
501
502        context.addComposable(token);
503
504    }
505
506    private void defineNamespacePrefix(AssemblerContext context)
507    {
508        DefineNamespacePrefixToken token = context.next(DefineNamespacePrefixToken.class);
509
510        context.addComposable(token);
511    }
512
513    private void dtd(AssemblerContext context)
514    {
515        final DTDToken token = context.next(DTDToken.class);
516
517        context.add(new PageAssemblyAction()
518        {
519            public void execute(PageAssembly pageAssembly)
520            {
521                if (!pageAssembly.checkAndSetFlag("dtd-page-element-added"))
522                {
523
524                    // It doesn't really matter where this ends up in the tree as long as its inside
525                    // a portion that always renders.
526
527                    pageAssembly.addRenderCommand(token);
528                }
529            }
530        });
531    }
532
533    private void parameter(AssemblerContext context)
534    {
535        final ParameterToken token = context.next(ParameterToken.class);
536
537        context.add(new PageAssemblyAction()
538        {
539            public void execute(PageAssembly pageAssembly)
540            {
541                String parameterName = token.name;
542
543                ComponentPageElement element = pageAssembly.createdElement.peek();
544
545                Location location = token.getLocation();
546
547                BlockImpl block = new BlockImpl(location, interner.format("Parameter %s of %s",
548                        parameterName, element.getCompleteId()));
549
550                Binding binding = new LiteralBinding(location, "block parameter " + parameterName, block);
551
552                EmbeddedComponentAssembler embeddedAssembler = pageAssembly.embeddedAssembler.peek();
553
554                ParameterBinder binder = embeddedAssembler.createParameterBinder(parameterName);
555
556                if (binder == null)
557                {
558                    throw new UnknownValueException(
559                            String.format("Component %s does not include a formal parameter '%s' (and does not support informal parameters).",
560                                    element.getCompleteId(), parameterName), location,
561                            null,
562                            new AvailableValues("Formal parameters", embeddedAssembler.getFormalParameterNames()));
563                }
564
565                binder.bind(pageAssembly.createdElement.peek(), binding);
566
567                pageAssembly.bodyElement.push(block);
568            }
569        });
570
571        consumeToEndElementAndPopBodyElement(context);
572    }
573
574    private void block(AssemblerContext context)
575    {
576        final BlockToken token = context.next(BlockToken.class);
577
578        context.add(new PageAssemblyAction()
579        {
580            public void execute(PageAssembly pageAssembly)
581            {
582                String blockId = token.getId();
583
584                ComponentPageElement element = pageAssembly.activeElement.peek();
585
586                String description = blockId == null ? interner.format("Anonymous within %s", element.getCompleteId())
587                        : interner.format("%s within %s", blockId, element.getCompleteId());
588
589                BlockImpl block = new BlockImpl(token.getLocation(), description);
590
591                if (blockId != null)
592                    element.addBlock(blockId, block);
593
594                // Start directing template content into the Block
595                pageAssembly.bodyElement.push(block);
596            }
597        });
598
599        consumeToEndElementAndPopBodyElement(context);
600    }
601
602    private void consumeToEndElementAndPopBodyElement(AssemblerContext context)
603    {
604        while (true)
605        {
606            switch (context.peekType())
607            {
608                case END_ELEMENT:
609
610                    context.next();
611
612                    context.add(new PageAssemblyAction()
613                    {
614                        public void execute(PageAssembly pageAssembly)
615                        {
616                            pageAssembly.bodyElement.pop();
617                        }
618                    });
619
620                    return;
621
622                default:
623                    processTemplateToken(context);
624            }
625        }
626    }
627
628    private void comment(AssemblerContext context)
629    {
630        CommentToken token = context.next(CommentToken.class);
631
632        context.addComposable(token);
633    }
634
635    private void component(AssemblerContext context)
636    {
637        EmbeddedComponentAssembler embeddedAssembler = startComponent(context);
638
639        while (true)
640        {
641            switch (context.peekType())
642            {
643                case ATTRIBUTE:
644
645                    bindAttributeAsParameter(context, embeddedAssembler);
646
647                    break;
648
649                case END_ELEMENT:
650
651                    context.next();
652
653                    context.add(POP_EMBEDDED_COMPONENT_ACTION);
654
655                    return;
656
657                default:
658                    processTemplateToken(context);
659            }
660        }
661    }
662
663    private void bindAttributeAsParameter(AssemblerContext context, EmbeddedComponentAssembler embeddedAssembler)
664    {
665        AttributeToken token = context.next(AttributeToken.class);
666
667        addParameterBindingAction(context, embeddedAssembler, token.name, token.value,
668                BindingConstants.LITERAL, token.getLocation(), true);
669    }
670
671    private void element(AssemblerContext context)
672    {
673        StartElementToken token = context.next(StartElementToken.class);
674
675        context.addComposable(token);
676
677        while (true)
678        {
679            switch (context.peekType())
680            {
681                case ATTRIBUTE:
682                    attribute(context);
683                    break;
684
685                case END_ELEMENT:
686
687                    context.next();
688
689                    context.addComposable(END_ELEMENT);
690
691                    // Pop out a level.
692                    return;
693
694                default:
695                    processTemplateToken(context);
696            }
697        }
698
699    }
700
701    private EmbeddedComponentAssembler startComponent(AssemblerContext context)
702    {
703        StartComponentToken token = context.next(StartComponentToken.class);
704
705        ComponentAssembler assembler = context.assembler;
706        String elementName = token.getElementName();
707
708        // Initial guess: the type from the token (but this may be null in many cases).
709        String embeddedType = token.getComponentType();
710
711        // This may be null for an anonymous component.
712        String embeddedId = token.getId();
713
714        String embeddedComponentClassName = null;
715
716        final EmbeddedComponentModel embeddedModel = embeddedId == null ? null : assembler.getModel()
717                .getEmbeddedComponentModel(embeddedId);
718
719        if (embeddedId == null)
720            embeddedId = assembler.generateEmbeddedId(embeddedType);
721
722        if (embeddedModel != null)
723        {
724            String modelType = embeddedModel.getComponentType();
725
726            if (InternalUtils.isNonBlank(modelType) && embeddedType != null)
727            {
728                throw new TapestryException(
729                        String.format("Embedded component '%s' provides a type attribute in the template ('%s') " +
730                                "as well as in the component class ('%s'). You should not provide a type attribute " +
731                                "in the template when defining an embedded component within the component class.", embeddedId, embeddedType, modelType), token, null);
732            }
733
734            embeddedType = modelType;
735            embeddedComponentClassName = embeddedModel.getComponentClassName();
736        }
737
738        String componentClassName = embeddedComponentClassName;
739
740        // This awkwardness is making me think that the page loader should resolve the component
741        // type before invoking this method (we would then remove the componentType parameter).
742
743        if (InternalUtils.isNonBlank(embeddedType))
744        {
745            // The type actually overrides the specified class name. The class name is defined
746            // by the type of the field. In many scenarios, the field type is a common
747            // interface,
748            // and the type is used to determine the concrete class to instantiate.
749
750            try
751            {
752                componentClassName = componentClassResolver.resolveComponentTypeToClassName(embeddedType);
753            } catch (RuntimeException ex)
754            {
755                throw new TapestryException(ex.getMessage(), token, ex);
756            }
757        }
758
759        // OK, now we can record an action to get it instantiated.
760
761        EmbeddedComponentAssembler embeddedAssembler = assembler.createEmbeddedAssembler(embeddedId,
762                componentClassName, embeddedModel, token.getMixins(), token.getLocation());
763
764        addActionForEmbeddedComponent(context, embeddedAssembler, embeddedId, elementName, componentClassName);
765
766        addParameterBindingActions(context, embeddedAssembler, embeddedModel);
767
768        if (embeddedModel != null && embeddedModel.getInheritInformalParameters())
769        {
770            // Another two-step: The first "captures" the container and embedded component. The second
771            // occurs at the end of the page setup.
772
773            assembler.add(new PageAssemblyAction()
774            {
775                public void execute(PageAssembly pageAssembly)
776                {
777                    final ComponentPageElement container = pageAssembly.activeElement.peek();
778                    final ComponentPageElement embedded = pageAssembly.createdElement.peek();
779
780                    pageAssembly.deferred.add(new PageAssemblyAction()
781                    {
782                        public void execute(PageAssembly pageAssembly)
783                        {
784                            copyInformalParameters(container, embedded);
785                        }
786                    });
787                }
788            });
789
790        }
791
792        return embeddedAssembler;
793
794    }
795
796    private void copyInformalParameters(ComponentPageElement container, ComponentPageElement embedded)
797    {
798        // TODO: Much more, this is an area where we can make things a bit more efficient by tracking
799        // what has and hasn't been bound in the EmbeddedComponentAssembler (and identifying what is
800        // and isn't informal).
801
802        ComponentModel model = embedded.getComponentResources().getComponentModel();
803
804        Map<String, Binding> informals = container.getInformalParameterBindings();
805
806        for (String name : informals.keySet())
807        {
808            if (model.getParameterModel(name) != null)
809                continue;
810
811            Binding binding = informals.get(name);
812
813            embedded.bindParameter(name, binding);
814        }
815    }
816
817    private void addParameterBindingActions(AssemblerContext context, EmbeddedComponentAssembler embeddedAssembler,
818                                            EmbeddedComponentModel embeddedModel)
819    {
820        if (embeddedModel == null)
821            return;
822
823        for (String parameterName : embeddedModel.getParameterNames())
824        {
825            String parameterValue = embeddedModel.getParameterValue(parameterName);
826
827            addParameterBindingAction(context, embeddedAssembler, parameterName, parameterValue, BindingConstants.PROP,
828                    embeddedModel.getLocation(), false);
829        }
830    }
831
832    private void addParameterBindingAction(AssemblerContext context,
833                                           final EmbeddedComponentAssembler embeddedAssembler, final String parameterName,
834                                           final String parameterValue, final String metaDefaultBindingPrefix, final Location location, final boolean ignoreUnmatchedFormal)
835    {
836        if (embeddedAssembler.isBound(parameterName))
837            return;
838
839        embeddedAssembler.setBound(parameterName);
840
841        if (parameterValue.startsWith(InternalConstants.INHERIT_BINDING_PREFIX))
842        {
843            String containerParameterName = parameterValue.substring(InternalConstants.INHERIT_BINDING_PREFIX.length());
844
845            addInheritedBindingAction(context, parameterName, containerParameterName);
846            return;
847        }
848
849        context.add(new PageAssemblyAction()
850        {
851            public void execute(PageAssembly pageAssembly)
852            {
853                // Because of published parameters, we have to wait until page assembly time to throw out
854                // informal parameters bound to components that don't support informal parameters ...
855                // otherwise we'd throw out (sometimes!) published parameters.
856
857                final ParameterBinder binder = embeddedAssembler.createParameterBinder(parameterName);
858
859                // Null meaning an informal parameter and the component (and mixins) doesn't support informals.
860
861                if (binder == null)
862                {
863                    if (ignoreUnmatchedFormal)
864                    {
865                        return;
866                    }
867
868                    throw new UnknownValueException(
869                            String.format("Component %s does not include a formal parameter '%s' (and does not support informal parameters).",
870                                    pageAssembly.createdElement.peek().getCompleteId(), parameterName), null,
871                            null,
872                            new AvailableValues("Formal parameters", embeddedAssembler.getFormalParameterNames()));
873                }
874
875                final String defaultBindingPrefix = binder.getDefaultBindingPrefix(metaDefaultBindingPrefix);
876
877                InternalComponentResources containerResources = pageAssembly.activeElement.peek()
878                        .getComponentResources();
879
880                ComponentPageElement embeddedElement = pageAssembly.createdElement.peek();
881                InternalComponentResources embeddedResources = embeddedElement.getComponentResources();
882
883                Binding binding = elementFactory.newBinding(parameterName, containerResources, embeddedResources,
884                        defaultBindingPrefix, parameterValue, location);
885
886                binder.bind(embeddedElement, binding);
887            }
888        }
889
890        );
891    }
892
893    /**
894     * Adds a deferred action to the PageAssembly, to handle connecting the embedded components' parameter to the
895     * container component's parameter once everything else has been built.
896     *
897     * @param context
898     * @param parameterName
899     * @param containerParameterName
900     */
901    private void addInheritedBindingAction(AssemblerContext context, final String parameterName,
902                                           final String containerParameterName)
903    {
904        context.add(new PageAssemblyAction()
905        {
906            public void execute(PageAssembly pageAssembly)
907            {
908                // At the time this action executes, we'll be able to capture the containing and embedded
909                // component. We can then defer the connection logic until after all other construction.
910
911                final ComponentPageElement container = pageAssembly.activeElement.peek();
912                final ComponentPageElement embedded = pageAssembly.createdElement.peek();
913
914                // Parameters are normally bound bottom to top. Inherited parameters run differently, and should be
915                // top to bottom.
916                pageAssembly.deferred.add(new PageAssemblyAction()
917                {
918                    public void execute(PageAssembly pageAssembly)
919                    {
920                        connectInheritedParameter(container, embedded, parameterName, containerParameterName);
921                    }
922                });
923            }
924        });
925    }
926
927    private void connectInheritedParameter(ComponentPageElement container, ComponentPageElement embedded,
928                                           String parameterName, String containerParameterName)
929    {
930        // TODO: This assumes that the two parameters are both on the core component and not on
931        // a mixin. I think this could be improved with more static analysis.
932
933        Binding containerBinding = container.getBinding(containerParameterName);
934
935        if (containerBinding == null)
936            return;
937
938        // This helps with debugging, and re-orients any thrown exceptions
939        // to the location of the inherited binding, rather than the container component's
940        // binding.
941
942        // Binding inherited = new InheritedBinding(description, containerBinding, embedded.getLocation());
943
944        embedded.bindParameter(parameterName, containerBinding);
945    }
946
947    private void addActionForEmbeddedComponent(AssemblerContext context,
948                                               final EmbeddedComponentAssembler embeddedAssembler, final String embeddedId, final String elementName,
949                                               final String componentClassName)
950    {
951        context.add(new PageAssemblyAction()
952        {
953            public void execute(PageAssembly pageAssembly)
954            {
955                pageAssembly.checkForRecursion(componentClassName, embeddedAssembler.getLocation());
956
957                ComponentResourceSelector selector = pageAssembly.page.getSelector();
958
959                ComponentAssembler assemblerForSubcomponent = getAssembler(componentClassName, selector);
960
961                // Remember: this pushes onto to the createdElement stack, but does not pop it.
962
963                assemblerForSubcomponent.assembleEmbeddedComponent(pageAssembly, embeddedAssembler, embeddedId,
964                        elementName, embeddedAssembler.getLocation());
965
966                // ... which is why we can find it via peek() here. And it's our responsibility
967                // to clean it up.
968
969                ComponentPageElement embeddedElement = pageAssembly.createdElement.peek();
970
971                // Add the new element to the template of its container.
972
973                pageAssembly.addRenderCommand(embeddedElement);
974
975                // And redirect any futher content from this component's template to go into
976                // the body of the embedded element.
977
978                pageAssembly.bodyElement.push(embeddedElement);
979                pageAssembly.embeddedAssembler.push(embeddedAssembler);
980
981                // The means we need to pop the createdElement, bodyElement and embeddedAssembler stacks
982                // when done with this sub-component, which is what POP_EMBEDDED_COMPONENT_ACTION does.
983            }
984        });
985    }
986
987    private void attribute(AssemblerContext context)
988    {
989        final AttributeToken token = context.next(AttributeToken.class);
990
991        String value = token.value;
992
993        // No expansion makes this easier, more efficient.
994        if (value.indexOf(InternalConstants.EXPANSION_START) < 0)
995        {
996
997            context.addComposable(token);
998
999            return;
1000        }
1001
1002        context.add(new PageAssemblyAction()
1003        {
1004            public void execute(PageAssembly pageAssembly)
1005            {
1006                // A little extra weight for token containing one or more expansions.
1007
1008                pageAssembly.weight++;
1009
1010                InternalComponentResources resources = pageAssembly.activeElement.peek().getComponentResources();
1011
1012                RenderCommand command = elementFactory.newAttributeElement(resources, token);
1013
1014                pageAssembly.addRenderCommand(command);
1015            }
1016        });
1017    }
1018
1019    private void body(AssemblerContext context)
1020    {
1021        context.add(new PageAssemblyAction()
1022        {
1023            public void execute(PageAssembly pageAssembly)
1024            {
1025                ComponentPageElement element = pageAssembly.activeElement.peek();
1026
1027                pageAssembly.addRenderCommand(new RenderBodyElement(element));
1028            }
1029        });
1030    }
1031
1032    private void expansion(AssemblerContext context)
1033    {
1034        final ExpansionToken token = context.next(ExpansionToken.class);
1035
1036        context.add(new PageAssemblyAction()
1037        {
1038            public void execute(PageAssembly pageAssembly)
1039            {
1040                ComponentResources resources = pageAssembly.activeElement.peek().getComponentResources();
1041
1042                RenderCommand command = elementFactory.newExpansionElement(resources, token);
1043
1044                pageAssembly.addRenderCommand(command);
1045            }
1046        });
1047    }
1048
1049    private void text(AssemblerContext context)
1050    {
1051        TextToken textToken = context.next(TextToken.class);
1052
1053        context.addComposable(textToken);
1054    }
1055
1056}