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