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