4

I would like to create my own custom scope bean which will use HTTP session (kind of Flash scope).

According to Spring Manual I need to implement org.springframework.beans.factory.config.Scope interface

public class CustomScope implements Scope {

    @Override
    public Object get(String arg0, ObjectFactory<?> arg1) {
        // TODO Auto-generated method stub
        return null;
    }
    @Override
    public String getConversationId() {
        // TODO Auto-generated method stub
        return null;
    }
    @Override
    public void registerDestructionCallback(String arg0, Runnable arg1) {
        // TODO Auto-generated method stub
    }
    @Override
    public Object remove(String arg0) {
        // TODO Auto-generated method stub
        return null;
    }
    @Override
    public Object resolveContextualObject(String arg0) {
        // TODO Auto-generated method stub
        return null;
    }
}

My question is how can I obtain HTTP session inside of this bean? I understand that if I was creating bean in ServletContext scope I would implement ServletContextAware interface.

Please help :)

Sean Patrick Floyd
  • 292,901
  • 67
  • 465
  • 588
danny.lesnik
  • 18,479
  • 29
  • 135
  • 200

3 Answers3

9

I hope it will be useful for someone in the future, so I would like to share it.

I made some reserch on it and have found that unfortunately, it is impossible to get HTTP Session for Spring MVC.

My purpose was Flash Scope implementation for my Spring MVC Controller using PRG pattern.

Making more research in Spring Forum I've found the way to do it using HandlerInterceptor.

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import java.util.*;
import java.util.Map.Entry;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

public class FlashScopeInterceptor implements HandlerInterceptor {

    public static final String DEFAULT_ATTRIBUTE_NAME = "flashScope";
    public static final String DEFAULT_SESSION_ATTRIBUTE_NAME = FlashScopeInterceptor.class.getName();
    public static final int DEFAULT_RETENTION_COUNT = 2;

    private String sessionAttributeName = DEFAULT_SESSION_ATTRIBUTE_NAME;
    private String attributeName = DEFAULT_ATTRIBUTE_NAME;
    private int retentionCount = DEFAULT_RETENTION_COUNT;

    /**
     * Unbinds current flashScope from session. Rolls request's flashScope to
     * the next scope. Binds request's flashScope, if not empty, to the session.
     * 
     */

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception {
        if (request.getSession( false ) != null)
        {
            request.getSession().removeAttribute( this.sessionAttributeName );
        }
        Object requestAttribute = request.getAttribute( this.attributeName );
        if (requestAttribute instanceof MultiScopeModelMap)
        {
            MultiScopeModelMap attributes = (MultiScopeModelMap) requestAttribute;
            if (!attributes.isEmpty())
            {
                attributes.next();
                if (!attributes.isEmpty())
                {
                    request.getSession( true ).setAttribute( this.sessionAttributeName, attributes );
                }
            }
        }
    }

    /**
     * merge modelAndView.model['flashScope'] to current flashScope
     */
    @Override
    public void postHandle( HttpServletRequest request, HttpServletResponse response, Object handler,
            ModelAndView modelAndView) throws Exception {
        if (modelAndView != null)
        {
            Map<String, Object> modelFlashScopeMap = null;
            for (Iterator<Entry<String, Object>> iterator = ((Map<String, Object>) modelAndView.getModel()).entrySet()
                    .iterator(); iterator.hasNext();)
            {
                Entry<String, Object> entry = iterator.next();
                if (this.attributeName.equals( entry.getKey() ) && entry.getValue() instanceof Map)
                {
                    if (modelFlashScopeMap == null)
                    {
                        modelFlashScopeMap = (Map) entry.getValue();
                    }
                    else
                    {
                        modelFlashScopeMap.putAll( (Map) entry.getValue() );
                    }
                    iterator.remove();
                }
                else if (entry.getKey().startsWith( this.attributeName + "." ))
                {
                    String key = entry.getKey().substring( this.attributeName.length() + 1 );
                    if (modelFlashScopeMap == null)
                    {
                        modelFlashScopeMap = new HashMap<String, Object>();
                    }
                    modelFlashScopeMap.put( key, entry.getValue() );
                    iterator.remove();
                }
            }
            if (modelFlashScopeMap != null)
            {
                MultiScopeModelMap flashScopeMap;
                if (request.getAttribute( this.attributeName ) instanceof MultiScopeModelMap)
                {
                    flashScopeMap = (MultiScopeModelMap) request.getAttribute( this.attributeName );
                }
                else
                {
                    flashScopeMap = new MultiScopeModelMap( this.retentionCount );
                }
                flashScopeMap.putAll( modelFlashScopeMap );
                request.setAttribute( this.attributeName, flashScopeMap );
            }
        }
    }

    /**
     * binds session flashScope to current session, if not empty. Otherwise cleans up empty
     * flashScope
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        HttpSession session = request.getSession( false );
        if (session != null)
        {
            Object sessionAttribute = session.getAttribute( this.sessionAttributeName );
            if (sessionAttribute instanceof MultiScopeModelMap)
            {
                MultiScopeModelMap flashScope = (MultiScopeModelMap) sessionAttribute;
                if (flashScope.isEmpty())
                {
                    session.removeAttribute( this.sessionAttributeName );
                }
                else
                {
                    request.setAttribute( this.attributeName, flashScope );
                }
            }
        }
        return true;
    }
}

Now MultiScopeModelMap.java

import java.io.Serializable;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;

import org.apache.commons.collections.map.CompositeMap;
import org.apache.commons.collections.map.CompositeMap.MapMutator;

public class MultiScopeModelMap extends CompositeMap implements Serializable, MapMutator
{
    public MultiScopeModelMap(int num)
    {
        super();
        setMutator( this );
        for(int i = 0; i < num; ++i)
        {
            addComposited( new HashMap() );
        }
    }

    /** Shadows composite map. */
    private final LinkedList<Map> maps = new LinkedList<Map>();

