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