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.el.unified.resolver;
20  
21  import org.apache.myfaces.config.ManagedBeanBuilder;
22  import org.apache.myfaces.config.RuntimeConfig;
23  import org.apache.myfaces.config.element.ManagedBean;
24  import org.apache.myfaces.context.servlet.StartupServletExternalContextImpl;
25  
26  import javax.el.ELContext;
27  import javax.el.ELException;
28  import javax.el.ELResolver;
29  import javax.el.PropertyNotFoundException;
30  import javax.el.PropertyNotWritableException;
31  import javax.faces.FacesException;
32  import javax.faces.application.ProjectStage;
33  import javax.faces.component.UIViewRoot;
34  import javax.faces.context.ExternalContext;
35  import javax.faces.context.FacesContext;
36  import java.beans.FeatureDescriptor;
37  import java.util.ArrayList;
38  import java.util.HashMap;
39  import java.util.Iterator;
40  import java.util.List;
41  import java.util.Map;
42  import java.util.logging.Level;
43  import java.util.logging.Logger;
44  
45  /**
46   * See JSF 1.2 spec section 5.6.1.2
47   * 
48   * @author Stan Silvert
49   */
50  public class ManagedBeanResolver extends ELResolver
51  {
52      private static final Logger log = Logger.getLogger(ManagedBeanResolver.class.getName());
53      private static final String BEANS_UNDER_CONSTRUCTION =
54              "org.apache.myfaces.el.unified.resolver.managedbean.beansUnderConstruction";
55  
56      private static final String CUSTOM_SCOPE_CYCLIC_REFERENCE_DETECTION =
57              "org.apache.myfaces.el.unified.resolver.managedbean.customScopeCyclicReferenceDetection";
58  
59      // adapted from Manfred's JSF 1.1 VariableResolverImpl
60      protected static final Map<String, Scope> STANDARD_SCOPES = new HashMap<String, Scope>(16);
61  
62      static
63      {
64          STANDARD_SCOPES.put("request", new Scope()
65          {
66              public void put(FacesContext facesContext, ExternalContext extContext, String name, Object obj)
67              {
68                  extContext.getRequestMap().put(name, obj);
69              }
70          });
71  
72          STANDARD_SCOPES.put("session", new Scope()
73          {
74              public void put(FacesContext facesContext, ExternalContext extContext, String name, Object obj)
75              {
76                  extContext.getSessionMap().put(name, obj);
77              }
78          });
79  
80          STANDARD_SCOPES.put("application", new Scope()
81          {
82              public void put(FacesContext facesContext, ExternalContext extContext, String name, Object obj)
83              {
84                  extContext.getApplicationMap().put(name, obj);
85              }
86          });
87  
88          STANDARD_SCOPES.put("none", new Scope()
89          {
90              public void put(FacesContext facesContext, ExternalContext extContext, String name, Object obj)
91              {
92                  // do nothing
93              }
94          });
95          
96          // jsf 2.0 view scope
97          STANDARD_SCOPES.put("view", new Scope()
98          {
99              public void put(FacesContext facesContext, ExternalContext extContext, String name, Object obj)
100             {
101                 facesContext.getViewRoot().getViewMap().put(name, obj);
102             }
103         });
104     }
105 
106     /**
107      * Stores all scopes defined for this instance of <code>VariableResolver</code>
108      * <p>
109      * Can store instances of <code>Scope</code> which have the ability to dynamically resolve against ExternalContext
110      * for put operations.
111      * </p>
112      * <p>
113      * WARNING: this implementation is not serialized as it is thread safe because it does not update/add to _scopes
114      * after object initialization. If you need to add your own scopes, either extend and add more in an initialization
115      * block, or add proper sychronization
116      * </p>
117      */
118     protected final Map<String, Scope> _scopes = new HashMap<String, Scope>(16);
119     {
120         _scopes.putAll(STANDARD_SCOPES);
121     }
122 
123     /**
124      * RuntimeConfig is instantiated once per servlet and never changes--we can safely cache it
125      */
126     private RuntimeConfig runtimeConfig;
127 
128     private ManagedBeanBuilder beanBuilder = new ManagedBeanBuilder();
129 
130     /** Creates a new instance of ManagedBeanResolver */
131     public ManagedBeanResolver()
132     {
133     }
134 
135     @Override
136     public void setValue(final ELContext context, final Object base, final Object property, final Object value)
137         throws NullPointerException, PropertyNotFoundException, PropertyNotWritableException, ELException
138     {
139 
140         if ((base == null) && (property == null))
141         {
142             throw new PropertyNotFoundException();
143         }
144 
145     }
146 
147     @Override
148     public boolean isReadOnly(final ELContext context, final Object base, final Object property)
149         throws NullPointerException, PropertyNotFoundException, ELException
150     {
151 
152         if ((base == null) && (property == null))
153         {
154             throw new PropertyNotFoundException();
155         }
156 
157         return false;
158     }
159 
160     @Override
161     @SuppressWarnings("unchecked")
162     public Object getValue(final ELContext context, final Object base, final Object property)
163         throws NullPointerException, PropertyNotFoundException, ELException
164     {
165         // we only resolve ManagedBean instances, not properties of those  
166         if (base != null)
167         {
168             return null;
169         }
170 
171         if (property == null)
172         {
173             throw new PropertyNotFoundException();
174         }
175 
176         final FacesContext facesContext = facesContext(context);
177         if (facesContext == null)
178         {
179             return null;
180         }
181         final ExternalContext extContext = facesContext.getExternalContext();
182         if (extContext == null)
183         {
184             return null;
185         }
186 
187         final boolean startup = (extContext instanceof StartupServletExternalContextImpl);
188         
189         // request scope (not available at startup)
190         if (!startup)
191         {
192             if (extContext.getRequestMap().containsKey(property))
193             {
194                 return null;
195             }
196         }
197 
198         // view scope
199         UIViewRoot root = facesContext.getViewRoot();
200         if (root != null)
201         {
202             Map<String, Object> viewMap = root.getViewMap(false);
203             if (viewMap != null && viewMap.containsKey(property))
204             {
205                 return null;
206             }
207         }
208 
209         // session scope (not available at startup)
210         if (!startup)
211         {
212             if (extContext.getSessionMap().containsKey(property))
213             {
214                 return null;
215             }
216         }
217 
218         // application scope
219         if (extContext.getApplicationMap().containsKey(property))
220         {
221             return null;
222         }
223 
224         // not found in standard scopes - get ManagedBean metadata object
225         // In order to get the metadata object, we need property to be the managed bean name (--> String)
226         if (!(property instanceof String))
227         {
228             return null;
229         }
230         final String strProperty = (String) property;
231 
232         final ManagedBean managedBean = runtimeConfig(context).getManagedBean(strProperty);
233         Object beanInstance = null;
234         if (managedBean != null)
235         {
236             context.setPropertyResolved(true);
237             
238             // managed-bean-scope could be a ValueExpression pointing to a Map (since 2.0) --> custom scope
239             if (managedBean.isManagedBeanScopeValueExpression())
240             {
241                 // check for cyclic references in custom scopes, if we are not in Production stage
242                 boolean checkCyclicReferences = !facesContext.isProjectStage(ProjectStage.Production);
243                 List<String> cyclicReferences = null;
244                 
245                 if (checkCyclicReferences)
246                 {
247                     final Map<String, Object> requestMap = facesContext.getExternalContext().getRequestMap();
248                     final String managedBeanName = managedBean.getManagedBeanName();
249                     
250                     cyclicReferences = (List<String>) requestMap.get(CUSTOM_SCOPE_CYCLIC_REFERENCE_DETECTION);
251                     if (cyclicReferences == null)
252                     {
253                         cyclicReferences = new ArrayList<String>();
254                         requestMap.put(CUSTOM_SCOPE_CYCLIC_REFERENCE_DETECTION, cyclicReferences);
255                     }
256                     else if (cyclicReferences.contains(managedBeanName))
257                     {
258                         throw new ELException("Detected cyclic reference to managedBean " + managedBeanName);
259                     }
260 
261                     cyclicReferences.add(managedBeanName);
262                 }
263                 try
264                 {
265                     Object customScope = managedBean.getManagedBeanScopeValueExpression(facesContext)
266                                                 .getValue(facesContext.getELContext());
267                     if (customScope instanceof Map)
268                     {
269                         beanInstance = ((Map) customScope).get(managedBean.getManagedBeanName());
270                     }
271                     else if (customScope != null)
272                     {
273                         throw new FacesException("The expression '" + managedBean.getManagedBeanScope() + 
274                                 "' does not evaluate to java.util.Map. It evaluates to '" + customScope + 
275                                 "' of type " + customScope.getClass().getName());
276                     }
277                     else
278                     {
279                         log.warning("Custom scope '" + managedBean.getManagedBeanScope() +
280                                 "' evaluated to null. Unable to determine if managed bean '" +
281                                 managedBean.getManagedBeanName() + "' exists.");
282                     }
283                 }
284                 finally
285                 {
286                     if (checkCyclicReferences)
287                     {
288                         cyclicReferences.remove(managedBean.getManagedBeanName());
289                     }
290                 }
291             }
292 
293             // not found in any scope - create instance!
294             if (beanInstance == null)
295             {
296                 beanInstance = createManagedBean(managedBean, facesContext);
297             }
298         }
299 
300         return beanInstance;
301     }
302 
303     // Create a managed bean. If the scope of the bean is "none" then
304     // return it right away. Otherwise store the bean in the appropriate
305     // scope and return null.
306     //
307     // adapted from Manfred's JSF 1.1 VariableResolverImpl
308     @SuppressWarnings("unchecked")
309     private Object createManagedBean(final ManagedBean managedBean, final FacesContext facesContext) throws ELException
310     {
311 
312         final ExternalContext extContext = facesContext.getExternalContext();
313         final Map<Object, Object> facesContextMap = facesContext.getAttributes();
314         final String managedBeanName = managedBean.getManagedBeanName();
315 
316         // check for cyclic references
317         List<String> beansUnderConstruction = (List<String>) facesContextMap.get(BEANS_UNDER_CONSTRUCTION);
318         if (beansUnderConstruction == null)
319         {
320             beansUnderConstruction = new ArrayList<String>();
321             facesContextMap.put(BEANS_UNDER_CONSTRUCTION, beansUnderConstruction);
322         }
323         else if (beansUnderConstruction.contains(managedBeanName))
324         {
325             throw new ELException("Detected cyclic reference to managedBean " + managedBeanName);
326         }
327 
328         beansUnderConstruction.add(managedBeanName);
329 
330         Object obj = null;
331         try
332         {
333             obj = beanBuilder.buildManagedBean(facesContext, managedBean);
334         }
335         finally
336         {
337             beansUnderConstruction.remove(managedBeanName);
338         }
339 
340         putInScope(managedBean, facesContext, extContext, obj);
341 
342         return obj;
343     }
344 
345     @SuppressWarnings("unchecked")
346     private void putInScope(final ManagedBean managedBean, final FacesContext facesContext,
347             final ExternalContext extContext, final Object obj)
348     {
349 
350         final String managedBeanName = managedBean.getManagedBeanName();
351 
352         if (obj == null)
353         {
354             if (log.isLoggable(Level.FINE))
355             {
356                 log.fine("Variable '" + managedBeanName + "' could not be resolved.");
357             }
358         }
359         else
360         {
361             final String scopeKey = managedBean.getManagedBeanScope();
362 
363             // find the scope handler object
364             final Scope scope = _scopes.get(scopeKey);
365             if (scope != null)
366             {
367                 scope.put(facesContext, extContext, managedBeanName, obj);
368             }
369             else if (managedBean.isManagedBeanScopeValueExpression())
370             {
371                 // managed-bean-scope could be a ValueExpression pointing to a Map (since 2.0)
372                 // Optimisation: We do NOT check for cyclic references here, because when we reach this code,
373                 // we have already checked for cyclic references in the custom scope
374                 Object customScope = managedBean.getManagedBeanScopeValueExpression(facesContext)
375                         .getValue(facesContext.getELContext());
376                 
377                 if (customScope instanceof Map)
378                 {
379                     ((Map) customScope).put(managedBeanName, obj);
380                 }
381                 else if (customScope != null)
382                 {
383                     throw new FacesException("The expression '" + scopeKey + "' does not evaluate to " +
384                             "java.util.Map. It evaluates to '" + customScope + "' of type " + 
385                             customScope.getClass().getName());
386                 }
387                 else
388                 {
389                     log.warning("Custom scope '" + scopeKey + "' evaluated to null. " +
390                             "Cannot store managed bean '" + managedBeanName + "' in custom scope.");
391                 }
392             }
393             else
394             {
395                 log.severe("Managed bean '" + managedBeanName + "' has illegal scope: " + scopeKey);
396             }
397         }
398 
399     }
400 
401     // get the FacesContext from the ELContext
402     private static FacesContext facesContext(final ELContext context)
403     {
404         return (FacesContext)context.getContext(FacesContext.class);
405     }
406 
407     @Override
408     public Class<?> getType(final ELContext context, final Object base, final Object property)
409         throws NullPointerException, PropertyNotFoundException, ELException
410     {
411 
412         if ((base == null) && (property == null))
413         {
414             throw new PropertyNotFoundException();
415         }
416 
417         return null;
418     }
419 
420     @Override
421     public Iterator<FeatureDescriptor> getFeatureDescriptors(final ELContext context, final Object base)
422     {
423 
424         if (base != null)
425         {
426             return null;
427         }
428 
429         final ArrayList<FeatureDescriptor> descriptors = new ArrayList<FeatureDescriptor>();
430 
431         final Map<String, ManagedBean> managedBeans = runtimeConfig(context).getManagedBeans();
432         for (Map.Entry<String, ManagedBean> managedBean : managedBeans.entrySet())
433         {
434             descriptors.add(makeDescriptor(managedBean.getKey(), managedBean.getValue()));
435         }
436 
437         return descriptors.iterator();
438     }
439 
440     private static FeatureDescriptor makeDescriptor(final String beanName, final ManagedBean managedBean)
441     {
442         final FeatureDescriptor fd = new FeatureDescriptor();
443         fd.setValue(ELResolver.RESOLVABLE_AT_DESIGN_TIME, Boolean.TRUE);
444         fd.setValue(ELResolver.TYPE, managedBean.getManagedBeanClass());
445         fd.setName(beanName);
446         fd.setDisplayName(beanName);
447         fd.setShortDescription(managedBean.getDescription());
448         fd.setExpert(false);
449         fd.setHidden(false);
450         fd.setPreferred(true);
451         return fd;
452     }
453 
454     protected RuntimeConfig runtimeConfig(final ELContext context)
455     {
456         final FacesContext facesContext = facesContext(context);
457 
458         // application-level singleton - we can safely cache this
459         if (this.runtimeConfig == null)
460         {
461             this.runtimeConfig = RuntimeConfig.getCurrentInstance(facesContext.getExternalContext());
462         }
463 
464         return runtimeConfig;
465     }
466 
467     @Override
468     public Class<?> getCommonPropertyType(final ELContext context, final Object base)
469     {
470         if (base == null)
471         {
472             return Object.class;
473         }
474 
475         return null;
476     }
477 
478     interface Scope
479     {
480         public void put(FacesContext facesContext, ExternalContext extContext, String name, Object obj);
481     }
482 }