/*******************************************************************************
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.wink.server.internal.registry;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Member;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Cookie;
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.UriInfo;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.Providers;
import org.apache.wink.common.RuntimeContext;
import org.apache.wink.common.internal.PathSegmentImpl;
import org.apache.wink.common.internal.i18n.Messages;
import org.apache.wink.common.internal.registry.BoundInjectable;
import org.apache.wink.common.internal.registry.ContextAccessor;
import org.apache.wink.common.internal.registry.Injectable;
import org.apache.wink.common.internal.registry.InjectableFactory;
import org.apache.wink.common.internal.registry.ValueConvertor.ConversionException;
import org.apache.wink.common.internal.runtime.RuntimeContextTLS;
import org.apache.wink.common.internal.uri.UriEncoder;
import org.apache.wink.common.internal.utils.MediaTypeUtils;
import org.apache.wink.common.internal.utils.StringUtils;
import org.apache.wink.common.utils.ProviderUtils;
import org.apache.wink.common.utils.ProviderUtils.PROVIDER_EXCEPTION_ORIGINATOR;
import org.apache.wink.server.internal.handlers.SearchResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ServerInjectableFactory extends InjectableFactory {
private final static Logger logger = LoggerFactory.getLogger(ServerInjectableFactory.class);
@Override
public Injectable createContextParam(Class<?> classType, Annotation[] annotations, Member member) {
return new ServerContextParam(classType, annotations, member);
}
@Override
public Injectable createCookieParam(String value,
Class<?> classType,
Type genericType,
Annotation[] annotations,
Member member) {
return new CookieParamBinding(value, classType, genericType, annotations, member);
}
@Override
public Injectable createEntityParam(Class<?> classType,
Type genericType,
Annotation[] annotations,
Member member) {
return new EntityParam(classType, genericType, annotations, member);
}
@Override
public Injectable createFormParam(String value,
Class<?> classType,
Type genericType,
Annotation[] annotations,
Member member) {
return new FormParamBinding(value, classType, genericType, annotations, member);
}
@Override
public Injectable createHeaderParam(String value,
Class<?> classType,
Type genericType,
Annotation[] annotations,
Member member) {
return new HeaderParamBinding(value, classType, genericType, annotations, member);
}
@Override
public Injectable createMatrixParam(String value,
Class<?> classType,
Type genericType,
Annotation[] annotations,
Member member) {
return new MatrixParamBinding(value, classType, genericType, annotations, member);
}
@Override
public Injectable createPathParam(String value,
Class<?> classType,
Type genericType,
Annotation[] annotations,
Member member) {
return new PathParamBinding(value, classType, genericType, annotations, member);
}
@Override
public Injectable createQueryParam(String value,
Class<?> classType,
Type genericType,
Annotation[] annotations,
Member member) {
return new QueryParamBinding(value, classType, genericType, annotations, member);
}
/**
* Used for injecting a field or parameter of JAX-RS resource with a
* context, as defined by the JAX-RS spec. First searches for a
* ContextResolver to get the context to inject, and if none is found, then
* tries one of the built-in types of context
*/
public static class ServerContextParam extends Injectable {
private ContextAccessor contextAccessor;
public ServerContextParam(Class<?> type, Annotation[] annotations, Member member) {
super(ParamType.CONTEXT, type, type, annotations, member);
if (type != HttpServletRequest.class && type != HttpServletResponse.class) {
contextAccessor = new ContextAccessor();
} else {
// due to strict checking of HttpServletRequest and
// HttpServletResponse
// injections, a special injector must be used
contextAccessor = new ServletContextAccessor();
}
}
@Override
public Object getValue(RuntimeContext runtimeContext) {
return contextAccessor.getContext(getType(), runtimeContext);
}
}
/**
* Used for injecting a field or parameter of JAX-RS resource that has no
* annotation on it - represents the request entity.
*/
public static class EntityParam extends Injectable {
public EntityParam(Class<?> type, Type genericType, Annotation[] annotations, Member member) {
super(ParamType.ENTITY, type, genericType, annotations, member);
}
@SuppressWarnings("unchecked")
public Object getValue(RuntimeContext runtimeContext) throws IOException {
if (runtimeContext == null) {
return null;
}
Class<?> paramType = getType();
// check if there is a provider that can handle this parameter
Providers providers = runtimeContext.getProviders();
if (providers != null) {
MediaType mediaType = runtimeContext.getHttpHeaders().getMediaType();
if (mediaType == null) {
mediaType = MediaType.APPLICATION_OCTET_STREAM_TYPE;
}
MessageBodyReader mbr =
providers.getMessageBodyReader(paramType,
getGenericType(),
getAnnotations(),
mediaType);
if (mbr != null) {
Object read;
try {
read = mbr.readFrom(paramType,
getGenericType(),
getAnnotations(),
mediaType,
runtimeContext.getHttpHeaders().getRequestHeaders(),
runtimeContext.getInputStream());
} catch (RuntimeException e) {
ProviderUtils.logUserProviderException(e, mbr, PROVIDER_EXCEPTION_ORIGINATOR.readFrom, new Object[]{paramType, getGenericType(), getAnnotations(), mediaType, runtimeContext.getHttpHeaders().getRequestHeaders(),
runtimeContext.getInputStream()}, runtimeContext);
throw e;
}
return read;
}
}
throw new WebApplicationException(Response.Status.UNSUPPORTED_MEDIA_TYPE);
}
}
/**
* Used for injecting a field or parameter of JAX-RS resource with the value
* of a matrix parameter
*/
public static class MatrixParamBinding extends BoundInjectable {
public MatrixParamBinding(String variableName,
Class<?> type,
Type genericType,
Annotation[] annotations,
Member member) {
super(ParamType.MATRIX, variableName, type, genericType, annotations, member);
}
@Override
public Object getValue(RuntimeContext runtimeContext) throws IOException {
if (runtimeContext == null) {
return null;
}
List<String> allValues = new ArrayList<String>();
List<PathSegment> segments =
runtimeContext.getAttribute(SearchResult.class).getData().getMatchedURIs().get(0);
// get the matrix parameter only from the last segment
PathSegment segment = segments.get(segments.size() - 1);
MultivaluedMap<String, String> matrixParameters = segment.getMatrixParameters();
List<String> values = matrixParameters.get(getName());
if (values != null) {
allValues.addAll(values);
}
if (allValues.size() == 0 && hasDefaultValue()) {
allValues.add(getDefaultValue());
}
decodeValues(allValues);
// we found matrix parameters with the specified name
try {
return getConvertor().convert(allValues);
} catch (ConversionException e) {
throw new WebApplicationException(e.getCause(), Response.Status.NOT_FOUND);
}
}
}
/**
* Used for injecting a field or parameter of JAX-RS resource with the value
* of a query parameter
*/
public static class QueryParamBinding extends BoundInjectable {
public QueryParamBinding(String variableName,
Class<?> type,
Type genericType,
Annotation[] annotations,
Member member) {
super(ParamType.QUERY, variableName, type, genericType, annotations, member);
}
@Override
public Object getValue(RuntimeContext runtimeContext) throws IOException {
if (runtimeContext == null) {
return null;
}
UriInfo uriInfo = runtimeContext.getUriInfo();
MultivaluedMap<String, String> queryParameters = uriInfo.getQueryParameters(false);
List<String> values = queryParameters.get(getName());
if (values == null) {
values = new LinkedList<String>();
}
if (values.size() == 0 && hasDefaultValue()) {
values.add(getDefaultValue());
}
decodeValues(values);
// we found query parameter values with the specified name
try {
return getConvertor().convert(values);
} catch (ConversionException e) {
throw new WebApplicationException(e.getCause(), Response.Status.NOT_FOUND);
}
}
@Override
protected String decodeValue(String value) {
// also decodes the '+' signs into spaces
return UriEncoder.decodeQuery(value);
}
}
/**
* Used for injecting a field or parameter of JAX-RS resource with the value
* of a Form parameter
*/
public static class FormParamBinding extends BoundInjectable {
static final String FORM_PARAMATERS =
"wink.formParameters"; //$NON-NLS-1$
public final static MultivaluedMap<String, String> dummyMultivaluedMap = null;
private static Type MULTIVALUED_MAP_STRING_TYPE = null;
static {
try {
MULTIVALUED_MAP_STRING_TYPE =
FormParamBinding.class.getField("dummyMultivaluedMap").getGenericType(); //$NON-NLS-1$
} catch (SecurityException e) {
throw new WebApplicationException(e);
} catch (NoSuchFieldException e) {
throw new WebApplicationException(e);
}
}
public FormParamBinding(String variableName,
Class<?> type,
Type genericType,
Annotation[] annotations,
Member member) {
super(ParamType.FORM, variableName, type, genericType, annotations, member);
}
@SuppressWarnings("unchecked")
@Override
public Object getValue(RuntimeContext runtimeContext) throws IOException {
if (runtimeContext == null) {
return null;
}
// request must be application/x-www-form-urlencoded
MediaType mediaType = runtimeContext.getHttpHeaders().getMediaType();
if (!MediaTypeUtils.equalsIgnoreParameters(mediaType,
MediaType.APPLICATION_FORM_URLENCODED_TYPE)) {
return null;
}
// see if we already have the form parameters, which will happen if
// there
// is more than one form parameter on the method
MultivaluedMap<String, String> formParameters =
(MultivaluedMap<String, String>)runtimeContext.getAttributes().get(FORM_PARAMATERS);
if (formParameters == null) {
// read the request body as an entity parameter to get the form
// parameters
EntityParam entityParam =
new EntityParam(MultivaluedMap.class, MULTIVALUED_MAP_STRING_TYPE,
getAnnotations(), null);
formParameters =
(MultivaluedMap<String, String>)entityParam.getValue(runtimeContext);
if (formParameters.isEmpty()) {
// see E011 at
// http://jcp.org/aboutJava/communityprocess/maintenance/jsr311/311ChangeLog.html
// Perhaps the message body was already consumed by a
// servlet filter. Let's try the servlet request parameters
// instead.
Map map =
RuntimeContextTLS.getRuntimeContext()
.getAttribute(HttpServletRequest.class).getParameterMap();
// We can't easily use MultivaluedMap.putAll because we have
// a map whose values are String[]
// Let's iterate and call the appropriate MultivaluedMap.put
// method.
for (Iterator it = map.keySet().iterator(); it.hasNext();) {
String key = (String)it.next();
String[] value = (String[])map.get(key);
formParameters.put(key, Arrays.asList(value));
}
}
runtimeContext.getAttributes().put(FORM_PARAMATERS, formParameters);
}
// get the values of the parameter
List<String> values = formParameters.get(getName());
if (values == null) {
values = new LinkedList<String>();
}
// TODO: do we add also all the query parameters???
if (values.size() == 0 && hasDefaultValue()) {
values.add(getDefaultValue());
}
// decode all values
decodeValues(values);
try {
return getConvertor().convert(values);
} catch (ConversionException e) {
// See E010
// http://jcp.org/aboutJava/communityprocess/maintenance/jsr311/311ChangeLog.html:
// "400 status code should be returned if an exception is
// raised during @FormParam-annotated parameter construction"
logger.error(Messages.getMessage("conversionError", this, values), e);
throw new WebApplicationException(e.getCause(), Response.Status.BAD_REQUEST);
}
}
@Override
protected String decodeValue(String value) {
// also decodes the '+' signs into spaces
return UriEncoder.decodeQuery(value);
}
}
/**
* Used for injecting a field or parameter of JAX-RS resource with the value
* of a path template variable
*/
public static class PathParamBinding extends BoundInjectable {
public PathParamBinding(String variableName,
Class<?> type,
Type genericType,
Annotation[] annotations,
Member member) {
super(ParamType.PATH, variableName, type, genericType, annotations, member);
}
@Override
public Object getValue(RuntimeContext runtimeContext) throws IOException {
if (runtimeContext == null) {
return null;
}
MultivaluedMap<String, List<PathSegment>> pathSegmentsMap =
runtimeContext.getAttribute(SearchResult.class).getData()
.getMatchedVariablesPathSegments();
List<PathSegment> segments = null;
List<List<PathSegment>> listOfListPathSegments = pathSegmentsMap.get(getName());
if (listOfListPathSegments != null && listOfListPathSegments.size() > 0) {
segments = listOfListPathSegments.get(listOfListPathSegments.size() - 1);
}
if (segments != null && segments.size() > 0) {
// special handling for PathSegment
if (isTypeOf(PathSegment.class)) {
// return only the last segment
PathSegment segment = segments.get(segments.size() - 1);
if (!isEncoded()) {
segment = PathSegmentImpl.decode(segment);
}
return segment;
}
// special handling for collection of PathSegment
if (isTypeCollectionOf(PathSegment.class)) {
// return all segments
List<PathSegment> list = segments;
if (!isEncoded()) {
// decode all path segments
list = new ArrayList<PathSegment>(segments.size());
for (PathSegment segment : segments) {
list.add(PathSegmentImpl.decode(segment));
}
}
return asTypeCollection(list, null);
}
}
// for all other types and for cases where the default value should
// be used
UriInfo uriInfo = runtimeContext.getUriInfo();
MultivaluedMap<String, String> variables = uriInfo.getPathParameters(false);
List<String> values = variables.get(getName());
if (values == null) {
values = new LinkedList<String>();
}
// use default value
if (values.size() == 0 && hasDefaultValue()) {
String defaultValue = getDefaultValue();
// if the injected type is a PathSegment or some collection of
// PathSegment then
// split the default value
// into separate segments, otherwise, pass the default value
// as-is.
if (isTypeOf(PathSegment.class) || isTypeCollectionOf(PathSegment.class)) {
String[] segmentsArray = StringUtils.fastSplit(defaultValue, "/", true); //$NON-NLS-1$
values.addAll(Arrays.asList(segmentsArray));
} else {
values.add(defaultValue);
}
decodeValues(values);
try {
return getConvertor().convert(values);
} catch (ConversionException e) {
throw new WebApplicationException(e.getCause(), Response.Status.NOT_FOUND);
}
}
decodeValues(values);
try {
Collections.reverse(values);
return getConvertor().convert(values);
} catch (ConversionException e) {
throw new WebApplicationException(e.getCause(), Response.Status.NOT_FOUND);
}
}
}
/**
* Used for injecting a field or parameter of JAX-RS resource with the value
* of a request header
*/
public static class HeaderParamBinding extends BoundInjectable {
public HeaderParamBinding(String variableName,
Class<?> type,
Type genericType,
Annotation[] annotations,
Member member) {
super(ParamType.HEADER, variableName, type, genericType, annotations, member);
}
@Override
public Object getValue(RuntimeContext runtimeContext) throws IOException {
if (runtimeContext == null) {
return null;
}
// for all headers
HttpHeaders httpHeaders = runtimeContext.getHttpHeaders();
List<String> values = httpHeaders.getRequestHeader(getName());
if (values == null) {
values = new LinkedList<String>();
}
if (values.size() == 0 && hasDefaultValue()) {
values.add(getDefaultValue());
}
try {
return getConvertor().convert(values);
} catch (ConversionException e) {
logger.error(Messages.getMessage("conversionError", this, values), e);
throw new WebApplicationException(e.getCause(), Response.Status.BAD_REQUEST);
}
}
}
/**
* Used for injecting a field or parameter of JAX-RS resource with the value
* of a request cookie
*/
public static class CookieParamBinding extends BoundInjectable {
public CookieParamBinding(String variableName,
Class<?> type,
Type genericType,
Annotation[] annotations,
Member member) {
super(ParamType.COOKIE, variableName, type, genericType, annotations, member);
}
@Override
public Object getValue(RuntimeContext runtimeContext) throws IOException {
if (runtimeContext == null) {
return null;
}
String value = null;
HttpHeaders httpHeaders = runtimeContext.getHttpHeaders();
Map<String, Cookie> values = httpHeaders.getCookies();
Cookie cookie = null;
if (values.size() > 0) {
cookie = values.get(getName());
}
if (cookie == null && hasDefaultValue()) {
cookie = new Cookie(getName(), getDefaultValue());
}
if (cookie != null) {
// special handling for List<Cookie>
if (isTypeCollectionOf(Cookie.class)) {
return elementAsTypeCollection(cookie, new CookieComparator());
}
// special handling for Cookie
if (isTypeOf(Cookie.class)) {
return cookie;
}
// for all other types
value = cookie.getValue();
}
try {
return getConvertor().convert(value);
} catch (ConversionException e) {
logger.error(Messages.getMessage("conversionError", this, value), e);
throw new WebApplicationException(e.getCause(), Response.Status.BAD_REQUEST);
}
}
public static class CookieComparator implements Comparator<Cookie> {
public int compare(Cookie o1, Cookie o2) {
int val = 0;
if (o1.getName() != null) {
val = o1.getName().compareTo(o2.getName());
if (val != 0) {
return val;
}
}
if (o1.getValue() != null) {
val = o1.getValue().compareTo(o2.getValue());
if (val != 0) {
return val;
}
}
if (o1.getPath() != null) {
val = o1.getPath().compareTo(o2.getPath());
if (val != 0) {
return val;
}
}
if (o1.getDomain() != null) {
val = o1.getDomain().compareTo(o2.getDomain());
if (val != 0) {
return val;
}
}
return o1.getVersion() - o2.getVersion();
}
}
}
}