2009/05/20 - Apache Shale has been retired.

For more information, please explore the Attic.

View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to you under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.shale.tiger.faces;
19  
20  import java.util.ArrayList;
21  import java.util.HashMap;
22  import java.util.List;
23  import java.util.Map;
24  import javax.faces.context.ExternalContext;
25  import javax.faces.context.FacesContext;
26  import javax.faces.el.EvaluationException;
27  import javax.faces.el.PropertyNotFoundException;
28  import javax.faces.el.ValueBinding;
29  import javax.faces.el.VariableResolver;
30  import org.apache.commons.logging.Log;
31  import org.apache.commons.logging.LogFactory;
32  import org.apache.shale.tiger.config.FacesConfigConfig;
33  import org.apache.shale.tiger.managed.Bean;
34  import org.apache.shale.tiger.managed.Value;
35  import org.apache.shale.tiger.managed.config.ListEntriesConfig;
36  import org.apache.shale.tiger.managed.config.ListEntryConfig;
37  import org.apache.shale.tiger.managed.config.ManagedBeanConfig;
38  import org.apache.shale.tiger.managed.config.ManagedPropertyConfig;
39  import org.apache.shale.tiger.managed.config.MapEntriesConfig;
40  import org.apache.shale.tiger.managed.config.MapEntryConfig;
41  import org.apache.shale.tiger.view.faces.LifecycleListener2;
42  import org.apache.shale.util.ConverterHelper;
43  import org.apache.shale.util.Messages;
44  import org.apache.shale.util.PropertyHelper;
45  
46  /***
47   * <p>Implementation of <code>VariableResolver</code> that delegates
48   * to the original handler under these circumstances:</p>
49   * <ul>
50   * <li>There is bean already in existence with the specified name.</li>
51   * <li>There is no managed beans definition for the specified name.</li>
52   * </ul>
53   *
54   * <p>If control is not delegated, implement the standard functionality
55   * for creating managed beans (this is necessary because JSF does not
56   * expose any direct API to access these capabilities), with the following
57   * additions:</p>
58   * <ul>
59   * <li>If the specified managed bean class includes the
60   *     {@link Bean} annotation, the type and scope properties
61   *     will have been preconfigured from the corresponding
62   *     attribute values.  (These default settings can be
63   *     overridden by specifying an actual managed bean element
64   *     in a <code>faces-config.xml</code> resource.)</li>
65   * <li>If a field of the specified bean class includes the
66   *     {@link Value} annotation, the property initialization
67   *     will be preconfigured to the literal or expression
68   *     specified by the <code>value</code> attribute of the
69   *     annotation.  (These default settings can be overridden
70   *     by specifiying an actual managed bean element, and
71   *     including a property definition that sets the value.</li>
72   * </ul>
73   *
74   * <p>FIXME - Incomplete implemetnation of standard managed beans
75   * functionality in the following areas:</p>
76   * <ul>
77   * <li>Partial support for list entries on managed beans and managed properties.
78   *     It currently works for lists, but not for arrays.</li>
79   * </ul>
80   *
81   * <p><strong>IMPLEMENTATION NOTE</strong> - There is no <code>faces-config.xml</code>
82   * resource that registers this variable resolver, since we could end up with
83   * ordering issues with the standard Shale variable resolver.  Therefore, the
84   * standard implementation will dynamically insert an instance of this
85   * resolver (below the standard instance, but above the implementation
86   * provided instance) in the standard decorator chain that is implemented
87   * as the JavaServer Faces runtime parses configuration resources.</p>
88   */
89  public class VariableResolverImpl extends VariableResolver {
90  
91      /***
92       * <p>Create a new <code>VariableResolverImpl</code> wrapping the
93       * specified <code>VariableResolver</code>.</p>
94       *
95       * @param original Original <code>VariableResolver</code> to wrap
96       */
97      public VariableResolverImpl(VariableResolver original) {
98  
99          this.original = original;
100 
101         if (log().isInfoEnabled()) {
102             log().info(messages().getMessage("variable.resolver",
103                                               new Object[] { original }));
104         }
105 
106     }
107 
108     // ----------------------------------------------------- Instance Variables
109 
110 
111     /***
112      * <p>Helper bean for performing conversions.</p>
113      */
114     private ConverterHelper convHelper = new ConverterHelper();
115 
116 
117     /***
118      * <p>Log instance for this class.</p>
119      */
120     private transient Log log = null;
121 
122 
123     /***
124      * <p><code>Messages</code> instance for this class.</p>
125      */
126     private transient Messages messages = null;
127 
128 
129     /***
130      * <p>The original <code>VariableResolver</code> to which we should
131      * delegate when necessary.</p>
132      */
133     private VariableResolver original = null;
134 
135 
136     /***
137      * <p>Helper bean for accessing properties.</p>
138      */
139     private PropertyHelper propHelper = new PropertyHelper();
140 
141 
142 
143     // ----------------------------------------------- VariableResolver Methods
144 
145 
146     /***
147      * <p>Resolve the specified variable name, creating and initializing
148      * a new managed bean if necessary.</p>
149      *
150      * @param context <code>FacesContext</code> used to resolve variables
151      * @param name Name of the variable to be resolved
152      *
153      * @exception EvaluationException if an evaluation error occurs
154      */
155     public Object resolveVariable(FacesContext context,
156             String name) throws EvaluationException {
157 
158         if (log().isDebugEnabled()) {
159             log().debug("resolveVariable(" + name + ")");
160         }
161 
162         // Is there an existing bean by this name?  If so, delegate
163         Object value = context.getExternalContext().getRequestMap().get(name);
164         if (value == null) {
165             value = context.getExternalContext().getSessionMap().get(name);
166         }
167         if (value == null) {
168             value = context.getExternalContext().getApplicationMap().get(name);
169         }
170         if (value != null) {
171             if (log().isTraceEnabled()) {
172                 log().trace("resolveVariable(" + name + ") --> existing bean, so delegate");
173             }
174             return original.resolveVariable(context, name);
175         }
176 
177         // Do we have a managed bean definition for this name?  If not, delegate
178         FacesConfigConfig config = config(context);
179         if (config == null) {
180             if (log().isTraceEnabled()) {
181                 log().trace("resolveVariable(" + name + ") --> no FacesConfigConfig, so delegate");
182             }
183             return original.resolveVariable(context, name);
184         }
185         ManagedBeanConfig mb = config.getManagedBean(name);
186         if (mb == null) {
187             if (log().isTraceEnabled()) {
188                 log().trace("resolveVariable(" + name + ") --> no ManagedBeanConfig, so delegate");
189             }
190             return original.resolveVariable(context, name);
191         }
192 
193         // Configure and return a new managed bean
194         Object created = create(context, mb);
195         return created;
196 
197     }
198 
199 
200     // -------------------------------------------------------- Private Methods
201 
202 
203     /***
204      * <p>The {@link FacesConfigConfig} entry containing our managed bean
205      * definitions, lazily instantiated.</p>
206      */
207     private FacesConfigConfig config = null;
208 
209 
210     /***
211      * <p>Return the {@link FacesConfigConfig} bean describing the
212      * configuration information for this applicaiton, if any.</p>
213      *
214      * @param context FacesContext for the current request
215      */
216     private FacesConfigConfig config(FacesContext context) {
217 
218         if (config == null) {
219             config = (FacesConfigConfig)
220               context.getExternalContext().getApplicationMap().
221               get(LifecycleListener2.FACES_CONFIG_CONFIG);
222         }
223         return config;
224 
225     }
226 
227 
228     /***
229      * <p>Create, configure, and return a new instance based on the
230      * specified managed bean, after storing it in the configured
231      * scope (if any).</p>
232      *
233      * @param context <code>FacesContext</code> for the current request
234      * @param mb ManagedBeanConfig describing the bean to be created
235      *
236      * @exception EvaluationException if an evaluation error occurs
237      */
238     private Object create(FacesContext context, ManagedBeanConfig mb)
239         throws EvaluationException {
240 
241         if (log().isDebugEnabled()) {
242             log().debug("create(" + mb.getName() + ")");
243         }
244 
245         // Instantiate a new instance of the appropriate bean class
246         Object instance = instance(context, mb);
247 
248         // Configure properties as necessary
249         for (ManagedPropertyConfig mp : mb.getProperties().values()) {
250             property(context, mb, mp, instance);
251         }
252 
253         // Configure list entries as necessary
254         ListEntriesConfig listEntries = mb.getListEntries();
255         if (listEntries != null) {
256             // FIXME - arrays are not yet supported
257             if (!List.class.isAssignableFrom(instance.getClass())) {
258                 throw new EvaluationException(messages().
259                     getMessage("list.list",
260                                context.getViewRoot().getLocale(),
261                                new Object[] { instance.getClass().getName() }));
262             }
263             list(context, listEntries, (List) instance);
264         }
265 
266         // Configure map entries as necessary
267         MapEntriesConfig mapEntries = mb.getMapEntries();
268         if (mapEntries != null) {
269             if (!Map.class.isAssignableFrom(instance.getClass())) {
270                 throw new EvaluationException(messages().
271                     getMessage("map.map",
272                                context.getViewRoot().getLocale(),
273                                new Object[] { instance.getClass().getName() }));
274             }
275             map(context, mapEntries, (Map) instance);
276         }
277 
278         // Place the bean into a scope, if necessary
279         scope(context, mb, instance);
280 
281         return instance;
282 
283     }
284 
285 
286     /***
287      * <p>Create and return an instance of the class named by the
288      * specified managed bean configuration bean.
289      *
290      * @param context <code>FacesContext</code> for the current request
291      * @param mb {@link ManagedBeanConfig} defining the bean to create
292      *
293      * @exception EvaluationException if an evaluation error occurs
294      */
295     private Object instance(FacesContext context, ManagedBeanConfig mb)
296         throws EvaluationException {
297 
298         // Is there actually a class name available for us to use?
299         if (mb.getType() == null) {
300             throw new EvaluationException(messages().
301                 getMessage("variable.type",
302                     context.getViewRoot().getLocale(),
303                     new Object[] { mb.getName() }));
304         }
305 
306         // Instantiate an instance of the appropriate class
307         ClassLoader cl = Thread.currentThread().getContextClassLoader();
308         Class clazz = null;
309         Object instance = null;
310         try {
311             clazz = cl.loadClass(mb.getType());
312             instance = clazz.newInstance();
313         } catch (ClassNotFoundException e) {
314             throw new EvaluationException(messages().
315                 getMessage("variable.class",
316                            context.getViewRoot().getLocale(),
317                            new Object[] { mb.getName(), mb.getType() }), e);
318         } catch (IllegalAccessException e) {
319             throw new EvaluationException(messages().
320                 getMessage("variable.access",
321                            context.getViewRoot().getLocale(),
322                            new Object[] { mb.getName(), mb.getType() }), e);
323         } catch (InstantiationException e) {
324             throw new EvaluationException(messages().
325                 getMessage("variable.instantiate",
326                            context.getViewRoot().getLocale(),
327                            new Object[] { mb.getName(), mb.getType() }), e);
328         }
329 
330         return instance;
331 
332     }
333 
334 
335     /***
336      * <p>Populate the contents of the specified <code>List</code> from the
337      * specified list entries configuration information.</p>
338      *
339      * @param context <code>FacesContext</code> for the current request
340      * @param config {@link ListEntriesConfig} describing this list
341      * @param list <code>List</code> instance to have entries appended
342      *
343      * @exception EvaluationException if an evaluation error occurs
344      */
345     private void list(FacesContext context, ListEntriesConfig config, List list) {
346 
347         // Determine the type to which list entries should be conveted, if any
348         String valueType = config.getValueType();
349         ClassLoader cl = Thread.currentThread().getContextClassLoader();
350         Class type = null;
351         try {
352             if (valueType != null) {
353                 type = cl.loadClass(valueType);
354             }
355         } catch (ClassNotFoundException e) {
356             throw new EvaluationException(messages().
357                 getMessage("list.class",
358                            context.getViewRoot().getLocale(),
359                            new Object[] { valueType }), e);
360         }
361 
362         // Add a list entry for each configuration element that is present
363         for (ListEntryConfig entry : config.getEntries()) {
364             if (entry.isNullValue()) {
365                 list.add(null);
366             } else if (entry.isExpression()) {
367                 // Evaluate the specified value binding expression
368                 ValueBinding vb =
369                     context.getApplication().createValueBinding(entry.getValue());
370                 list.add(vb.getValue(context));
371             } else {
372                 if (type != null) {
373                     list.add(convHelper.asObject(context, type, entry.getValue()));
374                 } else {
375                     list.add(entry.getValue());
376                 }
377             }
378         }
379 
380     }
381 
382 
383     /***
384      * <p>Return the <code>Log</code> instance to be used for this class,
385      * instantiating a new one if necessary.</p>
386      */
387     private Log log() {
388 
389         if (log == null) {
390             log = LogFactory.getLog(VariableResolverImpl.class);
391         }
392         return log;
393 
394     }
395 
396 
397     /***
398      * <p>Populate the contents of the specified <code>Map</code> from the
399      * specified map entries configuration information.</p>
400      *
401      * @param context <code>FacesContext</code> for the current request
402      * @param config {@link MapEntriesConfig} describing this list
403      * @param map <code>Map</code> instance to have entries appended
404      *
405      * @exception EvaluationException if an evaluation error occurs
406      */
407     private void map(FacesContext context, MapEntriesConfig config, Map map) {
408 
409         ClassLoader cl = Thread.currentThread().getContextClassLoader();
410 
411         // Determine the type to which map keys should be conveted, if any
412         String keyType = config.getKeyType();
413         Class keyClass = null;
414         try {
415             if (keyType != null) {
416                 keyClass = cl.loadClass(keyType);
417             }
418         } catch (ClassNotFoundException e) {
419             throw new EvaluationException(messages().
420                 getMessage("map.keyClass",
421                            context.getViewRoot().getLocale(),
422                            new Object[] { keyType }), e);
423         }
424 
425         // Determine the type to which map values should be conveted, if any
426         String valueType = config.getValueType();
427         Class valueClass = null;
428         try {
429             if (valueType != null) {
430                 valueClass = cl.loadClass(valueType);
431             }
432         } catch (ClassNotFoundException e) {
433             throw new EvaluationException(messages().
434                 getMessage("map.valueClass",
435                            context.getViewRoot().getLocale(),
436                            new Object[] { valueType }), e);
437         }
438 
439         // Add a map key/value pair for each configuration element that is present
440         for (MapEntryConfig entry : config.getEntries()) {
441             Object key = null;
442             if (keyClass != null) {
443                 key = convHelper.asObject(context, keyClass, entry.getKey());
444             } else {
445                 key = entry.getKey();
446             }
447             if (entry.isNullValue()) {
448                 map.put(key, null);
449             } else if (entry.isExpression()) {
450                 // Evaluate the specified value binding expression
451                 ValueBinding vb =
452                     context.getApplication().createValueBinding(entry.getValue());
453                 map.put(key, vb.getValue(context));
454             } else {
455                 if (valueClass != null) {
456                     map.put(key, convHelper.asObject(context, valueClass, entry.getValue()));
457                 } else {
458                     map.put(key, entry.getValue());
459                 }
460             }
461         }
462 
463     }
464 
465 
466     /***
467      * <p>Return the <code>Messages</code> instance to be used for this class,
468      * instantiating a new one if necessary.</p>
469      */
470     private Messages messages() {
471 
472         if (messages == null) {
473             messages = new Messages("org.apache.shale.tiger.faces.Bundle",
474                                     Thread.currentThread().getContextClassLoader());
475         }
476         return messages;
477 
478     }
479 
480 
481     /***
482      * <p>Configure the specified property of the specified bean instance,
483      * based on information from the specified managed property entry.</p>
484      *
485      * @param context <code>FacesContext</code> for the current request
486      * @param mb {@link ManagedBeanConfig} for the bean being scoped
487      * @param mp {@link ManagedPropertyConfig} for the property being configured
488      * @param instance Bean instance to be potentially placed in scope
489      *
490      * @exception EvaluationException if an evaluation error occurs
491      */
492     private void property(FacesContext context, ManagedBeanConfig mb,
493                           ManagedPropertyConfig mp, Object instance)
494         throws EvaluationException {
495 
496         // Configure list entries as necessary
497         ListEntriesConfig listEntries = mp.getListEntries();
498         if (listEntries != null) {
499 
500             // Acquire the value of the specified property from the
501             // specified instance, if it exists
502             Object property = null;
503             try {
504                 property = propHelper.getValue(instance, mp.getName());
505 //                property = PropertyUtils.getProperty(instance, mp.getName());
506             } catch (PropertyNotFoundException e) {
507                 ; // Fall through to creating our own list
508             } catch (Exception e) {
509                 throw new EvaluationException(messages().
510                     getMessage("list.get",
511                                context.getViewRoot().getLocale(),
512                                new Object[] { mp.getName(), mb.getName()}), e);
513             }
514             if (property == null) {
515                 property = new ArrayList();
516             }
517 
518             // FIXME - arrays are not yet supported
519             if (!List.class.isAssignableFrom(property.getClass())) {
520                 throw new EvaluationException(messages().
521                     getMessage("list.listProperty",
522                                context.getViewRoot().getLocale(),
523                                new Object[] { mp.getName(), mb.getName(), property.getClass().getName() }));
524             }
525 
526             // Accumulate the new values for the list
527             list(context, listEntries, (List) property);
528 
529             // Store the value of the property
530             try {
531                 propHelper.setValue(instance, mp.getName(), property);
532 //                BeanUtils.setProperty(instance, mp.getName(), property);
533             } catch (Exception e) {
534                 throw new EvaluationException(messages().
535                     getMessage("list.set",
536                                context.getViewRoot().getLocale(),
537                                new Object[] { mp.getName(), mb.getName()}), e);
538             }
539 
540             // We are through with this property, so return
541             return;
542 
543         }
544 
545         // Configure map entries as necessary
546         MapEntriesConfig mapEntries = mp.getMapEntries();
547         if (mapEntries != null) {
548 
549             // Acquire the value of the specified property from the
550             // specified instance, if it exists
551             Object property = null;
552             try {
553                 property = propHelper.getValue(instance, mp.getName());
554 //                property = PropertyUtils.getProperty(instance, mp.getName());
555             } catch (PropertyNotFoundException e) {
556                 ; // Fall through to creating our own map
557             } catch (Exception e) {
558                 throw new EvaluationException(messages().
559                     getMessage("map.get",
560                                context.getViewRoot().getLocale(),
561                                new Object[] { mp.getName(), mb.getName()}), e);
562             }
563             if (property == null) {
564                 property = new HashMap();
565             }
566 
567             if (!Map.class.isAssignableFrom(property.getClass())) {
568                 throw new EvaluationException(messages().
569                     getMessage("map.mapProperty",
570                                context.getViewRoot().getLocale(),
571                                new Object[] { mp.getName(), mb.getName(), property.getClass().getName() }));
572             }
573 
574             // Accumulate the new values for the list
575             map(context, mapEntries, (Map) property);
576 
577             // Store the value of the property
578             try {
579                 propHelper.setValue(instance, mp.getName(), property);
580 //                BeanUtils.setProperty(instance, mp.getName(), property);
581             } catch (Exception e) {
582                 throw new EvaluationException(messages().
583                     getMessage("map.set",
584                                context.getViewRoot().getLocale(),
585                                new Object[] { mp.getName(), mb.getName()}), e);
586             }
587 
588             // We are through with this property, so return
589             return;
590 
591         }
592 
593         // Does this managed property specify initialization?
594         if ((mp.getValue() == null) && !mp.isNullValue()) {
595             return;
596         }
597 
598         // Acquire the value to be set (possibly requiring conversion)
599         Object value = null;
600         if (!mp.isNullValue()) {
601             if (mp.isExpression()) {
602                 // Evaluate the specified value binding expression
603                 ValueBinding vb =
604                     context.getApplication().createValueBinding(mp.getValue());
605                 value = vb.getValue(context);
606             } else {
607                 // Grab the string literal version of the value to set
608                 value = mp.getValue();
609             }
610         }
611 
612         // Assign the acquired value to the specified bean property
613         try {
614             Class type = propHelper.getType(instance, mp.getName());
615             if ((value != null) && (value instanceof String)) {
616                 value = convHelper.asObject(context, type, (String) value);
617             }
618             propHelper.setValue(instance, mp.getName(), value);
619         } catch (Exception e) {
620             throw new EvaluationException(messages().
621                 getMessage("variable.evaluate",
622                            context.getViewRoot().getLocale(),
623                            new Object[] { mb.getName(), mp.getName(), mp.getValue() }), e);
624         }
625 
626     }
627 
628 
629     /***
630      * <p>Install the specified bean instance in the specified scope (if any).
631      * </p>
632      *
633      * @param context <code>FacesContext</code> for the current request
634      * @param mb {@link ManagedBeanConfig} for the bean being scoped
635      * @param instance Bean instance to be potentially placed in scope
636      *
637      * @exception EvaluationException if an evaluation error occurs
638      */
639     private void scope(FacesContext context, ManagedBeanConfig mb, Object instance)
640         throws EvaluationException {
641 
642         if (log().isTraceEnabled()) {
643             log().trace("Store bean " + mb.getName() + " in scope " + mb.getScope());
644         }
645 
646         ExternalContext econtext = context.getExternalContext();
647         String scope = mb.getScope();
648         if ("request".equalsIgnoreCase(scope)) {
649             econtext.getRequestMap().put(mb.getName(), instance);
650         } else if ("session".equalsIgnoreCase(scope)) {
651             econtext.getSessionMap().put(mb.getName(), instance);
652         } else if ("application".equalsIgnoreCase(scope)) {
653             econtext.getApplicationMap().put(mb.getName(), instance);
654         }
655 
656     }
657 
658 
659 }