/*
* Copyright (c) 2012, 2019 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.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.HttpHeaders;
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.internal.PropertiesDelegate;
import org.glassfish.jersey.internal.guava.Preconditions;
import org.glassfish.jersey.internal.util.collection.Ref;
import org.glassfish.jersey.internal.util.collection.Refs;
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.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 ApplicationHandler
for each incoming client request. Author: Marek Potociar
/**
* Jersey container request context.
* <p/>
* 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 {
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;
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.
Params: - baseUri – base application URI.
- requestUri – request URI.
- httpMethod – request HTTP method name.
- securityContext – security context of the current request. Must not be
null
. The SecurityContext.getUserPrincipal()
must return null
if the current request has not been authenticated by the container. - propertiesDelegate – custom
properties delegate
to be used by the context. - configuration – the server
Configuration
. If null
, the default behaviour is expected.
/**
* 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.
Params: - baseUri – base application URI.
- requestUri – request URI.
- httpMethod – request HTTP method name.
- securityContext – security context of the current request. Must not be
null
. The SecurityContext.getUserPrincipal()
must return null
if the current request has not been authenticated by the container. - propertiesDelegate – custom
properties delegate
to be used by the context.
See Also:
/**
* 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, (Configuration) null);
}
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.
Returns: custom container extensions initializer or null
if not available.
/**
* Get a custom container extensions initializer for the current request.
* <p/>
* 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.
Params: - requestScopedInitializer – custom container extensions initializer.
/**
* Set a custom container extensions initializer for the current request.
* <p/>
* 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.
Returns: container response writer.
/**
* 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.
Params: - responseWriter – container response writer. Must not be
null
.
/**
* 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.
Params: - rawType – raw Java entity type.
Type parameters: - <T> – entity Java object type.
Returns: entity read from a context entity input stream.
/**
* Read entity from a context entity input stream.
*
* @param <T> entity Java object type.
* @param rawType raw Java entity type.
* @return entity read from a context entity input stream.
*/
public <T> T readEntity(final Class<T> rawType) {
return readEntity(rawType, propertiesDelegate);
}
Read entity from a context entity input stream.
Params: - rawType – raw Java entity type.
- annotations – entity annotations.
Type parameters: - <T> – entity Java object type.
Returns: entity read from a context entity input stream.
/**
* Read entity from a context entity input stream.
*
* @param <T> 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> T readEntity(final Class<T> rawType, final Annotation[] annotations) {
return super.readEntity(rawType, annotations, propertiesDelegate);
}
Read entity from a context entity input stream.
Params: - rawType – raw Java entity type.
- type – generic Java entity type.
Type parameters: - <T> – entity Java object type.
Returns: entity read from a context entity input stream.
/**
* Read entity from a context entity input stream.
*
* @param <T> 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> T readEntity(final Class<T> rawType, final Type type) {
return super.readEntity(rawType, type, propertiesDelegate);
}
Read entity from a context entity input stream.
Params: - rawType – raw Java entity type.
- type – generic Java entity type.
- annotations – entity annotations.
Type parameters: - <T> – entity Java object type.
Returns: entity read from a context entity input stream.
/**
* Read entity from a context entity input stream.
*
* @param <T> 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> T readEntity(final Class<T> rawType, final Type type, final Annotation[] annotations) {
return super.readEntity(rawType, type, annotations, propertiesDelegate);
}
@Override
public Object getProperty(final String name) {
return propertiesDelegate.getProperty(name);
}
@Override
public Collection<String> 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.
Returns: underlying properties delegate.
/**
* 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.
Returns: All bound (dynamically or by name) request filters applicable to the matched inflector (or an empty
collection if no inflector matched yet).
/**
* 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<RankedProvider<ContainerRequestFilter>> getRequestFilters() {
final Inflector<RequestProcessingContext, ContainerResponse> 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.
Returns: All bound (dynamically or by name) response filters applicable to the matched inflector (or an empty
collection if no inflector matched yet).
/**
* 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<RankedProvider<ContainerResponseFilter>> getResponseFilters() {
final Inflector<RequestProcessingContext, ContainerResponse> 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.
Returns: All reader interceptors applicable to the matched inflector (or an empty
collection if no inflector matched yet).
/**
* 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<ReaderInterceptor> getReaderInterceptors() {
final Inflector<RequestProcessingContext, ContainerResponse> inflector = getInflector();
return inflector instanceof ResourceMethodInvoker
? ((ResourceMethodInvoker) inflector).getReaderInterceptors()
: processingProviders.getSortedGlobalReaderInterceptors();
}
Get all writer interceptors applicable to this request.
Returns: All writer interceptors applicable to the matched inflector (or an empty
collection if no inflector matched yet).
/**
* 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<WriterInterceptor> getWriterInterceptors() {
final Inflector<RequestProcessingContext, ContainerResponse> inflector = getInflector();
return inflector instanceof ResourceMethodInvoker
? ((ResourceMethodInvoker) inflector).getWriterInterceptors()
: processingProviders.getSortedGlobalWriterInterceptors();
}
private Inflector<RequestProcessingContext, ContainerResponse> getInflector() {
return uriRoutingContext.getEndpoint();
}
private static <T> Iterable<T> emptyIfNull(final Iterable<T> iterable) {
return iterable == null ? Collections.<T>emptyList() : iterable;
}
Get base request URI.
Returns: base request URI.
/**
* Get base request URI.
*
* @return base request URI.
*/
public URI getBaseUri() {
return baseUri;
}
Get request URI.
Returns: request URI.
/**
* 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.
Returns: the absolute path of the request.
/**
* 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.
Params: - decode – controls whether sequences of escaped octets are decoded (
true
) or not (false
).
Returns: relative request path.
/**
* 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 setMethod(String)
but does not throw IllegalStateException
if the method is invoked in other than pre-matching phase. Params: - method – HTTP 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.
/**
* 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).
* <p/>
* 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 null
otherwise. Returns: request filter chain aborting response if set, or null
otherwise.
/**
* 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<String, Cookie> getCookies() {
return super.getRequestCookies();
}
@Override
public List<MediaType> getAcceptableMediaTypes() {
return getQualifiedAcceptableMediaTypes().stream()
.map((Function<AcceptableMediaType, MediaType>) input -> input)
.collect(Collectors.toList());
}
@Override
public List<Locale> getAcceptableLanguages() {
return getQualifiedAcceptableLanguages().stream().map(LanguageTag::getAsLocale).collect(Collectors.toList());
}
// JAX-RS request
@Override
public Variant selectVariant(final List<Variant> variants) throws IllegalArgumentException {
if (variants == null || variants.isEmpty()) {
throw new IllegalArgumentException(METHOD_PARAMETER_CANNOT_BE_NULL_OR_EMPTY);
}
final Ref<String> 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 null
if no value is to be set. Returns: value of HTTP Vary response header to be set in the response if available, null
otherwise.
/**
* 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<MatchingEntityTag> 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<MatchingEntityTag> 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<MatchingEntityTag> 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.
Params: - time – the time to round down.
Returns: the rounded down time.
/**
* 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 getRequestHeaders().get(name)
. Params: - name – the header name, case insensitive.
Throws: - IllegalStateException – if called outside the scope of a request.
Returns: a read-only list of header values.
/**
* 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<String> 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 null
. Throws: - IllegalStateException – if called outside the scope of a request.
Returns: a read-only map of header names and values.
/**
* 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<String, String> 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.
/**
* 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.");
}
}
}