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   * @author Manfred Geiler (latest modification by $Author: lu4242 $)
48   * @version $Revision: 1325543 $ $Date: 2012-04-12 16:54:36 -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 }