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