View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.myfaces.el;
20  
21  import org.apache.commons.beanutils.MethodUtils;
22  import org.apache.commons.logging.Log;
23  import org.apache.commons.logging.LogFactory;
24  
25  import javax.faces.el.EvaluationException;
26  import javax.faces.el.PropertyNotFoundException;
27  import javax.faces.el.PropertyResolver;
28  import javax.faces.el.ReferenceSyntaxException;
29  import java.beans.BeanInfo;
30  import java.beans.IntrospectionException;
31  import java.beans.Introspector;
32  import java.beans.PropertyDescriptor;
33  import java.lang.reflect.Array;
34  import java.lang.reflect.Method;
35  import java.util.List;
36  import java.util.Map;
37  
38  
39  /**
40   * @author Manfred Geiler (latest modification by $Author: mmarinschek $)
41   * @author Anton Koinov
42   * @version $Revision: 600632 $ $Date: 2007-12-03 13:55:04 -0500 (Mon, 03 Dec 2007) $
43   */
44  public class PropertyResolverImpl extends PropertyResolver
45  {
46      private static final Log log =
47          LogFactory.getLog(PropertyResolverImpl.class);
48  
49      //~ Static fields/initializers ---------------------------------------------
50  
51      private static final Object[] NO_ARGS = {};
52  
53      //~ Public PropertyResolver Methods ----------------------------------------
54  
55      public Object getValue(Object base, Object property)
56              throws EvaluationException, PropertyNotFoundException
57      {
58          try
59          {
60             if(base == null)
61             {
62                 if(log.isDebugEnabled())
63                   log.debug("property : "+property +" could not be retrieved as base was null.");
64  
65                 return null;
66             }
67  
68             //fix for myfaces-315 - empty string as key to a map-value is allowed
69             //thanks to duffy gillman
70             if (property == null ||
71                 (property instanceof String && ((String)property).length() == 0 &&
72                  !(base instanceof Map)))
73             {
74                 if(log.isDebugEnabled())
75                   log.debug("property for base with class: "+ base.getClass().getName()  +" could not be retrieved as property was null.");
76  
77                 return null;
78             }
79             if (base instanceof Map)
80             {
81                 return ((Map) base).get(property);
82             }
83  
84              // If none of the special bean types, then process as normal Bean
85              return getProperty(base, property.toString());
86          }
87          catch (PropertyNotFoundException e) {
88  
89              if(log.isDebugEnabled())
90                log.debug("Exception while retrieving property; base with class : "+base.getClass().getName()+", property : "+property,e);
91  
92              throw e;
93          }
94          catch (RuntimeException e)
95          {
96              if(log.isDebugEnabled())
97                log.debug("Exception while retrieving property; base : "+base.getClass().getName()+", property : "+property,e);
98  
99              throw new EvaluationException("Exception getting value of property " + property
100                 + " of base of type : "
101                 + base.getClass().getName(), e);
102         }
103     }
104 
105     public Object getValue(Object base, int index)
106             throws EvaluationException, PropertyNotFoundException
107     {
108         try
109         {
110             if (base == null)
111             {
112                 if(log.isDebugEnabled())
113                     log.debug("index : "+index+" not retrievable cause base is null.");
114                 return null;
115             }
116 
117             try
118             {
119                 if (base.getClass().isArray())
120                 {
121                     return Array.get(base, index);
122                 }
123                 if (base instanceof List)
124                 {
125                     return ((List) base).get(index);
126                 }
127             }
128             catch (IndexOutOfBoundsException e)
129             {
130                 if(log.isDebugEnabled())
131                     log.debug("IndexOutOfBoundException while getting property; base with class: "+base.getClass().getName()+", index : "+index,e);
132 
133                 // Note: ArrayIndexOutOfBoundsException also here
134                 return null;
135             }
136 
137             throw new ReferenceSyntaxException("Must be array or List. Bean with class: "
138                 + base.getClass().getName() + ", index " + index);
139         }
140         catch (RuntimeException e)
141         {
142             if(log.isDebugEnabled())
143                 log.debug("Exception while getting property; base with class: "+base.getClass().getName()+", index : "+index,e);
144 
145             throw new EvaluationException("Exception getting value for index " + index
146                 + " of bean "
147                 + base != null ? base.getClass().getName() : "NULL", e);
148         }
149     }
150 
151     public void setValue(Object base, Object property, Object newValue)
152             throws EvaluationException, PropertyNotFoundException
153     {
154         try
155         {
156             if (base == null)
157             {
158                 throw new PropertyNotFoundException(
159                     "Null bean, property: " + property);
160             }
161             if (property == null ||
162                 property instanceof String && ((String)property).length() == 0)
163             {
164                 throw new PropertyNotFoundException("Bean with class : "
165                     + base.getClass().getName()
166                     + ", null or empty property name");
167             }
168 
169             if (base instanceof Map)
170             {
171                 ((Map) base).put(property, newValue);
172 
173                 return;
174             }
175 
176             // If none of the special bean types, then process as normal Bean
177             setProperty(base, property.toString(), newValue);
178         }
179         catch (PropertyNotFoundException e) {
180             if(log.isDebugEnabled())
181                 log.debug("Exception while setting property; base with class : "+base.getClass().getName()+", property : "+property,e);
182             throw e;
183         }
184         catch (RuntimeException e)
185         {
186             if(log.isDebugEnabled())
187                 log.debug("Exception while setting property; base : "+base.getClass().getName()+", property : "+property,e);
188 
189             throw new EvaluationException("Exception setting property " + property
190                 + " of base with class "
191                 + base.getClass().getName(), e);
192         }
193     }
194 
195     public void setValue(Object base, int index, Object newValue)
196             throws EvaluationException, PropertyNotFoundException
197     {
198         try
199         {
200             if (base == null)
201             {
202                 throw new PropertyNotFoundException(
203                     "Null bean, index: " + index);
204             }
205 
206             try
207             {
208                 if (base.getClass().isArray())
209                 {
210                     Array.set(base, index, newValue);
211 
212                     return;
213                 }
214                 if (base instanceof List)
215                 {
216                     // REVISIT: should we try to grow the list, if growable type
217                     //          (e.g., ArrayList, etc.), and if not large
218                     //          enough?
219                     ((List) base).set(index, newValue);
220 
221                     return;
222                 }
223             }
224             catch (IndexOutOfBoundsException e)
225             {
226                 throw new PropertyNotFoundException("Base with class : "
227                     + base.getClass().getName() + ", index " + index, e);
228             }
229 
230             throw new EvaluationException(
231                 "Bean must be array or List. Base with class: "
232                 + base.getClass().getName() + ", index " + index);
233         }
234         catch (PropertyNotFoundException e) {
235             throw e;
236         }
237         catch (RuntimeException e)
238         {
239             throw new EvaluationException("Exception setting value of index " + index + " of bean "
240                 + base.getClass().getName(), e);
241         }
242     }
243 
244     public boolean isReadOnly(Object base, Object property)
245     {
246         try
247         {
248             if (base == null || property == null ||
249                 property instanceof String && ((String)property).length() == 0)
250             {
251                 // Cannot determine read-only, return false (is this what the spec requires?)
252                 return false;
253             }
254 
255             // Is there any way to determine whether Map.put() will fail?
256             if (base instanceof Map)
257             {
258                 return false;
259             }
260 
261             // If none of the special bean types, then process as normal Bean
262             PropertyDescriptor propertyDescriptor =
263                 getPropertyDescriptor(base, property.toString());
264 
265             return propertyDescriptor.getWriteMethod() == null;
266         }
267         catch (Exception e)
268         {
269             // Cannot determine read-only, return false (is this what the spec requires?)
270             return false;
271         }
272     }
273 
274     public boolean isReadOnly(Object base, int index)
275     {
276         try
277         {
278             /*todo: actually implement something here*/
279             if (base == null)
280             {
281                 // Cannot determine read-only, return false (is this what the spec requires?)
282                 return false;
283             }
284             if (base instanceof List || base.getClass().isArray())
285             {
286                 // Is there any way to determine whether List.set() will fail?
287                 return false;
288             }
289 
290             // Cannot determine read-only, return false (is this what the spec requires?)
291             return false;
292         }
293         catch (Exception e)
294         {
295             // Cannot determine read-only, return false (is this what the spec requires?)
296             return false;
297         }
298     }
299 
300     public Class getType(Object base, Object property)
301     {
302         try
303         {
304             if (base == null)
305             {
306                 throw new PropertyNotFoundException("Base bean is null.");
307             }
308             else if (property == null)
309             {
310                 throw new PropertyNotFoundException("Property name is null.");
311             }
312             else if (property instanceof String && ((String)property).length() == 0)
313             {
314                 throw new PropertyNotFoundException("Property name is an empty String.");
315             }
316 
317             if (base instanceof Map)
318             {
319                 Object value = ((Map) base).get(property);
320 
321                 // REVISIT: when generics are imlemented in JVM 1.5
322                 return (value == null) ? Object.class : value.getClass();
323             }
324 
325             // If none of the special bean types, then process as normal Bean
326             PropertyDescriptor propertyDescriptor =
327                 getPropertyDescriptor(base, property.toString());
328 
329             return propertyDescriptor.getPropertyType();
330         }
331         catch (PropertyNotFoundException e) {
332             throw e;
333         }
334         catch (Exception e)
335         {
336             return null;
337         }
338     }
339 
340     public Class getType(Object base, int index)
341     {
342         if (base == null)
343         {
344             throw new PropertyNotFoundException("Bean is null");
345         }
346 
347         try
348         {
349             if (base.getClass().isArray())
350             {
351                 if (base instanceof Object[] && ((Object[])base)[index] != null) {
352                     Object[] array = (Object[]) base;
353                     return array[index].getClass();
354                 } else {
355                     return base.getClass().getComponentType();
356                 }
357             }
358 
359             if (base instanceof List)
360             {
361                 // REVISIT: does it make sense to do this or simply return
362                 //          Object.class? What if the new value is not of
363                 //          the old value's class?
364                 Object value = ((List) base).get(index);
365 
366                 // REVISIT: when generics are implemented in JVM 1.5
367                 return (value != null) ? value.getClass() : Object.class;
368             }
369 
370             // Cannot determine type, return null per JSF spec
371             return null;
372         }
373         catch (IndexOutOfBoundsException e) {
374             throw new PropertyNotFoundException("Bean: "
375                 + base.getClass().getName() + ", index " + index, e);
376         }
377         catch (Exception e)
378         {
379             throw new EvaluationException("Exception getting type of index " + index + " of bean with class : "
380                 + base.getClass().getName(), e);
381         }
382     }
383 
384 
385     //~ Internal Helper Methods ------------------------------------------------
386 
387     public static void setProperty(Object base, String name, Object newValue)
388     {
389         PropertyDescriptor propertyDescriptor =
390             getPropertyDescriptor(base, name);
391 
392         Method m = propertyDescriptor.getWriteMethod();
393         if (m == null)
394         {
395             throw new PropertyNotFoundException(
396                 getMessage(base, name)+ " (no write method for property!)");
397         }
398 
399         // Check if the concrete class of this method is accessible and if not
400         // search for a public interface that declares this method
401         m = MethodUtils.getAccessibleMethod(m);
402         if (m == null)
403         {
404             throw new PropertyNotFoundException(
405                 getMessage(base, name) + " (not accessible!)");
406         }
407 
408         try
409         {
410             m.invoke(base, new Object[] {newValue});
411         }
412         catch (Throwable t)
413         {
414             if(log.isDebugEnabled())
415                 log.debug("Exception while invoking setter method.",t);
416             throw new EvaluationException(getMessage(base, name, newValue, m), t);
417         }
418     }
419 
420     private static String getMessage(Object base, String name, Object newValue, Method m)
421     {
422         return "Bean: "
423             + base.getClass().getName() + ", property: " + name +", newValue: "+(newValue==null?" null ":newValue)+
424                 ",newValue class: "+(newValue==null?" null ":newValue.getClass().getName())+" method parameter class: "
425                 +((m.getParameterTypes()!=null&&m.getParameterTypes().length>0)
426                     ?m.getParameterTypes()[0].getName():"null");
427 
428     }
429 
430     private static String getMessage(Object base, String name)
431     {
432         return "Bean: "
433             + base.getClass().getName() + ", property: " + name;
434     }
435 
436     public static Object getProperty(Object base, String name)
437     {
438         PropertyDescriptor propertyDescriptor =
439             getPropertyDescriptor(base, name);
440 
441         Method m = propertyDescriptor.getReadMethod();
442         if (m == null)
443         {
444             throw new PropertyNotFoundException(
445                 getMessage(base, name));
446         }
447 
448         // Check if the concrete class of this method is accessible and if not
449         // search for a public interface that declares this method
450         m = MethodUtils.getAccessibleMethod(m);
451         if (m == null)
452         {
453             throw new PropertyNotFoundException(
454                 getMessage(base, name) + " (not accessible!)");
455         }
456 
457         try
458         {
459             return m.invoke(base, NO_ARGS);
460         }
461         catch (Throwable t)
462         {
463             throw new EvaluationException(getMessage(base, name), t);
464         }
465     }
466 
467     public static PropertyDescriptor getPropertyDescriptor(
468         Object base, String name)
469     {
470         PropertyDescriptor propertyDescriptor;
471 
472         try
473         {
474             propertyDescriptor =
475                 getPropertyDescriptor(
476                     Introspector.getBeanInfo(base.getClass()), name);
477         }
478         catch (IntrospectionException e)
479         {
480             throw new PropertyNotFoundException(getMessage(base, name), e);
481         }
482 
483         return propertyDescriptor;
484     }
485 
486     public static PropertyDescriptor getPropertyDescriptor(
487         BeanInfo beanInfo, String propertyName)
488     {
489         PropertyDescriptor[] propDescriptors =
490             beanInfo.getPropertyDescriptors();
491 
492         if (propDescriptors != null)
493         {
494             // TODO: cache this in classLoader safe way
495             for (int i = 0, len = propDescriptors.length; i < len; i++)
496             {
497                 if (propDescriptors[i].getName().equals(propertyName))
498                     return propDescriptors[i];
499             }
500         }
501 
502         throw new PropertyNotFoundException("Bean: "
503             + beanInfo.getBeanDescriptor().getBeanClass().getName()
504             + ", property: " + propertyName);
505     }
506 
507 }