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.config;
20  
21  import org.apache.commons.beanutils.PropertyUtils;
22  import org.apache.myfaces.config.annotation.LifecycleProvider;
23  import org.apache.myfaces.config.annotation.LifecycleProvider2;
24  import org.apache.myfaces.config.annotation.LifecycleProviderFactory;
25  import org.apache.myfaces.config.element.ListEntries;
26  import org.apache.myfaces.config.element.ListEntry;
27  import org.apache.myfaces.config.element.ManagedBean;
28  import org.apache.myfaces.config.element.ManagedProperty;
29  import org.apache.myfaces.config.element.MapEntries;
30  import org.apache.myfaces.config.element.MapEntry;
31  import org.apache.myfaces.context.servlet.StartupServletExternalContextImpl;
32  import org.apache.myfaces.shared.util.ClassUtils;
33  import org.apache.myfaces.util.ContainerUtils;
34  
35  import javax.el.ELContext;
36  import javax.el.ELException;
37  import javax.el.ELResolver;
38  import javax.el.ExpressionFactory;
39  import javax.el.ValueExpression;
40  import javax.faces.FacesException;
41  import javax.faces.application.Application;
42  import javax.faces.application.ProjectStage;
43  import javax.faces.context.ExternalContext;
44  import javax.faces.context.FacesContext;
45  import javax.naming.NamingException;
46  import java.lang.reflect.Array;
47  import java.lang.reflect.InvocationTargetException;
48  import java.util.ArrayList;
49  import java.util.Comparator;
50  import java.util.HashMap;
51  import java.util.Iterator;
52  import java.util.List;
53  import java.util.Map;
54  import java.util.logging.Level;
55  import java.util.logging.Logger;
56  
57  
58  /**
59   * Create and initialize managed beans
60   *
61   * @author <a href="mailto:oliver@rossmueller.com">Oliver Rossmueller</a> (latest modification by $Author: bommel $)
62   * @author Anton Koinov
63   */
64  public class ManagedBeanBuilder
65  {
66      //private static Log log = LogFactory.getLog(ManagedBeanBuilder.class);
67      private static Logger log = Logger.getLogger(ManagedBeanBuilder.class.getName());
68      private RuntimeConfig _runtimeConfig;
69      public final static String REQUEST = "request";
70      public final static String VIEW = "view";
71      public final static String APPLICATION = "application";
72      public final static String SESSION = "session";
73      public final static String NONE = "none";
74      
75      /**
76       * Comparator used to compare Scopes in the following order:
77       * REQUEST VIEW SESSION APPLICATION NONE
78       * @author Jakob Korherr
79       */
80      private final static Comparator<String> scopeComparator = new Comparator<String>()
81      {
82  
83          public int compare(String o1, String o2)
84          {
85              if (o1.equalsIgnoreCase(o2))
86              {
87                  // the same scope
88                  return 0;
89              }
90              if (o1.equalsIgnoreCase(NONE))
91              {
92                  // none is greater than any other scope
93                  return 1;
94              }
95              if (o1.equalsIgnoreCase(APPLICATION))
96              {
97                  if (o2.equalsIgnoreCase(NONE))
98                  {
99                      // application is smaller than none
100                     return -1;
101                 }
102                 else
103                 {
104                     // ..but greater than any other scope
105                     return 1;
106                 }
107             }
108             if (o1.equalsIgnoreCase(SESSION))
109             {
110                 if (o2.equalsIgnoreCase(REQUEST) || o2.equalsIgnoreCase(VIEW))
111                 {
112                     // session is greater than request and view
113                     return 1;
114                 }
115                 else
116                 {
117                     // but smaller than any other scope
118                     return -1;
119                 }
120             }
121             if (o1.equalsIgnoreCase(VIEW))
122             {
123                 if (o2.equalsIgnoreCase(REQUEST))
124                 {
125                     // view is greater than request
126                     return 1;
127                 }
128                 else
129                 {
130                     // ..but smaller than any other scope
131                     return -1;
132                 }
133             }
134             if (o1.equalsIgnoreCase(REQUEST))
135             {
136                 // request is smaller than any other scope
137                 return -1;
138             }
139             
140             // not a valid scope
141             throw new IllegalArgumentException(o1 + " is not a valid scope");
142         }
143         
144     };
145 
146     @SuppressWarnings("unchecked")
147     public Object buildManagedBean(FacesContext facesContext, ManagedBean beanConfiguration) throws FacesException
148     {
149         try
150         {
151             ExternalContext externalContext = facesContext.getExternalContext();
152             LifecycleProvider lifecycleProvider = LifecycleProviderFactory
153                     .getLifecycleProviderFactory( externalContext).getLifecycleProvider(externalContext);
154             
155             final Object bean = lifecycleProvider.newInstance(beanConfiguration.getManagedBeanClassName());
156 
157             switch (beanConfiguration.getInitMode())
158             {
159                 case ManagedBean.INIT_MODE_PROPERTIES:
160                     try
161                     {
162                         initializeProperties(facesContext, beanConfiguration, bean);
163                     }
164                     catch (IllegalArgumentException e)
165                     {
166                         throw new IllegalArgumentException(
167                                 e.getMessage()
168                                         + " for bean '"
169                                         + beanConfiguration.getManagedBeanName()
170                                         + "' check the configuration to make sure all properties correspond with get/set methods", e);
171                     }
172                     break;
173 
174                 case ManagedBean.INIT_MODE_MAP:
175                     if (!(bean instanceof Map))
176                     {
177                         throw new IllegalArgumentException("Class " + bean.getClass().getName()
178                                 + " of managed bean "
179                                 + beanConfiguration.getManagedBeanName()
180                                 + " is not a Map.");
181                     }
182                     initializeMap(facesContext, beanConfiguration.getMapEntries(), (Map<Object, Object>) bean);
183                     break;
184 
185                 case ManagedBean.INIT_MODE_LIST:
186                     if (!(bean instanceof List))
187                     {
188                         throw new IllegalArgumentException("Class " + bean.getClass().getName()
189                                 + " of managed bean "
190                                 + beanConfiguration.getManagedBeanName()
191                                 + " is not a List.");
192                     }
193                     initializeList(facesContext, beanConfiguration.getListEntries(), (List<Object>) bean);
194                     break;
195 
196                 case ManagedBean.INIT_MODE_NO_INIT:
197                     // no init values
198                     break;
199 
200                 default:
201                     throw new IllegalStateException("Unknown managed bean type "
202                             + bean.getClass().getName() + " for managed bean "
203                             + beanConfiguration.getManagedBeanName() + '.');
204             }
205             
206             // MYFACES-1761 if implements LifecycleProvider,
207             //PostConstruct was already called, but if implements
208             //LifecycleProvider2, call it now.
209             if (lifecycleProvider instanceof LifecycleProvider2)
210             {
211                 ((LifecycleProvider2)lifecycleProvider).postConstruct(bean);
212             }
213             return bean;
214         }
215         catch (IllegalAccessException e)
216         {
217             throw new FacesException(e);
218         }
219         catch (InvocationTargetException e)
220         {
221             throw new FacesException(e);
222         }
223         catch (NamingException e)
224         {
225             throw new FacesException(e);
226         }
227         catch (ClassNotFoundException e)
228         {
229             throw new FacesException(e);
230         }
231         catch (InstantiationException e)
232         {
233             throw new FacesException(e);
234         }
235 
236     }
237 
238 
239     @SuppressWarnings("unchecked")
240     private void initializeProperties(FacesContext facesContext, 
241                                       ManagedBean beanConfiguration, Object bean)
242     {
243         ELResolver elResolver = facesContext.getApplication().getELResolver();
244         ELContext elContext = facesContext.getELContext();
245 
246         for (ManagedProperty property : beanConfiguration.getManagedProperties())
247         {
248             Object value = null;
249 
250             switch (property.getType())
251             {
252                 case ManagedProperty.TYPE_LIST:
253 
254                     // JSF 1.1, 5.3.1.3
255                     // Call the property getter, if it exists.
256                     // If the getter returns null or doesn't exist, create a java.util.ArrayList,
257                     // otherwise use the returned Object ...
258                     if (PropertyUtils.isReadable(bean, property.getPropertyName()))
259                     {
260                         value = elResolver.getValue(elContext, bean, property.getPropertyName());
261                     }
262                     
263                     value = value == null ? new ArrayList<Object>() : value;
264 
265                     if (value instanceof List)
266                     {
267                         initializeList(facesContext, property.getListEntries(), (List<Object>)value);
268 
269                     }
270                     else if (value != null && value.getClass().isArray())
271                     {
272                         int length = Array.getLength(value);
273                         ArrayList<Object> temp = new ArrayList<Object>(length);
274                         for (int i = 0; i < length; i++)
275                         {
276                             temp.add(Array.get(value, i));
277                         }
278                         initializeList(facesContext, property.getListEntries(), temp);
279                         value = Array.newInstance(value.getClass().getComponentType(), temp.size());
280                         length = temp.size();
281 
282                         for (int i = 0; i < length; i++)
283                         {
284                             Array.set(value, i, temp.get(i));
285                         }
286                     }
287                     else
288                     {
289                         value = new ArrayList<Object>();
290                         initializeList(facesContext, property.getListEntries(), (List<Object>) value);
291                     }
292 
293                     break;
294                 case ManagedProperty.TYPE_MAP:
295 
296                     // JSF 1.1, 5.3.1.3
297                     // Call the property getter, if it exists.
298                     // If the getter returns null or doesn't exist, create a java.util.HashMap,
299                     // otherwise use the returned java.util.Map .
300                     if (PropertyUtils.isReadable(bean, property.getPropertyName()))
301                         value = elResolver.getValue(elContext, bean, property.getPropertyName());
302                     value = value == null ? new HashMap<Object, Object>() : value;
303 
304                     if (!(value instanceof Map))
305                     {
306                         value = new HashMap<Object, Object>();
307                     }
308 
309                     initializeMap(facesContext, property.getMapEntries(), (Map<Object, Object>) value);
310                     break;
311                 case ManagedProperty.TYPE_NULL:
312                     break;
313                 case ManagedProperty.TYPE_VALUE:
314                     // check for correct scope of a referenced bean
315                     if (!isInValidScope(facesContext, property, beanConfiguration))
316                     {
317                         throw new FacesException("Property " + property.getPropertyName() +
318                                 " references object in a scope with shorter lifetime than the target scope " +
319                                 beanConfiguration.getManagedBeanScope());
320                     }
321                     value = property.getRuntimeValue(facesContext);
322                     break;
323             }
324             
325             Class<?> propertyClass = null;
326 
327             if (property.getPropertyClass() == null)
328             {
329                 propertyClass = elResolver.getType(elContext, bean, property.getPropertyName());
330             }
331             else
332             {
333                 propertyClass = ClassUtils.simpleJavaTypeToClass(property.getPropertyClass());
334             }
335             
336             if (null == propertyClass)
337             {
338                 throw new IllegalArgumentException("unable to find the type of property " + property.getPropertyName());
339             }
340             
341             Object coercedValue = coerceToType(facesContext, value, propertyClass);
342             elResolver.setValue(elContext, bean, property.getPropertyName(), coercedValue);
343         }
344     }
345 
346     // We no longer use the convertToType from shared impl because we switched
347     // to unified EL in JSF 1.2
348     @SuppressWarnings("unchecked")
349     public static <T> T coerceToType(FacesContext facesContext, Object value, Class<? extends T> desiredClass)
350     {
351         if (value == null) return null;
352 
353         try
354         {
355             ExpressionFactory expFactory = facesContext.getApplication().getExpressionFactory();
356             // Use coersion implemented by JSP EL for consistency with EL
357             // expressions. Additionally, it caches some of the coersions.
358             return (T)expFactory.coerceToType(value, desiredClass);
359         }
360         catch (ELException e)
361         {
362             String message = "Cannot coerce " + value.getClass().getName()
363                     + " to " + desiredClass.getName();
364             log.log(Level.SEVERE, message , e);
365             throw new FacesException(message, e);
366         }
367     }
368 
369 
370     /**
371      * Checks if the scope of the property value is valid for a bean to be stored in targetScope.
372      * If one of the scopes is a custom scope (since jsf 2.0), this method only checks the
373      * references if the current ProjectStage is not Production.
374      * @param facesContext
375      * @param property           the property to be checked
376      * @param beanConfiguration  the ManagedBean, which will be created
377      */
378     private boolean isInValidScope(FacesContext facesContext, ManagedProperty property, ManagedBean beanConfiguration)
379     {
380         if (!property.isValueReference())
381         {
382             // no value reference but a literal value -> nothing to check
383             return true;
384         }
385         
386         // get the targetScope (since 2.0 this could be an EL ValueExpression)
387         String targetScope = null;
388         if (beanConfiguration.isManagedBeanScopeValueExpression())
389         {
390             // the scope is a custom scope
391             // Spec says, that the developer has to take care about the references
392             // to and from managed-beans in custom scopes.
393             // However, we do check the references, if we are not in Production stage
394             if (facesContext.isProjectStage(ProjectStage.Production))
395             {
396                 return true;
397             }
398             else
399             {
400                 targetScope = getNarrowestScope(facesContext, 
401                                                 beanConfiguration
402                                                     .getManagedBeanScopeValueExpression(facesContext)
403                                                     .getExpressionString());
404                 // if we could not obtain a targetScope, return true
405                 if (targetScope == null)
406                 {
407                     return true;
408                 }
409             }
410         }
411         else
412         {
413             targetScope = beanConfiguration.getManagedBeanScope();
414             if (targetScope == null)
415             {
416                 targetScope = NONE;
417             }
418         }
419         
420         // optimization: 'request' scope can reference any value scope
421         if (targetScope.equalsIgnoreCase(REQUEST))
422         {
423             return true;
424         }
425         
426         String valueScope = getNarrowestScope(facesContext, 
427                                               property.getValueBinding(facesContext)
428                                                   .getExpressionString());
429         
430         // if we could not obtain a valueScope, return true
431         if (valueScope == null)
432         {
433             return true;
434         }
435         
436         // the target scope needs to have a shorter (or equal) lifetime than the value scope
437         return (scopeComparator.compare(targetScope, valueScope) <= 0);
438     }
439 
440     /**
441      * Gets the narrowest scope to which the ValueExpression points.
442      * @param facesContext
443      * @param valueExpression
444      * @return
445      */
446     private String getNarrowestScope(FacesContext facesContext, String valueExpression)
447     {
448         List<String> expressions = extractExpressions(valueExpression);
449         // exclude NONE scope, if there are more than one ValueExpressions (see Spec for details)
450         String narrowestScope = expressions.size() == 1 ? NONE : APPLICATION;
451         boolean scopeFound = false;
452         
453         for (String expression : expressions)
454         {
455             String valueScope = getScope(facesContext, expression);
456             if (valueScope == null)
457             {
458                 continue;
459             }
460             // we have found at least one valid scope at this point
461             scopeFound = true;
462             if (scopeComparator.compare(valueScope, narrowestScope) < 0)
463             {
464                 narrowestScope = valueScope;
465             }
466         }
467         
468         return scopeFound ? narrowestScope : null;
469     }
470     
471     private String getScope(FacesContext facesContext, String expression)
472     {
473         String beanName = getFirstSegment(expression);
474         ExternalContext externalContext = facesContext.getExternalContext();
475 
476         // check scope objects
477         if (beanName.equalsIgnoreCase("requestScope"))
478         {
479             return REQUEST;
480         }
481         if (beanName.equalsIgnoreCase("sessionScope"))
482         {
483             return SESSION;
484         }
485         if (beanName.equalsIgnoreCase("applicationScope"))
486         {
487             return APPLICATION;
488         }
489 
490         // check implicit objects
491         if (beanName.equalsIgnoreCase("cookie"))
492         {
493             return REQUEST;
494         }
495         if (beanName.equalsIgnoreCase("facesContext"))
496         {
497             return REQUEST;
498         }
499         if (beanName.equalsIgnoreCase("header"))
500         {
501             return REQUEST;
502         }
503         if (beanName.equalsIgnoreCase("headerValues"))
504         {
505             return REQUEST;
506         }
507         if (beanName.equalsIgnoreCase("param"))
508         {
509             return REQUEST;
510         }
511         if (beanName.equalsIgnoreCase("paramValues"))
512         {
513             return REQUEST;
514         }
515         if (beanName.equalsIgnoreCase("request"))
516         {
517             return REQUEST;
518         }
519         if (beanName.equalsIgnoreCase("view")) // Spec says that view is considered to be in request scope
520         {
521             return REQUEST;
522         }
523         if (beanName.equalsIgnoreCase("application"))
524         {
525             return APPLICATION;
526         }
527         if (beanName.equalsIgnoreCase("initParam"))
528         {
529             return APPLICATION;
530         }
531 
532         // not found so far - check all scopes
533         final boolean startup = (externalContext instanceof StartupServletExternalContextImpl);
534         if (!startup)
535         {
536             // request and session maps are only available at runtime - not at startup
537             // (the following code would throw an UnsupportedOperationException).
538             if (externalContext.getRequestMap().get(beanName) != null)
539             {
540                 return REQUEST;
541             }
542             if (externalContext.getSessionMap().get(beanName) != null)
543             {
544                 return SESSION;
545             }
546         }
547         if (externalContext.getApplicationMap().get(beanName) != null)
548         {
549             return APPLICATION;
550         }
551         if (facesContext.getViewRoot().getViewMap().get(beanName) != null)
552         {
553             return VIEW;
554         }
555 
556         //not found - check mangaged bean config
557         ManagedBean mbc = getRuntimeConfig(facesContext).getManagedBean(beanName);
558         if (mbc != null)
559         {
560             // managed-bean-scope could be a EL ValueExpression (since 2.0)
561             if (mbc.isManagedBeanScopeValueExpression())
562             {   
563                 // the scope is a custom scope
564                 // Spec says, that the developer has to take care about the references
565                 // to and from managed-beans in custom scopes.
566                 // However, we do check the references, if we are not in Production stage
567                 if (facesContext.isProjectStage(ProjectStage.Production))
568                 {
569                     return null;
570                 }
571                 else
572                 {
573                     String scopeExpression = mbc.getManagedBeanScopeValueExpression(facesContext).getExpressionString();
574                     return getNarrowestScope(facesContext, scopeExpression);
575                 }
576             }
577             else
578             {
579                 return mbc.getManagedBeanScope();
580             }
581         }
582 
583         return null;
584     }
585 
586     /**
587      * Extract the first expression segment, that is the substring up to the first '.' or '['
588      *
589      * @param expression
590      * @return first segment of the expression
591      */
592     private String getFirstSegment(String expression)
593     {
594         int indexDot = expression.indexOf('.');
595         int indexBracket = expression.indexOf('[');
596 
597         if (indexBracket < 0)
598         {
599 
600             return indexDot < 0 ? expression : expression.substring(0, indexDot);
601 
602         }
603 
604         if (indexDot < 0)
605         {
606             return expression.substring(0, indexBracket);
607         }
608 
609         return expression.substring(0, Math.min(indexDot, indexBracket));
610 
611     }
612 
613     private List<String> extractExpressions(String expressionString)
614     {
615         List<String> expressions = new ArrayList<String>();
616         for (String expression : expressionString.split("\\#\\{"))
617         {
618             int index = expression.indexOf('}');
619             if (index >= 0)
620             {
621                 expressions.add(expression.substring(0, index));
622             }
623         }
624         return expressions;
625     }
626 
627 
628     private void initializeMap(FacesContext facesContext, MapEntries mapEntries, 
629                                Map<? super Object, ? super Object> map)
630     {
631         Application application = facesContext.getApplication();
632         
633         Class<?> keyClass = (mapEntries.getKeyClass() == null)
634                 ? String.class : ClassUtils.simpleJavaTypeToClass(mapEntries.getKeyClass());
635         
636         Class<?> valueClass = (mapEntries.getValueClass() == null)
637                 ? String.class : ClassUtils.simpleJavaTypeToClass(mapEntries.getValueClass());
638         
639         ValueExpression valueExpression;
640         ExpressionFactory expFactory = application.getExpressionFactory();
641         ELContext elContext = facesContext.getELContext();
642 
643         for (Iterator<? extends MapEntry> iterator = mapEntries.getMapEntries(); iterator.hasNext();)
644         {
645             MapEntry entry = iterator.next();
646             Object key = entry.getKey();
647 
648             if (ContainerUtils.isValueReference((String) key))
649             {
650                 valueExpression = expFactory.createValueExpression(elContext, (String) key, Object.class);
651                 key = valueExpression.getValue(elContext);
652             }
653 
654             if (entry.isNullValue())
655             {
656                 map.put(coerceToType(facesContext, key, keyClass), null);
657             }
658             else
659             {
660                 Object value = entry.getValue();
661                 if (ContainerUtils.isValueReference((String) value))
662                 {
663                     valueExpression = expFactory.createValueExpression(elContext, (String) value, Object.class);
664                     value = valueExpression.getValue(elContext);
665                 }
666                 
667                 map.put(coerceToType(facesContext, key, keyClass), coerceToType(facesContext, value, valueClass));
668             }
669         }
670     }
671 
672 
673     private void initializeList(FacesContext facesContext, ListEntries listEntries, List<? super Object> list)
674     {
675         Application application = facesContext.getApplication();
676         
677         Class<?> valueClass = (listEntries.getValueClass() == null)
678                 ? String.class : ClassUtils.simpleJavaTypeToClass(listEntries.getValueClass());
679         
680         ExpressionFactory expFactory = application.getExpressionFactory();
681         ELContext elContext = facesContext.getELContext();
682 
683         for (Iterator<? extends ListEntry> iterator = listEntries.getListEntries(); iterator.hasNext();)
684         {
685             ListEntry entry = iterator.next();
686             if (entry.isNullValue())
687             {
688                 list.add(null);
689             }
690             else
691             {
692                 Object value = entry.getValue();
693                 if (ContainerUtils.isValueReference((String) value))
694                 {
695                     ValueExpression valueExpression = expFactory.createValueExpression(elContext, (String) value, Object.class);
696                     value = valueExpression.getValue(elContext);
697                 }
698                 
699                 list.add(coerceToType(facesContext, value, valueClass));
700             }
701         }
702     }
703 
704     private RuntimeConfig getRuntimeConfig(FacesContext facesContext)
705     {
706         if (_runtimeConfig == null)
707         {
708             _runtimeConfig = RuntimeConfig.getCurrentInstance(facesContext.getExternalContext());
709         }
710         
711         return _runtimeConfig;
712     }
713 }