Revision: 500
          http://svn.sourceforge.net/stripes/?rev=500&view=rev
Author:   bengunter
Date:     2007-03-29 21:09:09 -0700 (Thu, 29 Mar 2007)

Log Message:
-----------
Resolved STS-319: Request and response objects should be released when using 
flash scope.
At the end of each request, any ActionBeans in the current flash scope have 
their
contexts' request and response objects replaced with objects that are safe to 
use on the
next request.

- Added FlashRequest and FlashResponseInvocationHandler classes

- Deprecated FlashScope(HttpServletRequest) in favor of
  FlashScope(HttpServletRequest, Integer)

- The key for the current flash scope for a request is now stored as a request 
attribute

- Some cleanup code was moved from StripesFilter.flashOutbound() to
  FlashScope.requestComplete()

- Added additional flash scope tests, including tests for the new request and 
response

Modified Paths:
--------------
    trunk/stripes/src/net/sourceforge/stripes/controller/FlashScope.java
    trunk/stripes/src/net/sourceforge/stripes/controller/StripesConstants.java
    trunk/stripes/src/net/sourceforge/stripes/controller/StripesFilter.java
    trunk/tests/src/net/sourceforge/stripes/controller/FlashScopeTests.java

Added Paths:
-----------
    trunk/stripes/src/net/sourceforge/stripes/controller/FlashRequest.java
    
trunk/stripes/src/net/sourceforge/stripes/controller/FlashResponseInvocationHandler.java

Added: trunk/stripes/src/net/sourceforge/stripes/controller/FlashRequest.java
===================================================================
--- trunk/stripes/src/net/sourceforge/stripes/controller/FlashRequest.java      
                        (rev 0)
