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