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