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