com.sun.jersey.spi.container.ContainerRequest Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ehcache Show documentation
Show all versions of ehcache Show documentation
Ehcache is an open source, standards-based cache used to boost performance,
offload the database and simplify scalability. Ehcache is robust, proven and full-featured and
this has made it the most widely-used Java-based cache.
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2010-2011 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* http://glassfish.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package com.sun.jersey.spi.container;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.net.URI;
import java.security.Principal;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import javax.ws.rs.WebApplicationException;
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.PathSegment;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder;
import javax.ws.rs.core.SecurityContext;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.Variant;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.MessageBodyWriter;
import com.sun.jersey.api.MessageException;
import com.sun.jersey.api.Responses;
import com.sun.jersey.api.container.MappableContainerException;
import com.sun.jersey.api.core.HttpRequestContext;
import com.sun.jersey.api.core.ResourceConfig;
import com.sun.jersey.api.core.TraceInformation;
import com.sun.jersey.api.representation.Form;
import com.sun.jersey.api.uri.UriComponent;
import com.sun.jersey.core.header.AcceptableLanguageTag;
import com.sun.jersey.core.header.InBoundHeaders;
import com.sun.jersey.core.header.MatchingEntityTag;
import com.sun.jersey.core.header.MediaTypes;
import com.sun.jersey.core.header.QualitySourceMediaType;
import com.sun.jersey.core.header.reader.HttpHeaderReader;
import com.sun.jersey.core.reflection.ReflectionHelper;
import com.sun.jersey.core.util.MultivaluedMapImpl;
import com.sun.jersey.core.util.ReaderWriter;
import com.sun.jersey.server.impl.VariantSelector;
import com.sun.jersey.server.impl.model.HttpHelper;
import com.sun.jersey.spi.MessageBodyWorkers;
/**
* An in-bound HTTP request to be processed by the web application.
*
* Containers instantiate, or inherit, and provide an instance to the
* {@link WebApplication}.
*
* By default the implementation of {@link SecurityContext} will throw
* {@link UnsupportedOperationException} if the methods are invoked.
* Containers SHOULD use the method {@link #setSecurityContext(javax.ws.rs.core.SecurityContext) }
* to define security context behaviour rather than extending from this class
* and overriding the methods.
*
* @author [email protected]
* @author [email protected]
*/
public class ContainerRequest implements HttpRequestContext {
private static final Logger LOGGER = Logger.getLogger(ContainerRequest.class.getName());
private static final Annotation[] EMPTY_ANNOTATIONS = new Annotation[0];
public static final String VARY_HEADER = "Vary";
private final WebApplication wa;
private final boolean isTraceEnabled;
private Map properties;
private String method;
private InputStream entity;
private URI baseUri;
private URI requestUri;
private URI absolutePathUri;
private String encodedPath;
private String decodedPath;
private List decodedPathSegments;
private List encodedPathSegments;
private MultivaluedMap decodedQueryParameters;
private MultivaluedMap encodedQueryParameters;
private InBoundHeaders headers;
private int headersModCount;
private MediaType contentType;
private List accept;
private List acceptLanguages;
private Map cookies;
private MultivaluedMap cookieNames;
private SecurityContext securityContext;
/**
* Create a new container request.
*
* The base URI and the request URI must contain the same scheme, user info,
* host and port components.
*
* The base URI must not contain the query and fragment components. The
* encoded path component of the request URI must start with the encoded
* path component of the base URI. The encoded path component of the base
* URI must end in a '/' character.
*
* @param wa the web application
* @param method the HTTP method
* @param baseUri the base URI of the request
* @param requestUri the request URI
* @param headers the request headers
* @param entity the InputStream of the request entity
*/
public ContainerRequest(
WebApplication wa,
String method,
URI baseUri,
URI requestUri,
InBoundHeaders headers,
InputStream entity) {
this.wa = wa;
this.isTraceEnabled = wa.isTracingEnabled();
this.method = method;
this.baseUri = baseUri;
this.requestUri = requestUri;
this.headers = headers;
this.headersModCount = headers.getModCount();
this.entity = entity;
}
/* package */ ContainerRequest(ContainerRequest r) {
this.wa = r.wa;
this.isTraceEnabled = r.isTraceEnabled;
}
// ContainerRequest
/**
* Get the mutable properties.
*
* Care should be taken not to clear the properties or remove properties
* that are unknown otherwise unspecified behaviour may result.
*
* @return the properties.
*/
public Map getProperties() {
if (properties != null) {
return properties;
}
return properties = new HashMap();
}
/**
* Set the HTTP method.
*
* @param method the method.
*/
public void setMethod(String method) {
this.method = method;
}
/**
* Set the base and request URI.
*
* @param baseUri the base URI.
* @param requestUri the (complete) request URI.
*/
public void setUris(URI baseUri, URI requestUri) {
this.baseUri = baseUri;
this.requestUri = requestUri;
// reset state
absolutePathUri = null;
encodedPath = decodedPath = null;
decodedPathSegments = encodedPathSegments = null;
decodedQueryParameters = encodedQueryParameters = null;
}
/**
* Get the input stream of the entity.
*
* @return the input stream of the entity.
*/
public InputStream getEntityInputStream() {
return entity;
}
/**
* Set the input stream of the entity.
*
* @param entity the input stream of the entity.
*/
public void setEntityInputStream(InputStream entity) {
this.entity = entity;
}
/**
* Set the request headers.
*
* @param headers the request headers.
*/
public void setHeaders(InBoundHeaders headers) {
this.headers = headers;
this.headersModCount = headers.getModCount();
// reset state
contentType = null;
accept = null;
cookies = null;
cookieNames = null;
}
/**
* Set the security context.
*
* @param securityContext the security context.
*/
public void setSecurityContext(SecurityContext securityContext) {
this.securityContext = securityContext;
}
/**
* Get the security context.
*
* @return the security context.
*/
public SecurityContext getSecurityContext() {
return securityContext;
}
/**
* Get the message body workers.
*
* @return the message body workers.
*/
public MessageBodyWorkers getMessageBodyWorkers() {
return wa.getMessageBodyWorkers();
}
// Traceable
@Override
public boolean isTracingEnabled() {
return isTraceEnabled;
}
@Override
public void trace(String message) {
if (!isTracingEnabled())
return;
if (wa.getFeaturesAndProperties().getFeature(ResourceConfig.FEATURE_TRACE_PER_REQUEST) &&
!getRequestHeaders().containsKey("X-Jersey-Trace-Accept"))
return;
TraceInformation ti = (TraceInformation) getProperties().
get(TraceInformation.class.getName());
ti.trace(message);
}
// HttpRequestContext
@Override
public URI getBaseUri() {
return baseUri;
}
@Override
public UriBuilder getBaseUriBuilder() {
return UriBuilder.fromUri(getBaseUri());
}
@Override
public URI getRequestUri() {
return requestUri;
}
@Override
public UriBuilder getRequestUriBuilder() {
return UriBuilder.fromUri(getRequestUri());
}
@Override
public URI getAbsolutePath() {
if (absolutePathUri != null) return absolutePathUri;
return absolutePathUri = UriBuilder.fromUri(requestUri).
replaceQuery("").fragment("").
build();
}
@Override
public UriBuilder getAbsolutePathBuilder() {
return UriBuilder.fromUri(getAbsolutePath());
}
@Override
public String getPath() {
return getPath(true);
}
@Override
public String getPath(boolean decode) {
if (decode) {
if (decodedPath != null) return decodedPath;
return decodedPath = UriComponent.decode(
getEncodedPath(),
UriComponent.Type.PATH);
} else {
return getEncodedPath();
}
}
private String getEncodedPath() {
if (encodedPath != null) return encodedPath;
final int length = getBaseUri().getRawPath().length();
if(length < getRequestUri().getRawPath().length()) {
return encodedPath = getRequestUri().getRawPath().substring(length);
} else {
return "";
}
}
@Override
public List getPathSegments() {
return getPathSegments(true);
}
@Override
public List getPathSegments(boolean decode) {
if (decode) {
if (decodedPathSegments != null)
return decodedPathSegments;
return decodedPathSegments = UriComponent.decodePath(getPath(false), true);
} else {
if (encodedPathSegments != null)
return encodedPathSegments;
return encodedPathSegments = UriComponent.decodePath(getPath(false), false);
}
}
@Override
public MultivaluedMap getQueryParameters() {
return getQueryParameters(true);
}
@Override
public MultivaluedMap getQueryParameters(boolean decode) {
if (decode) {
if (decodedQueryParameters != null)
return decodedQueryParameters;
return decodedQueryParameters = UriComponent.decodeQuery(
getRequestUri(), true);
} else {
if (encodedQueryParameters != null)
return encodedQueryParameters;
return encodedQueryParameters = UriComponent.decodeQuery(
getRequestUri(), false);
}
}
@Override
public String getHeaderValue(String name) {
final List v = getRequestHeaders().get(name);
if (v == null) return null;
if (v.isEmpty()) return "";
if (v.size() == 1) return v.get(0);
StringBuilder sb = new StringBuilder(v.get(0));
for (int i = 1; i < v.size(); i++) {
final String s = v.get(i);
if (s.length() > 0)
sb.append(',').append(s);
}
return sb.toString();
}
@Override
public T getEntity(Class type, Type genericType, Annotation[] as) {
MediaType mediaType = getMediaType();
if (mediaType == null) {
mediaType = MediaType.APPLICATION_OCTET_STREAM_TYPE;
}
MessageBodyReader bw = getMessageBodyWorkers().getMessageBodyReader(
type, genericType,
as, mediaType);
if (bw == null) {
String message = "A message body reader for Java class " + type.getName() +
", and Java type " + genericType +
", and MIME media type " + mediaType + " was not found.\n";
Map> m = getMessageBodyWorkers().
getReaders(mediaType);
message += "The registered message body readers compatible with the MIME media type are:\n" +
getMessageBodyWorkers().readersToString(m);
LOGGER.severe(message);
throw new WebApplicationException(
new MessageException(message),
Responses.unsupportedMediaType().build());
}
if (isTracingEnabled()) {
trace(String.format("matched message body reader: %s, \"%s\" -> %s",
genericType,
mediaType,
ReflectionHelper.objectToString(bw)));
}
try {
return bw.readFrom(type, genericType, as, mediaType, headers, entity);
} catch (WebApplicationException ex) {
throw ex;
} catch (Exception e) {
throw new MappableContainerException(e);
}
}
/**
* Set the request entity.
*
* @param type the class of object that is to be written.
* @param genericType the type of object to be written, obtained either
* by reflection of a resource method return type or by inspection
* of the returned instance. {@link javax.ws.rs.core.GenericEntity}
* provides a way to specify this information at runtime.
* @param annotations an array of the annotations on the resource
* method that returns the object.
* @param mediaType the media type of the HTTP entity.
* @param httpHeaders a mutable map of the HTTP response headers.
* @param entity the entity instance to write.
* @throws MappableContainerException encapsulates exceptions thrown while
* serializing the entity.
*/
public void setEntity(final Class type, final Type genericType,
final Annotation annotations[], final MediaType mediaType,
final MultivaluedMap httpHeaders, final T entity) {
final MessageBodyWriter writer = getMessageBodyWorkers().getMessageBodyWriter(type, genericType, annotations, mediaType);
if (writer == null) {
String message = "A message body writer for Java class " + type.getName() +
", and Java type " + genericType +
", and MIME media type " + mediaType + " was not found.\n";
Map> m = getMessageBodyWorkers().
getReaders(mediaType);
message += "The registered message body readers compatible with the MIME media type are:\n" +
getMessageBodyWorkers().readersToString(m);
LOGGER.severe(message);
throw new WebApplicationException(
new MessageException(message),
Responses.unsupportedMediaType().build());
}
if (isTracingEnabled()) {
trace(String.format("matched message body writer: %s, \"%s\" -> %s",
genericType,
mediaType,
ReflectionHelper.objectToString(writer)));
}
final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
try {
writer.writeTo(entity, type, genericType, annotations, mediaType, httpHeaders, byteArrayOutputStream);
} catch (IOException e) {
throw new MappableContainerException(e);
}
this.entity = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
}
@Override
public T getEntity(Class type) {
return getEntity(type, type, EMPTY_ANNOTATIONS);
}
@Override
public MediaType getAcceptableMediaType(List mediaTypes) {
if (mediaTypes.isEmpty())
return getAcceptableMediaTypes().get(0);
for (MediaType a : getAcceptableMediaTypes()) {
if (a.getType().equals(MediaType.MEDIA_TYPE_WILDCARD))
return mediaTypes.get(0);
for (MediaType m : mediaTypes)
if (m.isCompatible(a) && !m.isWildcardType() && !m.isWildcardSubtype())
return m;
}
return null;
}
@Override
public List getAcceptableMediaTypes(List priorityMediaTypes) {
return new ArrayList(HttpHelper.getAccept(this, priorityMediaTypes));
}
@Override
public MultivaluedMap getCookieNameValueMap() {
if (cookieNames == null || headersModCount != headers.getModCount()) {
cookieNames = new MultivaluedMapImpl();
for (Map.Entry e : getCookies().entrySet()) {
cookieNames.putSingle(e.getKey(), e.getValue().getValue());
}
}
return cookieNames;
}
@Override
public Form getFormParameters() {
if (MediaTypes.typeEquals(MediaType.APPLICATION_FORM_URLENCODED_TYPE, getMediaType())) {
InputStream in = getEntityInputStream();
if (in.getClass() != ByteArrayInputStream.class) {
// Buffer input
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
try {
ReaderWriter.writeTo(in, byteArrayOutputStream);
} catch (IOException e) {
throw new IllegalArgumentException(e);
}
in = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
setEntityInputStream(in);
}
ByteArrayInputStream byteArrayInputStream = (ByteArrayInputStream) in;
Form f = getEntity(Form.class);
byteArrayInputStream.reset();
return f;
} else {
return new Form();
}
}
// HttpHeaders
@Override
public MultivaluedMap getRequestHeaders() {
return headers;
}
@Override
public List getRequestHeader(String name) {
return headers.get(name);
}
@Override
public List getAcceptableMediaTypes() {
if (accept == null || headersModCount != headers.getModCount())
accept = new ArrayList(HttpHelper.getAccept(this));
return accept;
}
@Override
public List getAcceptableLanguages() {
if (acceptLanguages == null || headersModCount != headers.getModCount()) {
List alts = HttpHelper.getAcceptLangauge(this);
acceptLanguages = new ArrayList(alts.size());
for (AcceptableLanguageTag alt : alts) {
acceptLanguages.add(alt.getAsLocale());
}
}
return acceptLanguages;
}
@Override
public MediaType getMediaType() {
if (contentType == null || headersModCount != headers.getModCount())
contentType = HttpHelper.getContentType(this);
return contentType;
}
@Override
public Locale getLanguage() {
return HttpHelper.getContentLanguageAsLocale(this);
}
@Override
public Map getCookies() {
if (cookies == null || headersModCount != headers.getModCount()) {
cookies = new HashMap();
List cl = getRequestHeaders().get(HttpHeaders.COOKIE);
if (cl != null) {
for (String cookie : cl) {
if (cookie != null)
cookies.putAll(
HttpHeaderReader.readCookies(cookie));
}
}
}
return cookies;
}
// Request
@Override
public String getMethod() {
return method;
}
@Override
public Variant selectVariant(List variants) {
if (variants == null || variants.isEmpty())
throw new IllegalArgumentException("The list of variants is null or empty");
// TODO mark the Vary header to be added to the response
return VariantSelector.selectVariant(this, variants);
}
@Override
public ResponseBuilder evaluatePreconditions() {
Set matchingTags = HttpHelper.getIfMatch(this);
if (matchingTags == null) {
return null;
}
// Since the resource does not exist the method must not be
// perform and 412 Precondition Failed is returned
return Responses.preconditionFailed();
}
@Override
public ResponseBuilder evaluatePreconditions(EntityTag eTag) {
if(eTag == null) {
throw new IllegalArgumentException("Parameter 'eTag' cannot be null.");
}
ResponseBuilder r = evaluateIfMatch(eTag);
if (r != null)
return r;
return evaluateIfNoneMatch(eTag);
}
@Override
public ResponseBuilder evaluatePreconditions(Date lastModified) {
if(lastModified == null) {
throw new IllegalArgumentException("Parameter 'lastModified' cannot be null.");
}
final long lastModifiedTime = lastModified.getTime();
ResponseBuilder r = evaluateIfUnmodifiedSince(lastModifiedTime);
if (r != null)
return r;
return evaluateIfModifiedSince(lastModifiedTime);
}
@Override
public ResponseBuilder evaluatePreconditions(Date lastModified, EntityTag eTag) {
if(lastModified == null || eTag == null) {
throw new IllegalArgumentException("Parameters 'lastModified' and 'eTag' cannot be null.");
}
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 = getMethod().equals("GET") || getMethod().equals("HEAD");
final Set matchingTags = HttpHelper.getIfNoneMatch(this);
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 r;
// 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 = getRequestHeaders().getFirst("If-Modified-Since");
if (ifModifiedSinceHeader != null && isGetOrHead) {
r = evaluateIfModifiedSince(lastModifiedTime, ifModifiedSinceHeader);
if (r != null)
r.tag(eTag);
}
return r;
}
private ResponseBuilder evaluateIfMatch(EntityTag eTag) {
Set matchingTags = HttpHelper.getIfMatch(this);
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 Responses.preconditionFailed();
}
if (matchingTags != MatchingEntityTag.ANY_MATCH &&
!matchingTags.contains(eTag)) {
// 412 Precondition Failed
return Responses.preconditionFailed();
}
return null;
}
private ResponseBuilder evaluateIfNoneMatch(EntityTag eTag) {
Set matchingTags = HttpHelper.getIfNoneMatch(this);
if (matchingTags == null)
return null;
final String httpMethod = getMethod();
return evaluateIfNoneMatch(
eTag,
matchingTags,
httpMethod.equals("GET") || httpMethod.equals("HEAD"));
}
private ResponseBuilder evaluateIfNoneMatch(
EntityTag eTag,
Set matchingTags,
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 Responses.preconditionFailed();
}
}
return null;
}
private ResponseBuilder evaluateIfUnmodifiedSince(long lastModified) {
String ifUnmodifiedSinceHeader = getRequestHeaders().getFirst("If-Unmodified-Since");
if (ifUnmodifiedSinceHeader != null) {
try {
long ifUnmodifiedSince = HttpHeaderReader.readDate(ifUnmodifiedSinceHeader).getTime();
if (roundDown(lastModified) > ifUnmodifiedSince) {
// 412 Precondition Failed
return Responses.preconditionFailed();
}
} catch (ParseException ex) {
// Ignore the header if parsing error
}
}
return null;
}
private ResponseBuilder evaluateIfModifiedSince(long lastModified) {
String ifModifiedSinceHeader = getRequestHeaders().getFirst("If-Modified-Since");
if (ifModifiedSinceHeader == null)
return null;
final String httpMethod = getMethod();
if (httpMethod.equals("GET") || httpMethod.equals("HEAD")) {
return evaluateIfModifiedSince(
lastModified,
ifModifiedSinceHeader);
} else {
return null;
}
}
private ResponseBuilder evaluateIfModifiedSince(
long lastModified,
String ifModifiedSinceHeader) {
try {
long ifModifiedSince = HttpHeaderReader.readDate(ifModifiedSinceHeader).getTime();
if (roundDown(lastModified) <= ifModifiedSince) {
// 304 Not modified
return Responses.notModified();
}
} catch (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(long time) {
return time - time % 1000;
}
// SecurityContext
@Override
public Principal getUserPrincipal() {
if (securityContext == null)
throw new UnsupportedOperationException();
return securityContext.getUserPrincipal();
}
@Override
public boolean isUserInRole(String role) {
if (securityContext == null)
throw new UnsupportedOperationException();
return securityContext.isUserInRole(role);
}
@Override
public boolean isSecure() {
if (securityContext == null)
throw new UnsupportedOperationException();
return securityContext.isSecure();
}
@Override
public String getAuthenticationScheme() {
if (securityContext == null)
throw new UnsupportedOperationException();
return securityContext.getAuthenticationScheme();
}
}