alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

Java example source code file (ServletScopes.java)

This example Java source code file (ServletScopes.java) is included in the alvinalexander.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Learn more about this Java project at its project page.

Java - Java tags/keywords

callable, closeablescope, context, http, key, map, object, outofscopeexception, override, provider, request, requestscoper, response, servlet, servletscopes, string, suppresswarnings, threading, util

The ServletScopes.java Java example source code

/**
 * 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.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.common.collect.Maps.EntryTransformer;
import com.google.inject.Binding;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.OutOfScopeException;
import com.google.inject.Provider;
import com.google.inject.Scope;
import com.google.inject.Scopes;

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

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

/**
 * Servlet scopes.
 *
 * @author crazybob@google.com (Bob Lee)
 */
public class ServletScopes {

  private ServletScopes() {}

  /**
   * 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 alternative.
   */
  private static final ThreadLocal<Context> requestScopeContext
      = new ThreadLocal<Context>();

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

  /**
   * HTTP servlet request scope.
   */
  public static final Scope REQUEST = new RequestScope();

  private static final class RequestScope implements Scope {
    @Override
    public <T> Provider scope(final Key key, final Provider creator) {
      return new Provider<T>() {

        /** Keys bound in request-scope which are handled directly by GuiceFilter. */
        private final ImmutableSet<Key REQUEST_CONTEXT_KEYS = ImmutableSet.of(
                Key.get(HttpServletRequest.class),
                Key.get(HttpServletResponse.class),
                new Key<Map(RequestParameters.class) {});

        @Override
        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.
            Context context = requestScopeContext.get();
            if (null != context) {
              @SuppressWarnings("unchecked")
              T t = (T) context.map.get(key);

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

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

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

          // Always synchronize and get/set attributes on the underlying request
          // object since Filters may wrap the request and change the value of
          // {@code GuiceFilter.getRequest()}.
          //
          // This _correctly_ throws up if the thread is out of scope.
          HttpServletRequest request = GuiceFilter.getOriginalRequest(key);
          if (REQUEST_CONTEXT_KEYS.contains(key)) {
            // Don't store these keys as attributes, since they are handled by
            // GuiceFilter itself.
            return creator.get();
          }
          String name = key.toString();
          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();
              if (!Scopes.isCircularProxy(t)) {
                request.setAttribute(name, (t != null) ? t : NullObject.INSTANCE);
              }
            }
            return t;
          }
        }

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

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

  /**
   * HTTP session scope.
   */
  public static final Scope SESSION = new SessionScope();

  private static final class SessionScope implements Scope {
    @Override
    public <T> Provider scope(final Key key, final Provider creator) {
      final String name = key.toString();
      return new Provider<T>() {
        @Override
        public T get() {
          HttpSession session = GuiceFilter.getRequest(key).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();
              if (!Scopes.isCircularProxy(t)) {
                session.setAttribute(name, (t != null) ? t : NullObject.INSTANCE);
              }
            }
            return t;
          }
        }
        @Override
        public String toString() {
          return String.format("%s[%s]", creator, SESSION);
        }
      };
    }

    @Override
    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.
   * <p>
   * There are some limitations:
   * <ul>
   *   <li>Derived objects (i.e. anything marked @RequestScoped will not be
   *      transported.</li>
   *   <li>State changes to the HttpServletRequest after this method is called
   *      will not be seen in the continued thread.</li>
   *   <li>Only the HttpServletRequest, ServletContext and request parameter
   *      map are available in the continued thread. The response and session
   *      are not available.</li>
   * </ul>
   *
   * <p>The returned callable will throw a {@link ScopingException} when called
   * if the HTTP request scope is still active on the current thread.
   *
   * @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
   * @deprecated You probably want to use {@code transferRequest} instead
   */
  @Deprecated
  public static <T> Callable continueRequest(Callable callable,
      Map<Key seedMap) {
    return wrap(callable, continueRequest(seedMap));
  }

  private static RequestScoper continueRequest(Map<Key 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(Key.get(HttpServletRequest.class)));
    for (Map.Entry<Key entry : seedMap.entrySet()) {
      Object value = validateAndCanonicalizeValue(entry.getKey(), entry.getValue());
      continuingRequest.setAttribute(entry.getKey().toString(), value);
    }

    return new RequestScoper() {
      @Override public CloseableScope open() {
        checkScopingState(null == GuiceFilter.localContext.get(),
            "Cannot continue request in the same thread as a HTTP request!");
        return new GuiceFilter.Context(continuingRequest, continuingRequest, null).open();
      }
    };
  }

