org.glassfish.jersey.server.ContainerRequest Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jaxrs-ri Show documentation
Show all versions of jaxrs-ri Show documentation
A bundle project producing JAX-RS RI bundles. The primary artifact is an "all-in-one" OSGi-fied JAX-RS RI bundle
(jaxrs-ri.jar).
Attached to that are two compressed JAX-RS RI archives. The first archive (jaxrs-ri.zip) consists of binary RI bits and
contains the API jar (under "api" directory), RI libraries (under "lib" directory) as well as all external
RI dependencies (under "ext" directory). The secondary archive (jaxrs-ri-src.zip) contains buildable JAX-RS RI source
bundle and contains the API jar (under "api" directory), RI sources (under "src" directory) as well as all external
RI dependencies (under "ext" directory). The second archive also contains "build.xml" ANT script that builds the RI
sources. To build the JAX-RS RI simply unzip the archive, cd to the created jaxrs-ri directory and invoke "ant" from
the command line.
/*
* Copyright (c) 2012, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package org.glassfish.jersey.server;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.net.URI;
import java.text.ParseException;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import javax.ws.rs.RuntimeType;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.container.ContainerResponseFilter;
import javax.ws.rs.core.Configuration;
import javax.ws.rs.core.Cookie;
import javax.ws.rs.core.EntityTag;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Request;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.SecurityContext;
import javax.ws.rs.core.Variant;
import javax.ws.rs.ext.ReaderInterceptor;
import javax.ws.rs.ext.WriterInterceptor;
import org.glassfish.jersey.http.HttpHeaders;
import org.glassfish.jersey.internal.PropertiesDelegate;
import org.glassfish.jersey.internal.guava.Preconditions;
import org.glassfish.jersey.internal.PropertiesResolver;
import org.glassfish.jersey.internal.util.collection.LazyValue;
import org.glassfish.jersey.internal.util.collection.Ref;
import org.glassfish.jersey.internal.util.collection.Refs;
import org.glassfish.jersey.internal.util.collection.Value;
import org.glassfish.jersey.internal.util.collection.Values;
import org.glassfish.jersey.message.internal.AcceptableMediaType;
import org.glassfish.jersey.message.internal.HttpHeaderReader;
import org.glassfish.jersey.message.internal.InboundMessageContext;
import org.glassfish.jersey.message.internal.LanguageTag;
import org.glassfish.jersey.message.internal.MatchingEntityTag;
import org.glassfish.jersey.message.internal.OutboundJaxrsResponse;
import org.glassfish.jersey.message.internal.TracingAwarePropertiesDelegate;
import org.glassfish.jersey.message.internal.VariantSelector;
import org.glassfish.jersey.model.internal.CommonConfig;
import org.glassfish.jersey.model.internal.ComponentBag;
import org.glassfish.jersey.model.internal.RankedProvider;
import org.glassfish.jersey.process.Inflector;
import org.glassfish.jersey.server.internal.LocalizationMessages;
import org.glassfish.jersey.server.internal.ProcessingProviders;
import org.glassfish.jersey.server.internal.process.RequestProcessingContext;
import org.glassfish.jersey.server.internal.routing.UriRoutingContext;
import org.glassfish.jersey.server.model.ResourceMethodInvoker;
import org.glassfish.jersey.server.spi.ContainerResponseWriter;
import org.glassfish.jersey.server.spi.RequestScopedInitializer;
import org.glassfish.jersey.uri.UriComponent;
import org.glassfish.jersey.uri.internal.JerseyUriBuilder;
/**
* Jersey container request context.
*
* An instance of the request context is passed by the container to the
* {@link ApplicationHandler} for each incoming client request.
*
* @author Marek Potociar
*/
public class ContainerRequest extends InboundMessageContext
implements ContainerRequestContext, Request, HttpHeaders, PropertiesDelegate, PropertiesResolver {
private static final URI DEFAULT_BASE_URI = URI.create("/");
// Request-scoped properties delegate
private final PropertiesDelegate propertiesDelegate;
// Routing context and UriInfo implementation
private final UriRoutingContext uriRoutingContext;
// Absolute application root URI (base URI)
private URI baseUri;
// Absolute request URI
private URI requestUri;
// Lazily computed encoded request path (relative to application root URI)
private String encodedRelativePath = null;
// Lazily computed decoded request path (relative to application root URI)
private String decodedRelativePath = null;
// Lazily computed "absolute path" URI
private URI absolutePathUri = null;
// Request method
private String httpMethod;
// Request security context
private SecurityContext securityContext;
// Request filter chain execution aborting response
private Response abortResponse;
// Vary header value to be set in the response
private String varyValue;
// Processing providers
private ProcessingProviders processingProviders;
// Custom Jersey container request scoped initializer
private RequestScopedInitializer requestScopedInitializer;
// Request-scoped response writer of the invoking container
private ContainerResponseWriter responseWriter;
// True if the request is used in the response processing phase (for example in ContainerResponseFilter)
private boolean inResponseProcessingPhase;
// lazy PropertiesResolver
private final LazyValue propertiesResolver = Values.lazy(
(Value) () -> PropertiesResolver.create(getConfiguration(), getPropertiesDelegate())
);
private static final String ERROR_REQUEST_SET_ENTITY_STREAM_IN_RESPONSE_PHASE =
LocalizationMessages.ERROR_REQUEST_SET_ENTITY_STREAM_IN_RESPONSE_PHASE();
private static final String ERROR_REQUEST_SET_SECURITY_CONTEXT_IN_RESPONSE_PHASE =
LocalizationMessages.ERROR_REQUEST_SET_SECURITY_CONTEXT_IN_RESPONSE_PHASE();
private static final String ERROR_REQUEST_ABORT_IN_RESPONSE_PHASE =
LocalizationMessages.ERROR_REQUEST_ABORT_IN_RESPONSE_PHASE();
private static final String METHOD_PARAMETER_CANNOT_BE_NULL_OR_EMPTY =
LocalizationMessages.METHOD_PARAMETER_CANNOT_BE_NULL_OR_EMPTY("variants");
private static final String METHOD_PARAMETER_CANNOT_BE_NULL_ETAG =
LocalizationMessages.METHOD_PARAMETER_CANNOT_BE_NULL("eTag");
private static final String METHOD_PARAMETER_CANNOT_BE_NULL_LAST_MODIFIED =
LocalizationMessages.METHOD_PARAMETER_CANNOT_BE_NULL("lastModified");
/**
* Create new Jersey container request context.
*
* @param baseUri base application URI.
* @param requestUri request URI.
* @param httpMethod request HTTP method name.
* @param securityContext security context of the current request. Must not be {@code null}.
* The {@link SecurityContext#getUserPrincipal()} must return
* {@code null} if the current request has not been authenticated
* by the container.
* @param propertiesDelegate custom {@link PropertiesDelegate properties delegate}
* to be used by the context.
* @param configuration the server {@link Configuration}. If {@code null}, the default behaviour is expected.
*/
public ContainerRequest(
final URI baseUri,
final URI requestUri,
final String httpMethod,
final SecurityContext securityContext,
final PropertiesDelegate propertiesDelegate,
final Configuration configuration) {
super(configuration, true);
this.baseUri = baseUri == null ? DEFAULT_BASE_URI : baseUri.normalize();
this.requestUri = requestUri;
this.httpMethod = httpMethod;
this.securityContext = securityContext;
this.propertiesDelegate = new TracingAwarePropertiesDelegate(propertiesDelegate);
this.uriRoutingContext = new UriRoutingContext(this);
}
/**
* Create new Jersey container request context.
*
* @param baseUri base application URI.
* @param requestUri request URI.
* @param httpMethod request HTTP method name.
* @param securityContext security context of the current request. Must not be {@code null}.
* The {@link SecurityContext#getUserPrincipal()} must return
* {@code null} if the current request has not been authenticated
* by the container.
* @param propertiesDelegate custom {@link PropertiesDelegate properties delegate}
* to be used by the context.
* @see #ContainerRequest(URI, URI, String, SecurityContext, PropertiesDelegate, Configuration)
*/
@Deprecated
public ContainerRequest(
final URI baseUri,
final URI requestUri,
final String httpMethod,
final SecurityContext securityContext,
final PropertiesDelegate propertiesDelegate) {
this(baseUri, requestUri, httpMethod, securityContext, propertiesDelegate,
new CommonConfig(RuntimeType.SERVER, ComponentBag.EXCLUDE_EMPTY) {
{
this.property(ContainerRequest.class.getName(), Deprecated.class.getSimpleName());
}
});
}
/**
* Get a custom container extensions initializer for the current request.
*
* The initializer is guaranteed to be run from within the request scope of
* the current request.
*
* @return custom container extensions initializer or {@code null} if not
* available.
*/
public RequestScopedInitializer getRequestScopedInitializer() {
return requestScopedInitializer;
}
/**
* Set a custom container extensions initializer for the current request.
*
* The initializer is guaranteed to be run from within the request scope of
* the current request.
*
* @param requestScopedInitializer custom container extensions initializer.
*/
public void setRequestScopedInitializer(final RequestScopedInitializer requestScopedInitializer) {
this.requestScopedInitializer = requestScopedInitializer;
}
/**
* Get the container response writer for the current request.
*
* @return container response writer.
*/
public ContainerResponseWriter getResponseWriter() {
return responseWriter;
}
/**
* Set the container response writer for the current request.
*
* @param responseWriter container response writer. Must not be {@code null}.
*/
public void setWriter(final ContainerResponseWriter responseWriter) {
this.responseWriter = responseWriter;
}
/**
* Read entity from a context entity input stream.
*
* @param entity Java object type.
* @param rawType raw Java entity type.
* @return entity read from a context entity input stream.
*/
public T readEntity(final Class rawType) {
return readEntity(rawType, propertiesDelegate);
}
/**
* Read entity from a context entity input stream.
*
* @param entity Java object type.
* @param rawType raw Java entity type.
* @param annotations entity annotations.
* @return entity read from a context entity input stream.
*/
public T readEntity(final Class rawType, final Annotation[] annotations) {
return super.readEntity(rawType, annotations, propertiesDelegate);
}
/**
* Read entity from a context entity input stream.
*
* @param entity Java object type.
* @param rawType raw Java entity type.
* @param type generic Java entity type.
* @return entity read from a context entity input stream.
*/
public T readEntity(final Class rawType, final Type type) {
return super.readEntity(rawType, type, propertiesDelegate);
}
/**
* Read entity from a context entity input stream.
*
* @param entity Java object type.
* @param rawType raw Java entity type.
* @param type generic Java entity type.
* @param annotations entity annotations.
* @return entity read from a context entity input stream.
*/
public T readEntity(final Class rawType, final Type type, final Annotation[] annotations) {
return super.readEntity(rawType, type, annotations, propertiesDelegate);
}
@Override
public T resolveProperty(final String name, final Class type) {
return propertiesResolver.get().resolveProperty(name, type);
}
@Override
public T resolveProperty(final String name, final T defaultValue) {
return propertiesResolver.get().resolveProperty(name, defaultValue);
}
@Override
public Object getProperty(final String name) {
return propertiesDelegate.getProperty(name);
}
@Override
public Collection getPropertyNames() {
return propertiesDelegate.getPropertyNames();
}
@Override
public void setProperty(final String name, final Object object) {
propertiesDelegate.setProperty(name, object);
}
@Override
public void removeProperty(final String name) {
propertiesDelegate.removeProperty(name);
}
/**
* Get the underlying properties delegate.
*
* @return underlying properties delegate.
*/
public PropertiesDelegate getPropertiesDelegate() {
return propertiesDelegate;
}
@Override
public ExtendedUriInfo getUriInfo() {
return uriRoutingContext;
}
void setProcessingProviders(final ProcessingProviders providers) {
this.processingProviders = providers;
}
UriRoutingContext getUriRoutingContext() {
return uriRoutingContext;
}
/**
* Get all bound request filters applicable to this request.
*
* @return All bound (dynamically or by name) request filters applicable to the matched inflector (or an empty
* collection if no inflector matched yet).
*/
Iterable> getRequestFilters() {
final Inflector inflector = getInflector();
return emptyIfNull(inflector instanceof ResourceMethodInvoker
? ((ResourceMethodInvoker) inflector).getRequestFilters() : null);
}
/**
* Get all bound response filters applicable to this request.
* This is populated once the right resource method is matched.
*
* @return All bound (dynamically or by name) response filters applicable to the matched inflector (or an empty
* collection if no inflector matched yet).
*/
Iterable> getResponseFilters() {
final Inflector inflector = getInflector();
return emptyIfNull(inflector instanceof ResourceMethodInvoker
? ((ResourceMethodInvoker) inflector).getResponseFilters() : null);
}
/**
* Get all reader interceptors applicable to this request.
* This is populated once the right resource method is matched.
*
* @return All reader interceptors applicable to the matched inflector (or an empty
* collection if no inflector matched yet).
*/
@Override
protected Iterable getReaderInterceptors() {
final Inflector inflector = getInflector();
return inflector instanceof ResourceMethodInvoker
? ((ResourceMethodInvoker) inflector).getReaderInterceptors()
: processingProviders.getSortedGlobalReaderInterceptors();
}
/**
* Get all writer interceptors applicable to this request.
*
* @return All writer interceptors applicable to the matched inflector (or an empty
* collection if no inflector matched yet).
*/
Iterable getWriterInterceptors() {
final Inflector inflector = getInflector();
return inflector instanceof ResourceMethodInvoker
? ((ResourceMethodInvoker) inflector).getWriterInterceptors()
: processingProviders.getSortedGlobalWriterInterceptors();
}
private Inflector getInflector() {
return uriRoutingContext.getEndpoint();
}
private static Iterable emptyIfNull(final Iterable iterable) {
return iterable == null ? Collections.emptyList() : iterable;
}
/**
* Get base request URI.
*
* @return base request URI.
*/
public URI getBaseUri() {
return baseUri;
}
/**
* Get request URI.
*
* @return request URI.
*/
public URI getRequestUri() {
return requestUri;
}
/**
* Get the absolute path of the request. This includes everything preceding the path (host, port etc),
* but excludes query parameters or fragment.
*
* @return the absolute path of the request.
*/
public URI getAbsolutePath() {
if (absolutePathUri != null) {
return absolutePathUri;
}
return absolutePathUri = new JerseyUriBuilder().uri(requestUri).replaceQuery("").fragment("").build();
}
@Override
public void setRequestUri(final URI requestUri) throws IllegalStateException {
if (!uriRoutingContext.getMatchedURIs().isEmpty()) {
throw new IllegalStateException("Method could be called only in pre-matching request filter.");
}
this.encodedRelativePath = null;
this.decodedRelativePath = null;
this.absolutePathUri = null;
this.uriRoutingContext.invalidateUriComponentViews();
this.requestUri = requestUri;
}
@Override
public void setRequestUri(final URI baseUri, final URI requestUri) throws IllegalStateException {
if (!uriRoutingContext.getMatchedURIs().isEmpty()) {
throw new IllegalStateException("Method could be called only in pre-matching request filter.");
}
this.encodedRelativePath = null;
this.decodedRelativePath = null;
this.absolutePathUri = null;
this.uriRoutingContext.invalidateUriComponentViews();
this.baseUri = baseUri;
this.requestUri = requestUri;
OutboundJaxrsResponse.Builder.setBaseUri(baseUri);
}
/**
* Get the path of the current request relative to the application root (base)
* URI as a string.
*
* @param decode controls whether sequences of escaped octets are decoded
* ({@code true}) or not ({@code false}).
* @return relative request path.
*/
public String getPath(final boolean decode) {
if (decode) {
if (decodedRelativePath != null) {
return decodedRelativePath;
}
return decodedRelativePath = UriComponent.decode(encodedRelativePath(), UriComponent.Type.PATH);
} else {
return encodedRelativePath();
}
}
private String encodedRelativePath() {
if (encodedRelativePath != null) {
return encodedRelativePath;
}
final String requestUriRawPath = requestUri.getRawPath();
if (baseUri == null) {
return encodedRelativePath = requestUriRawPath;
}
final int baseUriRawPathLength = baseUri.getRawPath().length();
return encodedRelativePath = baseUriRawPathLength < requestUriRawPath.length()
? requestUriRawPath.substring(baseUriRawPathLength) : "";
}
@Override
public String getMethod() {
return httpMethod;
}
@Override
public void setMethod(final String method) throws IllegalStateException {
if (!uriRoutingContext.getMatchedURIs().isEmpty()) {
throw new IllegalStateException("Method could be called only in pre-matching request filter.");
}
this.httpMethod = method;
}
/**
* Like {@link #setMethod(String)} but does not throw {@link IllegalStateException} if the method is invoked in other than
* pre-matching phase.
*
* @param method HTTP method.
*/
public void setMethodWithoutException(final String method) {
this.httpMethod = method;
}
@Override
public SecurityContext getSecurityContext() {
return securityContext;
}
@Override
public void setSecurityContext(final SecurityContext context) {
Preconditions.checkState(!inResponseProcessingPhase, ERROR_REQUEST_SET_SECURITY_CONTEXT_IN_RESPONSE_PHASE);
this.securityContext = context;
}
@Override
public void setEntityStream(final InputStream input) {
Preconditions.checkState(!inResponseProcessingPhase, ERROR_REQUEST_SET_ENTITY_STREAM_IN_RESPONSE_PHASE);
super.setEntityStream(input);
}
@Override
public Request getRequest() {
return this;
}
@Override
public void abortWith(final Response response) {
Preconditions.checkState(!inResponseProcessingPhase, ERROR_REQUEST_ABORT_IN_RESPONSE_PHASE);
this.abortResponse = response;
}
/**
* Notify this request that the response created from this request is already being
* processed. This means that the request processing phase has finished and this
* request can be used only in the request processing phase (for example in
* ContainerResponseFilter).
*
* The request can be used for processing of more than one response (in async cases).
* Then this method should be called when the first response is created from this
* request. Multiple calls to this method has the same effect as calling the method
* only once.
*/
public void inResponseProcessing() {
this.inResponseProcessingPhase = true;
}
/**
* Get the request filter chain aborting response if set, or {@code null} otherwise.
*
* @return request filter chain aborting response if set, or {@code null} otherwise.
*/
public Response getAbortResponse() {
return abortResponse;
}
@Override
public Map getCookies() {
return super.getRequestCookies();
}
@Override
public List getAcceptableMediaTypes() {
return getQualifiedAcceptableMediaTypes().stream()
.map((Function) input -> input)
.collect(Collectors.toList());
}
@Override
public List getAcceptableLanguages() {
return getQualifiedAcceptableLanguages().stream().map(LanguageTag::getAsLocale).collect(Collectors.toList());
}
// JAX-RS request
@Override
public Variant selectVariant(final List variants) throws IllegalArgumentException {
if (variants == null || variants.isEmpty()) {
throw new IllegalArgumentException(METHOD_PARAMETER_CANNOT_BE_NULL_OR_EMPTY);
}
final Ref varyValueRef = Refs.emptyRef();
final Variant variant = VariantSelector.selectVariant(this, variants, varyValueRef);
this.varyValue = varyValueRef.get();
return variant;
}
/**
* Get the value of HTTP Vary response header to be set in the response,
* or {@code null} if no value is to be set.
*
* @return value of HTTP Vary response header to be set in the response if available,
* {@code null} otherwise.
*/
public String getVaryValue() {
return varyValue;
}
@Override
public Response.ResponseBuilder evaluatePreconditions(final EntityTag eTag) {
if (eTag == null) {
throw new IllegalArgumentException(METHOD_PARAMETER_CANNOT_BE_NULL_ETAG);
}
final Response.ResponseBuilder r = evaluateIfMatch(eTag);
if (r != null) {
return r;
}
return evaluateIfNoneMatch(eTag);
}
@Override
public Response.ResponseBuilder evaluatePreconditions(final Date lastModified) {
if (lastModified == null) {
throw new IllegalArgumentException(METHOD_PARAMETER_CANNOT_BE_NULL_LAST_MODIFIED);
}
final long lastModifiedTime = lastModified.getTime();
final Response.ResponseBuilder r = evaluateIfUnmodifiedSince(lastModifiedTime);
if (r != null) {
return r;
}
return evaluateIfModifiedSince(lastModifiedTime);
}
@Override
public Response.ResponseBuilder evaluatePreconditions(final Date lastModified, final EntityTag eTag) {
if (lastModified == null) {
throw new IllegalArgumentException(METHOD_PARAMETER_CANNOT_BE_NULL_LAST_MODIFIED);
}
if (eTag == null) {
throw new IllegalArgumentException(METHOD_PARAMETER_CANNOT_BE_NULL_ETAG);
}
Response.ResponseBuilder r = evaluateIfMatch(eTag);
if (r != null) {
return r;
}
final long lastModifiedTime = lastModified.getTime();
r = evaluateIfUnmodifiedSince(lastModifiedTime);
if (r != null) {
return r;
}
final boolean isGetOrHead = "GET".equals(getMethod()) || "HEAD".equals(getMethod());
final Set matchingTags = getIfNoneMatch();
if (matchingTags != null) {
r = evaluateIfNoneMatch(eTag, matchingTags, isGetOrHead);
// If the If-None-Match header is present and there is no
// match then the If-Modified-Since header must be ignored
if (r == null) {
return null;
}
// Otherwise if the If-None-Match header is present and there
// is a match then the If-Modified-Since header must be checked
// for consistency
}
final String ifModifiedSinceHeader = getHeaderString(HttpHeaders.IF_MODIFIED_SINCE);
if (ifModifiedSinceHeader != null && !ifModifiedSinceHeader.isEmpty() && isGetOrHead) {
r = evaluateIfModifiedSince(lastModifiedTime, ifModifiedSinceHeader);
if (r != null) {
r.tag(eTag);
}
}
return r;
}
@Override
public Response.ResponseBuilder evaluatePreconditions() {
final Set matchingTags = getIfMatch();
if (matchingTags == null) {
return null;
}
// Since the resource does not exist the method must not be
// perform and 412 Precondition Failed is returned
return Response.status(Response.Status.PRECONDITION_FAILED);
}
// Private methods
private Response.ResponseBuilder evaluateIfMatch(final EntityTag eTag) {
final Set extends EntityTag> matchingTags = getIfMatch();
if (matchingTags == null) {
return null;
}
// The strong comparison function must be used to compare the entity
// tags. Thus if the entity tag of the entity is weak then matching
// of entity tags in the If-Match header should fail.
if (eTag.isWeak()) {
return Response.status(Response.Status.PRECONDITION_FAILED);
}
if (matchingTags != MatchingEntityTag.ANY_MATCH && !matchingTags.contains(eTag)) {
// 412 Precondition Failed
return Response.status(Response.Status.PRECONDITION_FAILED);
}
return null;
}
private Response.ResponseBuilder evaluateIfNoneMatch(final EntityTag eTag) {
final Set matchingTags = getIfNoneMatch();
if (matchingTags == null) {
return null;
}
final String httpMethod = getMethod();
return evaluateIfNoneMatch(eTag, matchingTags, "GET".equals(httpMethod) || "HEAD".equals(httpMethod));
}
private Response.ResponseBuilder evaluateIfNoneMatch(final EntityTag eTag, final Set extends EntityTag> matchingTags,
final boolean isGetOrHead) {
if (isGetOrHead) {
if (matchingTags == MatchingEntityTag.ANY_MATCH) {
// 304 Not modified
return Response.notModified(eTag);
}
// The weak comparison function may be used to compare entity tags
if (matchingTags.contains(eTag) || matchingTags.contains(new EntityTag(eTag.getValue(), !eTag.isWeak()))) {
// 304 Not modified
return Response.notModified(eTag);
}
} else {
// The strong comparison function must be used to compare the entity
// tags. Thus if the entity tag of the entity is weak then matching
// of entity tags in the If-None-Match header should fail if the
// HTTP method is not GET or not HEAD.
if (eTag.isWeak()) {
return null;
}
if (matchingTags == MatchingEntityTag.ANY_MATCH || matchingTags.contains(eTag)) {
// 412 Precondition Failed
return Response.status(Response.Status.PRECONDITION_FAILED);
}
}
return null;
}
private Response.ResponseBuilder evaluateIfUnmodifiedSince(final long lastModified) {
final String ifUnmodifiedSinceHeader = getHeaderString(HttpHeaders.IF_UNMODIFIED_SINCE);
if (ifUnmodifiedSinceHeader != null && !ifUnmodifiedSinceHeader.isEmpty()) {
try {
final long ifUnmodifiedSince = HttpHeaderReader.readDate(ifUnmodifiedSinceHeader).getTime();
if (roundDown(lastModified) > ifUnmodifiedSince) {
// 412 Precondition Failed
return Response.status(Response.Status.PRECONDITION_FAILED);
}
} catch (final ParseException ex) {
// Ignore the header if parsing error
}
}
return null;
}
private Response.ResponseBuilder evaluateIfModifiedSince(final long lastModified) {
final String ifModifiedSinceHeader = getHeaderString(HttpHeaders.IF_MODIFIED_SINCE);
if (ifModifiedSinceHeader == null || ifModifiedSinceHeader.isEmpty()) {
return null;
}
final String httpMethod = getMethod();
if ("GET".equals(httpMethod) || "HEAD".equals(httpMethod)) {
return evaluateIfModifiedSince(lastModified, ifModifiedSinceHeader);
} else {
return null;
}
}
private Response.ResponseBuilder evaluateIfModifiedSince(final long lastModified, final String ifModifiedSinceHeader) {
try {
final long ifModifiedSince = HttpHeaderReader.readDate(ifModifiedSinceHeader).getTime();
if (roundDown(lastModified) <= ifModifiedSince) {
// 304 Not modified
return Response.notModified();
}
} catch (final ParseException ex) {
// Ignore the header if parsing error
}
return null;
}
/**
* Round down the time to the nearest second.
*
* @param time the time to round down.
* @return the rounded down time.
*/
private static long roundDown(final long time) {
return time - time % 1000;
}
/**
* Get the values of a HTTP request header. The returned List is read-only.
* This is a shortcut for {@code getRequestHeaders().get(name)}.
*
* @param name the header name, case insensitive.
* @return a read-only list of header values.
*
* @throws IllegalStateException if called outside the scope of a request.
*/
@Override
public List getRequestHeader(final String name) {
return getHeaders().get(name);
}
/**
* Get the values of HTTP request headers. The returned Map is case-insensitive
* wrt. keys and is read-only. The method never returns {@code null}.
*
* @return a read-only map of header names and values.
*
* @throws IllegalStateException if called outside the scope of a request.
*/
@Override
public MultivaluedMap getRequestHeaders() {
return getHeaders();
}
/**
* Check if the container request has been properly initialized for processing.
*
* @throws IllegalStateException in case the internal state is not ready for processing.
*/
void checkState() throws IllegalStateException {
if (securityContext == null) {
throw new IllegalStateException("SecurityContext set in the ContainerRequestContext must not be null.");
} else if (responseWriter == null) {
throw new IllegalStateException("ResponseWriter set in the ContainerRequestContext must not be null.");
}
}
}