package org.etk.core.rest.impl;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.Encoded;
import javax.ws.rs.MatrixParam;
import javax.ws.rs.PathParam;
import javax.ws.rs.QueryParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.Provider;
import org.etk.common.logging.Logger;
import org.etk.core.rest.ConstructorParameter;
import org.etk.core.rest.ConstructorParameterImpl;
import org.etk.core.rest.impl.method.ParameterHelper;
import org.etk.core.rest.impl.method.ParameterResolver;
import org.etk.core.rest.impl.method.ParameterResolverFactory;
import org.etk.core.rest.resource.ResourceDescriptorVisitor;
import org.etk.kernel.container.KernelContainer;
import org.etk.kernel.container.KernelContainerContext;
public class ConstructorDescriptorImpl implements ConstructorDescriptor {
/**
* Logger.
*/
private static final Logger LOG = Logger.getLogger(ConstructorDescriptorImpl.class);
/**
* ConstructorDescriptor comparator.
*/
public static final Comparator<ConstructorDescriptor> CONSTRUCTOR_COMPARATOR = new ConstructorComparator();
/**
* Compare two ConstructorDescriptor in number parameters order.
*/
private static class ConstructorComparator implements Comparator<ConstructorDescriptor> {
/**
* {@inheritDoc}
*/
public int compare(ConstructorDescriptor o1, ConstructorDescriptor o2) {
int r = o2.getParameters().size() - o1.getParameters().size();
if (r == 0)
LOG.warn("Two constructors with the same number of parameter found " + o1 + " and " + o2);
return r;
}
}
/**
* Constructor.
*/
private final Constructor<?> constructor;
/**
* Collection of constructor's parameters.
*/
private final List<ConstructorParameter> parameters;
/**
* Resource class.
*/
private final Class<?> resourceClass;
/**
* @param resourceClass resource class
* @param constructor {@link Constructor}
*/
public ConstructorDescriptorImpl(Class<?> resourceClass, Constructor<?> constructor) {
this.resourceClass = resourceClass;
this.constructor = constructor;
Class<?>[] paramTypes = constructor.getParameterTypes();
if (paramTypes.length == 0) {
parameters = java.util.Collections.emptyList();
} else {
Type[] getParamTypes = constructor.getGenericParameterTypes();
Annotation[][] annotations = constructor.getParameterAnnotations();
List<ConstructorParameter> params = new ArrayList<ConstructorParameter>(paramTypes.length);
for (int i = 0; i < paramTypes.length; i++) {
String defaultValue = null;
Annotation annotation = null;
boolean encoded = false;
// is resource provider
boolean provider = resourceClass.getAnnotation(Provider.class) != null;
List<String> allowedAnnotation;
if (provider)
allowedAnnotation = ParameterHelper.PROVIDER_CONSTRUCTOR_PARAMETER_ANNOTATIONS;
else
allowedAnnotation = ParameterHelper.RESOURCE_CONSTRUCTOR_PARAMETER_ANNOTATIONS;
for (Annotation a : annotations[i]) {
Class<?> ac = a.annotationType();
if (allowedAnnotation.contains(ac.getName())) {
if (annotation == null) {
annotation = a;
} else {
String msg = "JAX-RS annotations on one of constructor parameters are equivocality. "
+ "Annotations: " + annotation + " and " + a
+ " can't be applied to one parameter.";
throw new RuntimeException(msg);
}
// @Encoded has not sense for Provider. Provider may use only
// @Context annotation for constructor parameters
} else if (ac == Encoded.class && !provider) {
encoded = true;
// @Default has not sense for Provider. Provider may use only
// @Context annotation for constructor parameters
} else if (ac == DefaultValue.class && !provider) {
defaultValue = ((DefaultValue) a).value();
} else {
LOG.warn("Constructor parameter contains unknown or not valid JAX-RS annotation " + a
+ ". It will be ignored.");
}
}
encoded = encoded || resourceClass.getAnnotation(Encoded.class) != null;
ConstructorParameter cp = new ConstructorParameterImpl(annotation,
annotations[i],
paramTypes[i],
getParamTypes[i],
defaultValue,
encoded);
params.add(cp);
}
parameters = java.util.Collections.unmodifiableList(params);
}
}
/**
* {@inheritDoc}
*/
public void accept(ResourceDescriptorVisitor visitor) {
visitor.visitConstructorInjector(this);
}
/**
* {@inheritDoc}
*/
public Constructor<?> getConstructor() {
return constructor;
}
/**
* {@inheritDoc}
*/
public List<ConstructorParameter> getParameters() {
return parameters;
}
/**
* {@inheritDoc}
*/
public Object createInstance(ApplicationContext context) {
KernelContainer container = KernelContainerContext.getCurrentContainer();
Object[] p = new Object[parameters.size()];
int i = 0;
for (ConstructorParameter cp : parameters) {
Annotation a = cp.getAnnotation();
if (a != null) {
ParameterResolver<?> pr = ParameterResolverFactory.createParameterResolver(a);
try {
p[i] = pr.resolve(cp, context);
} catch (Exception e) {
if (LOG.isDebugEnabled())
e.printStackTrace();
Class<?> ac = a.annotationType();
if (ac == MatrixParam.class || ac == QueryParam.class || ac == PathParam.class)
throw new WebApplicationException(Response.status(Response.Status.NOT_FOUND).build());
throw new WebApplicationException(Response.status(Response.Status.BAD_REQUEST).build());
}
} else {
// If parameter not has not annotation then get constructor parameter
// from container, this is out of scope JAX-RS specification.
Object tmp = container.getComponentInstanceOfType(cp.getParameterClass());
if (tmp == null) {
String msg = "Can't instantiate resource " + resourceClass + " by using constructor "
+ this + ". Not found parameter " + cp;
throw new WebApplicationException(Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(msg)
.build());
}
p[i] = tmp;
}
i++;
}
try {
return constructor.newInstance(p);
} catch (IllegalArgumentException argExc) {
// should not be thrown, arguments already checked
throw new InternalException(argExc);
} catch (InstantiationException instExc) {
// should not be thrown
throw new InternalException(instExc);
} catch (IllegalAccessException accessExc) {
// should not be thrown
throw new InternalException(accessExc);
} catch (InvocationTargetException invExc) {
// constructor may produce exceptions
if (LOG.isDebugEnabled())
invExc.printStackTrace();
// get cause of exception that method produces
Throwable cause = invExc.getCause();
// if WebApplicationException than it may contain response
if (WebApplicationException.class == cause.getClass())
throw (WebApplicationException) cause;
throw new InternalException(cause);
} catch (Throwable thr) {
throw new InternalException(thr);
}
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
StringBuffer sb = new StringBuffer("[ ConstructorInjectorImpl: ");
sb.append("constructor: " + getConstructor().getName() + "; ");
for (ConstructorParameter cp : getParameters())
sb.append(cp.toString()).append(" ");
sb.append(" ]");
return sb.toString();
}
}