+++ trunk/stripes/src/net/sourceforge/stripes/controller/FlashRequest.java      
2007-03-30 04:09:09 UTC (rev 500)
@@ -0,0 +1,377 @@
+package net.sourceforge.stripes.controller;
+
+import java.io.BufferedReader;
+import java.io.Serializable;
+import java.security.Principal;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletInputStream;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+
+import net.sourceforge.stripes.exception.StripesRuntimeException;
+import net.sourceforge.stripes.exception.StripesServletException;
+
+/**
+ * Captures the state of an [EMAIL PROTECTED] 
javax.servlet.http.HttpServletRequest} so that the information
+ * contained therein can be carried over to the next request for use by the 
flash scope. There are
+ * several methods in here that cannot be faked and so must delegate to an 
active [EMAIL PROTECTED]
+ * javax.servlet.http.HttpServletRequest} object, the [EMAIL PROTECTED] 
#delegate}. If one of these methods is
+ * called and there is no delegate object set on the instance, they will throw 
a [EMAIL PROTECTED]
+ * net.sourceforge.stripes.exception.StripesRuntimeException}. Unless this 
class is used outside its
+ * intended context (during a live request processed through [EMAIL PROTECTED] 
StripesFilter}), you won't need
+ * to worry about that.
+ *
+ * @author Ben Gunter
+ * @since Stripes 1.4.3
+ */
+public class FlashRequest implements HttpServletRequest, Serializable {
+    private Cookie[] cookies;
+    private HttpServletRequest delegate;
+    private List<Locale> locales;
+    private Locale locale;
+    private Map<String, List<String>> headers = new HashMap<String, 
List<String>>();
+    private Map<String, Long> dateHeaders = new HashMap<String, Long>();
+    private Map<String, Object> attributes = new HashMap<String, Object>();
+    private Map<String, String[]> parameters = new HashMap<String, String[]>();
+    private String authType;
+    private String characterEncoding;
+    private String contentType;
+    private String contextPath;
+    private String localAddr;
+    private String localName;
+    private String method;
+    private String pathInfo;
+    private String pathTranslated;
+    private String protocol;
+    private String queryString;
+    private String remoteAddr;
+    private String remoteHost;
+    private String remoteUser;
+    private String requestURI;
+    private String requestedSessionId;
+    private String scheme;
+    private String serverName;
+    private String servletPath;
+    private StringBuffer requestURL;
+    private boolean requestedSessionIdFromCookie;
+    private boolean requestedSessionIdFromURL;
+    private boolean requestedSessionIdFromUrl;
+    private boolean requestedSessionIdValid;
+    private boolean secure;
+    private int localPort;
+    private int remotePort;
+    private int serverPort;
+    
+    public static StripesRequestWrapper wrapRequest(HttpServletRequest 
request) {
+        try {
+            return new StripesRequestWrapper(new FlashRequest(request));
+        }
+        catch (StripesServletException e) {
+            throw new StripesRuntimeException(e);
+        }
+    }
+
+    @SuppressWarnings({ "unchecked", "deprecation" })
+    public FlashRequest(HttpServletRequest prototype) {
+        // copy properties
+        authType = prototype.getAuthType();
+        characterEncoding = prototype.getCharacterEncoding();
+        contentType = prototype.getContentType();
+        contextPath = prototype.getContextPath();
+        cookies = prototype.getCookies();
+        localAddr = prototype.getLocalAddr();
+        localName = prototype.getLocalName();
+        localPort = prototype.getLocalPort();
+        locale = prototype.getLocale();
+        method = prototype.getMethod();
+        pathInfo = prototype.getPathInfo();
+        pathTranslated = prototype.getPathTranslated();
+        protocol = prototype.getProtocol();
+        queryString = prototype.getQueryString();
+        remoteAddr = prototype.getRemoteAddr();
+        remoteHost = prototype.getRemoteHost();
+        remotePort = prototype.getRemotePort();
+        remoteUser = prototype.getRemoteUser();
+        requestURI = prototype.getRequestURI();
+        requestURL = prototype.getRequestURL();
+        requestedSessionId = prototype.getRequestedSessionId();
+        requestedSessionIdFromCookie = 
prototype.isRequestedSessionIdFromCookie();
+        requestedSessionIdFromURL = prototype.isRequestedSessionIdFromURL();
+        requestedSessionIdFromUrl = prototype.isRequestedSessionIdFromUrl();
+        requestedSessionIdValid = prototype.isRequestedSessionIdValid();
+        scheme = prototype.getScheme();
+        secure = prototype.isSecure();
+        serverName = prototype.getServerName();
+        serverPort = prototype.getServerPort();
+        servletPath = prototype.getServletPath();
+
+        // copy attributes
+        for (String key : Collections.list((Enumeration<String>) 
prototype.getAttributeNames())) {
+            attributes.put(key, prototype.getAttribute(key));
+        }
+
+        // copy headers
+        for (String key : Collections.list((Enumeration<String>) 
prototype.getHeaderNames())) {
+            headers.put(key, Collections.list(prototype.getHeaders(key)));
+            try {
+                dateHeaders.put(key, prototype.getDateHeader(key));
+            }
+            catch (Exception e) {
+            }
+        }
+
+        // copy locales
+        locales = Collections.list(prototype.getLocales());
+
+        // copy parameters
+        parameters.putAll(prototype.getParameterMap());
+    }
+
+    protected HttpServletRequest getDelegate() {
+        if (delegate == null) {
+            throw new IllegalStateException(
+                    "Attempt to access a delegate method of " +
+                    FlashRequest.class.getName() +
+                    " but no delegate request has been set");
+        }
+        return delegate;
+    }
+
+    public void setDelegate(HttpServletRequest delegate) {
+        this.delegate = delegate;
+    }
+
+    public String getAuthType() {
+        return authType;
+    }
+
+    public Cookie[] getCookies() {
+        return cookies;
+    }
+
+    public long getDateHeader(String name) {
+        Long value = dateHeaders.get(name);
+        return value == null ? 0 : value;
+    }
+
+    public String getHeader(String name) {
+        List<String> values = headers.get(name);
+        return values != null && values.size() > 0 ? values.get(0) : null;
+    }
+
+    public Enumeration<String> getHeaders(String name) {
+        return Collections.enumeration(headers.get(name));
+    }
+
+    public Enumeration<String> getHeaderNames() {
+        return Collections.enumeration(headers.keySet());
+    }
+
+    public int getIntHeader(String name) {
+        try {
+            return Integer.parseInt(getHeader(name));
+        }
+        catch (Exception e) {
+            return 0;
+        }
+    }
+
+    public String getMethod() {
+        return method;
+    }
+
+    public String getPathInfo() {
+        return pathInfo;
+    }
+
+    public String getPathTranslated() {
+        return pathTranslated;
+    }
+
+    public String getContextPath() {
+        return contextPath;
+    }
+
+    public String getQueryString() {
+        return queryString;
+    }
+
+    public String getRemoteUser() {
+        return remoteUser;
+    }
+
+    public boolean isUserInRole(String role) {
+        return getDelegate().isUserInRole(role);
+    }
+
+    public Principal getUserPrincipal() {
+        return getDelegate().getUserPrincipal();
+    }
+
+    public String getRequestedSessionId() {
+        return requestedSessionId;
+    }
+
+    public String getRequestURI() {
+        return requestURI;
+    }
+
+    public StringBuffer getRequestURL() {
+        return new StringBuffer(requestURL.toString());
+    }
+
+    public String getServletPath() {
+        return servletPath;
+    }
+
+    public HttpSession getSession(boolean create) {
+        return getDelegate().getSession(create);
+    }
+
+    public HttpSession getSession() {
+        return getDelegate().getSession();
+    }
+
+    public boolean isRequestedSessionIdValid() {
+        return requestedSessionIdValid;
+    }
+
+    public boolean isRequestedSessionIdFromCookie() {
+        return requestedSessionIdFromCookie;
+    }
+
+    public boolean isRequestedSessionIdFromURL() {
+        return requestedSessionIdFromURL;
+    }
+
+    @Deprecated
+    public boolean isRequestedSessionIdFromUrl() {
+        return requestedSessionIdFromUrl;
+    }
+
+    public Object getAttribute(String name) {
+        return attributes.get(name);
+    }
+
+    public Enumeration<String> getAttributeNames() {
+        return Collections.enumeration(attributes.keySet());
+    }
+
+    public String getCharacterEncoding() {
+        return characterEncoding;
+    }
+
+    public void setCharacterEncoding(String characterEncoding) {
+        this.characterEncoding = characterEncoding;
+    }
+
+    public int getContentLength() {
+        return 0;
+    }
+
+    public String getContentType() {
+        return contentType;
+    }
+
+    public ServletInputStream getInputStream() {
+        return null;
+    }
+
+    public String getParameter(String name) {
+        String[] values = getParameterValues(name);
+        return values != null && values.length > 0 ? values[0] : null;
+    }
+
+    public Enumeration<String> getParameterNames() {
+        return Collections.enumeration(parameters.keySet());
+    }
+
+    public String[] getParameterValues(String name) {
+        return parameters.get(name);
+    }
+
+    public Map<String, String[]> getParameterMap() {
+        return Collections.unmodifiableMap(parameters);
+    }
+
+    public String getProtocol() {
+        return protocol;
+    }
+
+    public String getScheme() {
+        return scheme;
+    }
+
+    public String getServerName() {
+        return serverName;
+    }
+
+    public int getServerPort() {
+        return serverPort;
+    }
+
+    public BufferedReader getReader() {
+        return null;
+    }
+
+    public String getRemoteAddr() {
+        return remoteAddr;
+    }
+
+    public String getRemoteHost() {
+        return remoteHost;
+    }
+
+    public void setAttribute(String name, Object value) {
+        attributes.put(name, value);
+    }
+
+    public void removeAttribute(String name) {
+        attributes.remove(name);
+    }
+
+    public Locale getLocale() {
+        return locale;
+    }
+
+    public Enumeration<Locale> getLocales() {
+        return Collections.enumeration(locales);
+    }
+
+    public boolean isSecure() {
+        return secure;
+    }
+
+    public RequestDispatcher getRequestDispatcher(String name) {
+        return getDelegate().getRequestDispatcher(name);
+    }
+
+    @Deprecated
+    public String getRealPath(String name) {
+        return getDelegate().getRealPath(name);
+    }
+
+    public int getRemotePort() {
+        return remotePort;
+    }
+
+    public String getLocalName() {
+        return localName;
+    }
+
+    public String getLocalAddr() {
+        return localAddr;
+    }
+
+    public int getLocalPort() {
+        return localPort;
+    }
+}

