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