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