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