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