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