You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@myfaces.apache.org by lu...@apache.org on 2009/10/27 18:11:45 UTC

svn commit: r830253 - in /myfaces/core/trunk/impl/src: main/java/org/apache/myfaces/config/ main/java/org/apache/myfaces/config/element/ main/java/org/apache/myfaces/config/impl/digester/elements/ main/java/org/apache/myfaces/el/unified/resolver/ test/...

Author: lu4242
Date: Tue Oct 27 17:11:44 2009
New Revision: 830253

URL: http://svn.apache.org/viewvc?rev=830253&view=rev
Log:
MYFACES-2375 <managed-bean-scope> could also be an EL expression, to allow easy implementation of new scopes (Thanks to Jakob Korherr for provide this patch)

Added:
    myfaces/core/trunk/impl/src/test/java/org/apache/myfaces/config/ManagedBeanBuilderTest.java
    myfaces/core/trunk/impl/src/test/java/org/apache/myfaces/el/unified/resolver/ManagedBeanResolverTest.java
Modified:
    myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/config/ManagedBeanBuilder.java
    myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/config/element/ManagedBean.java
    myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/config/impl/digester/elements/ManagedBean.java
    myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/el/unified/resolver/ManagedBeanResolver.java

Modified: myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/config/ManagedBeanBuilder.java
URL: http://svn.apache.org/viewvc/myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/config/ManagedBeanBuilder.java?rev=830253&r1=830252&r2=830253&view=diff
==============================================================================
--- myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/config/ManagedBeanBuilder.java (original)
+++ myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/config/ManagedBeanBuilder.java Tue Oct 27 17:11:44 2009
@@ -21,7 +21,7 @@
 import java.lang.reflect.Array;
 import java.lang.reflect.InvocationTargetException;
 import java.util.ArrayList;
-import java.util.Collection;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
@@ -36,6 +36,7 @@
 import javax.el.ValueExpression;
 import javax.faces.FacesException;
 import javax.faces.application.Application;
+import javax.faces.application.ProjectStage;
 import javax.faces.context.ExternalContext;
 import javax.faces.context.FacesContext;
 import javax.naming.NamingException;
@@ -69,6 +70,65 @@
     public final static String APPLICATION = "application";
     public final static String SESSION = "session";
     public final static String NONE = "none";
+    
+    /**
+     * Comparator used to compare Scopes in the following order:
+     * REQUEST SESSION APPLICATION NONE
+     * @author Jakob Korherr
+     */
+    private final static Comparator<String> scopeComparator = new Comparator<String>()
+    {
+
+        @Override
+        public int compare(String o1, String o2)
+        {
+            if (o1.equalsIgnoreCase(o2))
+            {
+                // the same scope
+                return 0;
+            }
+            if (o1.equalsIgnoreCase(NONE))
+            {
+                // none is greater than any other scope
+                return 1;
+            }
+            if (o1.equalsIgnoreCase(APPLICATION))
+            {
+                if (o2.equalsIgnoreCase(NONE))
+                {
+                    // application is smaller than none
+                    return -1;
+                }
+                else
+                {
+                    // ..but greater than any other scope
+                    return 1;
+                }
+            }
+            if (o1.equalsIgnoreCase(SESSION))
+            {
+                if (o2.equalsIgnoreCase(REQUEST))
+                {
+                    // session is greater than request
+                    return 1;
+                }
+                else
+                {
+                    // but smaller than any other scope
+                    return -1;
+                }
+            }
+            if (o1.equalsIgnoreCase(REQUEST))
+            {
+                // request is smaller than any other scope
+                return -1;
+            }
+            
+            // not a valid scope
+            throw new IllegalArgumentException(o1 + " is not a valid scope");
+        }
+        
+    };
 
     @SuppressWarnings("unchecked")
     public Object buildManagedBean(FacesContext facesContext, ManagedBean beanConfiguration) throws FacesException
