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 }