/**
* Copyright 2005-2014 Restlet
*
* The contents of this file are subject to the terms of one of the following
* open source licenses: Apache 2.0 or or EPL 1.0 (the "Licenses"). You can
* select the license that you prefer but you may not use this file except in
* compliance with one of these Licenses.
*
* You can obtain a copy of the Apache 2.0 license at
* http://www.opensource.org/licenses/apache-2.0
*
* You can obtain a copy of the EPL 1.0 license at
* http://www.opensource.org/licenses/eclipse-1.0
*
* See the Licenses for the specific language governing permissions and
* limitations under the Licenses.
*
* Alternatively, you can obtain a royalty free commercial license with less
* limitations, transferable or non-transferable, directly at
* http://restlet.com/products/restlet-framework
*
* Restlet is a registered trademark of Restlet S.A.S.
*/
package org.restlet.ext.jaxrs.internal.wrappers.params;
import static org.restlet.ext.jaxrs.internal.wrappers.WrapperUtil.isBeanSetter;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.PathSegment;
import javax.ws.rs.core.Request;
import javax.ws.rs.core.SecurityContext;
import javax.ws.rs.core.UriInfo;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Providers;
import org.restlet.ext.jaxrs.ExtendedUriInfo;
import org.restlet.ext.jaxrs.internal.core.ThreadLocalizedContext;
import org.restlet.ext.jaxrs.internal.core.ThreadLocalizedExtendedUriInfo;
import org.restlet.ext.jaxrs.internal.core.ThreadLocalizedUriInfo;
import org.restlet.ext.jaxrs.internal.exceptions.IllegalBeanSetterTypeException;
import org.restlet.ext.jaxrs.internal.exceptions.IllegalFieldTypeException;
import org.restlet.ext.jaxrs.internal.exceptions.IllegalTypeException;
import org.restlet.ext.jaxrs.internal.exceptions.ImplementationException;
import org.restlet.ext.jaxrs.internal.exceptions.InjectException;
import org.restlet.ext.jaxrs.internal.todo.NotYetImplementedException;
import org.restlet.ext.jaxrs.internal.util.Util;
import org.restlet.ext.jaxrs.internal.wrappers.params.ParameterList.AbstractParamGetter;
import org.restlet.ext.jaxrs.internal.wrappers.provider.ExtensionBackwardMapping;
/**
* Helper class to inject into fields annotated with @{@link Context}.
*
* @author Stephan Koops
* @see IntoRrcInjector
*/
public class ContextInjector {
static class BeanSetter implements InjectionAim {
private final Method beanSetter;
private BeanSetter(Method beanSetter) {
this.beanSetter = beanSetter;
this.beanSetter.setAccessible(true);
}
/**
* @throws InvocationTargetException
* @throws InjectException
* @throws IllegalArgumentException
* @see InjectionAim#injectInto(Object, Object, boolean)
*/
public void injectInto(Object resource, Object toInject,
boolean allMustBeAvailable) throws IllegalArgumentException,
InjectException, InvocationTargetException {
Util.inject(resource, this.beanSetter, toInject);
}
}
/**
* {@link Injector}, that injects the same object in every resource. Is is
* used for the @{@link Context} objects.
*/
private static class EverSameInjector implements Injector {
private final Object injectable;
private final InjectionAim injectionAim;
private EverSameInjector(InjectionAim injectionAim, Object injectable) {
this.injectionAim = injectionAim;
this.injectable = injectable;
}
/**
* @see Injector#injectInto(java.lang.Object, boolean)
*/
public void injectInto(Object resource, boolean allMustBeAvailable)
throws IllegalArgumentException, InjectException,
InvocationTargetException {
this.injectionAim.injectInto(resource, this.injectable,
allMustBeAvailable);
}
}
/**
* @author Stephan Koops
*/
private static final class ExtendedUriInfoInjector implements Injector {
private final InjectionAim aim;
private final ThreadLocalizedExtendedUriInfo uriInfo;
ExtendedUriInfoInjector(InjectionAim aim,
ThreadLocalizedContext tlContext) {
this.aim = aim;
this.uriInfo = new ThreadLocalizedExtendedUriInfo(tlContext);
}
public void injectInto(Object resource, boolean allMustBeAvailable)
throws IllegalArgumentException, InjectException,
InvocationTargetException {
this.uriInfo.saveStateForCurrentThread(allMustBeAvailable);
this.aim.injectInto(resource, this.uriInfo, allMustBeAvailable);
}
}
static class FieldWrapper implements InjectionAim {
private final Field field;
private FieldWrapper(Field field) {
this.field = field;
this.field.setAccessible(true);
}
/**
* @throws InvocationTargetException
* @throws InjectException
* @throws IllegalArgumentException
* @see InjectionAim#injectInto(Object, Object, boolean)
*/
public void injectInto(Object resource, Object toInject,
boolean allMustBeAvailable) throws IllegalArgumentException,
InjectException, InvocationTargetException {
Util.inject(resource, this.field, toInject);
}
}
private static class GetLastPathSegment implements PathSegment {
private final ThreadLocalizedContext tlContext;
GetLastPathSegment(ThreadLocalizedContext tlContext) {
this.tlContext = tlContext;
}
private PathSegment getLast() {
final List<PathSegment> pss = this.tlContext.getPathSegments();
return pss.get(pss.size() - 1);
}
/**
* @see javax.ws.rs.core.PathSegment#getMatrixParameters()
*/
public MultivaluedMap<String, String> getMatrixParameters() {
return getLast().getMatrixParameters();
}
/**
* @see javax.ws.rs.core.PathSegment#getPath()
*/
public String getPath() {
return getLast().getPath();
}
}
/**
* Represents a field or a bean setter, where the runtime injects something
* in.
*/
static interface InjectionAim {
/**
* Inject the toInject into this field or bean setter on object
* resource.
*
* @param resource
* @param toInject
* @param allMustBeAvailable
* @throws IllegalArgumentException
* @throws InjectException
* @throws InvocationTargetException
* @see FieldWrapper#set(Object, Object)
* @see Method#invoke(Object, Object...)
*/
void injectInto(Object resource, Object toInject,
boolean allMustBeAvailable) throws IllegalArgumentException,
InjectException, InvocationTargetException;
}
static interface Injector {
/**
* @param resource
* @param allMustBeAvailable
* @throws InvocationTargetException
* @throws InjectException
* @throws IllegalArgumentException
* @see InjectionAim#injectInto(Object, Object, boolean)
*/
public abstract void injectInto(Object resource,
boolean allMustBeAvailable) throws IllegalArgumentException,
InjectException, InvocationTargetException;
}
private class ParamValueInjector implements Injector {
private final AccessibleObject fieldOrBeanSetter;
private final AbstractParamGetter iog;
ParamValueInjector(AccessibleObject fieldOrBeanSetter,
AbstractParamGetter iog) {
this.fieldOrBeanSetter = fieldOrBeanSetter;
this.fieldOrBeanSetter.setAccessible(true);
this.iog = iog;
}
/**
* @see org.restlet.ext.jaxrs.internal.wrappers.params.ContextInjector.Injector#injectInto(java.lang.Object,
* boolean)
*/
public void injectInto(Object resource, boolean allMustBeAvailable)
throws IllegalArgumentException, InjectException,
InvocationTargetException {
Util.inject(resource, this.fieldOrBeanSetter,
this.iog.getParamValue());
}
}
/**
* @author Stephan Koops
*/
private static final class UriInfoInjector implements Injector {
private final InjectionAim aim;
private final ThreadLocalizedUriInfo uriInfo;
UriInfoInjector(InjectionAim aim, ThreadLocalizedContext tlContext) {
this.aim = aim;
this.uriInfo = new ThreadLocalizedUriInfo(tlContext);
}
public void injectInto(Object resource, boolean allMustBeAvailable)
throws IllegalArgumentException, InjectException,
InvocationTargetException {
this.uriInfo.saveStateForCurrentThread(allMustBeAvailable);
this.aim.injectInto(resource, this.uriInfo, allMustBeAvailable);
}
}
private static Logger logger = org.restlet.Context.getCurrentLogger();
/**
* @param declaringClass
* the class / interface to injecto into; must not be
* {@link UriInfo}
* @param tlContext
* @param providers
* @param extensionBackwardMapping
* @param aim
* @return
* @throws IllegalTypeException
* if the given class is not valid to be annotated with @
* {@link Context}.
* @throws ImplementationException
* the declaringClass must not be {@link UriInfo}
*/
static Object getInjectObject(Class<?> declaringClass,
ThreadLocalizedContext tlContext, Providers providers,
ExtensionBackwardMapping extensionBackwardMapping)
throws IllegalTypeException, ImplementationException {
if (declaringClass.equals(Providers.class)) {
return providers;
}
if (declaringClass.equals(ContextResolver.class)) {
// NICE also throw, where the error occurs.
throw new IllegalTypeException(
"The ContextResolver is not allowed for @Context annotated fields yet. Use javax.ws.rs.ext.Providers#getContextResolver(...)");
}
if (declaringClass.equals(ExtensionBackwardMapping.class)) {
return extensionBackwardMapping;
}
if (declaringClass.equals(PathSegment.class)) {
String msg = "The use of PathSegment annotated with @Context is not standard.";
logger.config(msg);
return new GetLastPathSegment(tlContext);
}
if (declaringClass.equals(SecurityContext.class)
|| declaringClass.equals(HttpHeaders.class)
|| declaringClass.equals(Request.class)) {
return tlContext;
}
if (declaringClass.equals(UriInfo.class)) {
throw new ImplementationException(
"You must not call the method ContextInjector.getInjectObject(.......) with class UriInfo");
}
String declaringClassName = declaringClass.getName();
// compare names to avoid ClassNotFoundExceptions, if the Servlet-API is
// not in the classpath
if (declaringClassName.equals("javax.servlet.http.HttpServletRequest")
|| declaringClassName
.equals("javax.servlet.http.HttpServletResponse")) {
throw new NotYetImplementedException(
"The returnin of Servlet depending Context is not implemented for now.");
}
// NICE also allow injection of ClientInfo and Conditions. Proxies are
// required, because the injected objects must be thread local.
throw new IllegalTypeException(declaringClass
+ " must not be annotated with @Context");
}
/**
*
* @param declaringClass
* @param aim
* @param tlContext
* @param allProviders
* @param extensionBackwardMapping
* @return
* @throws IllegalTypeException
* if the given class is not valid to be annotated with @
* {@link Context}.
*/
static Injector getInjector(Class<?> declaringClass, InjectionAim aim,
ThreadLocalizedContext tlContext, Providers allProviders,
ExtensionBackwardMapping extensionBackwardMapping)
throws IllegalTypeException {
if (declaringClass.equals(UriInfo.class)) {
return new UriInfoInjector(aim, tlContext);
}
if (declaringClass.equals(ExtendedUriInfo.class)) {
return new ExtendedUriInfoInjector(aim, tlContext);
}
return new EverSameInjector(aim, getInjectObject(declaringClass,
tlContext, allProviders, extensionBackwardMapping));
}
/**
* This {@link List} contains the fields in this class which are annotated
* to inject ever the same object.
*
* @see javax.ws.rs.ext.ContextResolver
* @see Providers
*/
private final List<Injector> injEverSameAims = new ArrayList<Injector>();
/**
* @param jaxRsClass
* @param tlContext
* @param providers
* all entity providers.
* @param extensionBackwardMapping
* the extension backward mapping
* @throws IllegalBeanSetterTypeException
* if one of the bean setters annotated with @
* {@link Context} has a type that must not be annotated with
* @{@link Context}.
* @throws IllegalFieldTypeException
* if one of the fields annotated with @{@link Context} has
* a type that must not be annotated with @{@link Context}.
* @throws ImplementationException
*/
public ContextInjector(Class<?> jaxRsClass,
ThreadLocalizedContext tlContext, Providers providers,
ExtensionBackwardMapping extensionBackwardMapping)
throws IllegalFieldTypeException, IllegalBeanSetterTypeException {
this.init(jaxRsClass, tlContext, providers, extensionBackwardMapping);
}
protected void add(AccessibleObject fieldOrBeanSetter,
AbstractParamGetter iog) {
this.injEverSameAims
.add(new ParamValueInjector(fieldOrBeanSetter, iog));
}
/**
* initiates the fields to cache the fields that needs injection.
*
* @param tlContext
* the {@link ThreadLocalizedContext} of the
* {@link org.restlet.ext.jaxrs.JaxRsRestlet}.
* @param allProviders
* all entity providers.
* @param extensionBackwardMapping
* the extension backward mapping
*
* @throws IllegalFieldTypeException
* if one of the fields annotated with @{@link Context} has
* a type that must not be annotated with @{@link Context}.
* @throws IllegalBeanSetterTypeException
* if one of the bean setters annotated with @
* {@link Context} has a type that must not be annotated with
* @{@link Context}.
*/
private void init(Class<?> jaxRsClass, ThreadLocalizedContext tlContext,
Providers allProviders,
ExtensionBackwardMapping extensionBackwardMapping)
throws IllegalFieldTypeException, IllegalBeanSetterTypeException {
do {
try {
for (final Field field : jaxRsClass.getDeclaredFields()) {
if (field.isAnnotationPresent(Context.class)) {
InjectionAim aim = new FieldWrapper(field);
Class<?> declaringClass = field.getType();
Injector injector = getInjector(declaringClass, aim,
tlContext, allProviders,
extensionBackwardMapping);
this.injEverSameAims.add(injector);
}
}
} catch (SecurityException e) {
// NICE handle SecurityException
throw e;
} catch (IllegalTypeException e) {
throw new IllegalFieldTypeException(e);
}
try {
for (final Method method : jaxRsClass.getDeclaredMethods()) {
if (isBeanSetter(method, Context.class)) {
BeanSetter aim = new BeanSetter(method);
Class<?> paramClass = method.getParameterTypes()[0];
Injector injector = getInjector(paramClass, aim,
tlContext, allProviders,
extensionBackwardMapping);
this.injEverSameAims.add(injector);
}
}
} catch (SecurityException e) {
// NICE handle SecurityException
throw e;
} catch (IllegalTypeException e) {
throw new IllegalBeanSetterTypeException(e);
}
jaxRsClass = jaxRsClass.getSuperclass();
} while (jaxRsClass != null);
}
/**
* Injects all the supported dependencies into the the given resource object
* of this class.
*
* @param jaxRsResObj
* @param allMustBeAvailable
* if true, all information in @{@link Context} annotated
* objects must be available, especially the ancestor resource
* info (false for singelton lifecycle)
* @throws InjectException
* if the injection was not possible. See
* {@link InjectException#getCause()} for the reason.
* @throws InvocationTargetException
* if a setter throws an exception
*/
public void injectInto(Object jaxRsResObj, boolean allMustBeAvailable)
throws InjectException, InvocationTargetException {
for (final Injector injectAim : this.injEverSameAims) {
injectAim.injectInto(jaxRsResObj, allMustBeAvailable);
}
}
}