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.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.xml.bind.annotation.XmlRootElement;
import org.i2cat.utils.StringBuilderUtils;
import org.mqnaas.api.ContentType;
import org.mqnaas.api.RESTAPIGenerator;
import org.mqnaas.api.exceptions.InvalidCapabilityDefinionException;
import org.mqnaas.core.api.ICapability;
import org.mqnaas.core.api.annotations.AddsResource;
import org.mqnaas.core.api.annotations.ListsResources;
import org.mqnaas.core.api.annotations.RemovesResource;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.Multimap;
public class InterfaceWriter extends AbstractWriter implements Opcodes {
private static final Logger log = LoggerFactory.getLogger(InterfaceWriter.class);
private RESTAPIGenerator restAPIGenerator = new RESTAPIGenerator();
private CapabilityMetaDataContainer metaDataContainer;
private String name;
private AnnotationWriter[] annotationWriters;
// private List<MethodWriter> methodWriters;
private Map<Method, MethodWriter> method2writer;
public InterfaceWriter(Class<? extends ICapability> capabilityClass, String endpoint) throws InvalidCapabilityDefinionException {
log.debug("Writing REST API interface for class " + capabilityClass + " in endpoint " + endpoint + " .");
// (1) Collect the metadata necessary to write the REST API interface. This process also checks the validity of the given capability.
metaDataContainer = new CapabilityMetaDataContainer(capabilityClass);
// (2) Generate a name for the REST API interface
name = generateAPIInterfaceName(capabilityClass);
// (3) Initialize the interfaces' path annotation
// annotationWriters = new AnnotationWriter[] { new AnnotationWriter(Path.class, new AnnotationParamWriter("value", endpoint)) };
// (4) Define all available services, e.g. methods of the interface
method2writer = new HashMap<Method, MethodWriter>();
for (Method m : metaDataContainer.getServiceMethods(AddsResource.class)) {
log.trace("Found AddsResource annotated method: " + m);
// Translate the result
Class<?> resultClass = restAPIGenerator.getResultTranslation(m.getReturnType());
// Translate the parameters
Class<?>[] parameterClasses = new Class<?>[m.getParameterTypes().length];
for (int i = 0; i < m.getParameterTypes().length; i++) {
parameterClasses[i] = restAPIGenerator.getParameterTranslation(m.getParameterTypes()[i]);
}
MethodWriter writer = new MethodWriter(m.getName(), resultClass, parameterClasses);
String[] names = null; // TODO read names using asm
if (m.getParameterTypes().length > 0) {
// TODO treat multiple parameters annotated with XMLRootElement
for (int i = 0; i < m.getParameterTypes().length; i++) {
if (!m.getParameterTypes()[i].isAnnotationPresent(XmlRootElement.class)) {
String name = names != null ? names[i] : "arg" + i;
writer.addAnnotationWriter(new AnnotationWriter(i, QueryParam.class, new AnnotationParamWriter("value", name)));
}
}
}
writer.addAnnotationWriter(new AnnotationWriter(PUT.class));
writer.addAnnotationWriter(new AnnotationWriter(Consumes.class, new AnnotationParamWriter("value",
new String[] { MediaType.APPLICATION_XML })));
method2writer.put(m, writer);
}
for (Method m : metaDataContainer.getServiceMethods(RemovesResource.class)) {
log.trace("Found RemovesResource annotated method: " + m);
Class<?> parameterClass = restAPIGenerator.getParameterTranslation(m.getParameterTypes()[0]);
MethodWriter writer = new MethodWriter(m.getName(), void.class, new Class<?>[] { parameterClass },
new AnnotationWriter(DELETE.class),
new AnnotationWriter(Path.class, new AnnotationParamWriter("value", "{id}")),
new AnnotationWriter(0, PathParam.class, new AnnotationParamWriter("value", "id")));
method2writer.put(m, writer);
}
for (Method m : metaDataContainer.getServiceMethods(ListsResources.class)) {
log.trace("Found ListsResources annotated method: " + m);
MethodWriter writer = new MethodWriter(m.getName(), m.getReturnType(),
m.getParameterTypes(),
new AnnotationWriter(GET.class),
new AnnotationWriter(ContentType.class, new AnnotationParamWriter("value", metaDataContainer.getEntityClass())),
new AnnotationWriter(Produces.class, new AnnotationParamWriter("value", new String[] { MediaType.APPLICATION_XML })));
// Map all parameters as QueryParams
String[] names = null; // TODO read names using asm
for (int i = 0; i < m.getParameterTypes().length; i++) {
String name = names != null ? names[i] : "arg" + i;
writer.addAnnotationWriter(new AnnotationWriter(i, QueryParam.class, new AnnotationParamWriter("value", name)));
}
method2writer.put(m, writer);
}
// Add all the remaining services to the REST API interface
// 1. Determine all remaining services
List<Method> methods = metaDataContainer.getServiceMethods();
methods.removeAll(metaDataContainer.getServiceMethods(AddsResource.class));
methods.removeAll(metaDataContainer.getServiceMethods(RemovesResource.class));
methods.removeAll(metaDataContainer.getServiceMethods(ListsResources.class));
// 2. Write them as services methods with their names as path
for (Method method : methods) {
// Define the HTTP method type
Class<? extends Annotation> httpMethod = GET.class;
// Translate the result
Class<?> resultClass = restAPIGenerator.getResultTranslation(method.getReturnType());
// Translate the parameters
Class<?>[] parameterClasses = new Class<?>[method.getParameterTypes().length];
for (int i = 0; i < method.getParameterTypes().length; i++) {
parameterClasses[i] = restAPIGenerator.getParameterTranslation(method.getParameterTypes()[i]);
}
MethodWriter writer = new MethodWriter(method.getName(), resultClass, parameterClasses);
String serviceName = method.getName();
if (serviceName.startsWith("get") && serviceName.length() > 3) {
log.trace("Found \"get\" method: " + method);
serviceName = serviceName.substring(3, 4).toLowerCase() + serviceName.substring(4);
if (Collection.class.isAssignableFrom(method.getReturnType())) {
// FIXME, add wrapper instead of Collection
}
}
else if (serviceName.startsWith("set") && serviceName.length() > 3) {
log.trace("Found \"set\" method: " + method);
serviceName = serviceName.substring(3, 4).toLowerCase() + serviceName.substring(4);
httpMethod = PUT.class;
}
else if (serviceName.startsWith("delete") && serviceName.length() > 6) {
log.trace("Found \"delete\" method: " + method);
httpMethod = DELETE.class;
}
else if (serviceName.startsWith("add") && serviceName.length() > 3) {
log.trace("Found \"add\" method: " + method);
httpMethod = PUT.class;
}
else if (serviceName.startsWith("update") && serviceName.length() > 6) {
log.trace("Found \"update\" method: " + method);
httpMethod = PUT.class;
}
else if (serviceName.startsWith("remove") && serviceName.length() > 6) {
log.trace("Found \"remove\" method: " + method);
httpMethod = DELETE.class;
}
writer.addAnnotationWriter(new AnnotationWriter(httpMethod));
writer.addAnnotationWriter(new AnnotationWriter(Path.class, new AnnotationParamWriter("value", serviceName)));
String[] names = null; // TODO read names using asm
// treat one XMLRootElement annotated method parameter (or data structures)
// FIXME check XMLRootElement is an annotation present in the generic type of Multimap or Collection
if (method.getParameterTypes().length == 1 && (method.getParameterTypes()[0].isAnnotationPresent(XmlRootElement.class) || Multimap.class
.isAssignableFrom(method.getParameterTypes()[0]) || Collection.class.isAssignableFrom(method.getParameterTypes()[0]))) {
if (Multimap.class.isAssignableFrom(method.getParameterTypes()[0])) {
log.trace("Found Multimap method parameter of type " + method.getParameterTypes()[0]);
// FIXME, add wrapper instead of Multimap
} else if (Collection.class.isAssignableFrom(method.getParameterTypes()[0])) {
log.trace("Found Collection method parameter of type " + method.getParameterTypes()[0]);
// FIXME, add wrapper instead of Collection
} else {
log.trace("Found method parameter annotated with XMLRootElement of type " + method.getParameterTypes()[0]);
// just do nothing, it will generate a request with a body element
}
// add Consumes annotation
writer.addAnnotationWriter(new AnnotationWriter(Consumes.class, new AnnotationParamWriter("value",
new String[] { MediaType.APPLICATION_XML })));
} else {
// TODO treat parameters annotated with XMLRootElement
for (int i = 0; i < method.getParameterTypes().length; i++) {
String name = names != null ? names[i] : "arg" + i;
writer.addAnnotationWriter(new AnnotationWriter(i, QueryParam.class, new AnnotationParamWriter("value", name)));
}
}
// add Produces if there is a serializable object as the return type
// FIXME check XMLRootElement is an annotation present in the generic type of Multimap or Collection
if (resultClass.isAnnotationPresent(XmlRootElement.class)
|| Multimap.class.isAssignableFrom(resultClass) || Collection.class.isAssignableFrom(resultClass)) {
log.trace("Found XMLRootElement in the return type class " + resultClass);
writer.addAnnotationWriter(new AnnotationWriter(Produces.class, new AnnotationParamWriter("value",
new String[] { MediaType.APPLICATION_XML })));
}
method2writer.put(method, writer);
}
Class<?> entityClass = metaDataContainer.getEntityClass();
if (entityClass != null) {
// If there are entity classes used in the services, then provide a getter for the serialization of the resource
MethodWriter methodWriter = new MethodWriter("get" + entityClass.getSimpleName(), entityClass, new Class<?>[] { String.class },
new AnnotationWriter(GET.class),
new AnnotationWriter(Path.class, new AnnotationParamWriter("value", "{id}")),
new AnnotationWriter(Produces.class, new AnnotationParamWriter("value", new String[] { MediaType.APPLICATION_XML })),
new AnnotationWriter(0, PathParam.class, new AnnotationParamWriter("value", "id")));
method2writer.put(null, methodWriter);
}
}
public Class<?> toClass(ClassLoader classLoader) {
String className = name.replace("/", ".");
byte[] b = write();
Class<?> clazz = loadClass(classLoader, className, b);
return clazz;
}
/**
* Adds an additional "api" package to the given class' name and returns this new class name
*
* @param capabiltyClass
* @return
*/
private static String generateAPIInterfaceName(Class<? extends ICapability> capabiltyClass) {
String[] parts = capabiltyClass.getName().split("\\.");
StringBuilder apiNameBuilder = new StringBuilder();
for (int i = 0; i < parts.length - 1; i++)
apiNameBuilder.append(parts[i]).append("/");
apiNameBuilder.append("api").append("/");
apiNameBuilder.append(parts[parts.length - 1]);
return apiNameBuilder.toString();
}
public byte[] write() {
ClassWriter cw = new ClassWriter(0);
cw.visit(V1_6, ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE, name, null,
"java/lang/Object", null);
if (annotationWriters != null) {
for (AnnotationWriter annotation : annotationWriters) {
annotation.writeTo(cw);
}
}
for (MethodWriter methodWriter : method2writer.values()) {
methodWriter.writeTo(cw);
}
cw.visitEnd();
return cw.toByteArray();
}
public Set<Entry<Method, MethodWriter>> getMapping() {
return method2writer.entrySet();
}
public Method getListService() {
return metaDataContainer.getListService();
}
@Override
public String toString() {
StringBuilder sb = StringBuilderUtils.create("\n", annotationWriters).append("\n");
sb.append("interface ").append(name).append(" {\n");
StringBuilderUtils.append(sb, "\n\n", method2writer.values());
sb.append("}");
return sb.toString();
}
}