com.google.inject.servlet.ServletScopes Maven / Gradle / Ivy
/**
* 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
© 2015 - 2025 Weber Informatics LLC | Privacy Policy