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