Added: 
trunk/stripes/src/net/sourceforge/stripes/controller/FlashResponseInvocationHandler.java
===================================================================
--- 
trunk/stripes/src/net/sourceforge/stripes/controller/FlashResponseInvocationHandler.java
                            (rev 0)
+++ 
trunk/stripes/src/net/sourceforge/stripes/controller/FlashResponseInvocationHandler.java
    2007-03-30 04:09:09 UTC (rev 500)
@@ -0,0 +1,23 @@
+package net.sourceforge.stripes.controller;
+
+import java.io.Serializable;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+
+/**
+ * Used as the [EMAIL PROTECTED] java.lang.reflect.InvocationHandler} for a 
dynamic proxy that replaces the
+ * [EMAIL PROTECTED] javax.servlet.http.HttpServletResponse} on [EMAIL 
PROTECTED]
+ * net.sourceforge.stripes.action.ActionBeanContext}s in the flash scope after 
the current request
+ * cycle has completed.
+ * 
+ * @author Ben Gunter
+ * @since Stripes 1.4.3
+ */
+public class FlashResponseInvocationHandler implements InvocationHandler, 
Serializable {
+    public Object invoke(Object object, Method method, Object[] objects) 
throws Throwable {
+        throw new IllegalStateException(
+                "Attempt to call " + method + " after the request cycle has 
completed. " +
+                "This is most likely due to misuse of a flashed ActionBean or 
ActionBeanContext " +
+                "on the ensuing request.");
+    }
+}

