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