    @Override
    public synchronized void addComposited( Map map ) throws IllegalArgumentException
    {
        super.addComposited( map );
        this.maps.addLast( map );
    }



    @Override
    public synchronized Map removeComposited( Map map )
    {
        Map removed = super.removeComposited( map );
        this.maps.remove( map );
        return removed;
    }



    /** 
     * Starts a new scope. 
     * All items added in the session before the previous session are removed.
     * All items added in the previous scope are still retrievable and removable.
     */ 
    public void next()
    {
        removeComposited( this.maps.getFirst() );
        addComposited( new HashMap() );
    }

    public Object put( CompositeMap map, Map[] composited, Object key, Object value )
    {
        if(composited.length < 1)
        {
            throw new UnsupportedOperationException("No composites to add elements to");
        }
        Object result = map.get( key );
        if(result != null)
        {
            map.remove( key );
        }
        composited[composited.length-1].put( key, value );
        return result;
    }

    public void putAll( CompositeMap map, Map[] composited, Map mapToAdd )
    {
        for(Entry entry: (Set<Entry>)mapToAdd.entrySet())
        {
            put(map, composited, entry.getKey(), entry.getValue());
        }
    }

    public void resolveCollision( CompositeMap composite, Map existing, Map added, Collection intersect )
    {
        existing.keySet().removeAll( intersect );       
    }

    @Override
    public String toString()
    {
        return new HashMap(this).toString();
    }


}

Usage:

@RequestMapping(value="/login.do", method=RequestMethod.POST)
    public ModelAndView login(@Valid User user){
        ModelAndView mv = new ModelAndView("redirect:result.html");
        if (authService.authenticate(user.getUserName(), user.getPassword()))
            mv.addObject("flashScope.message", "Success");
        //else
            mv.addObject("flashScope.message", "Login Failed");
        return mv;
    }

@RequestMapping(value ="/result.html", method=RequestMethod.GET)
    public ModelAndView result(){
        ModelAndView mv = new ModelAndView("login/loginAction");
        return mv;
    }

In JSP the usage is very simple:

${flashScope.message}

In addition you need to configure FlashScopeInterceptor class as interceptor.

<bean id="flashScopeInterceptor" class="x.y.z.FlashScopeInterceptor" />
<bean id="handlerMapping" class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
  <property name="interceptors">
    <list><ref bean="flashScopeInterceptor"/></list>
  </property>
</bean> 
danny.lesnik
  • 18,479
  • 29
  • 135
  • 200
  • 1
    @nMoncho, but no longer relevant if you are using Spring 3.1 or higher where RedirectAttributes object was introduced. – danny.lesnik Apr 05 '13 at 20:55
  • yeah I know, but the app is already in production (and I don't know how risky is to change Spring versions, probably not very much) – nMoncho Apr 08 '13 at 19:27
  • It is risky, if you want to use RedirectAttributes you your Request mapped method should return only String instead of ModelAndView and if you do return ModelAndView you have to change MVC logic. – danny.lesnik Apr 08 '13 at 21:45
  • I have tried this approach and am having success all the way up to rendering in the jsp. I know this is old, but what might I be doing wrong here: http://stackoverflow.com/questions/19481378/need-help-debugging-a-jsp-model-rendering-issue – sydneyos Oct 20 '13 at 19:42
2

I recommend to have a look at the source code of org.springframework.web.context.request.SessionScope. This scope must have solved the same problem some how.

It looks like that they use: RequestContextHolder.currentRequestAttributes().getSessionId()

Ralph
  • 118,862
  • 56
  • 287
  • 383
  • I agree (+1). Generating Custom Scopes is a very advanced topic and it shouldn't be done without a reason – Sean Patrick Floyd May 03 '11 at 07:32
  • So what would be the correct way to implement Flash Scope in Spring MVC? – danny.lesnik May 03 '11 at 08:47
  • danny.lesnik: "My question is how can I obtain HTTP session inside of this bean?" - I gave you the hint to the SessionScope - it was only a hint – Ralph May 03 '11 at 08:58
  • @Ralph, I understand that it was the hint :), but @Sean just told that this is advanced topic, so maybe there might be any less complicated way to implement Flash scope in spring MVC :) – danny.lesnik May 03 '11 at 09:16
  • @danny.lesnik: I agree to Sean, so it is ONLY an other resource of information. -- I am sorry that I can not help you more. - Anyway it is a good guestion (+1) – Ralph May 03 '11 at 09:24
  • 1
    @danny: Maybe if you explained in your question what "flash scope" is. I'm aware that it's something from JSF, but I have no idea what it means. – skaffman May 03 '11 at 09:25
  • @danny.lesnik: Oh I found my mistake, I mixed the concept of flash scope with, conversetion scope - my fault – Ralph May 03 '11 at 09:31
  • Please look at my answer I think it completes this saga. – danny.lesnik May 04 '11 at 12:22
0

You can get access to session attributes using the next code within your scope class methods in Spring MVC (works in 3.2):

RequestAttributes attributes = RequestContextHolder.currentRequestAttributes();
attributes.getAttribute("some key", NativeWebRequest.SCOPE_SESSION);
attributes.setAttribute("some key", YouObject, NativeWebRequest.SCOPE_SESSION);

RequestAttributes implementation (ServletRequestAttributes) internally will call set/getAttribute() methods on current session object.