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    }