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