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
015package org.apache.tapestry5.internal.pageload;
016
017import org.apache.tapestry5.Binding;
018import org.apache.tapestry5.BindingConstants;
019import org.apache.tapestry5.ComponentResources;
020import org.apache.tapestry5.MarkupWriter;
021import org.apache.tapestry5.internal.InternalComponentResources;
022import org.apache.tapestry5.internal.InternalConstants;
023import org.apache.tapestry5.internal.bindings.LiteralBinding;
024import org.apache.tapestry5.internal.parser.*;
025import org.apache.tapestry5.internal.services.*;
026import org.apache.tapestry5.internal.structure.*;
027import org.apache.tapestry5.ioc.Invokable;
028import org.apache.tapestry5.ioc.Location;
029import org.apache.tapestry5.ioc.OperationTracker;
030import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
031import org.apache.tapestry5.ioc.internal.util.InternalUtils;
032import org.apache.tapestry5.ioc.internal.util.TapestryException;
033import org.apache.tapestry5.ioc.services.PerthreadManager;
034import org.apache.tapestry5.ioc.services.SymbolSource;
035import org.apache.tapestry5.ioc.util.AvailableValues;
036import org.apache.tapestry5.ioc.util.Stack;
037import org.apache.tapestry5.ioc.util.UnknownValueException;
038import org.apache.tapestry5.model.ComponentModel;
039import org.apache.tapestry5.model.EmbeddedComponentModel;
040import org.apache.tapestry5.runtime.RenderCommand;
041import org.apache.tapestry5.runtime.RenderQueue;
042import org.apache.tapestry5.services.ComponentClassResolver;
043import org.apache.tapestry5.services.InvalidationListener;
044import org.apache.tapestry5.services.Request;
045import org.apache.tapestry5.services.pageload.ComponentResourceSelector;
046
047import java.util.Collections;
048import java.util.List;
049import 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 */
067public 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}