Tushar Patel
Tushar Patel

Reputation: 365

How to use Play Framework's request and session scope in Google Guice?

I am using Guice for Dependency Injection in my Play (Java) Framework project, and struggling to understand how the concept of "session" is best used with Guice and Play?

I know that Play is stateless and there really is no concept of a session, other than that you can store values in a cookie. My understanding with Guice and Play is that, while Guice documentation describes supporting different scopes (singleton, session, request, no scope), because we are instantiating a new injector with every request, the only scopes that apply to Play are singleton and "no scope".

Where I am confused is: what is the best way to "simulate" a session using Guice and Play? Should I define a "custom scope"?

Note that I am using Redis to store my session data. Here are some options I'm thinking about:

Is there a standard practice here, or any other guidelines I might follow to setup a session concept in my Play app?

Upvotes: 2

Views: 2068

Answers (2)

Bryan Bowman
Bryan Bowman

Reputation: 31

I may be a bit late to the party but this worked for me. Using Play! 2.4 and Guice 4.0.

I landed on this posting while trying to figure out how to solve the problem of scoping an instance to the Http.Context.current.

Here's my solution:

import com.google.common.collect.Maps;
import com.google.inject.Key;
import com.google.inject.Provider;
import com.google.inject.Scope;
import com.google.inject.Scopes;
import play.mvc.Http;

import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Allows objects to be bound to Play! Http.Context.current.args with a ThreadLocal fallback.
 */
public class HttpContextScope implements Scope {

    private static final ThreadLocal<Context> httpContextScopeContext = new ThreadLocal<>();

    enum NullableObject {
        INSTANCE
    }

    @Override
    public <T> Provider<T> scope(final Key<T> key, final Provider<T> provider) {
        return new Provider<T>() {
            @Override
            public T get() {
                Http.Context currentContext = Http.Context.current();
                if (currentContext == null) {
                    Context context = httpContextScopeContext.get();
                    if (context != null) {
                        T t = (T) context.map.get(key);
                        if (t == NullableObject.INSTANCE) {
                            return null;
                        }

                        if (t == null) {
                            t = provider.get();
                            if (!Scopes.isCircularProxy(t)) {
                                context.map.put(key, t != null ? t : NullableObject.INSTANCE);
                            }
                        }
                        return t;
                    }
                }

                String name = key.toString();
                synchronized (currentContext) {
                    Object obj = currentContext.args.get(name);
                    if (obj == NullableObject.INSTANCE) {
                        return null;
                    }
                    T t = (T) obj;
                    if (t == null) {
                        t = provider.get();
                        if (!Scopes.isCircularProxy(t)) {
                            currentContext.args.put(name, t != null ? t : NullableObject.INSTANCE);
                        }
                    }
                    return t;
                }
            }
        };
    }

    @Override
    public String toString() {
        return "Http.Context.ARGS";
    }

    private static class Context implements ContextScoper {
        final Map<Key, Object> map = Maps.newHashMap();
        final Lock lock = new ReentrantLock();

        @Override
        public CloseableScope open() {
            lock.lock();
            final Context previous = httpContextScopeContext.get();
            httpContextScopeContext.set(this);
            return new CloseableScope() {
                @Override
                public void close() {
                    httpContextScopeContext.set(previous);
                    lock.unlock();
                }
            };
        }
    }
}

The ContextScoper and ContextScoper.CloseableScope interface:

import java.io.Closeable;

public interface ContextScoper {

    CloseableScope open();

    interface CloseableScope extends Closeable {
        @Override
        void close();
    }
}

And the ScopeAnnotation:

import com.google.inject.ScopeAnnotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@ScopeAnnotation
public @interface HttpContextScoped {
}

And wiring it all up:

public class AppModule extends AbstractModule {
    @Override
    protected void configure() {
        HttpContextScope httpContextScope = new HttpContextScope();
        bindScope(HttpContextScoped.class, httpContextScope);
    }

    @Provides
    @HttpContextScoped
    public TheThing providesTheThing() {
        return new TheThing();
    }
}

FWIW, this is an adaptation of Google's own ServletScopes found here:

Disclaimer: I don't have the ThreadLocal fallback tests done yet so I can't say for certain whether or not this part is solid.

Cheers!

Upvotes: 3

Will Sargent
Will Sargent

Reputation: 4396

There is no session in Play. If you want a session, you're going to have to provide one using action composition and WrappedRequest: in this case, you want a cookie with a session id, and then you want a service that looks up the session id in Redis and returns you the session data so you can put it in a WrappedRequest.

Once you've got a WrappedRequest that exposes your session data, you can refer to it: request.user, request.context, etc. Yes, you can expose Guice lookups directly with request.injector, but that is a bit more hacky, and not as type safe.

Upvotes: 4

Related Questions