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