package org.etk.core.rest.impl;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import javax.ws.rs.Path;
import javax.ws.rs.core.Application;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.MessageBodyWriter;
import javax.ws.rs.ext.Provider;
import javax.ws.rs.ext.RuntimeDelegate;
import org.etk.common.logging.Logger;
import org.etk.core.rest.PerRequestObjectFactory;
import org.etk.core.rest.SingletonObjectFactory;
import org.etk.core.rest.impl.resource.AbstractResourceDescriptorImpl;
import org.etk.core.rest.impl.resource.ResourceDescriptorValidator;
import org.etk.core.rest.impl.uri.UriPattern;
import org.etk.core.rest.resource.AbstractResourceDescriptor;
import org.etk.core.rest.resource.ResourceContainer;
import org.etk.core.rest.resource.ResourceDescriptorVisitor;
import org.etk.kernel.container.KernelContainer;
import org.etk.kernel.container.KernelContainerContext;
/**
* Lookup for root resource eXo container components at startup and
* register/unregister resources via specified methods.
*
*
*/
public final class ResourceBinder {
/**
* Logger.
*/
private static final Logger LOG = Logger.getLogger(ResourceBinder.class);
private static final Comparator<ObjectFactory<AbstractResourceDescriptor>> RESOURCE_COMPARATOR = new ResourceComparator();
/**
* Compare two {@link SingletonResourceFactory}.
*/
private static final class ResourceComparator implements Comparator<ObjectFactory<AbstractResourceDescriptor>> {
/**
* Compare two ResourceClass for order.
*
* @param o1 first ResourceClass to be compared
* @param o2 second ResourceClass to be compared
* @return positive , zero or negative dependent of {@link UriPattern}
* comparison
* @see Comparator#compare(Object, Object)
* @see UriPattern
* @see UriPattern#URIPATTERN_COMPARATOR
*/
public int compare(ObjectFactory<AbstractResourceDescriptor> o1, ObjectFactory<AbstractResourceDescriptor> o2) {
return UriPattern.URIPATTERN_COMPARATOR.compare(o1.getObjectModel().getUriPattern(),
o2.getObjectModel().getUriPattern());
}
};
/**
* Root resource descriptors.
*/
private final List<ObjectFactory<AbstractResourceDescriptor>> rootResources = new ArrayList<ObjectFactory<AbstractResourceDescriptor>>();
/**
* Validator.
*/
private final ResourceDescriptorVisitor rdv = ResourceDescriptorValidator.getInstance();
private final ProviderBinder providers;
private int size = 0;
/**
* @see RuntimeDelegate
*/
private final RuntimeDelegate rd;
/**
* @param containerContext eXo container context
* @throws Exception if can't set instance of {@link RuntimeDelegate}
*/
@SuppressWarnings("unchecked")
public ResourceBinder(KernelContainerContext containerContext) throws Exception {
// Initialize RuntimeDelegate instance
// This is first component in life cycle what needs.
// TODO better solution to initialize RuntimeDelegate
rd = new RuntimeDelegateImpl();
RuntimeDelegate.setInstance(rd);
providers = ProviderBinder.getInstance();
KernelContainer container = containerContext.getContainer();
// Lookup Applications
List<Application> al = container.getComponentInstancesOfType(Application.class);
for (Application a : al) {
try {
addApplication(a);
} catch (Exception e) {
LOG.error("Failed add JAX-RS application " + a.getClass().getName(), e);
}
}
// Lookup all object which implements ResourceContainer interface and
// process them to be add as root resources.
for (Object resource : container.getComponentInstancesOfType(ResourceContainer.class)) {
bind(resource);
}
}
/**
* @param application Application
* @see Application
*/
@SuppressWarnings("unchecked")
public void addApplication(Application application) {
for (Object obj : application.getSingletons()) {
if (obj.getClass().getAnnotation(Provider.class) != null) {
// singleton provider
if (obj instanceof ContextResolver) {
providers.addContextResolver((ContextResolver) obj);
}
if (obj instanceof ExceptionMapper) {
providers.addExceptionMapper((ExceptionMapper) obj);
}
if (obj instanceof MessageBodyReader) {
providers.addMessageBodyReader((MessageBodyReader) obj);
}
if (obj instanceof MessageBodyWriter) {
providers.addMessageBodyWriter((MessageBodyWriter) obj);
}
} else {
bind(obj); // singleton resource
}
}
for (Class clazz : application.getClasses()) {
if (clazz.getAnnotation(Provider.class) != null) {
// per-request provider
if (ContextResolver.class.isAssignableFrom(clazz)) {
providers.addContextResolver(clazz);
}
if (ExceptionMapper.class.isAssignableFrom(clazz)) {
providers.addExceptionMapper(clazz);
}
if (MessageBodyReader.class.isAssignableFrom(clazz)) {
providers.addMessageBodyReader(clazz);
}
if (MessageBodyWriter.class.isAssignableFrom(clazz)) {
providers.addMessageBodyWriter(clazz);
}
} else {
bind(clazz); // per-request resource
}
}
}
/**
* Register supplied Object as root resource if it has valid JAX-RS
* annotations and no one resource with the same UriPattern already
* registered.
*
* @param resource candidate to be root resource
* @return true if resource was bound and false if resource was not bound
* cause it is not root resource
*/
public boolean bind(final Object resource) {
final Path path = resource.getClass().getAnnotation(Path.class);
AbstractResourceDescriptor descriptor = null;
if (path != null) {
try {
descriptor = new AbstractResourceDescriptorImpl(resource);
} catch (Exception e) {
String msg = "Unexpected error occurs when process resource class "
+ resource.getClass().getName();
LOG.error(msg, e);
return false;
}
} else {
String msg = "Resource class " + resource.getClass().getName() + " it is not root resource. "
+ "Path annotation javax.ws.rs.Path is not specified for this class.";
LOG.warn(msg);
return false;
}
// validate AbstractResourceDescriptor
try {
descriptor.accept(rdv);
} catch (Exception e) {
LOG.error("Validation of root resource failed. ", e);
return false;
}
synchronized (rootResources) {
// check does exist other resource with the same URI pattern
for (ObjectFactory<AbstractResourceDescriptor> exist : rootResources) {
if (exist.getObjectModel().getUriPattern().equals(descriptor.getUriPattern())) {
String msg = "Resource class " + descriptor.getObjectClass().getName()
+ " can't be registered. Resource class " + exist.getClass().getName()
+ " with the same pattern " + exist.getObjectModel().getUriPattern().getTemplate()
+ " already registered.";
LOG.warn(msg);
return false;
}
}
// Singleton resource
ObjectFactory<AbstractResourceDescriptor> res = new SingletonObjectFactory<AbstractResourceDescriptor>(descriptor, resource);
rootResources.add(res);
Collections.sort(rootResources, RESOURCE_COMPARATOR);
LOG.info("Bind new resource " + res.getObjectModel().getUriPattern().getTemplate() + " : "
+ descriptor.getObjectClass());
}
size++;
return true;
}
/**
* @param resourceClass class of candidate to be root resource
* @return true if resource was bound and false if resource was not bound
* cause it is not root resource
*/
public boolean bind(final Class<?> resourceClass) {
final Path path = resourceClass.getAnnotation(Path.class);
AbstractResourceDescriptor descriptor = null;
if (path != null) {
try {
descriptor = new AbstractResourceDescriptorImpl(resourceClass);
} catch (Exception e) {
String msg = "Unexpected error occurs when process resource class "
+ resourceClass.getName();
LOG.error(msg, e);
return false;
}
} else {
String msg = "Resource class " + resourceClass.getName() + " it is not root resource. "
+ "Path annotation javax.ws.rs.Path is not specified for this class.";
LOG.warn(msg);
return false;
}
// validate AbstractResourceDescriptor
try {
descriptor.accept(rdv);
} catch (Exception e) {
LOG.error("Validation of root resource failed. ", e);
return false;
}
synchronized (rootResources) {
// check does exist other resource with the same URI pattern
for (ObjectFactory<AbstractResourceDescriptor> exist : rootResources) {
AbstractResourceDescriptor existDescriptor = exist.getObjectModel();
if (exist.getObjectModel().getUriPattern().equals(descriptor.getUriPattern())) {
String msg = "Resource class " + descriptor.getObjectClass().getName()
+ " can't be registered. Resource class "
+ existDescriptor.getObjectClass().getName() + " with the same pattern "
+ exist.getObjectModel().getUriPattern().getTemplate() + " already registered.";
LOG.warn(msg);
return false;
}
}
// per-request resource
ObjectFactory<AbstractResourceDescriptor> res = new PerRequestObjectFactory<AbstractResourceDescriptor>(descriptor);
rootResources.add(res);
Collections.sort(rootResources, RESOURCE_COMPARATOR);
LOG.info("Bind new resource " + res.getObjectModel().getUriPattern().getRegex() + " : "
+ resourceClass);
}
size++;
return true;
}
/**
* Remove root resource of supplied class from root resource collection.
*
* @param clazz root resource class
* @return true if resource was unbound false otherwise
*/
@SuppressWarnings("unchecked")
public boolean unbind(Class clazz) {
synchronized (rootResources) {
Iterator<ObjectFactory<AbstractResourceDescriptor>> i = rootResources.iterator();
while (i.hasNext()) {
ObjectFactory<AbstractResourceDescriptor> res = i.next();
Class c = res.getObjectModel().getObjectClass();
if (clazz.equals(c)) {
i.remove();
LOG.info("Remove ResourceContainer " + res.getObjectModel().getUriPattern().getTemplate()
+ " : " + c);
size--;
return true;
}
}
return false;
}
}
public boolean unbind(String uriTemplate) {
synchronized (rootResources) {
Iterator<ObjectFactory<AbstractResourceDescriptor>> i = rootResources.iterator();
while (i.hasNext()) {
ObjectFactory<AbstractResourceDescriptor> res = i.next();
String t = res.getObjectModel().getUriPattern().getTemplate();
if (t.equals(uriTemplate)) {
i.remove();
LOG.info("Remove ResourceContainer " + res.getObjectModel().getUriPattern().getTemplate());
size--;
return true;
}
}
return false;
}
}
/**
* Clear the list of ResourceContainer description.
*/
public void clear() {
rootResources.clear();
size = 0;
}
/**
* @return all registered root resources
*/
public List<ObjectFactory<AbstractResourceDescriptor>> getResources() {
return rootResources;
}
/**
* @return number of bound resources
*/
public int getSize() {
return size;
}
/**
* @return all registered root resources
*/
@Deprecated
public List<AbstractResourceDescriptor> getRootResources() {
List<AbstractResourceDescriptor> l = new ArrayList<AbstractResourceDescriptor>(rootResources.size());
synchronized (rootResources) {
for (ObjectFactory<AbstractResourceDescriptor> f : rootResources)
l.add(f.getObjectModel());
}
return l;
}
}