Modified: trunk/stripes/src/net/sourceforge/stripes/controller/FlashScope.java
===================================================================
--- trunk/stripes/src/net/sourceforge/stripes/controller/FlashScope.java        
2007-03-27 13:30:38 UTC (rev 499)
+++ trunk/stripes/src/net/sourceforge/stripes/controller/FlashScope.java        
2007-03-30 04:09:09 UTC (rev 500)
@@ -15,15 +15,23 @@
 package net.sourceforge.stripes.controller;
 
 import net.sourceforge.stripes.action.ActionBean;
+import net.sourceforge.stripes.action.ActionBeanContext;
+import net.sourceforge.stripes.exception.StripesRuntimeException;
+import net.sourceforge.stripes.exception.StripesServletException;
 import net.sourceforge.stripes.util.Log;
 
 import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpSession;
 import java.io.Serializable;
+import java.lang.reflect.Proxy;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.Map;
+import java.util.Random;
+import java.util.concurrent.ConcurrentHashMap;
 
 /**
  * <p>A FlashScope is an object that can be used to store objects and make 
them available as
@@ -77,19 +85,45 @@
     public static final int DEFAULT_TIMEOUT_IN_SECONDS = 120;
 
     private static final Log log = Log.getInstance(FlashScope.class);
+    private static final Random random = new Random();
     private long startTime;
     private int timeout = DEFAULT_TIMEOUT_IN_SECONDS;
     private HttpServletRequest request;
+    private Integer key;
 
     /**
+     * <p>
+     * Protected constructor to prevent random creation of FlashScopes. Uses 
the request to generate
+     * a key under which the flash scope will be stored, and can be identified 
by later.
+     * </p>
+     * <p>
+     * This constructor is deprecated. [EMAIL PROTECTED] 
#FlashScope(HttpServletRequest, Integer)} is the
+     * preferred constructor.
+     * </p>
+     * 
+     * @param request the request for which this flash scope will be used.
+     */
+    @Deprecated
+    protected FlashScope(HttpServletRequest request) {
+        this.request = request;
+        StripesRequestWrapper wrapper = 
StripesRequestWrapper.findStripesWrapper(request);
+        if (wrapper == null)
+            throw new StripesRuntimeException(
+                    "No StripesRequestWrapper was found for the given 
request");
+        this.key = wrapper.hashCode();
+    }
+
+    /**
      * Protected constructor to prevent random creation of FlashScopes. Uses 
the request
      * to generate a key under which the flash scope will be stored, and can 
be identified
      * by later.
      *
      * @param request the request for which this flash scope will be used.
+     * @param key the key by which this flash scope can be looked up in the map
      */
-    protected FlashScope(HttpServletRequest request) {
+    protected FlashScope(HttpServletRequest request, Integer key) {
         this.request = request;
+        this.key = key;
     }
 
     /** Returns the timeout in seconds after which the flash scope will be 
discarded. */
@@ -102,7 +136,7 @@
      * Returns the key used to store this flash scope in the colleciton of 
flash scopes.
      */
     public Integer key() {
-        return this.request.hashCode();
+        return key;
     }
 
     /**
@@ -115,6 +149,35 @@
      * request was not made) after a period of time, so that it can be removed 
from session.</p>
      */
     public void requestComplete() {
+        // Clean up any old-age flash scopes
+        Map<Integer, FlashScope> scopes = getContainer(request, false);
+        if (scopes != null && !scopes.isEmpty()) {
+            Iterator<FlashScope> iterator = scopes.values().iterator();
+            while (iterator.hasNext()) {
+                if (iterator.next().isExpired()) {
+                    iterator.remove();
+                }
+            }
+        }
+
+        // Replace the request and response objects for the request cycle that 
is ending
+        // with objects that are safe to use on the ensuing request.
+        HttpServletRequest flashRequest = FlashRequest.wrapRequest(request);
+        HttpServletResponse flashResponse = (HttpServletResponse) 
Proxy.newProxyInstance(
+                getClass().getClassLoader(),
+                new Class<?>[] { HttpServletResponse.class },
+                new FlashResponseInvocationHandler());
+        for (Object o : this.values()) {
+            if (o instanceof ActionBean) {
+                ActionBeanContext context = ((ActionBean) o).getContext();
+                if (context != null) {
+                    context.setRequest(flashRequest);
+                    context.setResponse(flashResponse);
+                }
+            }
+        }
+
+        // start timer, clear request
         this.startTime = System.currentTimeMillis();
         this.request = null;
     }
@@ -213,15 +276,28 @@
             return null;
         }
         else {
-            Integer id = new Integer(keyString);
-            Map<Integer,FlashScope> scopes = getContainer(req, false);
-            return (scopes == null) ? null : scopes.remove(id);
+            try {
+                Integer id = new Integer(keyString);
+                Map<Integer, FlashScope> scopes = getContainer(req, false);
+                return scopes == null ? null : scopes.remove(id);
+            }
+            catch (NumberFormatException e) {
+                return null;
+            }
         }
     }
 
     /**
-     * Gets the current flash scope into which items can be stored temporarily.
-     *
+     * <p>
+     * Gets the current flash scope into which items can be stored 
temporarily. If
+     * <code>create</code> is true, then a new one will be created.
+     * </p>
+     * <p>
+     * It is assumed that the request object will be used by only one thread 
so access to the
+     * request is not synchronized. Access to the scopes map that is stored in 
the session and the
+     * static [EMAIL PROTECTED] Random} that is used to generate the keys for 
the map is synchronized.
+     * </p>
+     * 
      * @param req the current request
      * @param create if true then the FlashScope will be created when it does 
not exist already
      * @return the current FlashScope, or null if it does not exist and create 
is false
@@ -233,11 +309,21 @@
             return null;
         }
         else {
-            FlashScope scope = scopes.get(req.hashCode());
-            if (scope == null && create) {
-                scope = new FlashScope(req);
-                scopes.put(req.hashCode(), scope);
+            FlashScope scope = null;
+            Integer key = (Integer) 
req.getAttribute(StripesConstants.REQ_ATTR_CURRENT_FLASH_SCOPE);
+            if (key != null) {
+                scope = scopes.get(key);
             }
+            else if (create) {
+                synchronized (random) {
+                    do {
+                        key = random.nextInt();
+                    } while (scopes.containsKey(key));
+                    scope = new FlashScope(req, key);
+                    scopes.put(scope.key(), scope);
+                }
+                
req.setAttribute(StripesConstants.REQ_ATTR_CURRENT_FLASH_SCOPE, key);
+            }
 
             return scope;
         }
@@ -258,12 +344,19 @@
             HttpSession session =  req.getSession(create);
             Map<Integer,FlashScope> scopes = null;
             if (session != null) {
-                 scopes = (Map<Integer,FlashScope>)
-                        
session.getAttribute(StripesConstants.REQ_ATTR_FLASH_SCOPE_LOCATION);
+                scopes = getContainer(session);
 
                 if (scopes == null && create) {
-                    scopes = new HashMap<Integer,FlashScope>();
-                    
req.getSession().setAttribute(StripesConstants.REQ_ATTR_FLASH_SCOPE_LOCATION, 
scopes);
+                    synchronized 
(StripesConstants.REQ_ATTR_FLASH_SCOPE_LOCATION) {
+                        // after obtaining a lock, try looking it up again
+                        scopes = getContainer(session);
+
+                        // if still not there, then create and save it
+                        if (scopes == null) {
+                            scopes = new ConcurrentHashMap<Integer, 
FlashScope>();
+                            
session.setAttribute(StripesConstants.REQ_ATTR_FLASH_SCOPE_LOCATION, scopes);
+                        }
+                    }
                 }
             }
 
@@ -279,4 +372,19 @@
             return null;
         }
     }
+    
+    /**
+     * Internal helper method to retrieve the container for all the flash 
scopes. Will return null
+     * if the container does not exist.
+     * 
+     * @param session
+     * @return a Map of integer keys to FlashScope objects
+     * @throws IllegalStateException if the session has been invalidated
+     */
+    @SuppressWarnings("unchecked")
+    private static Map<Integer, FlashScope> getContainer(HttpSession session)
+            throws IllegalStateException {
+        return (Map<Integer, FlashScope>) session
+                .getAttribute(StripesConstants.REQ_ATTR_FLASH_SCOPE_LOCATION);
+    }
 }

