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.context.ExternalContext;
33  import javax.faces.context.FacesContext;
34  import org.apache.commons.logging.Log;
35  import org.apache.commons.logging.LogFactory;
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  
43  /**
44   * See JSF 1.2 spec section 5.6.1.2
45   * 
46   * @author Stan Silvert
47   */
48  public class ManagedBeanResolver extends ELResolver
49  {
50      private static final Log log              = LogFactory.getLog(ManagedBeanResolver.class);
51      private static final String BEANS_UNDER_CONSTRUCTION =
52              "org.apache.myfaces.el.unified.resolver.managedbean.beansUnderConstruction";
53  
54      // adapted from Manfred's JSF 1.1 VariableResolverImpl
55      protected static final Map<String, Scope> s_standardScopes = new HashMap<String, Scope>(16);
56  
57      static
58      {
59          s_standardScopes.put("request", new Scope()
60          {
61              public void put(FacesContext facesContext, ExternalContext extContext, String name, Object obj)
62              {
63                  extContext.getRequestMap().put(name, obj);
64              }
65          });
66  
67          s_standardScopes.put("session", new Scope()
68          {
69              public void put(FacesContext facesContext, ExternalContext extContext, String name, Object obj)
70              {
71                  extContext.getSessionMap().put(name, obj);
72              }
73          });
74  
75          s_standardScopes.put("application", new Scope()
76          {
77              public void put(FacesContext facesContext, ExternalContext extContext, String name, Object obj)
78              {
79                  extContext.getApplicationMap().put(name, obj);
80              }
81          });
82  
83          s_standardScopes.put("none", new Scope()
84          {
85              public void put(FacesContext facesContext, ExternalContext extContext, String name, Object obj)
86              {
87                  // do nothing
88              }
89          });
90      }
91  
92      /**
93       * Stores all scopes defined for this instance of <code>VariableResolver</code>
94       * <p>
95       * Can store instances of <code>Scope</code> which have the ability to dynamically resolve against ExternalContext
96       * for put operations.
97       * </p>
98       * <p>
99       * WARNING: this implementation is not serialized as it is thread safe because it does not update/add to _scopes
100      * after object initialization. If you need to add your own scopes, either extend and add more in an initialization
101      * block, or add proper sychronization
102      * </p>
103      */
104     protected final Map<String, Scope> _scopes = new HashMap<String, Scope>(16);
105     {
106         _scopes.putAll(s_standardScopes);
107     }
108 
109     /**
110      * RuntimeConfig is instantiated once per servlet and never changes--we can safely cache it
111      */
112     private RuntimeConfig runtimeConfig;
113 
114     private ManagedBeanBuilder beanBuilder = new ManagedBeanBuilder();
115 
116     /** Creates a new instance of ManagedBeanResolver */
117     public ManagedBeanResolver()
118     {
119     }
120 
121     @Override
122     public void setValue(final ELContext context, final Object base, final Object property, final Object value)
123         throws NullPointerException, PropertyNotFoundException, PropertyNotWritableException, ELException
124     {
125 
126         if ((base == null) && (property == null))
127         {
128             throw new PropertyNotFoundException();
129         }
130 
131     }
132 
133     @Override
134     public boolean isReadOnly(final ELContext context, final Object base, final Object property)
135         throws NullPointerException, PropertyNotFoundException, ELException
136     {
137 
138         if ((base == null) && (property == null))
139         {
140             throw new PropertyNotFoundException();
141         }
142 
143         return false;
144     }
145 
146     @Override
147     @SuppressWarnings("unchecked")
148     public Object getValue(final ELContext context, final Object base, final Object property)
149         throws NullPointerException, PropertyNotFoundException, ELException
150     {
151         // we only resolve ManagedBean instances, not properties of those  
152         if (base != null)
153         {
154             return null;
155         }
156 
157         if (property == null)
158         {
159             throw new PropertyNotFoundException();
160         }
161 
162         final FacesContext facesContext = facesContext(context);
163         if (facesContext == null)
164         {
165             return null;
166         }
167         final ExternalContext extContext = facesContext.getExternalContext();
168         if (extContext == null)
169         {
170             return null;
171         }
172 
173         final boolean startup = (extContext instanceof StartupServletExternalContextImpl);
174         
175         // request scope (not available at startup)
176         if (!startup)
177         {
178             if (extContext.getRequestMap().containsKey(property))
179             {
180                 return null;
181             }
182         }
183 
184         // session scope (not available at startup)
185         if (!startup)
186         {
187             if (extContext.getSessionMap().containsKey(property))
188             {
189                 return null;
190             }
191         }
192 
193         // application scope
194         if (extContext.getApplicationMap().containsKey(property))
195         {
196             return null;
197         }
198 
199         // not found in standard scopes - get ManagedBean metadata object
200         // In order to get the metadata object, we need property to be the managed bean name (--> String)
201         if (!(property instanceof String))
202         {
203             return null;
204         }
205         final String strProperty = (String) property;
206 
207         final ManagedBean managedBean = runtimeConfig(context).getManagedBean(strProperty);
208         Object beanInstance = null;
209         if (managedBean != null)
210         {
211             context.setPropertyResolved(true);
212             
213             beanInstance = createManagedBean(managedBean, facesContext);
214         }
215 
216         return beanInstance;
217     }
218 
219     // Create a managed bean. If the scope of the bean is "none" then
220     // return it right away. Otherwise store the bean in the appropriate
221     // scope and return null.
222     //
223     // adapted from Manfred's JSF 1.1 VariableResolverImpl
224     @SuppressWarnings("unchecked")
225     private Object createManagedBean(final ManagedBean managedBean, final FacesContext facesContext) throws ELException
226     {
227 
228         final ExternalContext extContext = facesContext.getExternalContext();
229         final Map requestMap = extContext.getRequestMap();
230 
231         // check for cyclic references
232         List beansUnderConstruction = (List<String>) requestMap.get(BEANS_UNDER_CONSTRUCTION);
233         if (beansUnderConstruction == null)
234         {
235             beansUnderConstruction = new ArrayList<String>();
236             requestMap.put(BEANS_UNDER_CONSTRUCTION, beansUnderConstruction);
237         }
238 
239         final String managedBeanName = managedBean.getManagedBeanName();
240         if (beansUnderConstruction.contains(managedBeanName))
241         {
242             throw new ELException("Detected cyclic reference to managedBean " + managedBeanName);
243         }
244 
245         beansUnderConstruction.add(managedBeanName);
246 
247         Object obj = null;
248         try
249         {
250             obj = beanBuilder.buildManagedBean(facesContext, managedBean);
251         }
252         finally
253         {
254             beansUnderConstruction.remove(managedBeanName);
255         }
256 
257         putInScope(managedBean, facesContext, extContext, obj);
258 
259         return obj;
260     }
261 
262     @SuppressWarnings("unchecked")
263     private void putInScope(final ManagedBean managedBean, final FacesContext facesContext,
264             final ExternalContext extContext, final Object obj)
265     {
266 
267         final String managedBeanName = managedBean.getManagedBeanName();
268 
269         if (obj == null)
270         {
271             if (log.isDebugEnabled())
272                 log.debug("Variable '" + managedBeanName + "' could not be resolved.");
273         }
274         else
275         {
276             final String scopeKey = managedBean.getManagedBeanScope();
277 
278             // find the scope handler object
279             final Scope scope = _scopes.get(scopeKey);
280             if (scope != null)
281             {
282                 scope.put(facesContext, extContext, managedBeanName, obj);
283             }
284             else
285 
286             {
287                 log.error("Managed bean '" + managedBeanName + "' has illegal scope: " + scopeKey);
288             }
289         }
290 
291     }
292 
293     // get the FacesContext from the ELContext
294     private static FacesContext facesContext(final ELContext context)
295     {
296         return (FacesContext)context.getContext(FacesContext.class);
297     }
298 
299     @Override
300     public Class<?> getType(final ELContext context, final Object base, final Object property)
301         throws NullPointerException, PropertyNotFoundException, ELException
302     {
303 
304         if ((base == null) && (property == null))
305         {
306             throw new PropertyNotFoundException();
307         }
308 
309         return null;
310     }
311 
312     @Override
313     public Iterator<FeatureDescriptor> getFeatureDescriptors(final ELContext context, final Object base)
314     {
315 
316         if (base != null)
317             return null;
318 
319         final ArrayList<FeatureDescriptor> descriptors = new ArrayList<FeatureDescriptor>();
320 
321         final Map<String, ManagedBean> managedBeans = runtimeConfig(context).getManagedBeans();
322         for (Map.Entry<String, ManagedBean> managedBean : managedBeans.entrySet())
323         {
324             descriptors.add(makeDescriptor(managedBean.getKey(), managedBean.getValue()));
325         }
326 
327         return descriptors.iterator();
328     }
329 
330     private static FeatureDescriptor makeDescriptor(final String beanName, final ManagedBean managedBean)
331     {
332         final FeatureDescriptor fd = new FeatureDescriptor();
333         fd.setValue(ELResolver.RESOLVABLE_AT_DESIGN_TIME, Boolean.TRUE);
334         fd.setValue(ELResolver.TYPE, managedBean.getManagedBeanClass());
335         fd.setName(beanName);
336         fd.setDisplayName(beanName);
337         fd.setShortDescription(managedBean.getDescription());
338         fd.setExpert(false);
339         fd.setHidden(false);
340         fd.setPreferred(true);
341         return fd;
342     }
343 
344     protected RuntimeConfig runtimeConfig(final ELContext context)
345     {
346         final FacesContext facesContext = facesContext(context);
347 
348         // application-level singleton - we can safely cache this
349         if (this.runtimeConfig == null)
350         {
351             this.runtimeConfig = RuntimeConfig.getCurrentInstance(facesContext.getExternalContext());
352         }
353 
354         return runtimeConfig;
355     }
356 
357     @Override
358     public Class<?> getCommonPropertyType(final ELContext context, final Object base)
359     {
360         if (base == null)
361         {
362             return Object.class;
363         }
364 
365         return null;
366     }
367 
368     interface Scope
369     {
370         public void put(FacesContext facesContext, ExternalContext extContext, String name, Object obj);
371     }
372 }