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