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