All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.inject.servlet.ServletScopes Maven / Gradle / Ivy

There is a newer version: 3.9
Show newest version
/**
 * Copyright (C) 2006 Google Inc.
 *
 * Licensed 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 com.google.inject.servlet;

import com.google.inject.Key;
import com.google.inject.OutOfScopeException;
import com.google.inject.Provider;
import com.google.inject.Scope;
import com.google.inject.internal.util.Maps;
import com.google.inject.internal.util.Preconditions;
import java.util.Map;
import java.util.concurrent.Callable;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

/**
 * Servlet scopes.
 *
 * @author [email protected] (Bob Lee)
 */
public class ServletScopes {

  private ServletScopes() {}

  /** A sentinel attribute value representing null. */
  enum NullObject { INSTANCE }

  /**
   * HTTP servlet request scope.
   */
  public static final Scope REQUEST = new Scope() {
    public  Provider scope(Key key, final Provider creator) {
      final String name = key.toString();
      return new Provider() {
        public T get() {
          // Check if the alternate request scope should be used, if no HTTP
          // request is in progress.
          if (null == GuiceFilter.localContext.get()) {

            // NOTE(dhanji): We don't need to synchronize on the scope map
            // unlike the HTTP request because we're the only ones who have
            // a reference to it, and it is only available via a threadlocal.
            Map scopeMap = requestScopeContext.get();
            if (null != scopeMap) {
              @SuppressWarnings("unchecked")
              T t = (T) scopeMap.get(name);

              // Accounts for @Nullable providers.
              if (NullObject.INSTANCE == t) {
                return null;
              }

              if (t == null) {
                t = creator.get();
                // Store a sentinel for provider-given null values.
                scopeMap.put(name, t != null ? t : NullObject.INSTANCE);
              }

              return t;
            } // else: fall into normal HTTP request scope and out of scope
              // exception is thrown.
          }

          HttpServletRequest request = GuiceFilter.getRequest();

          synchronized (request) {
            Object obj = request.getAttribute(name);
            if (NullObject.INSTANCE == obj) {
              return null;
            }
            @SuppressWarnings("unchecked")
            T t = (T) obj;
            if (t == null) {
              t = creator.get();
              request.setAttribute(name, (t != null) ? t : NullObject.INSTANCE);
            }
            return t;
          }
        }

        public String toString() {
          return String.format("%s[%s]", creator, REQUEST);
        }
      };
    }

    public String toString() {
      return "ServletScopes.REQUEST";
    }
  };

  /**
   * HTTP session scope.
   */
  public static final Scope SESSION = new Scope() {
    public  Provider scope(Key key, final Provider creator) {
      final String name = key.toString();
      return new Provider() {
        public T get() {
          HttpSession session = GuiceFilter.getRequest().getSession();
          synchronized (session) {
            Object obj = session.getAttribute(name);
            if (NullObject.INSTANCE == obj) {
              return null;
            }
            @SuppressWarnings("unchecked")
            T t = (T) obj;
            if (t == null) {
              t = creator.get();
              session.setAttribute(name, (t != null) ? t : NullObject.INSTANCE);
            }
            return t;
          }
        }
        public String toString() {
          return String.format("%s[%s]", creator, SESSION);
        }
      };
    }

    public String toString() {
      return "ServletScopes.SESSION";
    }
  };

  /**
   * Wraps the given callable in a contextual callable that "continues" the
   * HTTP request in another thread. This acts as a way of transporting
   * request context data from the request processing thread to to worker
   * threads.
   * 

* There are some limitations: *

    *
  • Derived objects (i.e. anything marked @RequestScoped will not be * transported.
  • *
  • State changes to the HttpServletRequest after this method is called * will not be seen in the continued thread.
  • *
  • Only the HttpServletRequest, ServletContext and request parameter * map are available in the continued thread. The response and session * are not available.
  • *
* * @param callable code to be executed in another thread, which depends on * the request scope. * @param seedMap the initial set of scoped instances for Guice to seed the * request scope with. To seed a key with null, use {@code null} as * the value. * @return a callable that will invoke the given callable, making the request * context available to it. * @throws OutOfScopeException if this method is called from a non-request * thread, or if the request has completed. * * @since 3.0 */ public static Callable continueRequest(final Callable callable, final Map, Object> seedMap) { Preconditions.checkArgument(null != seedMap, "Seed map cannot be null, try passing in Collections.emptyMap() instead."); // Snapshot the seed map and add all the instances to our continuing HTTP request. final ContinuingHttpServletRequest continuingRequest = new ContinuingHttpServletRequest(GuiceFilter.getRequest()); for (Map.Entry, Object> entry : seedMap.entrySet()) { Object value = validateAndCanonicalizeValue(entry.getKey(), entry.getValue()); continuingRequest.setAttribute(entry.getKey().toString(), value); } return new Callable() { private HttpServletRequest request = continuingRequest; public T call() throws Exception { GuiceFilter.Context context = GuiceFilter.localContext.get(); Preconditions.checkState(null == context, "Cannot continue request in the same thread as a HTTP request!"); // Only set up the request continuation if we're running in a // new vanilla thread. GuiceFilter.localContext.set(new GuiceFilter.Context(request, null)); try { return callable.call(); } finally { // Clear the copied context if we set one up. if (null == context) { GuiceFilter.localContext.remove(); } } } }; } /** * A threadlocal scope map for non-http request scopes. The {@link #REQUEST} * scope falls back to this scope map if no http request is available, and * requires {@link #scopeRequest} to be called as an alertnative. */ private static final ThreadLocal> requestScopeContext = new ThreadLocal>(); /** * Scopes the given callable inside a request scope. This is not the same * as the HTTP request scope, but is used if no HTTP request scope is in * progress. In this way, keys can be scoped as @RequestScoped and exist * in non-HTTP requests (for example: RPC requests) as well as in HTTP * request threads. * * @param callable code to be executed which depends on the request scope. * Typically in another thread, but not necessarily so. * @param seedMap the initial set of scoped instances for Guice to seed the * request scope with. To seed a key with null, use {@code null} as * the value. * @return a callable that when called will run inside the a request scope * that exposes the instances in the {@code seedMap} as scoped keys. * @since 3.0 */ public static Callable scopeRequest(final Callable callable, Map, Object> seedMap) { Preconditions.checkArgument(null != seedMap, "Seed map cannot be null, try passing in Collections.emptyMap() instead."); // Copy the seed values into our local scope map. final Map scopeMap = Maps.newHashMap(); for (Map.Entry, Object> entry : seedMap.entrySet()) { Object value = validateAndCanonicalizeValue(entry.getKey(), entry.getValue()); scopeMap.put(entry.getKey().toString(), value); } return new Callable() { public T call() throws Exception { Preconditions.checkState(null == GuiceFilter.localContext.get(), "An HTTP request is already in progress, cannot scope a new request in this thread."); Preconditions.checkState(null == requestScopeContext.get(), "A request scope is already in progress, cannot scope a new request in this thread."); requestScopeContext.set(scopeMap); try { return callable.call(); } finally { requestScopeContext.remove(); } } }; } /** * Validates the key and object, ensuring the value matches the key type, and * canonicalizing null objects to the null sentinel. */ private static Object validateAndCanonicalizeValue(Key key, Object object) { if (object == null || object == NullObject.INSTANCE) { return NullObject.INSTANCE; } if (!key.getTypeLiteral().getRawType().isInstance(object)) { throw new IllegalArgumentException("Value[" + object + "] of type[" + object.getClass().getName() + "] is not compatible with key[" + key + "]"); } return object; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy