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