package org.mqnaas.api.writers;
/*
* #%L
* MQNaaS :: REST API Provider
* %%
* Copyright (C) 2007 - 2015 Fundació Privada i2CAT, Internet i Innovació a Catalunya
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/lgpl-3.0.html>.
* #L%
*/
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.mqnaas.api.exceptions.InvalidCapabilityDefinionException;
import org.mqnaas.core.api.ICapability;
import org.mqnaas.core.api.IIdentifiable;
import org.mqnaas.core.api.annotations.AddsResource;
import org.mqnaas.core.api.annotations.ListsResources;
import org.mqnaas.core.api.annotations.RemovesResource;
/**
* Holds meta information about a {@link ICapability} used during the REST API mapping process
*
* @author Georg Mansky-Kummert (i2CAT)
*
*/
class CapabilityMetaDataContainer {
// If the capability, for which this container hold meta information is a management capability:
// (1) entityClass represents the Class of the managed objects
private Class<?> entityClass;
// (2) listService is the method listing all managed resources without parameters.
private Method listService;
private List<Method> services;
/**
* Checks the usability of the given {@link ICapability} class
*
* @param capabilityClass
* @throws InvalidCapabilityDefinionException
* An invalid capability exception is thrown when the capability defines a service which does not comply with the basic capability
* contract
*/
public CapabilityMetaDataContainer(Class<? extends ICapability> capabilityClass) throws InvalidCapabilityDefinionException {
// All methods defined in the capability interface are services (inherited ones, too)
services = new ArrayList<Method>(Arrays.asList(capabilityClass.getMethods()));
// All resource entities used in the services are collected to be able to check them
Set<Class<?>> entityClasses = new HashSet<Class<?>>();
// CHECK THE BASIC CONTRACT
for (Method m : getServiceMethods(AddsResource.class)) {
// (1) Check validity
// Either has to returns IIdentifiable or to define a single IIdentifiable parameters
boolean returnsIdentifiable = IIdentifiable.class.isAssignableFrom(m.getReturnType());
boolean hasIdentifiableParameter = m.getParameterTypes().length == 1 && IIdentifiable.class.isAssignableFrom(m.getParameterTypes()[0]);
if (!returnsIdentifiable && !hasIdentifiableParameter) {
throw new InvalidCapabilityDefinionException(
"Creation service " + m.getName() + " has to either: (a) return a subclass of " + IIdentifiable.class + " or (b) has a single parameter subclassing " + IIdentifiable.class);
}
// (2) Collect used entities
if (IIdentifiable.class.isAssignableFrom(m.getReturnType())) {
entityClasses.add(m.getReturnType());
} else {
entityClasses.add(m.getParameterTypes()[0]);
}
}
for (Method m : getServiceMethods(RemovesResource.class)) {
// (1) Check validity
// Have to have one parameter subclassing IIdentifiable
if (m.getParameterTypes().length != 1) {
throw new InvalidCapabilityDefinionException(
"Deletion service " + m.getName() + " has to have exactly one parameter extending " + IIdentifiable.class);
} else if (!IIdentifiable.class.isAssignableFrom(m.getParameterTypes()[0])) {
throw new InvalidCapabilityDefinionException(
"Deletion service " + m.getName() + ": Parameter must be a subclass of " + IIdentifiable.class);
}
// (2) Collect used entities
entityClasses.add(m.getParameterTypes()[0]);
}
for (Method m : getServiceMethods(ListsResources.class)) {
// (1) Check validity
// Has to return a collection of IIdentifiables
if (!Collection.class.isAssignableFrom(m.getReturnType())) {
throw new InvalidCapabilityDefinionException("List service " + m.getName() + " has to return a collection of " + IIdentifiable.class);
}
Class<?> returnParameterType = getReturnTypeParameter(m);
if (!IIdentifiable.class.isAssignableFrom(returnParameterType)) {
throw new InvalidCapabilityDefinionException(
"List service " + m.getName() + " has to return collections of " + IIdentifiable.class);
}
// (2) Collect used entities
entityClasses.add(getReturnTypeParameter(m));
// (3) Search for a list service without parameters
if (m.getParameterTypes().length == 0 && listService == null) {
listService = m;
}
}
// Check the collected entity classed
switch (entityClasses.size()) {
case 0: // There are no entity classes, e.g. there are no services returning or receiving entities. Nothing to be done.
break;
case 1: // There is just one entity class. This is the correct case. All services in the capability are working on the same entity bean.
// Initialize the bean for later use.
entityClass = entityClasses.iterator().next();
break;
default: // There is more than one entity class. The user has to separate the management capabilities.
throw new InvalidCapabilityDefinionException("More than one entity class found: " + entityClasses);
}
if (listService == null && !getServiceMethods(ListsResources.class).isEmpty()) {
throw new InvalidCapabilityDefinionException("No list service without parameters found.");
}
}
private Class<?> getReturnTypeParameter(Method m) {
Class<?> clazz = null;
Type returnType = m.getGenericReturnType();
if (returnType instanceof ParameterizedType) {
ParameterizedType collectionType = (ParameterizedType) returnType;
Type type = collectionType.getActualTypeArguments()[0];
if (type instanceof Class<?>) {
clazz = (Class<?>) type;
}
}
return clazz;
}
public Class<?> getEntityClass() {
return entityClass;
}
public List<Method> getServiceMethods(Class<? extends Annotation> annotationClass) {
List<Method> filtered = new ArrayList<Method>();
for (Method service : services) {
if (service.getAnnotation(annotationClass) != null) {
filtered.add(service);
}
}
return filtered;
}
public List<Method> getServiceMethods() {
return services;
}
public Method getListService() {
return listService;
}
}