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 javax.faces.component;
20
21 import java.lang.reflect.Array;
22 import java.lang.reflect.Method;
23 import java.util.ArrayList;
24 import java.util.Arrays;
25 import java.util.Collection;
26 import java.util.HashSet;
27 import java.util.Iterator;
28 import java.util.LinkedList;
29 import java.util.Queue;
30 import java.util.Set;
31 import java.util.SortedSet;
32 import java.util.TreeSet;
33
34 import javax.el.ValueExpression;
35 import javax.faces.FacesException;
36 import javax.faces.context.FacesContext;
37 import javax.faces.convert.Converter;
38 import javax.faces.convert.ConverterException;
39 import javax.faces.model.SelectItem;
40 import javax.faces.model.SelectItemGroup;
41
42 /**
43 * The util methods in this class are shared between the javax.faces.component package and the
44 * org.apache.myfaces.renderkit package. Please note: Any changes here must also apply to the class in the other
45 * package!
46 *
47 * @author Manfred Geiler (latest modification by $Author: lu4242 $)
48 * @version $Revision: 1325542 $ $Date: 2012-04-12 16:54:16 -0500 (Thu, 12 Apr 2012) $
49 */
50 class _SharedRendererUtils
51 {
52 static final String COLLECTION_TYPE_KEY = "collectionType";
53 static final String VALUE_TYPE_KEY = "valueType";
54
55 static Converter findUIOutputConverter(FacesContext facesContext,
56 UIOutput component)
57 {
58 // Attention!
59 // This code is duplicated in jsfapi component package.
60 // If you change something here please do the same in the other class!
61
62 Converter converter = component.getConverter();
63 if (converter != null)
64 {
65 return converter;
66 }
67
68 //Try to find out by value expression
69 ValueExpression expression = component.getValueExpression("value");
70 if (expression == null)
71 {
72 return null;
73 }
74
75 Class<?> valueType = expression.getType(facesContext.getELContext());
76 if (valueType == null)
77 {
78 return null;
79 }
80
81 if (Object.class.equals(valueType))
82 {
83 return null; //There is no converter for Object class
84 }
85
86 try
87 {
88 return facesContext.getApplication().createConverter(valueType);
89 }
90 catch (FacesException e)
91 {
92 log(facesContext, "No Converter for type " + valueType.getName()
93 + " found", e);
94 return null;
95 }
96 }
97
98 static Object getConvertedUISelectManyValue(FacesContext facesContext, UISelectMany component,
99 String[] submittedValue) throws ConverterException
100 {
101 return getConvertedUISelectManyValue(facesContext, component,
102 submittedValue, false);
103 }
104
105 /**
106 * Gets the converted value of a UISelectMany component.
107 * If the considerValueType is true, this method will also consider the
108 * valueType attribute of Tomahawk UISelectMany components.
109 *
110 * @param facesContext
111 * @param component
112 * @param submittedValue
113 * @param considerValueType
114 * @return
115 * @throws ConverterException
116 */
117 static Object getConvertedUISelectManyValue(FacesContext facesContext, UISelectMany component,
118 String[] submittedValue, boolean considerValueType) throws ConverterException
119 {
120 // Attention!
121 // This code is duplicated in shared renderkit package (except for considerValueType).
122 // If you change something here please do the same in the other class!
123
124 if (submittedValue == null)
125 {
126 throw new NullPointerException("submittedValue");
127 }
128
129 ValueExpression expression = component.getValueExpression("value");
130 Object targetForConvertedValues = null;
131
132 // if the component has an attached converter, use it
133 Converter converter = component.getConverter();
134 // at this point the valueType attribute is handled in shared.
135 if (converter == null && considerValueType)
136 {
137 // try to get a converter from the valueType attribute
138 converter = getValueTypeConverter(facesContext, component);
139 }
140
141 if (expression != null)
142 {
143 Class<?> modelType = expression
144 .getType(facesContext.getELContext());
145 if (modelType == null)
146 {
147 // FIXME temporal workaround for MYFACES-2552
148 return submittedValue;
149 }
150 else if (modelType.isArray())
151 {
152 // the target should be an array
153 Class<?> componentType = modelType.getComponentType();
154 // check for optimization if the target is
155 // a string array --> no conversion needed
156 if (String.class.equals(componentType))
157 {
158 return submittedValue;
159 }
160 if (converter == null)
161 {
162 // the compononent does not have an attached converter
163 // --> try to get a registered-by-class converter
164 converter = facesContext.getApplication().createConverter(
165 componentType);
166
167 if (converter == null)
168 {
169 // could not obtain a Converter
170 // --> check if we maybe do not really have to convert
171 if (!Object.class.equals(componentType))
172 {
173 // target is not an Object array
174 // and not a String array (checked some lines above)
175 // and we do not have a Converter
176 throw new ConverterException(
177 "Could not obtain a Converter for "
178 + componentType.getName());
179 }
180 }
181 }
182 // instantiate the array
183 targetForConvertedValues = Array.newInstance(componentType,
184 submittedValue.length);
185 }
186 else if (Collection.class.isAssignableFrom(modelType) || Object.class.equals(modelType))
187 {
188 if (converter == null)
189 {
190 // try to get the by-type-converter from the type of the SelectItems
191 _SelectItemsIterator iterator = new _SelectItemsIterator(component, facesContext);
192 converter = getSelectItemsValueConverter(iterator, facesContext);
193 }
194
195 Object collectionTypeAttr = component.getAttributes().get(
196 COLLECTION_TYPE_KEY);
197 if (collectionTypeAttr != null)
198 {
199 Class<?> collectionType = getClassFromAttribute(facesContext, collectionTypeAttr);
200 if (collectionType == null)
201 {
202 throw new FacesException(
203 "The attribute "
204 + COLLECTION_TYPE_KEY
205 + " of component "
206 + component.getClientId(facesContext)
207 + " does not evaluate to a "
208 + "String, a Class object or a ValueExpression pointing "
209 + "to a String or a Class object.");
210 }
211 // now we have a collectionType --> but is it really some kind of Collection
212 if (!Collection.class.isAssignableFrom(collectionType))
213 {
214 throw new FacesException("The attribute "
215 + COLLECTION_TYPE_KEY + " of component "
216 + component.getClientId(facesContext)
217 + " does not point to a valid type of Collection.");
218 }
219 // now we have a real collectionType --> try to instantiate it
220 try
221 {
222 targetForConvertedValues = collectionType.newInstance();
223 }
224 catch (Exception e)
225 {
226 throw new FacesException("The Collection "
227 + collectionType.getName()
228 + "can not be instantiated.", e);
229 }
230 }
231 else if (Collection.class.isAssignableFrom(modelType))
232 {
233 // component.getValue() will implement Collection at this point
234 Collection<?> componentValue = (Collection<?>) component
235 .getValue();
236 // can we clone the Collection
237 if (componentValue instanceof Cloneable)
238 {
239 // clone method of Object is protected --> use reflection
240 try
241 {
242 Method cloneMethod = componentValue.getClass()
243 .getMethod("clone");
244 Collection<?> clone = (Collection<?>) cloneMethod
245 .invoke(componentValue);
246 clone.clear();
247 targetForConvertedValues = clone;
248 }
249 catch (Exception e)
250 {
251 log(facesContext, "Could not clone "
252 + componentValue.getClass().getName(), e);
253 }
254 }
255
256 // if clone did not work
257 if (targetForConvertedValues == null)
258 {
259 // try to create the (concrete) collection from modelType
260 // or with the class object of componentValue (if any)
261 try
262 {
263 targetForConvertedValues = (componentValue != null
264 ? componentValue.getClass()
265 : modelType).newInstance();
266 }
267 catch (Exception e)
268 {
269 // this did not work either
270 // use the standard concrete type
271 if (SortedSet.class.isAssignableFrom(modelType))
272 {
273 targetForConvertedValues = new TreeSet();
274 }
275 else if (Queue.class.isAssignableFrom(modelType))
276 {
277 targetForConvertedValues = new LinkedList();
278 }
279 else if (Set.class.isAssignableFrom(modelType))
280 {
281 targetForConvertedValues = new HashSet(
282 submittedValue.length);
283 }
284 else
285 {
286 targetForConvertedValues = new ArrayList(
287 submittedValue.length);
288 }
289 }
290 }
291 }
292 else /* if (Object.class.equals(modelType)) */
293 {
294 // a modelType of Object is also permitted, in order to support
295 // managed bean properties of type Object
296
297 // optimization: if we don't have a converter, we can return the submittedValue
298 if (converter == null)
299 {
300 return submittedValue;
301 }
302
303 targetForConvertedValues = new Object[submittedValue.length];
304 }
305 }
306 else
307 {
308 // the expression does neither point to an array nor to a collection
309 throw new ConverterException(
310 "ValueExpression for UISelectMany must be of type Collection or Array.");
311 }
312 }
313 else
314 {
315 targetForConvertedValues = new Object[submittedValue.length];
316 }
317
318 // convert the values with the selected converter (if any)
319 // and store them in targetForConvertedValues
320 boolean isArray = (targetForConvertedValues.getClass().isArray());
321 for (int i = 0; i < submittedValue.length; i++)
322 {
323 // get the value
324 Object value;
325 if (converter != null)
326 {
327 value = converter.getAsObject(facesContext, component,
328 submittedValue[i]);
329 }
330 else
331 {
332 value = submittedValue[i];
333 }
334 // store it in targetForConvertedValues
335 if (isArray)
336 {
337 Array.set(targetForConvertedValues, i, value);
338 }
339 else
340 {
341 ((Collection) targetForConvertedValues).add(value);
342 }
343 }
344
345 return targetForConvertedValues;
346 }
347
348 /**
349 * Gets a Class object from a given component attribute. The attribute can
350 * be a ValueExpression (that evaluates to a String or a Class) or a
351 * String (that is a fully qualified Java class name) or a Class object.
352 *
353 * @param facesContext
354 * @param attribute
355 * @return
356 * @throws FacesException if the value is a String and the represented
357 * class cannot be found
358 */
359 static Class<?> getClassFromAttribute(FacesContext facesContext,
360 Object attribute) throws FacesException
361 {
362 // Attention!
363 // This code is duplicated in shared renderkit package.
364 // If you change something here please do the same in the other class!
365
366 Class<?> type = null;
367
368 // if there is a value, it must be a ...
369 // ... a ValueExpression that evaluates to a String or a Class
370 if (attribute instanceof ValueExpression)
371 {
372 // get the value of the ValueExpression
373 attribute = ((ValueExpression) attribute)
374 .getValue(facesContext.getELContext());
375 }
376 // ... String that is a fully qualified Java class name
377 if (attribute instanceof String)
378 {
379 try
380 {
381 type = Class.forName((String) attribute);
382 }
383 catch (ClassNotFoundException cnfe)
384 {
385 throw new FacesException(
386 "Unable to find class "
387 + attribute
388 + " on the classpath.", cnfe);
389 }
390
391 }
392 // ... a Class object
393 else if (attribute instanceof Class)
394 {
395 type = (Class<?>) attribute;
396 }
397
398 return type;
399 }
400
401 /**
402 * Uses the valueType attribute of the given UISelectMany component to
403 * get a by-type converter.
404 *
405 * @param facesContext
406 * @param component
407 * @return
408 */
409 static Converter getValueTypeConverter(FacesContext facesContext, UISelectMany component)
410 {
411 Converter converter = null;
412
413 Object valueTypeAttr = component.getAttributes().get(VALUE_TYPE_KEY);
414 if (valueTypeAttr != null)
415 {
416 // treat the valueType attribute exactly like the collectionType attribute
417 Class<?> valueType = getClassFromAttribute(facesContext, valueTypeAttr);
418 if (valueType == null)
419 {
420 throw new FacesException(
421 "The attribute "
422 + VALUE_TYPE_KEY
423 + " of component "
424 + component.getClientId(facesContext)
425 + " does not evaluate to a "
426 + "String, a Class object or a ValueExpression pointing "
427 + "to a String or a Class object.");
428 }
429 // now we have a valid valueType
430 // --> try to get a registered-by-class converter
431 converter = facesContext.getApplication().createConverter(valueType);
432
433 if (converter == null)
434 {
435 facesContext.getExternalContext().log("Found attribute valueType on component " +
436 _ComponentUtils.getPathToComponent(component) +
437 ", but could not get a by-type converter for type " +
438 valueType.getName());
439 }
440 }
441
442 return converter;
443 }
444
445 /**
446 * Iterates through the SelectItems with the given Iterator and tries to obtain
447 * a by-class-converter based on the Class of SelectItem.getValue().
448 * @param iterator
449 * @param facesContext
450 * @return The first suitable Converter for the given SelectItems or null.
451 */
452 static Converter getSelectItemsValueConverter(Iterator<SelectItem> iterator, FacesContext facesContext)
453 {
454 // Attention!
455 // This code is duplicated in jsfapi component package.
456 // If you change something here please do the same in the other class!
457
458 Converter converter = null;
459 while (converter == null && iterator.hasNext())
460 {
461 SelectItem item = iterator.next();
462 if (item instanceof SelectItemGroup)
463 {
464 Iterator<SelectItem> groupIterator = Arrays.asList(
465 ((SelectItemGroup) item).getSelectItems()).iterator();
466 converter = getSelectItemsValueConverter(groupIterator, facesContext);
467 }
468 else
469 {
470 Class<?> selectItemsType = item.getValue().getClass();
471
472 // optimization: no conversion for String values
473 if (String.class.equals(selectItemsType))
474 {
475 return null;
476 }
477
478 try
479 {
480 converter = facesContext.getApplication().createConverter(selectItemsType);
481 }
482 catch (FacesException e)
483 {
484 // nothing - try again
485 }
486 }
487 }
488 return converter;
489 }
490
491 /**
492 * This method is different in the two versions of _SharedRendererUtils.
493 */
494 private static void log(FacesContext context, String msg, Exception e)
495 {
496 context.getExternalContext().log(msg, e);
497 }
498 }