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.asset.AssetSource; 041 import org.apache.tapestry.binding.BindingConstants; 042 import org.apache.tapestry.binding.BindingSource; 043 import org.apache.tapestry.binding.ExpressionBinding; 044 import org.apache.tapestry.binding.ListenerBinding; 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.BSFManagerFactory; 050 import org.apache.tapestry.services.ComponentConstructor; 051 import org.apache.tapestry.services.ComponentConstructorFactory; 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.IListenerBindingSpecification; 060 import org.apache.tapestry.spec.IParameterSpecification; 061 import org.apache.tapestry.web.WebContextResource; 062 063 /** 064 * Runs the process of building the component hierarchy 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 String _defaultScriptLanguage; 082 083 /** @since 4.0 */ 084 085 private BindingSource _bindingSource; 086 087 /** @since 4.0 */ 088 089 private ComponentTemplateLoader _componentTemplateLoader; 090 091 /** @since 4.0 */ 092 093 private BSFManagerFactory _managerFactory; 094 095 private List _inheritedBindingQueue = new ArrayList(); 096 097 /** @since 4.0 */ 098 private IComponentVisitor _establishDefaultParameterValuesVisitor; 099 100 private ComponentTreeWalker _establishDefaultParameterValuesWalker; 101 102 private ComponentTreeWalker _verifyRequiredParametersWalker; 103 104 /** @since 4.0 */ 105 106 private ComponentConstructorFactory _componentConstructorFactory; 107 108 /** @since 4.0 */ 109 110 private ValueConverter _valueConverter; 111 112 /** @since 4.0 */ 113 114 private AssetSource _assetSource; 115 116 /** 117 * Used to find the correct Java component class for a page. 118 * 119 * @since 4.0 120 */ 121 122 private ComponentClassProvider _pageClassProvider; 123 124 /** 125 * Used to find the correct Java component class for a component (a similar process to resolving 126 * a page, but with slightly differen steps and defaults). 127 * 128 * @since 4.0 129 */ 130 131 private ComponentClassProvider _componentClassProvider; 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 { 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 if (type == BindingType.LISTENER) 294 { 295 constructListenerBinding( 296 component, 297 parameterName, 298 (IListenerBindingSpecification) bspec); 299 continue; 300 } 301 302 String description = PageloadMessages.parameterName(name); 303 304 IBinding binding = convert(container, description, BindingConstants.OGNL_PREFIX, bspec); 305 306 addBindingToComponent(component, parameterName, binding); 307 } 308 } 309 310 /** 311 * Adds a binding to the component, checking to see if there's a name conflict (an existing 312 * binding for the same parameter ... possibly because parameter names can be aliased). 313 * 314 * @param component 315 * to which the binding should be added 316 * @param parameterName 317 * the name of the parameter to bind, which should be a true name, not an alias 318 * @param binding 319 * the binding to add 320 * @throws ApplicationRuntimeException 321 * if a binding already exists 322 * @since 4.0 323 */ 324 325 static void addBindingToComponent(IComponent component, String parameterName, IBinding binding) 326 { 327 IBinding existing = component.getBinding(parameterName); 328 329 if (existing != null) 330 throw new ApplicationRuntimeException(PageloadMessages.duplicateParameter( 331 parameterName, 332 existing), component, binding.getLocation(), null); 333 334 component.setBinding(parameterName, binding); 335 } 336 337 private IBinding convert(IComponent container, String description, String defaultBindingType, 338 IBindingSpecification spec) 339 { 340 Location location = spec.getLocation(); 341 String bindingReference = spec.getValue(); 342 343 return _bindingSource.createBinding( 344 container, 345 description, 346 bindingReference, 347 defaultBindingType, 348 location); 349 } 350 351 /** 352 * Construct a {@link ListenerBinding} for the component, and add it. 353 * 354 * @since 3.0 355 */ 356 357 private void constructListenerBinding(IComponent component, String parameterName, 358 IListenerBindingSpecification spec) 359 { 360 String language = spec.getLanguage(); 361 362 // If not provided in the page or component specification, then 363 // search for a default (factory default is "jython"). 364 365 if (HiveMind.isBlank(language)) 366 language = _defaultScriptLanguage; 367 368 // Construct the binding. The first parameter is the compononent 369 // (not the DirectLink or Form, but the page or component containing the 370 // link or form). 371 372 String description = PageloadMessages.parameterName(parameterName); 373 374 IBinding binding = new ListenerBinding(description, _valueConverter, spec.getLocation(), 375 component.getContainer(), language, spec.getScript(), _managerFactory); 376 377 addBindingToComponent(component, parameterName, binding); 378 } 379 380 /** 381 * Sets up a component. This involves: 382 * <ul> 383 * <li>Instantiating any contained components. 384 * <li>Add the contained components to the container. 385 * <li>Setting up bindings between container and containees. 386 * <li>Construct the containees recursively. 387 * <li>Invoking 388 * {@link IComponent#finishLoad(IRequestCycle, IPageLoader, IComponentSpecification)} 389 * </ul> 390 * 391 * @param cycle 392 * the request cycle for which the page is being (initially) constructed 393 * @param page 394 * The page on which the container exists. 395 * @param container 396 * The component to be set up. 397 * @param containerSpec 398 * The specification for the container. 399 * @param the 400 * namespace of the container 401 */ 402 403 private void constructComponent(IRequestCycle cycle, IPage page, IComponent container, 404 IComponentSpecification containerSpec, INamespace namespace) 405 { 406 _depth++; 407 if (_depth > _maxDepth) 408 _maxDepth = _depth; 409 410 List ids = new ArrayList(containerSpec.getComponentIds()); 411 int count = ids.size(); 412 413 try 414 { 415 for (int i = 0; i < count; i++) 416 { 417 String id = (String) ids.get(i); 418 419 // Get the sub-component specification from the 420 // container's specification. 421 422 IContainedComponent contained = containerSpec.getComponent(id); 423 424 String type = contained.getType(); 425 Location location = contained.getLocation(); 426 427 _componentResolver.resolve(cycle, namespace, type, location); 428 429 IComponentSpecification componentSpecification = _componentResolver 430 .getSpecification(); 431 INamespace componentNamespace = _componentResolver.getNamespace(); 432 433 // Instantiate the contained component. 434 435 IComponent component = instantiateComponent( 436 page, 437 container, 438 id, 439 componentSpecification, 440 _componentResolver.getType(), 441 componentNamespace, 442 contained); 443 444 // Add it, by name, to the container. 445 446 container.addComponent(component); 447 448 // Set up any bindings in the IContainedComponent specification 449 450 bind(container, component, contained); 451 452 // Now construct the component recusively; it gets its chance 453 // to create its subcomponents and set their bindings. 454 455 constructComponent( 456 cycle, 457 page, 458 component, 459 componentSpecification, 460 componentNamespace); 461 } 462 463 addAssets(container, containerSpec); 464 465 // Finish the load of the component; most components (which 466 // subclass BaseComponent) load their templates here. 467 // Properties with initial values will be set here (or the 468 // initial value will be recorded for later use in pageDetach(). 469 // That may cause yet more components to be created, and more 470 // bindings to be set, so we defer some checking until 471 // later. 472 473 container.finishLoad(cycle, this, containerSpec); 474 475 // Have the component switch over to its active state. 476 477 container.enterActiveState(); 478 } 479 catch (ApplicationRuntimeException ex) 480 { 481 throw ex; 482 } 483 catch (RuntimeException ex) 484 { 485 throw new ApplicationRuntimeException(PageloadMessages.unableToInstantiateComponent( 486 container, 487 ex), container, null, ex); 488 } 489 490 _depth--; 491 } 492 493 /** 494 * Invoked to create an implicit component (one which is defined in the containing component's 495 * template, rather that in the containing component's specification). 496 * 497 * @see org.apache.tapestry.services.impl.ComponentTemplateLoaderImpl 498 * @since 3.0 499 */ 500 501 public IComponent createImplicitComponent(IRequestCycle cycle, IComponent container, 502 String componentId, String componentType, Location location) 503 { 504 IPage page = container.getPage(); 505 506 _componentResolver.resolve(cycle, container.getNamespace(), componentType, location); 507 508 INamespace componentNamespace = _componentResolver.getNamespace(); 509 IComponentSpecification spec = _componentResolver.getSpecification(); 510 511 IContainedComponent contained = new ContainedComponent(); 512 contained.setLocation(location); 513 contained.setType(componentType); 514 515 IComponent result = instantiateComponent( 516 page, 517 container, 518 componentId, 519 spec, 520 _componentResolver.getType(), 521 componentNamespace, 522 contained); 523 524 container.addComponent(result); 525 526 // Recusively build the component. 527 528 constructComponent(cycle, page, result, spec, componentNamespace); 529 530 return result; 531 } 532 533 /** 534 * Instantiates a component from its specification. We instantiate the component object, then 535 * set its specification, page, container and id. 536 * 537 * @see AbstractComponent 538 */ 539 540 private IComponent instantiateComponent(IPage page, IComponent container, String id, 541 IComponentSpecification spec, String type, INamespace namespace, 542 IContainedComponent containedComponent) 543 { 544 ComponentClassProviderContext context = new ComponentClassProviderContext(type, spec, 545 namespace); 546 String className = _componentClassProvider.provideComponentClassName(context); 547 548 // String className = spec.getComponentClassName(); 549 550 if (HiveMind.isBlank(className)) 551 className = BaseComponent.class.getName(); 552 else 553 { 554 Class componentClass = _classResolver.findClass(className); 555 556 if (!IComponent.class.isAssignableFrom(componentClass)) 557 throw new ApplicationRuntimeException(PageloadMessages 558 .classNotComponent(componentClass), container, spec.getLocation(), null); 559 560 if (IPage.class.isAssignableFrom(componentClass)) 561 throw new ApplicationRuntimeException(PageloadMessages.pageNotAllowed(id), 562 container, spec.getLocation(), null); 563 } 564 565 ComponentConstructor cc = _componentConstructorFactory.getComponentConstructor( 566 spec, 567 className); 568 569 IComponent result = (IComponent) cc.newInstance(); 570 571 result.setNamespace(namespace); 572 result.setPage(page); 573 result.setContainer(container); 574 result.setId(id); 575 result.setContainedComponent(containedComponent); 576 result.setLocation(containedComponent.getLocation()); 577 578 _count++; 579 580 return result; 581 } 582 583 /** 584 * Instantitates a page from its specification. 585 * 586 * @param name 587 * the unqualified, simple, name for the page 588 * @param namespace 589 * the namespace containing the page's specification 590 * @param spec 591 * the page's specification We instantiate the page object, then set its 592 * specification, names and locale. 593 * @see IEngine 594 * @see ChangeObserver 595 */ 596 597 private IPage instantiatePage(String name, INamespace namespace, IComponentSpecification spec) 598 { 599 Location location = spec.getLocation(); 600 ComponentClassProviderContext context = new ComponentClassProviderContext(name, spec, 601 namespace); 602 String className = _pageClassProvider.provideComponentClassName(context); 603 604 Class pageClass = _classResolver.findClass(className); 605 606 if (!IPage.class.isAssignableFrom(pageClass)) 607 throw new ApplicationRuntimeException(PageloadMessages.classNotPage(pageClass), 608 location, null); 609 610 String pageName = namespace.constructQualifiedName(name); 611 612 ComponentConstructor cc = _componentConstructorFactory.getComponentConstructor( 613 spec, 614 className); 615 616 IPage result = (IPage) cc.newInstance(); 617 618 result.setNamespace(namespace); 619 result.setPageName(pageName); 620 result.setPage(result); 621 result.setLocale(_locale); 622 result.setLocation(location); 623 624 return result; 625 } 626 627 public IPage loadPage(String name, INamespace namespace, IRequestCycle cycle, 628 IComponentSpecification specification) 629 { 630 IPage page = null; 631 632 _count = 0; 633 _depth = 0; 634 _maxDepth = 0; 635 636 _locale = _threadLocale.getLocale(); 637 638 try 639 { 640 page = instantiatePage(name, namespace, specification); 641 642 constructComponent(cycle, page, page, specification, namespace); 643 644 // Walk through the complete component tree to set up the default 645 // parameter values. 646 _establishDefaultParameterValuesWalker.walkComponentTree(page); 647 648 establishInheritedBindings(); 649 650 // Walk through the complete component tree to ensure that required 651 // parameters are bound 652 _verifyRequiredParametersWalker.walkComponentTree(page); 653 } 654 finally 655 { 656 _locale = null; 657 _inheritedBindingQueue.clear(); 658 } 659 660 if (_log.isDebugEnabled()) 661 _log.debug("Loaded page " + page + " with " + _count + " components (maximum depth " 662 + _maxDepth + ")"); 663 664 return page; 665 } 666 667 /** @since 4.0 */ 668 669 public void loadTemplateForComponent(IRequestCycle cycle, ITemplateComponent component) 670 { 671 _componentTemplateLoader.loadTemplate(cycle, component); 672 } 673 674 private void establishInheritedBindings() 675 { 676 _log.debug("Establishing inherited bindings"); 677 678 int count = _inheritedBindingQueue.size(); 679 680 for (int i = 0; i < count; i++) 681 { 682 IQueuedInheritedBinding queued = (IQueuedInheritedBinding) _inheritedBindingQueue 683 .get(i); 684 685 queued.connect(); 686 } 687 } 688 689 private void addAssets(IComponent component, IComponentSpecification specification) 690 { 691 List names = specification.getAssetNames(); 692 693 if (names.isEmpty()) 694 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 = convertAsset(assetSpec); 705 706 component.addAsset(name, asset); 707 } 708 } 709 710 /** 711 * Builds an instance of {@link IAsset} from the specification. 712 */ 713 714 private IAsset convertAsset(IAssetSpecification spec) 715 { 716 // AssetType type = spec.getType(); 717 String path = spec.getPath(); 718 Location location = spec.getLocation(); 719 720 Resource specResource = location.getResource(); 721 722 // And ugly, ugly kludge. For page and component specifications in the 723 // context (typically, somewhere under WEB-INF), we evaluate them 724 // relative the web application root. 725 726 if (isContextResource(specResource)) 727 specResource = specResource.getRelativeResource("/"); 728 729 return _assetSource.findAsset(specResource, path, _locale, location); 730 } 731 732 private boolean isContextResource(Resource resource) 733 { 734 return (resource instanceof WebContextResource) || (resource instanceof ContextResource); 735 } 736 737 /** @since 4.0 */ 738 739 public void setLog(Log log) 740 { 741 _log = log; 742 } 743 744 /** @since 4.0 */ 745 746 public void setComponentResolver(ComponentSpecificationResolver resolver) 747 { 748 _componentResolver = resolver; 749 } 750 751 /** @since 4.0 */ 752 753 public void setDefaultScriptLanguage(String string) 754 { 755 _defaultScriptLanguage = string; 756 } 757 758 /** @since 4.0 */ 759 760 public void setBindingSource(BindingSource bindingSource) 761 { 762 _bindingSource = bindingSource; 763 } 764 765 /** 766 * @since 4.0 767 */ 768 public void setComponentTemplateLoader(ComponentTemplateLoader componentTemplateLoader) 769 { 770 _componentTemplateLoader = componentTemplateLoader; 771 } 772 773 /** @since 4.0 */ 774 public void setEstablishDefaultParameterValuesVisitor( 775 IComponentVisitor establishDefaultParameterValuesVisitor) 776 { 777 _establishDefaultParameterValuesVisitor = establishDefaultParameterValuesVisitor; 778 } 779 780 /** @since 4.0 */ 781 public void setComponentConstructorFactory( 782 ComponentConstructorFactory componentConstructorFactory) 783 { 784 _componentConstructorFactory = componentConstructorFactory; 785 } 786 787 /** @since 4.0 */ 788 public void setValueConverter(ValueConverter valueConverter) 789 { 790 _valueConverter = valueConverter; 791 } 792 793 /** @since 4.0 */ 794 public void setAssetSource(AssetSource assetSource) 795 { 796 _assetSource = assetSource; 797 } 798 799 /** @since 4.0 */ 800 public void setManagerFactory(BSFManagerFactory managerFactory) 801 { 802 _managerFactory = managerFactory; 803 } 804 805 /** @since 4.0 */ 806 public void setPageClassProvider(ComponentClassProvider pageClassProvider) 807 { 808 _pageClassProvider = pageClassProvider; 809 } 810 811 /** @since 4.0 */ 812 public void setClassResolver(ClassResolver classResolver) 813 { 814 _classResolver = classResolver; 815 } 816 817 /** 818 * @since 4.0 819 */ 820 public void setComponentClassProvider(ComponentClassProvider componentClassProvider) 821 { 822 _componentClassProvider = componentClassProvider; 823 } 824 825 /** @since 4.0 */ 826 public void setThreadLocale(ThreadLocale threadLocale) 827 { 828 _threadLocale = threadLocale; 829 } 830 }