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