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