  /**
   * Wraps the given callable in a contextual callable that "transfers" the
   * request to another thread. This acts as a way of transporting
   * request context data from the current thread to a future thread.
   *
   * <p>As opposed to {@link #continueRequest}, this method propagates all
   * existing scoped objects. The primary use case is in server implementations
   * where you can detach the request processing thread while waiting for data,
   * and reattach to a different thread to finish processing at a later time.
   *
   * <p>Because request-scoped objects are not typically thread-safe, the
   * callable returned by this method must not be run on a different thread
   * until the current request scope has terminated. The returned callable will
   * block until the current thread has released the request scope.
   *
   * @param callable code to be executed in another thread, which depends on
   *     the request scope.
   * @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 4.0
   */
  public static <T> Callable transferRequest(Callable callable) {
    return wrap(callable, transferRequest());
  }

  /**
   * Returns an object that "transfers" the request to another thread. This acts
   * as a way of transporting request context data from the current thread to a
   * future thread. The transferred scope is the one active for the thread that
   * calls this method. A later call to {@code open()} activates the transferred
   * the scope, including propagating any objects scoped at that time.
   *
   * <p>As opposed to {@link #continueRequest}, this method propagates all
   * existing scoped objects. The primary use case is in server implementations
   * where you can detach the request processing thread while waiting for data,
   * and reattach to a different thread to finish processing at a later time.
   *
   * <p>Because request-scoped objects are not typically thread-safe, it is
   * important to avoid applying the same request scope concurrently. The
   * returned Scoper will block on open until the current thread has released
   * the request scope.
   *
   * @return an object that when opened will initiate the request scope
   * @throws OutOfScopeException if this method is called from a non-request
   *     thread, or if the request has completed.
   * @since 4.1
   */
  public static RequestScoper transferRequest() {
    return (GuiceFilter.localContext.get() != null)
        ? transferHttpRequest()
        : transferNonHttpRequest();
  }

  private static RequestScoper transferHttpRequest() {
    final GuiceFilter.Context context = GuiceFilter.localContext.get();
    if (context == null) {
      throw new OutOfScopeException("Not in a request scope");
    }
    return context;
  }

  private static RequestScoper transferNonHttpRequest() {
    final Context context = requestScopeContext.get();
    if (context == null) {
      throw new OutOfScopeException("Not in a request scope");
    }
    return context;
  }

  /**
   * Returns true if {@code binding} is request-scoped. If the binding is a
   * {@link com.google.inject.spi.LinkedKeyBinding linked key binding} and
   * belongs to an injector (i. e. it was retrieved via
   * {@link Injector#getBinding Injector.getBinding()}), then this method will
   * also return true if the target binding is request-scoped.
   *
   * @since 4.0
   */
  public static boolean isRequestScoped(Binding<?> binding) {
    return Scopes.isScoped(binding, ServletScopes.REQUEST, RequestScoped.class);
  }

  /**
   * 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.
   *
   * <p>The returned callable will throw a {@link ScopingException} when called
   * if there is a request scope already active on the current thread.
   *
   * @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 <T> Callable scopeRequest(Callable callable,
      Map<Key seedMap) {
    return wrap(callable, scopeRequest(seedMap));
  }

  /**
   * Returns an object that will apply request scope to a block of code. 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.
   *
   * <p>The returned object will throw a {@link ScopingException} when opened
   * if there is a request scope already active on the current thread.
   *
   * @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 an object that when opened will initiate the request scope
   * @since 4.1
   */
  public static RequestScoper scopeRequest(Map<Key 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 Context context = new Context();
    Map<Key validatedAndCanonicalizedMap =
        Maps.transformEntries(seedMap, new EntryTransformer<Key() {
          @Override public Object transformEntry(Key<?> key, Object value) {
            return validateAndCanonicalizeValue(key, value);
          }
        });
    context.map.putAll(validatedAndCanonicalizedMap);
    return new RequestScoper() {
      @Override public CloseableScope open() {
        checkScopingState(null == GuiceFilter.localContext.get(),
            "An HTTP request is already in progress, cannot scope a new request in this thread.");
        checkScopingState(null == requestScopeContext.get(),
            "A request scope is already in progress, cannot scope a new request in this thread.");
        return context.open();
      }
    };
  }

  /**
   * 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;
  }

  private static class Context implements RequestScoper {
    final Map<Key, Object> map = Maps.newHashMap();

    // Synchronized to prevent two threads from using the same request
    // scope concurrently.
    final Lock lock = new ReentrantLock();

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

  private static void checkScopingState(boolean condition, String msg) {
    if (!condition) {
      throw new ScopingException(msg);
    }
  }

  private static final <T> Callable wrap(
      final Callable<T> delegate, final RequestScoper requestScoper) {
    return new Callable<T>() {
      public T call() throws Exception {
        RequestScoper.CloseableScope scope = requestScoper.open();
        try {
          return delegate.call();
        } finally {
          scope.close();
        }
      }
    };
  }

}

Other Java examples (source code examples)

Here is a short list of links related to this Java ServletScopes.java source code file:

... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.