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