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