package org.jboss.resteasy.links.impl;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.annotation.security.RolesAllowed;
import javax.el.ELContext;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.SecurityContext;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import org.jboss.resteasy.annotations.Form;
import org.jboss.resteasy.core.ResourceInvoker;
import org.jboss.resteasy.core.ResourceMethod;
import org.jboss.resteasy.core.ResourceMethodRegistry;
import org.jboss.resteasy.links.ELProvider;
import org.jboss.resteasy.links.LinkELProvider;
import org.jboss.resteasy.links.LinkResource;
import org.jboss.resteasy.links.LinkResources;
import org.jboss.resteasy.links.RESTServiceDiscovery;
import org.jboss.resteasy.links.ResourceFacade;
import org.jboss.resteasy.logging.Logger;
import org.jboss.resteasy.specimpl.UriBuilderImpl;
import org.jboss.resteasy.spi.ResteasyProviderFactory;
import org.jboss.resteasy.util.FindAnnotation;
public class RESTUtils {
private final static Logger logger = Logger
.getLogger(RESTUtils.class);
public static <T> T addDiscovery(T entity, UriInfo uriInfo, ResourceMethodRegistry registry) {
// find the field to inject first
Field injectionField = findInjectionField(entity);
if(injectionField == null)
return entity;
List<Method> methods = getServiceMethods(registry);
RESTServiceDiscovery ret = new RESTServiceDiscovery();
for(Method m : methods){
processLinkResources(m, entity, uriInfo, ret);
}
// do not inject an empty service
if(ret.isEmpty())
return entity;
// now inject
injectionField.setAccessible(true);
try {
injectionField.set(entity, ret);
} catch (Exception e) {
logger.error("Failed to inject links in "+entity, e);
}
injectionField.setAccessible(false);
return entity;
}
private static Field findInjectionField(Object entity) {
Class<?> klass = entity.getClass();
do{
for(Field f : klass.getDeclaredFields()){
if(f.getType().equals(RESTServiceDiscovery.class))
return f;
}
klass = klass.getSuperclass();
}while(klass != null);
return null;
}
public static List<Method> getServiceMethods(ResourceMethodRegistry registry){
ArrayList<Method> results = new ArrayList<Method>();
for (Entry<String, List<ResourceInvoker>> entry : registry.getRoot()
.getBounded().entrySet())
{
List<ResourceInvoker> invokers = entry.getValue();
for (ResourceInvoker invoker : invokers)
{
if (invoker instanceof ResourceMethod)
{
ResourceMethod resourceMethod = (ResourceMethod)invoker;
Method method = resourceMethod.getMethod();
results.add(method);
} else
{
// TODO: fix this?
}
}
}
return results;
}
private static void processLinkResources(Method m, Object entity, UriInfo uriInfo,
RESTServiceDiscovery ret) {
// find a single service
LinkResource service = m
.getAnnotation(LinkResource.class);
if(service != null)
processLinkResource(m, entity, uriInfo, ret, service);
// find a multi-service
LinkResources services = m
.getAnnotation(LinkResources.class);
if(services != null)
for(LinkResource service2 : services.value())
processLinkResource(m, entity, uriInfo, ret, service2);
}
private static void processLinkResource(Method m, Object entity, UriInfo uriInfo,
RESTServiceDiscovery ret, LinkResource service) {
String rel = service.rel();
// if we have uri templates, we need a compatible instance
Class<?> type = getServiceType(service, m);
if(type.isInstance(entity)){
if(checkConstraint(service, entity, m))
addInstanceService(m, entity, uriInfo, ret, service, rel);
}else if(entity instanceof ResourceFacade<?> && ((ResourceFacade<?>)entity).facadeFor() == type){
if(checkConstraint(service, type, m))
addService(m, (ResourceFacade<?>) entity, uriInfo, ret, service, rel);
}
}
private static Class<?> getServiceType(LinkResource service, Method m) {
Class<?> type = service.value();
if(type != Void.class)
return type;
// are we looking at the return type or the body type?
type = findBodyType(m);
if(type == null){
// our next best bet is the return type
type = m.getReturnType();
}
if(Void.TYPE == type)
throw new ServiceDiscoveryException(m, "Cannot guess resource type for service discovery");
if(Collection.class.isAssignableFrom(type))
throw new ServiceDiscoveryException(m, "Cannot guess collection type for service discovery");
if(Response.class.isAssignableFrom(type))
throw new ServiceDiscoveryException(m, "Cannot guess type for Response");
return type;
}
private static Class<?> findBodyType(Method m) {
Annotation[][] annotations = m.getParameterAnnotations();
Class<?>[] types = m.getParameterTypes();
for (int i = 0; i < types.length; i++) {
// if there's no JAXRS annotation nor @Form, it's a body right?
if(FindAnnotation.findJaxRSAnnotations(annotations[i]).length == 0
&& FindAnnotation.findAnnotation(annotations[i], Form.class) == null)
return types[i];
}
return null;
}
private static boolean checkConstraint(LinkResource service, Object object, Method m) {
String constraint = service.constraint();
if(constraint == null || constraint.length() == 0)
return checkEJBConstraint(m);
Boolean ret = evaluateELBoolean(m, getELContext(m, object), object, constraint);
return ret != null && ret.booleanValue();
}
private static boolean checkEJBConstraint(Method m) {
// Use dynamic class loading here since if the EJB annotation class is not present
// it cannot be on the method, so we don't have to check for it
try {
Class.forName("javax.annotation.security.RolesAllowed");
} catch (ClassNotFoundException e) {
// class not here, therefore not on method either
return true;
}
// From now on we can use this class since it's there. I (Stef Epardaud) don't think we need to
// remove the reference here and use reflection.
RolesAllowed rolesAllowed = m.getAnnotation(RolesAllowed.class);
if(rolesAllowed == null)
return true;
SecurityContext context = ResteasyProviderFactory.getContextData(SecurityContext.class);
for(String role : rolesAllowed.value())
if(context.isUserInRole(role))
return true;
return false;
}
private static void addService(Method m, ResourceFacade<?> entity, UriInfo uriInfo,
RESTServiceDiscovery ret, LinkResource service, String rel) {
Map<String, ? extends Object> pathParameters = entity.pathParameters();
// do we need any path parameters?
UriBuilder uriBuilder = uriInfo.getBaseUriBuilder().path(m.getDeclaringClass());
if(m.isAnnotationPresent(Path.class))
uriBuilder.path(m);
URI uri;
List<String> paramNames = ((UriBuilderImpl)uriBuilder).getPathParamNamesInDeclarationOrder();
if(paramNames.isEmpty())
uri = uriBuilder.build();
else if(pathParameters.size() >= paramNames.size())
uri = uriBuilder.buildFromMap(pathParameters);
else
// just bail out since we don't have enough parameters, that must be an instance service
return;
if(rel.length() == 0){
if (m.isAnnotationPresent(GET.class))
rel = "list";
else if (m.isAnnotationPresent(POST.class))
rel = "add";
}
ret.addLink(uri, rel);
}
private static void addInstanceService(Method m, Object entity,
UriInfo uriInfo, RESTServiceDiscovery ret, LinkResource service,
String rel) {
UriBuilder uriBuilder = uriInfo.getBaseUriBuilder().path(m.getDeclaringClass());
if(m.isAnnotationPresent(Path.class))
uriBuilder.path(m);
URI uri = buildURI(uriBuilder, service, entity, m);
if (rel.length() == 0) {
if (m.isAnnotationPresent(GET.class)){
Class<?> type = m.getReturnType();
if(Collection.class.isAssignableFrom(type))
rel = "list";
else
rel = "self";
}else if (m.isAnnotationPresent(PUT.class))
rel = "update";
else if (m.isAnnotationPresent(POST.class))
rel = "add";
else if (m.isAnnotationPresent(DELETE.class))
rel = "remove";
}
ret.addLink(uri, rel);
}
private static URI buildURI(UriBuilder uriBuilder, LinkResource service,
Object entity, Method m) {
// see if we need parameters
String[] uriTemplates = service.pathParameters();
if (uriTemplates.length > 0) {
Object[] values = new Object[uriTemplates.length];
for (int i = 0; i < values.length; i++)
values[i] = evaluateEL(m, getELContext(m, entity), entity, uriTemplates[i]);
return uriBuilder.build(values);
}
// do we need any path parameters?
List<String> paramNames = ((UriBuilderImpl)uriBuilder).getPathParamNamesInDeclarationOrder();
if(paramNames.isEmpty())
return uriBuilder.build();
// try to find the IDs
List<Object> params = findURIParamsFromResource(entity);
if(params.size() == paramNames.size())
return uriBuilder.build(params.toArray());
// if we have too many, ignore the last ones
if(params.size() > paramNames.size())
return uriBuilder.build(params.subList(0, paramNames.size()).toArray());
throw new ServiceDiscoveryException(m, "Not enough URI parameters: expecting "+paramNames.size()+" but only found "+params.size());
}
private static List<Object> findURIParamsFromResource(Object entity) {
List<Object> ids = new ArrayList<Object>();
do{
List<Object> theseIDs = BeanUtils.findIDs(entity);
ids.addAll(0, theseIDs);
}while((entity = BeanUtils.findParentResource(entity)) != null);
return ids;
}
private static LinkELProvider findLinkELProvider(Method m){
if(m.isAnnotationPresent(LinkELProvider.class))
return m.getAnnotation(LinkELProvider.class);
Class<?> c = m.getDeclaringClass();
if(c.isAnnotationPresent(LinkELProvider.class))
return c.getAnnotation(LinkELProvider.class);
Package p = c.getPackage();
if(p != null && p.isAnnotationPresent(LinkELProvider.class))
return p.getAnnotation(LinkELProvider.class);
return null;
}
private static ELProvider getELProvider(Method m){
LinkELProvider linkElProvider = findLinkELProvider(m);
if(linkElProvider == null)
return null;
Class<? extends ELProvider> elProviderClass = linkElProvider.value();
try{
return elProviderClass.newInstance();
}catch(Exception x){
logger.error("Could not instantiate ELProvider class "+elProviderClass.getName(), x);
throw new ServiceDiscoveryException(m, "Failed to instantiate ELProvider: "+elProviderClass.getName(), x);
}
}
private static ELContext getELContext(Method m, Object base){
ELContext ours = EL.createELContext(base);
ELProvider elProvider = getELProvider(m);
if(elProvider != null)
return elProvider.getContext(ours);
return ours;
}
public static Map<String, ? extends Object> derivePathParameters(UriInfo uriInfo){
MultivaluedMap<String, String> pathParameters = uriInfo.getPathParameters();
Map<String, String> ret = new HashMap<String,String>();
for(Entry<String, List<String>> entry: pathParameters.entrySet()){
ret.put(entry.getKey(), entry.getValue().get(0));
}
return ret;
}
public static Object evaluateEL(Method m, ELContext context, Object base, String expression) {
try{
return EL.EXPRESSION_FACTORY.createValueExpression(context, expression,
Object.class).getValue(context);
}catch(Exception x){
throw new ServiceDiscoveryException(m, "Failed to evaluate EL expression: "+expression, x);
}
}
public static Boolean evaluateELBoolean(Method m, ELContext context, Object base, String expression) {
try{
return (Boolean) EL.EXPRESSION_FACTORY.createValueExpression(context, expression,
Boolean.class).getValue(context);
}catch(Exception x){
throw new ServiceDiscoveryException(m, "Failed to evaluate EL expression: "+expression, x);
}
}
}