@@ -93,8 +153,7 @@
                 case ManagedBean.INIT_MODE_PROPERTIES:
                     try
                     {
-                        initializeProperties(facesContext, beanConfiguration.getManagedProperties(),
-                                             beanConfiguration.getManagedBeanScope(), bean);
+                        initializeProperties(facesContext, beanConfiguration, bean);
                     }
                     catch (IllegalArgumentException e)
                     {
@@ -173,13 +232,12 @@
 
     @SuppressWarnings("unchecked")
     private void initializeProperties(FacesContext facesContext, 
-                                      Collection<? extends ManagedProperty> managedProperties, 
-                                      String targetScope, Object bean)
+                                      ManagedBean beanConfiguration, Object bean)
     {
         ELResolver elResolver = facesContext.getApplication().getELResolver();
         ELContext elContext = facesContext.getELContext();
 
-        for (ManagedProperty property : managedProperties)
+        for (ManagedProperty property : beanConfiguration.getManagedProperties())
         {
             Object value = null;
 
@@ -248,10 +306,11 @@
                     break;
                 case ManagedProperty.TYPE_VALUE:
                     // check for correct scope of a referenced bean
-                    if (!isInValidScope(facesContext, property, targetScope))
+                    if (!isInValidScope(facesContext, property, beanConfiguration))
                     {
                         throw new FacesException("Property " + property.getPropertyName() +
-                                " references object in a scope with shorter lifetime than the target scope " + targetScope);
+                                " references object in a scope with shorter lifetime than the target scope " +
+                                beanConfiguration.getManagedBeanScope());
                     }
                     value = property.getRuntimeValue(facesContext);
                     break;
@@ -303,78 +362,91 @@
 
 
     /**
-     * Check if the scope of the property value is valid for a bean to be stored in targetScope.
-     *
+     * Checks if the scope of the property value is valid for a bean to be stored in targetScope.
+     * If one of the scopes is a custom scope (since jsf 2.0), this method only checks the
+     * references if the current ProjectStage is not Production.
      * @param facesContext
-     * @param property     the property to be checked
-     * @param targetScope  name of the target scope of the bean under construction
+     * @param property           the property to be checked
+     * @param beanConfiguration  the ManagedBean, which will be created
      */
-    private boolean isInValidScope(FacesContext facesContext, ManagedProperty property, String targetScope)
+    private boolean isInValidScope(FacesContext facesContext, ManagedProperty property, ManagedBean beanConfiguration)
     {
         if (!property.isValueReference())
         {
             // no value reference but a literal value -> nothing to check
             return true;
         }
-        String[] expressions = extractExpressions(property.getValueBinding(facesContext).getExpressionString());
-
-        for (int i = 0; i < expressions.length; i++)
-        {
-            String expression = expressions[i];
-            if (expression == null)
+        
+        // get the targetScope (since 2.0 this could be an EL ValueExpression)
+        String targetScope = null;
+        if (beanConfiguration.isManagedBeanScopeValueExpression())
+        {
+            // the scope is a custom scope
+            // Spec says, that the developer has to take care about the references
+            // to and from managed-beans in custom scopes.
+            // However, we do check the references, if we are not in Production stage
+            if (facesContext.getApplication().getProjectStage() == ProjectStage.Production)
             {
-                continue;
+                return true;
             }
-
-            String valueScope = getScope(facesContext, expression);
-
-            // if the target scope is 'none' value scope has to be 'none', too
-            if (targetScope == null || targetScope.equalsIgnoreCase(NONE))
+            else
             {
-                if (valueScope != null && !(valueScope.equalsIgnoreCase(NONE)))
-                {
-                    return false;
-                }
-                return true;
+                targetScope = getNarrowestScope(facesContext, 
+                                                beanConfiguration
+                                                    .getManagedBeanScopeValueExpression(facesContext)
+                                                    .getExpressionString());
             }
-
-            // 'application' scope can reference 'application' and 'none'
-            if (targetScope.equalsIgnoreCase(APPLICATION))
+        }
+        else
+        {
+            targetScope = beanConfiguration.getManagedBeanScope();
+            if (targetScope == null)
             {
-                if (valueScope != null)
-                {
-                    if (valueScope.equalsIgnoreCase(REQUEST) ||
-                            valueScope.equalsIgnoreCase(SESSION))
-                    {
-                        return false;
-                    }
-                }
-                return true;
+                targetScope = NONE;
             }
+        }
+        
+        // optimization: 'request' scope can reference any value scope
+        if (targetScope.equalsIgnoreCase(REQUEST))
+        {
+            return true;
+        }
+        
+        String valueScope = getNarrowestScope(facesContext, 
+                                              property.getValueBinding(facesContext)
+                                                  .getExpressionString());
+        
+        // the target scope needs to have a shorter (or equal) lifetime than the value scope
+        return (scopeComparator.compare(targetScope, valueScope) <= 0);
+    }
 
-            // 'session' scope can reference 'session', 'application', and 'none' but not 'request'
-            if (targetScope.equalsIgnoreCase(SESSION))
+    /**
+     * Gets the narrowest scope to which the ValueExpression points.
+     * @param facesContext
+     * @param valueExpression
+     * @return
+     */
+    private String getNarrowestScope(FacesContext facesContext, String valueExpression)
+    {
+        List<String> expressions = extractExpressions(valueExpression);
+        String narrowestScope = NONE;
+        
+        for (String expression : expressions)
+        {
+            String valueScope = getScope(facesContext, expression);
+            if (valueScope == null)
             {
-                if (valueScope != null)
-                {
-                    if (valueScope.equalsIgnoreCase(REQUEST))
-                    {
-                        return false;
-                    }
-                }
-                return true;
+                continue;
             }
-
-            // 'request' scope can reference any value scope
-            if (targetScope.equalsIgnoreCase(REQUEST))
+            if (scopeComparator.compare(valueScope, narrowestScope) < 0)
             {
-                return true;
+                narrowestScope = valueScope;
             }
         }
-        return false;
+        
+        return narrowestScope;
     }
-
-
+    
     private String getScope(FacesContext facesContext, String expression)
     {
         String beanName = getFirstSegment(expression);
@@ -450,7 +522,29 @@
 
         if (mbc != null)
         {
-            return mbc.getManagedBeanScope();
+            // managed-bean-scope could be a EL ValueExpression (since 2.0)
+            if (mbc.isManagedBeanScopeValueExpression())
+            {   
+                // the scope is a custom scope
+                // Spec says, that the developer has to take care about the references
+                // to and from managed-beans in custom scopes.
+                // However, we do check the references, if we are not in Production stage
+                if (facesContext.getApplication().getProjectStage() == ProjectStage.Production)
+                {
+                    // we return NONE, because the NONE scope can be referenced by any other scope
+                    return NONE;
+                }
+                else
+                {
+                    return getNarrowestScope(facesContext, 
+                                             mbc.getManagedBeanScopeValueExpression(facesContext)
+                                                 .getExpressionString());
+                }
+            }
+            else
+            {
+                return mbc.getManagedBeanScope();
+            }
         }
 
         return null;
@@ -483,20 +577,15 @@
 
     }
 
-    private String[] extractExpressions(String expressionString)
+    private List<String> extractExpressions(String expressionString)
     {
-        String[] expressions = expressionString.split("\\#\\{");
-        for (int i = 0; i < expressions.length; i++)
+        List<String> expressions = new ArrayList<String>();
+        for (String expression : expressionString.split("\\#\\{"))
         {
-            String expression = expressions[i];
-            if (expression.trim().length() == 0)
-            {
-                expressions[i] = null;
-            }
-            else
+            int index = expression.indexOf('}');
+            if (index >= 0)
             {
-                int index = expression.indexOf('}');
-                expressions[i] = expression.substring(0, index);
+                expressions.add(expression.substring(0, index));
             }
         }
         return expressions;

Modified: myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/config/element/ManagedBean.java
URL: http://svn.apache.org/viewvc/myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/config/element/ManagedBean.java?rev=830253&r1=830252&r2=830253&view=diff
==============================================================================
--- myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/config/element/ManagedBean.java (original)
+++ myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/config/element/ManagedBean.java Tue Oct 27 17:11:44 2009
@@ -20,6 +20,9 @@
 
 import java.util.Collection;
 
+import javax.el.ValueExpression;
+import javax.faces.context.FacesContext;
+
 /**
  * @author Manfred Geiler (latest modification by $Author$)
  * @version $Revision$ $Date$
@@ -38,7 +41,7 @@
     public String getManagedBeanClassName();
     public Class<?> getManagedBeanClass();
     public String getManagedBeanScope();
-
+    
     public int getInitMode();
 
     /**
@@ -49,4 +52,21 @@
     public MapEntries getMapEntries();
 
     public ListEntries getListEntries();
+    
+    /**
+     * Is the value of managed-bean-scope a EL ValueExpression?
+     * @since 2.0
+     * @return
+     */
+    public boolean isManagedBeanScopeValueExpression();
+    
+    /**
+     * Returns the ValueExpression for managed-bean-scope
+     * or null, if managed-bean-scope is literal.
+     * @param facesContext
+     * @since 2.0
+     * @return
+     */
+    public ValueExpression getManagedBeanScopeValueExpression(FacesContext facesContext);
+    
 }

Modified: myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/config/impl/digester/elements/ManagedBean.java
URL: http://svn.apache.org/viewvc/myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/config/impl/digester/elements/ManagedBean.java?rev=830253&r1=830252&r2=830253&view=diff
==============================================================================
--- myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/config/impl/digester/elements/ManagedBean.java (original)
+++ myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/config/impl/digester/elements/ManagedBean.java Tue Oct 27 17:11:44 2009
@@ -22,7 +22,11 @@
 import java.util.Collection;
 import java.util.List;
 
+import javax.el.ValueExpression;
+import javax.faces.context.FacesContext;
+
 import org.apache.myfaces.shared_impl.util.ClassUtils;
+import org.apache.myfaces.view.facelets.el.ELText;
 
 
 /**
@@ -39,6 +43,7 @@
     private List<ManagedProperty> property = new ArrayList<ManagedProperty>();
     private MapEntries mapEntries;
     private ListEntries listEntries;
+    private ValueExpression scopeValueExpression;
 
 
     public int getInitMode()
@@ -151,4 +156,29 @@
     {
         return property;
     }
+    
+    @Override
+    public boolean isManagedBeanScopeValueExpression()
+    {
+        return (scope != null) 
+                   && (scopeValueExpression != null || !ELText.isLiteral(scope));
+    }
+    
+    @Override
+    public ValueExpression getManagedBeanScopeValueExpression(FacesContext facesContext)
+    {
+        if (scopeValueExpression == null)
+        {
+            // we need to set the expected type to Object, because we have to generate a 
+            // Exception text with the actual value and the actual type of the expression,
+            // if the expression does not resolve to java.util.Map
+            scopeValueExpression = 
+                isManagedBeanScopeValueExpression()
+                ? facesContext.getApplication().getExpressionFactory()
+                        .createValueExpression(facesContext.getELContext(), scope, Object.class)
+                : null;
+        }
+        return scopeValueExpression;
+    }
+    
 }

Modified: myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/el/unified/resolver/ManagedBeanResolver.java
URL: http://svn.apache.org/viewvc/myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/el/unified/resolver/ManagedBeanResolver.java?rev=830253&r1=830252&r2=830253&view=diff
==============================================================================
--- myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/el/unified/resolver/ManagedBeanResolver.java (original)
+++ myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/el/unified/resolver/ManagedBeanResolver.java Tue Oct 27 17:11:44 2009
@@ -32,6 +32,8 @@
 import javax.el.ELResolver;
 import javax.el.PropertyNotFoundException;
 import javax.el.PropertyNotWritableException;
+import javax.faces.FacesException;
+import javax.faces.application.ProjectStage;
 import javax.faces.context.ExternalContext;
 import javax.faces.context.FacesContext;
 
@@ -50,6 +52,8 @@
     private static final Logger log = Logger.getLogger(ManagedBeanResolver.class.getName());
     private static final String BEANS_UNDER_CONSTRUCTION =
             "org.apache.myfaces.el.unified.resolver.managedbean.beansUnderConstruction";
+    private static final String CUSTOM_SCOPE_CYCLIC_REFERENCE_DETECTION =
+            "org.apache.myfaces.el.unified.resolver.managedbean.customScopeCyclicReferenceDetection";
 
     // adapted from Manfred's JSF 1.1 VariableResolverImpl
     protected static final Map<String, Scope> s_standardScopes = new HashMap<String, Scope>(16);
@@ -144,6 +148,7 @@
     }
 
     @Override
+    @SuppressWarnings("unchecked")
     public Object getValue(final ELContext context, final Object base, final Object property)
         throws NullPointerException, PropertyNotFoundException, ELException
     {
@@ -178,7 +183,67 @@
         {
             FacesContext facesContext = facesContext(context);
             context.setPropertyResolved(true);
-            beanInstance = createManagedBean(managedBean, facesContext);
+            
+            // managed-bean-scope could be a ValueExpression pointing to a Map (since 2.0)
+            if (managedBean.isManagedBeanScopeValueExpression())
+            {
+                // check for cyclic references in custom scopes, if we are not in Production stage
+                boolean checkCyclicReferences = 
+                        facesContext.getApplication().getProjectStage() != ProjectStage.Production;
+                List<String> cyclicReferences = null;
+                
+                if (checkCyclicReferences)
+                {
+                    final Map<String, Object> requestMap = facesContext.getExternalContext().getRequestMap();
+                    final String managedBeanName = managedBean.getManagedBeanName();
+                    
+                    cyclicReferences = (List<String>) requestMap.get(CUSTOM_SCOPE_CYCLIC_REFERENCE_DETECTION);
+                    if (cyclicReferences == null)
+                    {
+                        cyclicReferences = new ArrayList<String>();
+                        requestMap.put(CUSTOM_SCOPE_CYCLIC_REFERENCE_DETECTION, cyclicReferences);
+                    }
+                    else if (cyclicReferences.contains(managedBeanName))
+                    {
+                        throw new ELException("Detected cyclic reference to managedBean " + managedBeanName);
+                    }
+
+                    cyclicReferences.add(managedBeanName);
+                }
+                try
+                {
+                    Object customScope = managedBean.getManagedBeanScopeValueExpression(facesContext)
+                                                .getValue(facesContext.getELContext());
+                    if (customScope instanceof Map)
+                    {
+                        beanInstance = ((Map) customScope).get(managedBean.getManagedBeanName());
+                    }
+                    else if (customScope != null)
+                    {
+                        throw new FacesException("The expression '" + managedBean.getManagedBeanScope() + 
+                                "' does not evaluate to java.util.Map. It evaluates to '" + customScope + 
+                                "' of type " + customScope.getClass().getName());
+                    }
+                    else
+                    {
+                        log.warning("Custom scope '" + managedBean.getManagedBeanScope() +
+                                "' evaluated to null. Unable to determine if managed bean '" +
+                                managedBean.getManagedBeanName() + "' exists.");
+                    }
+                }
+                finally
+                {
+                    if (checkCyclicReferences)
+                    {
+                        cyclicReferences.remove(managedBean.getManagedBeanName());
+                    }
+                }
+            }
+            
+            if (beanInstance == null)
+            {
+                beanInstance = createManagedBean(managedBean, facesContext);
+            }
         }
 
         return beanInstance;
@@ -195,6 +260,7 @@
 
         final ExternalContext extContext = facesContext.getExternalContext();
         final Map<String, Object> requestMap = extContext.getRequestMap();
+        final String managedBeanName = managedBean.getManagedBeanName();
 
         // check for cyclic references
         List<String> beansUnderConstruction = (List<String>)requestMap.get(BEANS_UNDER_CONSTRUCTION);
@@ -203,9 +269,7 @@
             beansUnderConstruction = new ArrayList<String>();
             requestMap.put(BEANS_UNDER_CONSTRUCTION, beansUnderConstruction);
         }
-
-        final String managedBeanName = managedBean.getManagedBeanName();
-        if (beansUnderConstruction.contains(managedBeanName))
+        else if (beansUnderConstruction.contains(managedBeanName))
         {
             throw new ELException("Detected cyclic reference to managedBean " + managedBeanName);
         }
@@ -222,12 +286,14 @@
             beansUnderConstruction.remove(managedBeanName);
         }
 
-        putInScope(managedBean, extContext, obj);
+        putInScope(managedBean, facesContext, extContext, obj);
 
         return obj;
     }
 
-    private void putInScope(final ManagedBean managedBean, final ExternalContext extContext, final Object obj)
+    @SuppressWarnings("unchecked")
+    private void putInScope(final ManagedBean managedBean, final FacesContext facesContext,
+            final ExternalContext extContext, final Object obj)
     {
 
         final String managedBeanName = managedBean.getManagedBeanName();
@@ -239,18 +305,41 @@
         }
         else
         {
-
             final String scopeKey = managedBean.getManagedBeanScope();
 
             // find the scope handler object
             final Scope scope = _scopes.get(scopeKey);
-            if (scope == null)
+            if (scope != null)
             {
-                log.severe("Managed bean '" + managedBeanName + "' has illegal scope: " + scopeKey);
+                scope.put(extContext, managedBeanName, obj);
+            }
+            else if (managedBean.isManagedBeanScopeValueExpression())
+            {
+                // managed-bean-scope could be a ValueExpression pointing to a Map (since 2.0)
+                // Optimisation: We do NOT check for cyclic references here, because when we reach this code,
+                // we have already checked for cyclic references in the custom scope
+                Object customScope = managedBean
+                                        .getManagedBeanScopeValueExpression(facesContext)
+                                            .getValue(facesContext.getELContext());
+                if (customScope instanceof Map)
+                {
+                    ((Map) customScope).put(managedBeanName, obj);
+                }
+                else if (customScope != null)
+                {
+                    throw new FacesException("The expression '" + scopeKey + "' does not evaluate to " +
+                            "java.util.Map. It evaluates to '" + customScope + "' of type " + 
+                            customScope.getClass().getName());
+                }
+                else
+                {
+                    log.warning("Custom scope '" + scopeKey + "' evaluated to null. " +
+                            "Cannot store managed bean '" + managedBeanName + "' in custom scope.");
+                }
             }
             else
             {
-                scope.put(extContext, managedBeanName, obj);
+                log.severe("Managed bean '" + managedBeanName + "' has illegal scope: " + scopeKey);
             }
         }
 

Added: myfaces/core/trunk/impl/src/test/java/org/apache/myfaces/config/ManagedBeanBuilderTest.java
URL: http://svn.apache.org/viewvc/myfaces/core/trunk/impl/src/test/java/org/apache/myfaces/config/ManagedBeanBuilderTest.java?rev=830253&view=auto
==============================================================================
--- myfaces/core/trunk/impl/src/test/java/org/apache/myfaces/config/ManagedBeanBuilderTest.java (added)
+++ myfaces/core/trunk/impl/src/test/java/org/apache/myfaces/config/ManagedBeanBuilderTest.java Tue Oct 27 17:11:44 2009
@@ -0,0 +1,199 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.myfaces.config;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.faces.FacesException;
+import javax.faces.application.ProjectStage;
+
+import org.apache.myfaces.config.impl.digester.elements.ManagedBean;
+import org.apache.myfaces.config.impl.digester.elements.ManagedProperty;
+import org.apache.myfaces.el.unified.resolver.ManagedBeanResolver;
+import org.apache.shale.test.base.AbstractJsfTestCase;
+import org.apache.shale.test.el.MockValueExpression;
+import org.apache.shale.test.mock.MockApplication20;
+
+/**
+ * Class used to test ManagedBeanBuilder
+ * @author Jakob Korherr (latest modification by $Author$)
+ */
+public class ManagedBeanBuilderTest extends AbstractJsfTestCase
+{
+
+    /**
+     * A managed bean used in the test cases
+     * @author Jakob Korherr
+     */
+    public static class TestBean {
+        
+        private Map<Object, Object> scope;
+        private TestBean anotherBean;
+
+        public Map<Object, Object> getScope()
+        {
+            if (scope == null)
+            {
+                scope = new HashMap<Object, Object>();
+            }
+            return scope;
+        }
+
+        public void setScope(Map<Object, Object> scope)
+        {
+            this.scope = scope;
+        }
+
+        public TestBean getAnotherBean()
+        {
+            return anotherBean;
+        }
+
+        public void setAnotherBean(TestBean anotherBean)
+        {
+            this.anotherBean = anotherBean;
+        }
+        
+    }
+    
+    private RuntimeConfig runtimeConfig;
+    
+    public ManagedBeanBuilderTest(String name)
+    {
+        super(name);
+    }
+    
+    @Override
+    protected void setUp() throws Exception
+    {
+        super.setUp();
+        
+        // override MockApplication20 to get a ProjectStage
+        application = new MockApplication20() {
+
+            @Override
+            public ProjectStage getProjectStage()
+            {
+                return ProjectStage.Development;
+            }
+            
+        };
+        // add the ManagedBeanResolver as a ELResolver
+        ManagedBeanResolver resolver = new ManagedBeanResolver();
+        application.addELResolver(resolver);
+        facesContext.setApplication(application);
+        
+        runtimeConfig = RuntimeConfig.getCurrentInstance(externalContext);
+    }
+    
+    @Override
+    protected void tearDown() throws Exception
+    {
+        runtimeConfig = null;
+        
+        super.tearDown();
+    }
+
+    
+    /**
+     * Tests, if the ManagedBeanBuilder checks that no property of the managed bean
+     * references to a scope with a potentially shorter lifetime.
+     * E.g. a managed bean of scope session is only allowed to reference an object in
+     * the session, the application and the none scope.
+     */
+    public void testIsInValidScope()
+    {
+        // create sessionBean referencing requestBean
+        ManagedBean sessionBean = new ManagedBean();
+        sessionBean.setBeanClass(TestBean.class.getName());
+        sessionBean.setName("sessionBean");
+        sessionBean.setScope("session");
+        ManagedProperty anotherBeanProperty = new ManagedProperty();
+        anotherBeanProperty.setPropertyName("anotherBean");
+        anotherBeanProperty.setValue("#{requestBean}");
+        sessionBean.addProperty(anotherBeanProperty);
+        runtimeConfig.addManagedBean("sessionBean", sessionBean);
+        
+        // create requestBean
+        ManagedBean requestBean = new ManagedBean();
+        requestBean.setBeanClass(TestBean.class.getName());
+        requestBean.setName("requestBean");
+        requestBean.setScope("request");
+        runtimeConfig.addManagedBean("requestBean", requestBean);
+        
+        try
+        {
+            new MockValueExpression("#{sessionBean}", TestBean.class).getValue(facesContext.getELContext());
+        }
+        catch (FacesException e)
+        {
+            // success --> the ManagedBeanBuilder discovered the reference to a shorter lifetime
+            return;
+        }
+        fail();
+    }
+    
+    /**
+     * Tests the same as testIsInValidScope, but this time the managed bean
+     * has a custom scope.
+     * The spec says, that if a managed bean has a custom scope, the runtime 
+     * is not required to check the references. However, MyFaces checks the
+     * references if the ProjectStage is not Production. 
+     */
+    public void testIsInValidScopeWithCustomScopes()
+    {
+        // create scopeBean
+        ManagedBean scopeBean = new ManagedBean();
+        scopeBean.setBeanClass(TestBean.class.getName());
+        scopeBean.setName("scopeBean");
+        scopeBean.setScope("session");
+        runtimeConfig.addManagedBean("scopeBean", scopeBean);
+        
+        // create sessionBean referencing requestBean
+        ManagedBean sessionBean = new ManagedBean();
+        sessionBean.setBeanClass(TestBean.class.getName());
+        sessionBean.setName("sessionBean");
+        sessionBean.setScope("#{scopeBean.scope}");
+        ManagedProperty anotherBeanProperty = new ManagedProperty();
+        anotherBeanProperty.setPropertyName("anotherBean");
+        anotherBeanProperty.setValue("#{requestBean}");
+        sessionBean.addProperty(anotherBeanProperty);
+        runtimeConfig.addManagedBean("sessionBean", sessionBean);
+        
+        // create requestBean
+        ManagedBean requestBean = new ManagedBean();
+        requestBean.setBeanClass(TestBean.class.getName());
+        requestBean.setName("requestBean");
+        requestBean.setScope("request");
+        runtimeConfig.addManagedBean("requestBean", requestBean);
+        
+        try
+        {
+            new MockValueExpression("#{sessionBean}", TestBean.class).getValue(facesContext.getELContext());
+        }
+        catch (FacesException e)
+        {
+            // success --> the ManagedBeanBuilder discovered the reference to a shorter lifetime
+            return;
+        }
+        fail();
+    }
+
+}

Added: myfaces/core/trunk/impl/src/test/java/org/apache/myfaces/el/unified/resolver/ManagedBeanResolverTest.java
URL: http://svn.apache.org/viewvc/myfaces/core/trunk/impl/src/test/java/org/apache/myfaces/el/unified/resolver/ManagedBeanResolverTest.java?rev=830253&view=auto
==============================================================================
--- myfaces/core/trunk/impl/src/test/java/org/apache/myfaces/el/unified/resolver/ManagedBeanResolverTest.java (added)
+++ myfaces/core/trunk/impl/src/test/java/org/apache/myfaces/el/unified/resolver/ManagedBeanResolverTest.java Tue Oct 27 17:11:44 2009
@@ -0,0 +1,219 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.myfaces.el.unified.resolver;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.el.ELException;
+import javax.faces.FacesException;
+import javax.faces.application.ProjectStage;
+
+import org.apache.myfaces.config.RuntimeConfig;
+import org.apache.myfaces.config.impl.digester.elements.ManagedBean;
+import org.apache.shale.test.base.AbstractJsfTestCase;
+import org.apache.shale.test.el.MockValueExpression;
+import org.apache.shale.test.mock.MockApplication20;
+
+/**
+ * Class used to test ManagedBeanResolver
+ * @author Jakob Korherr (latest modification by $Author$)
+ */
+public class ManagedBeanResolverTest extends AbstractJsfTestCase
+{
+    
+    /**
+     * A managed bean used in the test cases
+     * @author Jakob Korherr
+     */
+    public static class TestBean {
+        
+        private Map<Object, Object> scope;
+
+        public Map<Object, Object> getScope()
+        {
+            if (scope == null)
+            {
+                scope = new HashMap<Object, Object>();
+            }
+            return scope;
+        }
+
+        public void setScope(Map<Object, Object> scope)
+        {
+            this.scope = scope;
+        }
+        
+    }
+    
+    private RuntimeConfig runtimeConfig;
+    
+    public ManagedBeanResolverTest(String name)
+    {
+        super(name);
+    }
+    
+    @Override
+    protected void setUp() throws Exception
+    {
+        super.setUp();
+        
+        // override MockApplication20 to get a ProjectStage
+        application = new MockApplication20() {
+
+            @Override
+            public ProjectStage getProjectStage()
+            {
+                return ProjectStage.Development;
+            }
+            
+        };
+        // add the ManagedBeanResolver as a ELResolver
+        ManagedBeanResolver resolver = new ManagedBeanResolver();
+        application.addELResolver(resolver);
+        facesContext.setApplication(application);
+        
+        runtimeConfig = RuntimeConfig.getCurrentInstance(externalContext);
+    }
+    
+    @Override
+    protected void tearDown() throws Exception
+    {
+        runtimeConfig = null;
+        
+        super.tearDown();
+    }
+
+    /**
+     * Tests if the ManagedBeanResolver handles custom scopes correctly
+     */
+    @SuppressWarnings("unchecked")
+    public void testCustomScope()
+    {
+        // create the custom scope
+        ManagedBean scopeBean = new ManagedBean();
+        scopeBean.setBeanClass(HashMap.class.getName());
+        scopeBean.setName("scopeBean");
+        scopeBean.setScope("application");
+        runtimeConfig.addManagedBean("scopeBean", scopeBean);
+        
+        // create the managed bean
+        ManagedBean beanInCustomScope = new ManagedBean();
+        beanInCustomScope.setBeanClass(ArrayList.class.getName());
+        beanInCustomScope.setName("beanInCustomScope");
+        beanInCustomScope.setScope("#{scopeBean}");
+        runtimeConfig.addManagedBean("beanInCustomScope", beanInCustomScope);
+        
+        // resolve the managed bean
+        Object resolvedBeanInCustomScope = new MockValueExpression("#{beanInCustomScope}", List.class)
+                                                   .getValue(facesContext.getELContext());
+        
+        // get the custom scope
+        Map resolvedScopeBean = (Map) new MockValueExpression("#{scopeBean}", Map.class)
+                                              .getValue(facesContext.getELContext());
+        
+        // the custom scope has to contain the resolved bean
+        assertTrue(resolvedScopeBean.containsKey("beanInCustomScope"));
+        assertTrue(resolvedScopeBean.get("beanInCustomScope").equals(resolvedBeanInCustomScope));
+    }
+    
+    /**
+     * Tests if the ManagedBeanResolver throws the right Exception, if a custom scope
+     * does not evaluate to java.util.Map.
+     * Spec says: If the ValueExpression does not evaluate to a Map, a FacesException 
+     * must be thrown with a message that includes the expression string, the toString() 
+     * of the value, and the type of the value.
+     */
+    public void testCustomScopeNoMap()
+    {
+        // create the custom scope
+        ManagedBean scopeBean = new ManagedBean();
+        // Scope is ArrayList instead of HashMap
+        scopeBean.setBeanClass(ArrayList.class.getName()); 
+        scopeBean.setName("scopeBean");
+        scopeBean.setScope("application");
+        runtimeConfig.addManagedBean("scopeBean", scopeBean);
+        
+        // create the managed bean
+        ManagedBean beanInCustomScope = new ManagedBean();
+        beanInCustomScope.setBeanClass(ArrayList.class.getName());
+        beanInCustomScope.setName("beanInCustomScope");
+        beanInCustomScope.setScope("#{scopeBean}");
+        runtimeConfig.addManagedBean("beanInCustomScope", beanInCustomScope);
+        
+        // resolve the managed bean
+        try
+        {
+            new MockValueExpression("#{beanInCustomScope}", List.class)
+                    .getValue(facesContext.getELContext());
+        }
+        catch (FacesException fe)
+        {
+            // the message must contain ...
+            final String message = fe.getMessage();
+            // ... the expression string 
+            assertTrue(message.contains(beanInCustomScope.getManagedBeanScope()));
+            Object resolvedScopeBean = new MockValueExpression("#{scopeBean}", List.class)
+                                               .getValue(facesContext.getELContext());
+            // ... the toString() of the value
+            assertTrue(message.contains(resolvedScopeBean.toString()));
+            // ... and the type of the value
+            assertTrue(message.contains(resolvedScopeBean.getClass().getName()));
+            return;
+        }
+        // No FacesException was thrown
+        fail();
+    }
+    
+    /**
+     * Tests if the ManagedBeanResolver detects cyclic references in custom scopes.
+     * The ManagedBeanResolver only tries to detect cyclic references if ProjectStage != Production.
+     */
+    public void testCustomScopeCyclicReferences()
+    {
+        // create m1
+        ManagedBean m1 = new ManagedBean();
+        m1.setBeanClass(TestBean.class.getName());
+        m1.setName("m1");
+        m1.setScope("#{m2.scope}");
+        runtimeConfig.addManagedBean("m1", m1);
+        
+        // create m2
+        ManagedBean m2 = new ManagedBean();
+        m2.setBeanClass(TestBean.class.getName());
+        m2.setName("m2");
+        m2.setScope("#{m1.scope}");
+        runtimeConfig.addManagedBean("m2", m2);
+        
+        // try to resolve m1
+        try
+        {
+            new MockValueExpression("#{m1}", TestBean.class).getValue(facesContext.getELContext());
+        }
+        catch (ELException e)
+        {
+            // success
+            return;
+        }
+        fail();
+    }
+
+}