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