Java tutorial
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(); } }