Modified: 
trunk/stripes/src/net/sourceforge/stripes/controller/StripesConstants.java
===================================================================
--- trunk/stripes/src/net/sourceforge/stripes/controller/StripesConstants.java  
2007-03-27 13:30:38 UTC (rev 499)
+++ trunk/stripes/src/net/sourceforge/stripes/controller/StripesConstants.java  
2007-03-30 04:09:09 UTC (rev 500)
@@ -89,6 +89,9 @@
      * hashcode of the request that generated them.
      */
     String REQ_ATTR_FLASH_SCOPE_LOCATION = "__flash_scopes";
+    
+    /** The name of a request attribute that holds the lookup key of the 
current flash scope. */
+    String REQ_ATTR_CURRENT_FLASH_SCOPE = "__current_flash_scope";
 
     /**
      * Request attribute key defined by the servlet spec for storing the 
included servlet

Modified: 
trunk/stripes/src/net/sourceforge/stripes/controller/StripesFilter.java
===================================================================
--- trunk/stripes/src/net/sourceforge/stripes/controller/StripesFilter.java     
2007-03-27 13:30:38 UTC (rev 499)
+++ trunk/stripes/src/net/sourceforge/stripes/controller/StripesFilter.java     
2007-03-30 04:09:09 UTC (rev 500)
@@ -14,6 +14,7 @@
  */
 package net.sourceforge.stripes.controller;
 
