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