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