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$)
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() != null)
561         {
562             // Don't create the view Map during startup
563             Map<String, Object> viewMap = facesContext.getViewRoot().getViewMap(!startup);
564             if (viewMap!= null && viewMap.get(beanName) != null)
565             {
566                 return VIEW;
567             }
568         }
569 
570         //not found - check mangaged bean config
571         ManagedBean mbc = getRuntimeConfig(facesContext).getManagedBean(beanName);
572         if (mbc != null)
573         {
574             // managed-bean-scope could be a EL ValueExpression (since 2.0)
575             if (mbc.isManagedBeanScopeValueExpression())
576             {   
577                 // the scope is a custom scope
578                 // Spec says, that the developer has to take care about the references
579                 // to and from managed-beans in custom scopes.
580                 // However, we do check the references, if we are not in Production stage
581                 if (facesContext.isProjectStage(ProjectStage.Production))
582                 {
583                     return null;
584                 }
585                 else
586                 {
587                     String scopeExpression = mbc.getManagedBeanScopeValueExpression(facesContext).getExpressionString();
588                     return getNarrowestScope(facesContext, scopeExpression);
589                 }
590             }
591             else
592             {
593                 return mbc.getManagedBeanScope();
594             }
595         }
596 
597         return null;
598     }
599 
600     /**
601      * Extract the first expression segment, that is the substring up to the first '.' or '['
602      *
603      * @param expression
604      * @return first segment of the expression
605      */
606     private String getFirstSegment(String expression)
607     {
608         int indexDot = expression.indexOf('.');
609         int indexBracket = expression.indexOf('[');
610 
611         if (indexBracket < 0)
612         {
613 
614             return indexDot < 0 ? expression : expression.substring(0, indexDot);
615 
616         }
617 
618         if (indexDot < 0)
619         {
620             return expression.substring(0, indexBracket);
621         }
622 
623         return expression.substring(0, Math.min(indexDot, indexBracket));
624 
625     }
626 
627     private List<String> extractExpressions(String expressionString)
628     {
629         List<String> expressions = new ArrayList<String>();
630         for (String expression : expressionString.split("\\#\\{"))
631         {
632             int index = expression.indexOf('}');
633             if (index >= 0)
634             {
635                 expressions.add(expression.substring(0, index));
636             }
637         }
638         return expressions;
639     }
640 
641 
642     private void initializeMap(FacesContext facesContext, MapEntries mapEntries, 
643                                Map<? super Object, ? super Object> map)
644     {
645         Application application = facesContext.getApplication();
646         
647         Class<?> keyClass = (mapEntries.getKeyClass() == null)
648                 ? String.class : ClassUtils.simpleJavaTypeToClass(mapEntries.getKeyClass());
649         
650         Class<?> valueClass = (mapEntries.getValueClass() == null)
651                 ? String.class : ClassUtils.simpleJavaTypeToClass(mapEntries.getValueClass());
652         
653         ValueExpression valueExpression;
654         ExpressionFactory expFactory = application.getExpressionFactory();
655         ELContext elContext = facesContext.getELContext();
656 
657         for (Iterator<? extends MapEntry> iterator = mapEntries.getMapEntries(); iterator.hasNext();)
658         {
659             MapEntry entry = iterator.next();
660             Object key = entry.getKey();
661 
662             if (ContainerUtils.isValueReference((String) key))
663             {
664                 valueExpression = expFactory.createValueExpression(elContext, (String) key, Object.class);
665                 key = valueExpression.getValue(elContext);
666             }
667 
668             if (entry.isNullValue())
669             {
670                 map.put(coerceToType(facesContext, key, keyClass), null);
671             }
672             else
673             {
674                 Object value = entry.getValue();
675                 if (ContainerUtils.isValueReference((String) value))
676                 {
677                     valueExpression = expFactory.createValueExpression(elContext, (String) value, Object.class);
678                     value = valueExpression.getValue(elContext);
679                 }
680                 
681                 map.put(coerceToType(facesContext, key, keyClass), coerceToType(facesContext, value, valueClass));
682             }
683         }
684     }
685 
686 
687     private void initializeList(FacesContext facesContext, ListEntries listEntries, List<? super Object> list)
688     {
689         Application application = facesContext.getApplication();
690         
691         Class<?> valueClass = (listEntries.getValueClass() == null)
692                 ? String.class : ClassUtils.simpleJavaTypeToClass(listEntries.getValueClass());
693         
694         ExpressionFactory expFactory = application.getExpressionFactory();
695         ELContext elContext = facesContext.getELContext();
696 
697         for (Iterator<? extends ListEntry> iterator = listEntries.getListEntries(); iterator.hasNext();)
698         {
699             ListEntry entry = iterator.next();
700             if (entry.isNullValue())
701             {
702                 list.add(null);
703             }
704             else
705             {
706                 Object value = entry.getValue();
707                 if (ContainerUtils.isValueReference((String) value))
708                 {
709                     ValueExpression valueExpression = expFactory.createValueExpression(elContext, (String) value,
710                                                                                        Object.class);
711                     value = valueExpression.getValue(elContext);
712                 }
713                 
714                 list.add(coerceToType(facesContext, value, valueClass));
715             }
716         }
717     }
718 
719     private RuntimeConfig getRuntimeConfig(FacesContext facesContext)
720     {
721         if (_runtimeConfig == null)
722         {
723             _runtimeConfig = RuntimeConfig.getCurrentInstance(facesContext.getExternalContext());
724         }
725         
726         return _runtimeConfig;
727     }
728 }