/*******************************************************************************
* Copyright (c) 2012-2016 Codenvy, S.A.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.everrest.core.impl;
import com.google.common.collect.Iterables;
import org.everrest.core.ApplicationContext;
import org.everrest.core.ObjectFactory;
import org.everrest.core.PerRequestObjectFactory;
import org.everrest.core.ResourceBinder;
import org.everrest.core.ResourcePublicationException;
import org.everrest.core.SingletonObjectFactory;
import org.everrest.core.impl.resource.AbstractResourceDescriptor;
import org.everrest.core.resource.ResourceDescriptor;
import org.everrest.core.uri.UriPattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.ws.rs.Path;
import javax.ws.rs.core.MultivaluedMap;
import java.security.CodeSource;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author andrew00x
*/
public class ResourceBinderImpl implements ResourceBinder {
/** Logger. */
private static final Logger LOG = LoggerFactory.getLogger(ResourceBinderImpl.class);
/**
* Compare two ResourceClass for order. Method compare returns positive, zero or negative dependent of {@link UriPattern} comparison.
* @see UriPattern
* @see UriPattern#URIPATTERN_COMPARATOR
*/
static final Comparator<ObjectFactory<ResourceDescriptor>> RESOURCE_COMPARATOR =
(resourceOne, resourceTwo) -> UriPattern.URIPATTERN_COMPARATOR
.compare(resourceOne.getObjectModel().getUriPattern(), resourceTwo.getObjectModel().getUriPattern());
/** Root resource descriptors. */
private volatile List<ObjectFactory<ResourceDescriptor>> resources = new ArrayList<>();
/** Update resources (add, remove, clear) lock. */
private final ReentrantLock lock = new ReentrantLock();
@Override
public void addResource(Class<?> resourceClass, MultivaluedMap<String, String> properties) {
if (!resourceClass.isAnnotationPresent(Path.class)) {
throw new ResourcePublicationException(String.format(
"Resource class %s it is not root resource. Path annotation javax.ws.rs.Path is not specified for this class.",
resourceClass.getName()));
}
try {
addResource(new PerRequestObjectFactory<>(newResourceDescriptor(null, resourceClass, properties)));
} catch (ResourcePublicationException e) {
throw e;
} catch (Exception e) {
throw new ResourcePublicationException(e.getMessage(), e);
}
}
@Override
public void addResource(String uriPattern, Class<?> resourceClass, MultivaluedMap<String, String> properties) {
addResource(new PerRequestObjectFactory<>(newResourceDescriptor(uriPattern, resourceClass, properties)));
}
private ResourceDescriptor newResourceDescriptor(String path,
Class<?> resourceClass,
MultivaluedMap<String, String> properties) {
ResourceDescriptor descriptor = path == null ? new AbstractResourceDescriptor(resourceClass) : new AbstractResourceDescriptor(path, resourceClass);
if (properties != null) {
descriptor.getProperties().putAll(properties);
}
return descriptor;
}
@Override
public void addResource(Object resource, MultivaluedMap<String, String> properties) {
if (!resource.getClass().isAnnotationPresent(Path.class)) {
throw new ResourcePublicationException(String.format(
"Resource class %s it is not root resource. Path annotation javax.ws.rs.Path is not specified for this class.",
resource.getClass().getName()));
}
addResource(new SingletonObjectFactory<>(newResourceDescriptor(null, resource, properties), resource));
}
@Override
public void addResource(String uriPattern, Object resource, MultivaluedMap<String, String> properties) {
addResource(new SingletonObjectFactory<>(newResourceDescriptor(uriPattern, resource, properties), resource));
}
private ResourceDescriptor newResourceDescriptor(String path,
Object resource,
MultivaluedMap<String, String> properties) {
ResourceDescriptor descriptor =
path == null ? new AbstractResourceDescriptor(resource) : new AbstractResourceDescriptor(path, resource);
if (properties != null) {
descriptor.getProperties().putAll(properties);
}
return descriptor;
}
@Override
public void addResource(ObjectFactory<ResourceDescriptor> newResourceFactory) {
UriPattern pattern = newResourceFactory.getObjectModel().getUriPattern();
lock.lock();
try {
List<ObjectFactory<ResourceDescriptor>> snapshot = new ArrayList<>(resources);
for (ObjectFactory<ResourceDescriptor> resourceFactory : snapshot) {
if (resourceFactory.getObjectModel().getUriPattern().equals(newResourceFactory.getObjectModel().getUriPattern())) {
if (resourceFactory.getObjectModel().getObjectClass() == newResourceFactory.getObjectModel().getObjectClass()) {
LOG.debug("Resource {} already registered", newResourceFactory.getObjectModel().getObjectClass().getName());
return;
}
throw new ResourcePublicationException(String.format(
"Resource class %s loaded from %s can't be registered. Resource class %s loaded from %s with the same pattern %s already registered.",
newResourceFactory.getObjectModel().getObjectClass().getName(), getCodeSource(newResourceFactory.getObjectModel().getObjectClass()),
resourceFactory.getObjectModel().getObjectClass().getName(), getCodeSource(resourceFactory.getObjectModel().getObjectClass()), pattern));
}
}
snapshot.add(newResourceFactory);
Collections.sort(snapshot, RESOURCE_COMPARATOR);
LOG.debug("Add resource: {}", newResourceFactory.getObjectModel());
resources = snapshot;
} finally {
lock.unlock();
}
}
private CodeSource getCodeSource(Class<?> aClass) {
return aClass.getProtectionDomain().getCodeSource();
}
/** Clear the list of resources. */
public void clear() {
lock.lock();
try {
resources = new ArrayList<>();
} finally {
lock.unlock();
}
}
/**
* Get root resource matched to <code>requestPath</code>.
*
* @param requestPath
* request path
* @param parameterValues
* see {@link ApplicationContext#getParameterValues()}
* @return root resource matched to <code>requestPath</code> or
* <code>null</code>
*/
@Override
public ObjectFactory<ResourceDescriptor> getMatchedResource(String requestPath, List<String> parameterValues) {
ObjectFactory<ResourceDescriptor> resourceFactory = null;
List<ObjectFactory<ResourceDescriptor>> myResources = resources;
for (ObjectFactory<ResourceDescriptor> resource : myResources) {
if (resource.getObjectModel().getUriPattern().match(requestPath, parameterValues)) {
// all times will at least 1
String lastParameterValue = Iterables.getLast(parameterValues);
// If capturing group contains last element and this element is
// neither null nor '/' then ResourceClass must contains at least one
// sub-resource method or sub-resource locator.
if (lastParameterValue == null || lastParameterValue.equals("/") || hasSubResourceMethodsOrSubResourceLocators(resource)) {
resourceFactory = resource;
break;
}
}
}
return resourceFactory;
}
private boolean hasSubResourceMethodsOrSubResourceLocators(ObjectFactory<ResourceDescriptor> resource) {
return resource.getObjectModel().getSubResourceMethods().size()
+ resource.getObjectModel().getSubResourceLocators().size() > 0;
}
@Override
public List<ObjectFactory<ResourceDescriptor>> getResources() {
List<ObjectFactory<ResourceDescriptor>> myResources = resources;
return new ArrayList<>(myResources);
}
@Override
public int getSize() {
List<ObjectFactory<ResourceDescriptor>> myResources = resources;
return myResources.size();
}
@Override
public ObjectFactory<ResourceDescriptor> removeResource(Class<?> clazz) {
lock.lock();
try {
ObjectFactory<ResourceDescriptor> resource = null;
List<ObjectFactory<ResourceDescriptor>> snapshot = new ArrayList<>(resources);
for (Iterator<ObjectFactory<ResourceDescriptor>> iterator = snapshot.iterator(); iterator.hasNext() && resource == null; ) {
ObjectFactory<ResourceDescriptor> next = iterator.next();
Class<?> resourceClass = next.getObjectModel().getObjectClass();
if (clazz.equals(resourceClass)) {
resource = next;
iterator.remove();
}
}
if (resource != null) {
LOG.debug("Remove resource: {}", resource.getObjectModel());
resources = snapshot;
}
return resource;
} finally {
lock.unlock();
}
}
@Override
public ObjectFactory<ResourceDescriptor> removeResource(String path) {
lock.lock();
try {
ObjectFactory<ResourceDescriptor> resource = null;
List<ObjectFactory<ResourceDescriptor>> snapshot = new ArrayList<>(resources);
UriPattern pattern = new UriPattern(path);
for (Iterator<ObjectFactory<ResourceDescriptor>> iterator = snapshot.iterator(); iterator.hasNext() && resource == null; ) {
ObjectFactory<ResourceDescriptor> next = iterator.next();
UriPattern resourcePattern = next.getObjectModel().getUriPattern();
if (pattern.equals(resourcePattern)) {
resource = next;
iterator.remove();
}
}
if (resource != null) {
LOG.debug("Remove resource: {}", resource.getObjectModel());
resources = snapshot;
}
return resource;
} finally {
lock.unlock();
}
}
}