+import net.sourceforge.stripes.action.ActionBean;
 import net.sourceforge.stripes.config.BootstrapPropertyResolver;
 import net.sourceforge.stripes.config.Configuration;
 import net.sourceforge.stripes.config.RuntimeConfiguration;
@@ -212,8 +213,19 @@
         FlashScope flash = FlashScope.getPrevious(req);
 
         if (flash != null) {
-            for (Map.Entry<String,Object> entry : flash.entrySet()) {
-                req.setAttribute(entry.getKey(), entry.getValue());
+            for (Map.Entry<String, Object> entry : flash.entrySet()) {
+                Object value = entry.getValue();
+                if (value instanceof ActionBean) {
+                    HttpServletRequest tmp = ((ActionBean) 
value).getContext().getRequest();
+                    if (tmp != null) {
+                        tmp = StripesRequestWrapper.findStripesWrapper(tmp);
+                        if (tmp != null) {
+                            tmp = (HttpServletRequest) 
((StripesRequestWrapper) tmp).getRequest();
+                            ((FlashRequest) tmp).setDelegate(req);
+                        }
+                    }
+                }
+                req.setAttribute(entry.getKey(), value);
             }
         }
     }
@@ -230,16 +242,6 @@
         if (flash != null) {
             flash.requestComplete();
         }
