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.binding.ListenerBinding;
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.BSFManagerFactory;
050    import org.apache.tapestry.services.ComponentConstructor;
051    import org.apache.tapestry.services.ComponentConstructorFactory;
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.IListenerBindingSpecification;
060    import org.apache.tapestry.spec.IParameterSpecification;
061    import org.apache.tapestry.web.WebContextResource;
062    
063    /**
064     * Runs the process of building the component hierarchy 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 String _defaultScriptLanguage;
082    
083        /** @since 4.0 */
084    
085        private BindingSource _bindingSource;
086    
087        /** @since 4.0 */
088    
089        private ComponentTemplateLoader _componentTemplateLoader;
090    
091        /** @since 4.0 */
092    
093        private BSFManagerFactory _managerFactory;
094    
095        private List _inheritedBindingQueue = new ArrayList();
096    
097        /** @since 4.0 */
098        private IComponentVisitor _establishDefaultParameterValuesVisitor;
099    
100        private ComponentTreeWalker _establishDefaultParameterValuesWalker;
101    
102        private ComponentTreeWalker _verifyRequiredParametersWalker;
103    
104        /** @since 4.0 */
105    
106        private ComponentConstructorFactory _componentConstructorFactory;
107    
108        /** @since 4.0 */
109    
110        private ValueConverter _valueConverter;
111    
112        /** @since 4.0 */
113    
114        private AssetSource _assetSource;
115    
116        /**
117         * Used to find the correct Java component class for a page.
118         * 
119         * @since 4.0
120         */
121    
122        private ComponentClassProvider _pageClassProvider;
123    
124        /**
125         * Used to find the correct Java component class for a component (a similar process to resolving
126         * a page, but with slightly differen steps and defaults).
127         * 
128         * @since 4.0
129         */
130    
131        private ComponentClassProvider _componentClassProvider;
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        {
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                if (type == BindingType.LISTENER)
294                {
295                    constructListenerBinding(
296                            component,
297                            parameterName,
298                            (IListenerBindingSpecification) bspec);
299                    continue;
300                }
301    
302                String description = PageloadMessages.parameterName(name);
303    
304                IBinding binding = convert(container, description, BindingConstants.OGNL_PREFIX, bspec);
305    
306                addBindingToComponent(component, parameterName, binding);
307            }
308        }
309    
310        /**
311         * Adds a binding to the component, checking to see if there's a name conflict (an existing
312         * binding for the same parameter ... possibly because parameter names can be aliased).
313         * 
314         * @param component
315         *            to which the binding should be added
316         * @param parameterName
317         *            the name of the parameter to bind, which should be a true name, not an alias
318         * @param binding
319         *            the binding to add
320         * @throws ApplicationRuntimeException
321         *             if a binding already exists
322         * @since 4.0
323         */
324    
325        static void addBindingToComponent(IComponent component, String parameterName, IBinding binding)
326        {
327            IBinding existing = component.getBinding(parameterName);
328    
329            if (existing != null)
330                throw new ApplicationRuntimeException(PageloadMessages.duplicateParameter(
331                        parameterName,
332                        existing), component, binding.getLocation(), null);
333    
334            component.setBinding(parameterName, binding);
335        }
336    
337        private IBinding convert(IComponent container, String description, String defaultBindingType,
338                IBindingSpecification spec)
339        {
340            Location location = spec.getLocation();
341            String bindingReference = spec.getValue();
342    
343            return _bindingSource.createBinding(
344                    container,
345                    description,
346                    bindingReference,
347                    defaultBindingType,
348                    location);
349        }
350    
351        /**
352         * Construct a {@link ListenerBinding} for the component, and add it.
353         * 
354         * @since 3.0
355         */
356    
357        private void constructListenerBinding(IComponent component, String parameterName,
358                IListenerBindingSpecification spec)
359        {
360            String language = spec.getLanguage();
361    
362            // If not provided in the page or component specification, then
363            // search for a default (factory default is "jython").
364    
365            if (HiveMind.isBlank(language))
366                language = _defaultScriptLanguage;
367    
368            // Construct the binding. The first parameter is the compononent
369            // (not the DirectLink or Form, but the page or component containing the
370            // link or form).
371    
372            String description = PageloadMessages.parameterName(parameterName);
373    
374            IBinding binding = new ListenerBinding(description, _valueConverter, spec.getLocation(),
375                    component.getContainer(), language, spec.getScript(), _managerFactory);
376    
377            addBindingToComponent(component, parameterName, binding);
378        }
379    
380        /**
381         * Sets up a component. This involves:
382         * <ul>
383         * <li>Instantiating any contained components.
384         * <li>Add the contained components to the container.
385         * <li>Setting up bindings between container and containees.
386         * <li>Construct the containees recursively.
387         * <li>Invoking
388         * {@link IComponent#finishLoad(IRequestCycle, IPageLoader, IComponentSpecification)}
389         * </ul>
390         * 
391         * @param cycle
392         *            the request cycle for which the page is being (initially) constructed
393         * @param page
394         *            The page on which the container exists.
395         * @param container
396         *            The component to be set up.
397         * @param containerSpec
398         *            The specification for the container.
399         * @param the
400         *            namespace of the container
401         */
402    
403        private void constructComponent(IRequestCycle cycle, IPage page, IComponent container,
404                IComponentSpecification containerSpec, INamespace namespace)
405        {
406            _depth++;
407            if (_depth > _maxDepth)
408                _maxDepth = _depth;
409    
410            List ids = new ArrayList(containerSpec.getComponentIds());
411            int count = ids.size();
412    
413            try
414            {
415                for (int i = 0; i < count; i++)
416                {
417                    String id = (String) ids.get(i);
418    
419                    // Get the sub-component specification from the
420                    // container's specification.
421    
422                    IContainedComponent contained = containerSpec.getComponent(id);
423    
424                    String type = contained.getType();
425                    Location location = contained.getLocation();
426    
427                    _componentResolver.resolve(cycle, namespace, type, location);
428    
429                    IComponentSpecification componentSpecification = _componentResolver
430                            .getSpecification();
431                    INamespace componentNamespace = _componentResolver.getNamespace();
432    
433                    // Instantiate the contained component.
434    
435                    IComponent component = instantiateComponent(
436                            page,
437                            container,
438                            id,
439                            componentSpecification,
440                            _componentResolver.getType(),
441                            componentNamespace,
442                            contained);
443    
444                    // Add it, by name, to the container.
445    
446                    container.addComponent(component);
447    
448                    // Set up any bindings in the IContainedComponent specification
449    
450                    bind(container, component, contained);
451    
452                    // Now construct the component recusively; it gets its chance
453                    // to create its subcomponents and set their bindings.
454    
455                    constructComponent(
456                            cycle,
457                            page,
458                            component,
459                            componentSpecification,
460                            componentNamespace);
461                }
462    
463                addAssets(container, containerSpec);
464    
465                // Finish the load of the component; most components (which
466                // subclass BaseComponent) load their templates here.
467                // Properties with initial values will be set here (or the
468                // initial value will be recorded for later use in pageDetach().
469                // That may cause yet more components to be created, and more
470                // bindings to be set, so we defer some checking until
471                // later.
472    
473                container.finishLoad(cycle, this, containerSpec);
474    
475                // Have the component switch over to its active state.
476    
477                container.enterActiveState();
478            }
479            catch (ApplicationRuntimeException ex)
480            {
481                throw ex;
482            }
483            catch (RuntimeException ex)
484            {
485                throw new ApplicationRuntimeException(PageloadMessages.unableToInstantiateComponent(
486                        container,
487                        ex), container, null, ex);
488            }
489    
490            _depth--;
491        }
492    
493        /**
494         * Invoked to create an implicit component (one which is defined in the containing component's
495         * template, rather that in the containing component's specification).
496         * 
497         * @see org.apache.tapestry.services.impl.ComponentTemplateLoaderImpl
498         * @since 3.0
499         */
500    
501        public IComponent createImplicitComponent(IRequestCycle cycle, IComponent container,
502                String componentId, String componentType, Location location)
503        {
504            IPage page = container.getPage();
505    
506            _componentResolver.resolve(cycle, container.getNamespace(), componentType, location);
507    
508            INamespace componentNamespace = _componentResolver.getNamespace();
509            IComponentSpecification spec = _componentResolver.getSpecification();
510    
511            IContainedComponent contained = new ContainedComponent();
512            contained.setLocation(location);
513            contained.setType(componentType);
514    
515            IComponent result = instantiateComponent(
516                    page,
517                    container,
518                    componentId,
519                    spec,
520                    _componentResolver.getType(),
521                    componentNamespace,
522                    contained);
523    
524            container.addComponent(result);
525    
526            // Recusively build the component.
527    
528            constructComponent(cycle, page, result, spec, componentNamespace);
529    
530            return result;
531        }
532    
533        /**
534         * Instantiates a component from its specification. We instantiate the component object, then
535         * set its specification, page, container and id.
536         * 
537         * @see AbstractComponent
538         */
539    
540        private IComponent instantiateComponent(IPage page, IComponent container, String id,
541                IComponentSpecification spec, String type, INamespace namespace,
542                IContainedComponent containedComponent)
543        {
544            ComponentClassProviderContext context = new ComponentClassProviderContext(type, spec,
545                    namespace);
546            String className = _componentClassProvider.provideComponentClassName(context);
547    
548            // String className = spec.getComponentClassName();
549    
550            if (HiveMind.isBlank(className))
551                className = BaseComponent.class.getName();
552            else
553            {
554                Class componentClass = _classResolver.findClass(className);
555    
556                if (!IComponent.class.isAssignableFrom(componentClass))
557                    throw new ApplicationRuntimeException(PageloadMessages
558                            .classNotComponent(componentClass), container, spec.getLocation(), null);
559    
560                if (IPage.class.isAssignableFrom(componentClass))
561                    throw new ApplicationRuntimeException(PageloadMessages.pageNotAllowed(id),
562                            container, spec.getLocation(), null);
563            }
564    
565            ComponentConstructor cc = _componentConstructorFactory.getComponentConstructor(
566                    spec,
567                    className);
568    
569            IComponent result = (IComponent) cc.newInstance();
570    
571            result.setNamespace(namespace);
572            result.setPage(page);
573            result.setContainer(container);
574            result.setId(id);
575            result.setContainedComponent(containedComponent);
576            result.setLocation(containedComponent.getLocation());
577    
578            _count++;
579    
580            return result;
581        }
582    
583        /**
584         * Instantitates a page from its specification.
585         * 
586         * @param name
587         *            the unqualified, simple, name for the page
588         * @param namespace
589         *            the namespace containing the page's specification
590         * @param spec
591         *            the page's specification We instantiate the page object, then set its
592         *            specification, names and locale.
593         * @see IEngine
594         * @see ChangeObserver
595         */
596    
597        private IPage instantiatePage(String name, INamespace namespace, IComponentSpecification spec)
598        {
599            Location location = spec.getLocation();
600            ComponentClassProviderContext context = new ComponentClassProviderContext(name, spec,
601                    namespace);
602            String className = _pageClassProvider.provideComponentClassName(context);
603    
604            Class pageClass = _classResolver.findClass(className);
605    
606            if (!IPage.class.isAssignableFrom(pageClass))
607                throw new ApplicationRuntimeException(PageloadMessages.classNotPage(pageClass),
608                        location, null);
609    
610            String pageName = namespace.constructQualifiedName(name);
611    
612            ComponentConstructor cc = _componentConstructorFactory.getComponentConstructor(
613                    spec,
614                    className);
615    
616            IPage result = (IPage) cc.newInstance();
617    
618            result.setNamespace(namespace);
619            result.setPageName(pageName);
620            result.setPage(result);
621            result.setLocale(_locale);
622            result.setLocation(location);
623    
624            return result;
625        }
626    
627        public IPage loadPage(String name, INamespace namespace, IRequestCycle cycle,
628                IComponentSpecification specification)
629        {
630            IPage page = null;
631    
632            _count = 0;
633            _depth = 0;
634            _maxDepth = 0;
635    
636            _locale = _threadLocale.getLocale();
637    
638            try
639            {
640                page = instantiatePage(name, namespace, specification);
641    
642                constructComponent(cycle, page, page, specification, namespace);
643    
644                // Walk through the complete component tree to set up the default
645                // parameter values.
646                _establishDefaultParameterValuesWalker.walkComponentTree(page);
647    
648                establishInheritedBindings();
649    
650                // Walk through the complete component tree to ensure that required
651                // parameters are bound
652                _verifyRequiredParametersWalker.walkComponentTree(page);
653            }
654            finally
655            {
656                _locale = null;
657                _inheritedBindingQueue.clear();
658            }
659    
660            if (_log.isDebugEnabled())
661                _log.debug("Loaded page " + page + " with " + _count + " components (maximum depth "
662                        + _maxDepth + ")");
663    
664            return page;
665        }
666    
667        /** @since 4.0 */
668    
669        public void loadTemplateForComponent(IRequestCycle cycle, ITemplateComponent component)
670        {
671            _componentTemplateLoader.loadTemplate(cycle, component);
672        }
673    
674        private void establishInheritedBindings()
675        {
676            _log.debug("Establishing inherited bindings");
677    
678            int count = _inheritedBindingQueue.size();
679    
680            for (int i = 0; i < count; i++)
681            {
682                IQueuedInheritedBinding queued = (IQueuedInheritedBinding) _inheritedBindingQueue
683                        .get(i);
684    
685                queued.connect();
686            }
687        }
688    
689        private void addAssets(IComponent component, IComponentSpecification specification)
690        {
691            List names = specification.getAssetNames();
692    
693            if (names.isEmpty())
694                return;
695    
696            Iterator i = names.iterator();
697    
698            while (i.hasNext())
699            {
700                String name = (String) i.next();
701    
702                IAssetSpecification assetSpec = specification.getAsset(name);
703    
704                IAsset asset = convertAsset(assetSpec);
705    
706                component.addAsset(name, asset);
707            }
708        }
709    
710        /**
711         * Builds an instance of {@link IAsset} from the specification.
712         */
713    
714        private IAsset convertAsset(IAssetSpecification spec)
715        {
716            // AssetType type = spec.getType();
717            String path = spec.getPath();
718            Location location = spec.getLocation();
719    
720            Resource specResource = location.getResource();
721    
722            // And ugly, ugly kludge. For page and component specifications in the
723            // context (typically, somewhere under WEB-INF), we evaluate them
724            // relative the web application root.
725    
726            if (isContextResource(specResource))
727                specResource = specResource.getRelativeResource("/");
728    
729            return _assetSource.findAsset(specResource, path, _locale, location);
730        }
731    
732        private boolean isContextResource(Resource resource)
733        {
734            return (resource instanceof WebContextResource) || (resource instanceof ContextResource);
735        }
736    
737        /** @since 4.0 */
738    
739        public void setLog(Log log)
740        {
741            _log = log;
742        }
743    
744        /** @since 4.0 */
745    
746        public void setComponentResolver(ComponentSpecificationResolver resolver)
747        {
748            _componentResolver = resolver;
749        }
750    
751        /** @since 4.0 */
752    
753        public void setDefaultScriptLanguage(String string)
754        {
755            _defaultScriptLanguage = string;
756        }
757    
758        /** @since 4.0 */
759    
760        public void setBindingSource(BindingSource bindingSource)
761        {
762            _bindingSource = bindingSource;
763        }
764    
765        /**
766         * @since 4.0
767         */
768        public void setComponentTemplateLoader(ComponentTemplateLoader componentTemplateLoader)
769        {
770            _componentTemplateLoader = componentTemplateLoader;
771        }
772    
773        /** @since 4.0 */
774        public void setEstablishDefaultParameterValuesVisitor(
775                IComponentVisitor establishDefaultParameterValuesVisitor)
776        {
777            _establishDefaultParameterValuesVisitor = establishDefaultParameterValuesVisitor;
778        }
779    
780        /** @since 4.0 */
781        public void setComponentConstructorFactory(
782                ComponentConstructorFactory componentConstructorFactory)
783        {
784            _componentConstructorFactory = componentConstructorFactory;
785        }
786    
787        /** @since 4.0 */
788        public void setValueConverter(ValueConverter valueConverter)
789        {
790            _valueConverter = valueConverter;
791        }
792    
793        /** @since 4.0 */
794        public void setAssetSource(AssetSource assetSource)
795        {
796            _assetSource = assetSource;
797        }
798    
799        /** @since 4.0 */
800        public void setManagerFactory(BSFManagerFactory managerFactory)
801        {
802            _managerFactory = managerFactory;
803        }
804    
805        /** @since 4.0 */
806        public void setPageClassProvider(ComponentClassProvider pageClassProvider)
807        {
808            _pageClassProvider = pageClassProvider;
809        }
810    
811        /** @since 4.0 */
812        public void setClassResolver(ClassResolver classResolver)
813        {
814            _classResolver = classResolver;
815        }
816    
817        /**
818         * @since 4.0
819         */
820        public void setComponentClassProvider(ComponentClassProvider componentClassProvider)
821        {
822            _componentClassProvider = componentClassProvider;
823        }
824    
825        /** @since 4.0 */
826        public void setThreadLocale(ThreadLocale threadLocale)
827        {
828            _threadLocale = threadLocale;
829        }
830    }