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}