-
-        // Clean up any old-age flash scopes
-        Collection<FlashScope> flashes = FlashScope.getAllFlashScopes(req);
-        Iterator<FlashScope> iterator = flashes.iterator();
-        while (iterator.hasNext()) {
-            FlashScope f = iterator.next();
-            if (f.isExpired()) {
-                iterator.remove();
-            }
-        }
     }
 
     /** Does nothing. */

Modified: 
trunk/tests/src/net/sourceforge/stripes/controller/FlashScopeTests.java
===================================================================
--- trunk/tests/src/net/sourceforge/stripes/controller/FlashScopeTests.java     
2007-03-27 13:30:38 UTC (rev 499)
+++ trunk/tests/src/net/sourceforge/stripes/controller/FlashScopeTests.java     
2007-03-30 04:09:09 UTC (rev 500)
@@ -15,6 +15,9 @@
 import net.sourceforge.stripes.action.HandlesEvent;
 
 import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import java.lang.reflect.Proxy;
 import java.util.Map;
 import java.util.regex.Pattern;
 import java.util.regex.Matcher;
@@ -27,7 +30,7 @@
 @UrlBinding("/FlashScopeTests.action")
 public class FlashScopeTests implements ActionBean {
     static final Pattern FLASH_ID_REGEX =
-            Pattern.compile(".*" + StripesConstants.URL_KEY_FLASH_SCOPE_ID + 
"=(\\d+).*");
+            Pattern.compile(".*" + StripesConstants.URL_KEY_FLASH_SCOPE_ID + 
"=(-?\\d+).*");
 
     private ActionBeanContext context;
     public ActionBeanContext getContext() { return context; }
@@ -46,6 +49,11 @@
 
         return new RedirectResolution("/FlashScopeTests.action");
     }
+    
+    @HandlesEvent("FlashBean")
+    public Resolution flashBean() {
+        return new RedirectResolution("/FlashScopeTests.action").flash(this);
+    }
 
     /** A do-nothing test handler. */
     @HandlesEvent("DoNothing")
@@ -84,5 +92,28 @@
 
         
Assert.assertEquals(FlashScope.getAllFlashScopes(trip2.getRequest()).size(), 0,
                             "FlashScope should have been removed from session 
after use.");
+        
+        // Test flashing an ActionBean
+        MockRoundtrip trip3 = new MockRoundtrip(ctx, FlashScopeTests.class, 
(MockHttpSession) trip
+                .getRequest().getSession());
+
+        // Get the flash scope ID from the redirect URL and add it back as a 
parameter
+        trip3.addParameter(StripesConstants.URL_KEY_FLASH_SCOPE_ID, id);
+        trip3.execute("FlashBean");
+
+        try {
+            ActionBeanContext tmp = 
trip3.getActionBean(getClass()).getContext();
+            HttpServletResponse response = tmp.getResponse();
+            HttpServletRequest request = tmp.getRequest();
+            Assert.assertNotNull(request);
+            Assert.assertNotNull(response);
+            
Assert.assertTrue(Proxy.class.isAssignableFrom(response.getClass()));
+            Assert.assertEquals(StripesRequestWrapper.class, 
request.getClass());
+            response.isCommitted();
+            Assert.fail(
+                    "Response should have thrown IllegalStateException after 
request cycle complete");
+        }
+        catch (IllegalStateException e) {
+        }
     }
 }


This was sent by the SourceForge.net collaborative development platform, the 
world's largest Open Source development site.

-------------------------------------------------------------------------
Take Surveys. Earn Cash. Influence the Future of IT
Join SourceForge.net's Techsay panel and you'll get the chance to share your
opinions on IT & business topics through brief surveys-and earn cash
http://www.techsay.com/default.php?page=join.php&p=sourceforge&CID=DEVDEV
_______________________________________________
Stripes-development mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/stripes-development

Reply via email to