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    }