001 // Copyright 2004, 2005 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.tapestry.pageload; 016 017 import org.apache.commons.logging.Log; 018 import org.apache.hivemind.ApplicationRuntimeException; 019 import org.apache.hivemind.ClassResolver; 020 import org.apache.hivemind.HiveMind; 021 import org.apache.hivemind.Location; 022 import org.apache.hivemind.service.ThreadLocale; 023 import org.apache.tapestry.*; 024 import org.apache.tapestry.asset.AssetSource; 025 import org.apache.tapestry.binding.BindingSource; 026 import org.apache.tapestry.engine.IPageLoader; 027 import org.apache.tapestry.resolver.ComponentSpecificationResolver; 028 import org.apache.tapestry.services.ComponentConstructor; 029 import org.apache.tapestry.services.ComponentConstructorFactory; 030 import org.apache.tapestry.services.ComponentPropertySource; 031 import org.apache.tapestry.services.ComponentTemplateLoader; 032 import org.apache.tapestry.spec.*; 033 034 import java.util.*; 035 036 /** 037 * Implementation of tapestry.page.PageLoader. Runs the process of building the 038 * component hierarchy for an entire page. 039 * <p> 040 * This implementation is not threadsafe, therefore the pooled service model 041 * must be used. 042 * </p> 043 * 044 * @author Howard Lewis Ship 045 */ 046 047 public class PageLoader implements IPageLoader { 048 049 private Log _log; 050 051 /** @since 4.0 */ 052 053 private ComponentSpecificationResolver _componentResolver; 054 055 /** @since 4.0 */ 056 057 private BindingSource _bindingSource; 058 059 /** @since 4.0 */ 060 061 private ComponentTemplateLoader _componentTemplateLoader; 062 063 private List _inheritedBindingQueue = new ArrayList(); 064 065 /** @since 4.0 */ 066 private IComponentVisitor _establishDefaultParameterValuesVisitor; 067 068 private ComponentTreeWalker _establishDefaultParameterValuesWalker; 069 070 private ComponentTreeWalker _verifyRequiredParametersWalker; 071 072 private IComponentVisitor _eventConnectionVisitor; 073 074 private ComponentTreeWalker _eventConnectionWalker; 075 076 private IComponentVisitor _componentTypeVisitor; 077 078 /** @since 4.0 */ 079 080 private ComponentConstructorFactory _componentConstructorFactory; 081 082 /** @since 4.0 */ 083 084 private AssetSource _assetSource; 085 086 /** 087 * Used to find the correct Java component class for a page. 088 * 089 * @since 4.0 090 */ 091 092 private ComponentClassProvider _pageClassProvider; 093 094 /** 095 * Used to find the correct Java component class for a component (a similar 096 * process to resolving a page, but with slightly differen steps and 097 * defaults). 098 * 099 * @since 4.0 100 */ 101 102 private ComponentClassProvider _componentClassProvider; 103 104 /** 105 * Used to resolve meta-data properties related to a component. 106 * 107 * @since 4.0 108 */ 109 110 private ComponentPropertySource _componentPropertySource; 111 112 /** 113 * Tracks the current locale into which pages are loaded. 114 * 115 * @since 4.0 116 */ 117 118 private ThreadLocale _threadLocale; 119 120 /** 121 * The locale of the application, which is also the locale of the page being 122 * loaded. 123 */ 124 125 private Locale _locale; 126 127 /** 128 * Number of components instantiated, excluding the page itself. 129 */ 130 131 private int _count; 132 133 /** 134 * The recursion depth. A page with no components is zero. A component on a 135 * page is one. 136 */ 137 138 private int _depth; 139 140 /** 141 * The maximum depth reached while building the page. 142 */ 143 144 private int _maxDepth; 145 146 /** @since 4.0 */ 147 148 private ClassResolver _classResolver; 149 150 /** 151 * As each component is constructed it is placed on to the component stack, when construction is finished it is pushed 152 * back off the stack. This helps in detecting component nesting and properly reporting errors. 153 */ 154 private Stack _componentStack = new Stack(); 155 156 public void initializeService() 157 { 158 159 // Create the mechanisms for walking the component tree when it is 160 // complete 161 IComponentVisitor verifyRequiredParametersVisitor = new VerifyRequiredParametersVisitor(); 162 163 _verifyRequiredParametersWalker = 164 new ComponentTreeWalker( new IComponentVisitor[] { verifyRequiredParametersVisitor }); 165 166 _establishDefaultParameterValuesWalker = 167 new ComponentTreeWalker( new IComponentVisitor[] { _establishDefaultParameterValuesVisitor }); 168 169 _eventConnectionWalker = 170 new ComponentTreeWalker( new IComponentVisitor[] { _eventConnectionVisitor, _componentTypeVisitor }); 171 } 172 173 /** 174 * Binds properties of the component as defined by the container's 175 * specification. 176 * <p> 177 * This implementation is very simple, we will need a lot more sanity 178 * checking and eror checking in the final version. 179 * 180 * @param container 181 * The containing component. For a dynamic binding ({@link org.apache.tapestry.binding.ExpressionBinding}) 182 * the property name is evaluated with the container as the root. 183 * @param component 184 * The contained component being bound. 185 * @param contained 186 * The contained component specification (from the container's 187 * {@link IComponentSpecification}). 188 * @param defaultBindingPrefix 189 * The default binding prefix to be used with the component. 190 */ 191 192 void bind(IComponent container, IComponent component, 193 IContainedComponent contained, String defaultBindingPrefix) 194 { 195 IComponentSpecification spec = component.getSpecification(); 196 boolean formalOnly = !spec.getAllowInformalParameters(); 197 198 if (contained.getInheritInformalParameters()) 199 { 200 if (formalOnly) 201 throw new ApplicationRuntimeException(PageloadMessages.inheritInformalInvalidComponentFormalOnly(component), 202 component, contained.getLocation(), null); 203 204 IComponentSpecification containerSpec = container.getSpecification(); 205 206 if (!containerSpec.getAllowInformalParameters()) 207 throw new ApplicationRuntimeException(PageloadMessages.inheritInformalInvalidContainerFormalOnly(container, component), 208 component, contained.getLocation(), null); 209 210 IQueuedInheritedBinding queued = new QueuedInheritInformalBindings(component); 211 _inheritedBindingQueue.add(queued); 212 } 213 214 Iterator i = contained.getBindingNames().iterator(); 215 216 while(i.hasNext()) 217 { 218 String name = (String) i.next(); 219 220 IParameterSpecification pspec = spec.getParameter(name); 221 222 boolean isFormal = pspec != null; 223 224 String parameterName = isFormal ? pspec.getParameterName() : name; 225 226 IBindingSpecification bspec = contained.getBinding(name); 227 228 // If not allowing informal parameters, check that each binding 229 // matches 230 // a formal parameter. 231 232 if (formalOnly && !isFormal) 233 throw new ApplicationRuntimeException(PageloadMessages.formalParametersOnly(component, name), 234 component, bspec.getLocation(), null); 235 236 // If an informal parameter that conflicts with a reserved name, 237 // then skip it. 238 239 if (!isFormal && spec.isReservedParameterName(name)) 240 continue; 241 242 if (isFormal) 243 { 244 if (!name.equals(parameterName)) 245 { 246 _log.warn(PageloadMessages.usedParameterAlias(contained, name, parameterName, bspec.getLocation())); 247 } 248 else if (pspec.isDeprecated()) 249 _log.warn(PageloadMessages.deprecatedParameter(name, bspec.getLocation(), contained.getType())); 250 } 251 252 // The type determines how to interpret the value: 253 // As a simple static String 254 // As a nested property name (relative to the component) 255 // As the name of a binding inherited from the containing component. 256 // As the name of a public field 257 // As a script for a listener 258 259 BindingType type = bspec.getType(); 260 261 // For inherited bindings, defer until later. This gives components 262 // a chance to setup bindings from static values and expressions in 263 // the template. The order of operations is tricky, template 264 // bindings 265 // come later. Note that this is a hold over from the Tapestry 3.0 266 // DTD 267 // and will some day no longer be supported. 268 269 if (type == BindingType.INHERITED) 270 { 271 QueuedInheritedBinding queued = new QueuedInheritedBinding(component, bspec.getValue(), parameterName); 272 _inheritedBindingQueue.add(queued); 273 continue; 274 } 275 276 String description = PageloadMessages.parameterName(name); 277 278 IBinding binding = convert(container, description, pspec, defaultBindingPrefix, bspec); 279 280 addBindingToComponent(component, parameterName, binding); 281 } 282 } 283 284 /** 285 * Adds a binding to the component, checking to see if there's a name 286 * conflict (an existing binding for the same parameter ... possibly because 287 * parameter names can be aliased). 288 * 289 * @param component 290 * to which the binding should be added 291 * @param parameterName 292 * the name of the parameter to bind, which should be a true 293 * name, not an alias 294 * @param binding 295 * the binding to add 296 * @throws ApplicationRuntimeException 297 * if a binding already exists 298 * @since 4.0 299 */ 300 301 static void addBindingToComponent(IComponent component, String parameterName, IBinding binding) 302 { 303 IBinding existing = component.getBinding(parameterName); 304 305 if (existing != null) 306 throw new ApplicationRuntimeException(PageloadMessages.duplicateParameter(parameterName, existing), 307 component, binding.getLocation(), null); 308 309 component.setBinding(parameterName, binding); 310 } 311 312 private IBinding convert(IComponent container, String description, IParameterSpecification param, 313 String defaultBindingType, IBindingSpecification spec) 314 { 315 Location location = spec.getLocation(); 316 String bindingReference = spec.getValue(); 317 318 return _bindingSource.createBinding(container, param, description, 319 bindingReference, defaultBindingType, location); 320 } 321 322 /** 323 * Sets up a component. This involves: 324 * <ul> 325 * <li>Instantiating any contained components. 326 * <li>Add the contained components to the container. 327 * <li>Setting up bindings between container and containees. 328 * <li>Construct the containees recursively. 329 * <li>Invoking 330 * {@link IComponent#finishLoad(IRequestCycle, IPageLoader, IComponentSpecification)} 331 * </ul> 332 * 333 * @param cycle 334 * the request cycle for which the page is being (initially) 335 * constructed 336 * @param page 337 * The page on which the container exists. 338 * @param container 339 * The component to be set up. 340 * @param containerSpec 341 * The specification for the container. 342 * @param namespace 343 * The namespace of the container 344 */ 345 346 private void constructComponent(IRequestCycle cycle, IPage page, 347 IComponent container, IComponentSpecification containerSpec, 348 INamespace namespace) 349 { 350 _depth++; 351 if (_depth > _maxDepth) 352 _maxDepth = _depth; 353 354 beginConstructComponent(container, containerSpec); 355 356 String defaultBindingPrefix = _componentPropertySource.getComponentProperty(container, TapestryConstants.DEFAULT_BINDING_PREFIX_NAME); 357 358 List ids = new ArrayList(containerSpec.getComponentIds()); 359 int count = ids.size(); 360 361 try 362 { 363 for(int i = 0; i < count; i++) 364 { 365 String id = (String) ids.get(i); 366 367 // Get the sub-component specification from the 368 // container's specification. 369 370 IContainedComponent contained = containerSpec.getComponent(id); 371 372 String type = contained.getType(); 373 Location location = contained.getLocation(); 374 375 _componentResolver.resolve(cycle, namespace, type, location); 376 377 IComponentSpecification componentSpecification = _componentResolver.getSpecification(); 378 INamespace componentNamespace = _componentResolver.getNamespace(); 379 380 // Instantiate the contained component. 381 382 IComponent component = instantiateComponent(page, container, 383 id, componentSpecification, _componentResolver.getType(), componentNamespace, contained); 384 385 // Add it, by name, to the container. 386 387 container.addComponent(component); 388 389 // Set up any bindings in the IContainedComponent specification 390 391 bind(container, component, contained, defaultBindingPrefix); 392 393 // Now construct the component recusively; it gets its chance 394 // to create its subcomponents and set their bindings. 395 396 constructComponent(cycle, page, component, componentSpecification, componentNamespace); 397 } 398 399 addAssets(container, containerSpec); 400 401 // Finish the load of the component; most components (which 402 // subclass BaseComponent) load their templates here. 403 // Properties with initial values will be set here (or the 404 // initial value will be recorded for later use in pageDetach(). 405 // That may cause yet more components to be created, and more 406 // bindings to be set, so we defer some checking until later. 407 408 container.finishLoad(cycle, this, containerSpec); 409 410 // Have the component switch over to its active state. 411 412 container.enterActiveState(); 413 } 414 catch (ApplicationRuntimeException ex) 415 { 416 throw ex; 417 } 418 catch (RuntimeException ex) 419 { 420 throw new ApplicationRuntimeException(PageloadMessages.unableToInstantiateComponent(container, ex), 421 container, null, ex); 422 } finally { 423 424 endConstructComponent(container); 425 } 426 427 _depth--; 428 } 429 430 /** 431 * Checks the component stack to ensure that the specified component hasn't been improperly nested 432 * and referenced recursively within itself. 433 * 434 * @param component 435 * The component to add to the current component stack and check for recursion. 436 * @param specification 437 * The specification of the specified component. 438 */ 439 void beginConstructComponent(IComponent component, IComponentSpecification specification) 440 { 441 // check recursion 442 443 int position = _componentStack.search(component); 444 if (position > -1) 445 { 446 Location location = specification.getLocation(); 447 448 // try to get the more precise container position location that was referenced 449 // in the template to properly report the precise position of the recursive reference 450 451 IContainedComponent container = component.getContainedComponent(); 452 if (container != null) 453 location = container.getLocation(); 454 455 throw new ApplicationRuntimeException(PageloadMessages.recursiveComponent(component), location, null); 456 } 457 458 _componentStack.push(component); 459 } 460 461 /** 462 * Pops the current component off the stack. 463 * 464 * @param component 465 * The component that has just been constructed. 466 */ 467 void endConstructComponent(IComponent component) 468 { 469 _componentStack.pop(); 470 } 471 472 /** 473 * Invoked to create an implicit component (one which is defined in the 474 * containing component's template, rather that in the containing 475 * component's specification). 476 * 477 * @see org.apache.tapestry.services.impl.ComponentTemplateLoaderImpl 478 * @since 3.0 479 */ 480 481 public IComponent createImplicitComponent(IRequestCycle cycle, 482 IComponent container, String componentId, String componentType, 483 Location location) 484 { 485 IPage page = container.getPage(); 486 487 _componentResolver.resolve(cycle, container.getNamespace(), componentType, location); 488 489 INamespace componentNamespace = _componentResolver.getNamespace(); 490 IComponentSpecification spec = _componentResolver.getSpecification(); 491 492 IContainedComponent contained = new ContainedComponent(); 493 contained.setLocation(location); 494 contained.setType(componentType); 495 496 IComponent result = instantiateComponent(page, container, componentId, 497 spec, _componentResolver.getType(), componentNamespace, 498 contained); 499 500 container.addComponent(result); 501 502 // Recusively build the component. 503 504 constructComponent(cycle, page, result, spec, componentNamespace); 505 506 return result; 507 } 508 509 /** 510 * Instantiates a component from its specification. We instantiate the 511 * component object, then set its specification, page, container and id. 512 * 513 * @param page 514 * The page component is to be attached to. 515 * @param container 516 * The containing component. 517 * @param id 518 * The components unique id 519 * @param spec 520 * The specification for the component 521 * @param type 522 * The type (ie Any / For / DirectLink) 523 * @param namespace 524 * Which namespace / library 525 * @param containedComponent 526 * Possible contained component. 527 * 528 * @return The instantiated component instance. 529 * 530 * @see org.apache.tapestry.AbstractComponent 531 */ 532 533 private IComponent instantiateComponent(IPage page, IComponent container, 534 String id, IComponentSpecification spec, String type, 535 INamespace namespace, IContainedComponent containedComponent) 536 { 537 ComponentClassProviderContext context = new ComponentClassProviderContext(type, spec, namespace); 538 539 String className = _componentClassProvider.provideComponentClassName(context); 540 541 if (HiveMind.isBlank(className)) 542 className = BaseComponent.class.getName(); 543 else 544 { 545 Class componentClass = _classResolver.findClass(className); 546 547 if (!IComponent.class.isAssignableFrom(componentClass)) 548 throw new ApplicationRuntimeException(PageloadMessages.classNotComponent(componentClass), 549 container, spec.getLocation(), null); 550 551 if (IPage.class.isAssignableFrom(componentClass)) 552 throw new ApplicationRuntimeException(PageloadMessages.pageNotAllowed(id), 553 container, spec.getLocation(), null); 554 } 555 556 ComponentConstructor cc = _componentConstructorFactory.getComponentConstructor(spec, className); 557 558 IComponent result = (IComponent) cc.newInstance(); 559 560 result.setNamespace(namespace); 561 result.setPage(page); 562 result.setContainer(container); 563 result.setId(id); 564 result.setContainedComponent(containedComponent); 565 result.setLocation(containedComponent.getLocation()); 566 567 _count++; 568 569 return result; 570 } 571 572 /** 573 * Instantitates a page from its specification. 574 * 575 * @param name 576 * the unqualified, simple, name for the page 577 * @param namespace 578 * the namespace containing the page's specification 579 * @param spec 580 * the page's specification We instantiate the page object, then 581 * set its specification, names and locale. 582 * 583 * @return The instantiated page instance. 584 * 585 * @see org.apache.tapestry.IEngine 586 * @see org.apache.tapestry.event.ChangeObserver 587 */ 588 589 private IPage instantiatePage(String name, INamespace namespace, IComponentSpecification spec) 590 { 591 Location location = spec.getLocation(); 592 ComponentClassProviderContext context = new ComponentClassProviderContext(name, spec, namespace); 593 594 String className = _pageClassProvider.provideComponentClassName(context); 595 596 Class pageClass = _classResolver.findClass(className); 597 598 if (!IPage.class.isAssignableFrom(pageClass)) 599 throw new ApplicationRuntimeException(PageloadMessages.classNotPage(pageClass), location, null); 600 601 String pageName = namespace.constructQualifiedName(name); 602 603 ComponentConstructor cc = _componentConstructorFactory.getComponentConstructor(spec, className); 604 605 IPage result = (IPage) cc.newInstance(); 606 607 result.setNamespace(namespace); 608 result.setPageName(pageName); 609 result.setPage(result); 610 result.setLocale(_locale); 611 result.setLocation(location); 612 613 return result; 614 } 615 616 public IPage loadPage(String name, INamespace namespace, 617 IRequestCycle cycle, IComponentSpecification specification) 618 { 619 IPage page = null; 620 621 _count = 0; 622 _depth = 0; 623 _maxDepth = 0; 624 _componentStack.clear(); 625 626 _locale = _threadLocale.getLocale(); 627 628 try 629 { 630 page = instantiatePage(name, namespace, specification); 631 632 // The page is now attached to the engine and request cycle; some 633 // code 634 // inside the page's finishLoad() method may require this. 635 // TAPESTRY-763 636 637 page.attach(cycle.getEngine(), cycle); 638 639 constructComponent(cycle, page, page, specification, namespace); 640 641 // Walk through the complete component tree to set up the default 642 // parameter values. 643 644 _establishDefaultParameterValuesWalker.walkComponentTree(page); 645 646 establishInheritedBindings(); 647 648 // Walk through the complete component tree to ensure that required 649 // parameters are bound 650 651 _verifyRequiredParametersWalker.walkComponentTree(page); 652 653 // connect @EventListener style client side events 654 655 _eventConnectionWalker.walkComponentTree(page); 656 } 657 finally 658 { 659 _locale = null; 660 _inheritedBindingQueue.clear(); 661 } 662 663 if (_log.isDebugEnabled()) 664 _log.debug("Loaded page " + page + " with " + _count + " components (maximum depth " + _maxDepth + ")"); 665 666 return page; 667 } 668 669 /** @since 4.0 */ 670 671 public void loadTemplateForComponent(IRequestCycle cycle, ITemplateComponent component) 672 { 673 _componentTemplateLoader.loadTemplate(cycle, component); 674 } 675 676 private void establishInheritedBindings() 677 { 678 _log.debug("Establishing inherited bindings"); 679 680 int count = _inheritedBindingQueue.size(); 681 682 for(int i = 0; i < count; i++) 683 { 684 IQueuedInheritedBinding queued = (IQueuedInheritedBinding) _inheritedBindingQueue.get(i); 685 686 queued.connect(); 687 } 688 } 689 690 private void addAssets(IComponent component, IComponentSpecification specification) 691 { 692 List names = specification.getAssetNames(); 693 694 if (names.isEmpty()) return; 695 696 Iterator i = names.iterator(); 697 698 while(i.hasNext()) 699 { 700 String name = (String) i.next(); 701 702 IAssetSpecification assetSpec = specification.getAsset(name); 703 704 IAsset asset = _assetSource.findAsset(assetSpec.getLocation().getResource(), specification, 705 assetSpec.getPath(), _locale, assetSpec.getLocation()); 706 707 component.addAsset(name, asset); 708 } 709 } 710 711 public void setLog(Log log) 712 { 713 _log = log; 714 } 715 716 public void setComponentResolver(ComponentSpecificationResolver resolver) 717 { 718 _componentResolver = resolver; 719 } 720 721 public void setBindingSource(BindingSource bindingSource) 722 { 723 _bindingSource = bindingSource; 724 } 725 726 public void setComponentTemplateLoader(ComponentTemplateLoader componentTemplateLoader) 727 { 728 _componentTemplateLoader = componentTemplateLoader; 729 } 730 731 public void setEstablishDefaultParameterValuesVisitor(IComponentVisitor establishDefaultParameterValuesVisitor) 732 { 733 _establishDefaultParameterValuesVisitor = establishDefaultParameterValuesVisitor; 734 } 735 736 public void setEventConnectionVisitor(IComponentVisitor eventConnectionVisitor) 737 { 738 _eventConnectionVisitor = eventConnectionVisitor; 739 } 740 741 public void setComponentTypeVisitor(IComponentVisitor visitor) 742 { 743 _componentTypeVisitor = visitor; 744 } 745 746 public void setComponentConstructorFactory(ComponentConstructorFactory componentConstructorFactory) 747 { 748 _componentConstructorFactory = componentConstructorFactory; 749 } 750 751 public void setAssetSource(AssetSource assetSource) 752 { 753 _assetSource = assetSource; 754 } 755 756 public void setPageClassProvider(ComponentClassProvider pageClassProvider) 757 { 758 _pageClassProvider = pageClassProvider; 759 } 760 761 public void setClassResolver(ClassResolver classResolver) 762 { 763 _classResolver = classResolver; 764 } 765 766 public void setComponentClassProvider(ComponentClassProvider componentClassProvider) 767 { 768 _componentClassProvider = componentClassProvider; 769 } 770 771 public void setThreadLocale(ThreadLocale threadLocale) 772 { 773 _threadLocale = threadLocale; 774 } 775 776 public void setComponentPropertySource(ComponentPropertySource componentPropertySource) 777 { 778 _componentPropertySource = componentPropertySource; 779 } 780 }