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