package org.easysoa.discovery.code.handler; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.ws.rs.core.MediaType; import org.apache.maven.plugin.logging.Log; import org.easysoa.discovery.code.CodeDiscoveryMojo; import org.easysoa.discovery.code.CodeDiscoveryRegistryClient; import org.easysoa.discovery.code.ParsingUtils; import org.easysoa.discovery.code.model.JavaServiceImplementationInformation; import org.easysoa.discovery.code.model.JavaServiceInterfaceInformation; import org.easysoa.registry.rest.SoaNodeInformation; import org.easysoa.registry.rest.client.types.InformationServiceInformation; import org.easysoa.registry.rest.client.types.java.MavenDeliverableInformation; import org.easysoa.registry.types.OperationInformation; import org.easysoa.registry.types.Platform; import org.easysoa.registry.types.ids.ServiceImplementationName; import org.easysoa.registry.types.ids.ServiceNameType; import org.easysoa.registry.types.java.JavaServiceImplementation; import com.thoughtworks.qdox.model.JavaClass; import com.thoughtworks.qdox.model.JavaMethod; import com.thoughtworks.qdox.model.JavaParameter; import com.thoughtworks.qdox.model.JavaSource; /** * In project & current deliverable, reports : * * JAXRS service impl and their impl'd service interface if any (from impl and also by themselves), * both by having class or method(s) annotated by @Path * * thanks to InterfaceHandlerBase, member (field or bean setter)-injected service : * typed by interfaces having class or method(s) annotated by @Path * * About implementation : see references at * spec http://jax-rs-spec.java.net/ * doc http://jax-ws.java.net/jax-ws-ea3/docs/annotations.html * resteasy impl http://docs.jboss.org/resteasy/docs/1.2.GA/userguide/html_single/ * wink impl https://cwiki.apache.org/WINK/jax-rs-request-and-response-entities.html * fairly complete tutorial http://www.vogella.com/articles/REST/article.html * * For JAXRS, the documentation handler take the documention contained in the implementation (class). * * TODO in bytecode * */ public class JaxRSSourcesHandler extends AbstractJavaSourceHandler implements SourcesHandler { private static final String ANN_PATH = "javax.ws.rs.Path"; private static final String ANN_CONSUMES = "javax.ws.rs.Consumes"; private static final String ANN_PRODUCES = "javax.ws.rs.Produces"; private static final String[] ANN_METHODS = new String[] { "javax.ws.rs.GET", "javax.ws.rs.POST", "javax.ws.rs.PUT", "javax.ws.rs.HEAD", "javax.ws.rs.OPTIONS" }; private static final String ANN_PATH_PARAM = "javax.ws.rs.PathParam"; private static final String ANN_QUERY_PARAM = "javax.ws.rs.QueryParam"; private static final String ANN_FORM_PARAM = "javax.ws.rs.FormParam"; private static final String ANN_HEADER_PARAM = "javax.ws.rs.HeaderParam"; private static final String ANN_COOKIE_PARAM = "javax.ws.rs.CookieParam"; public JaxRSSourcesHandler(CodeDiscoveryMojo codeDiscovery) { super(codeDiscovery); } @Override public Map<String, JavaServiceInterfaceInformation> findWSInterfaces(JavaSource source, MavenDeliverableInformation mavenDeliverable, CodeDiscoveryRegistryClient registryClient, Log log) throws Exception { Map<String, JavaServiceInterfaceInformation> wsInjectableTypeSet = new HashMap<String, JavaServiceInterfaceInformation>(); // Pass 1 : Find all WS interfaces if any JavaClass[] classes = source.getClasses(); for (JavaClass c : classes) { if (c.isInterface() && !ParsingUtils.isTestClass(c)) { // Check JAX-RS annotation ArrayList<JavaMethod> pathMethods = null; for (JavaMethod method : c.getMethods()) { if (ParsingUtils.hasAnnotation(method, ANN_PATH)) { if (pathMethods == null) { pathMethods = new ArrayList<JavaMethod>(c.getMethods().length); } pathMethods.add(method); } } if (pathMethods != null || ParsingUtils.hasAnnotation(c, ANN_PATH)) { // TODO target wsName[space] wsInjectableTypeSet.put(c.getFullyQualifiedName(), new JavaServiceInterfaceInformation( this.codeDiscovery.getSubproject(), mavenDeliverable.getGroupId(), mavenDeliverable.getArtifactId(), c.getFullyQualifiedName(), null, null, null)); } } } return wsInjectableTypeSet; } @Override public JavaServiceInterfaceInformation findWSInterfaceInClasspath(Class<?> candidateClass, MavenDeliverableInformation mavenDeliverable, CodeDiscoveryRegistryClient registryClient, Log log) throws Exception { boolean hasPathMethods = false; for (Method method : candidateClass.getMethods()) { if (ParsingUtils.hasAnnotation(method, ANN_PATH)) { hasPathMethods = true; } } if (hasPathMethods || ParsingUtils.hasAnnotation(candidateClass, ANN_PATH)) { // TODO target wsName[space] return new JavaServiceInterfaceInformation(this.codeDiscovery.getSubproject(), mavenDeliverable.getGroupId(), mavenDeliverable.getArtifactId(), candidateClass.getName(), null, null, null); } else { return null; } } @Override public Collection<SoaNodeInformation> findWSImplementations(JavaSource[] sources, Map<String, JavaServiceInterfaceInformation> wsInterfaces, MavenDeliverableInformation mavenDeliverable, CodeDiscoveryRegistryClient registryClient, Log log) throws Exception { // Pass 2 : Find all WS impl, including those implementing known interfaces (though its not "classical" JAXRS) List<SoaNodeInformation> discoveredNodes = new ArrayList<SoaNodeInformation>(); for (JavaSource source : sources) { // TODO diff between main & tests JavaClass[] classes = source.getClasses(); for (JavaClass c : classes) { if (!c.isInterface()) { JavaClass itf = getWsItf(c, wsInterfaces); ArrayList<JavaMethod> pathMethods = null; if (itf == null) { // Check JAX-RS annotation for (JavaMethod method : c.getMethods()) { if (ParsingUtils.hasAnnotation(method, ANN_PATH)) { if (pathMethods == null) { pathMethods = new ArrayList<JavaMethod>(c.getMethods().length); } pathMethods.add(method); } } } if (itf != null || pathMethods != null || ParsingUtils.hasAnnotation(c, ANN_PATH)) { // extract base jaxrs conf : //TODO also methods & inheritance see http://fusesource.com/docs/esb/4.2/rest/RESTAnnotateInherit.html String baseRestPath = ParsingUtils.getAnnotationPropertyString(c, ANN_PATH, "value"); if (baseRestPath == null) { baseRestPath = ""; // else won't be known as REST in EasySOA Registry } String baseRestAccepts = ParsingUtils.getAnnotationPropertyString(c, ANN_CONSUMES, "value"); if (baseRestAccepts == null) { baseRestAccepts = MediaType.WILDCARD; // */* see https://cwiki.apache.org/WINK/jax-rs-request-and-response-entities.html } String baseRestContentType = ParsingUtils.getAnnotationPropertyString(c, ANN_PRODUCES, "value"); if (baseRestContentType == null) { baseRestContentType = MediaType.APPLICATION_OCTET_STREAM; // see https://cwiki.apache.org/WINK/jax-rs-request-and-response-entities.html } // Create name & impl info JavaServiceImplementationInformation serviceImpl = new JavaServiceImplementationInformation( this.codeDiscovery.getSubproject(), new ServiceImplementationName(ServiceNameType.REST, // ????? baseRestPath, c.getFullyQualifiedName())); // also baseRestAccepts, group operations by path prefix ?? serviceImpl.setTitle(c.getName()); // c.getName() (shortcut) or serviceImpl.getSoaName() ? serviceImpl.setProperty(JavaServiceImplementation.XPATH_IMPL_LANGUAGE, Platform.LANGUAGE_JAVA); serviceImpl.setProperty(JavaServiceImplementation.XPATH_IMPL_BUILD, Platform.BUILD_MAVEN); serviceImpl.setProperty(JavaServiceImplementation.XPATH_TECHNOLOGY, Platform.SERVICE_LANGUAGE_JAXRS); serviceImpl.setProperty(JavaServiceImplementation.XPATH_DOCUMENTATION, formatDoc(c)); serviceImpl.setProperty(JavaServiceImplementation.XPATH_ISMOCK, ParsingUtils.isTestClass(c)); serviceImpl.setProperty(JavaServiceImplementation.XPATH_IMPLEMENTATIONCLASS, c.getFullyQualifiedName()); serviceImpl.addParentDocument(mavenDeliverable.getSoaNodeId()); if (itf != null) { // Extract WS info serviceImpl.setProperty(JavaServiceImplementation.XPATH_IMPLEMENTEDINTERFACE, itf.getFullyQualifiedName()); JavaServiceInterfaceInformation interfaceInfo = wsInterfaces.get(itf.getFullyQualifiedName()); if (interfaceInfo != null) { serviceImpl.setProperty(JavaServiceImplementation.XPATH_IMPLEMENTEDINTERFACELOCATION, interfaceInfo.getMavenDeliverableId().getName()); } String itfSoaName = itf.getName(); // TODO better like JAXWS wsNamespace + ":" + wsName; ?? if (codeDiscovery.isMatchInterfacesFirst()) { itfSoaName = "matchFirst:" + itfSoaName; } InformationServiceInformation serviceDef = new InformationServiceInformation( this.codeDiscovery.getSubproject(), itfSoaName); // TODO LATER if refactoring to allow technical matching of service interfaces : /*serviceDef.setProperty(InformationService.XPATH_LANGUAGE, Platform.LANGUAGE_JAVA); serviceDef.setProperty(InformationService.XPATH_BUILD, Platform.BUILD_MAVEN); serviceDef.setProperty(InformationService.XPATH_TECHNOLOGY, Platform.SERVICE_LANGUAGE_JAXRS);*/ //serviceDef.setOperations(operations);//TODO serviceImpl.addParentDocument(serviceDef.getSoaNodeId()); if (this.codeDiscovery.isDiscoverInterfaces()) { discoveredNodes.add(serviceDef); } } // set jaxrs conf serviceImpl.setProperty(JavaServiceImplementation.XPATH_REST_PATH, baseRestPath); if (baseRestAccepts != null) { // or defaults to "*" ? serviceImpl.setProperty(JavaServiceImplementation.XPATH_REST_ACCEPTS, baseRestAccepts); } if (baseRestContentType != null) { // or defaults to "application/*" ?? serviceImpl.setProperty(JavaServiceImplementation.XPATH_REST_CONTENT_TYPE, baseRestContentType); } // Extract operations info List<OperationInformation> operations = serviceImpl.getOperations(); String baseRestPathPrefix = baseRestPath + ((baseRestPath.endsWith("/")) ? "" : '/'); if (pathMethods != null) { for (JavaMethod method : c.getMethods()) { // Get HTTP method if any String httpMethod = null; for (String annHttpMethod : ANN_METHODS) { if (ParsingUtils.hasAnnotation(method, annHttpMethod)) { httpMethod = annHttpMethod.replace("javax.ws.rs.", ""); break; } } if (httpMethod != null) { // Extract service path String path = ParsingUtils.getAnnotationPropertyString(method, ANN_PATH, "value"); if (path == null) { path = method.getName(); } // TODO LATER also base REST path but at first only show local path : //if (baseRestPath != null && !path.startsWith("/")) { // path = baseRestPathPrefix + path; //} String operationName = httpMethod + " " + path; String operationConsumes = ParsingUtils.getAnnotationPropertyString(method, ANN_PRODUCES, "value"); if (operationConsumes == null) { operationConsumes = baseRestAccepts; } String operationProduces = ParsingUtils.getAnnotationPropertyString(method, ANN_CONSUMES, "value"); if (operationProduces == null) { operationProduces = baseRestContentType; } // Extract parameters info StringBuilder parametersInfo = new StringBuilder(); for (JavaParameter parameter : method.getParameters()) { String paramName = extractRestParamName(parameter); if (paramName != null) { String parameterType = getParameterType(parameter.getType()); parametersInfo.append(formatParameter(paramName, parameterType) + ", "); } // else can't be provided through REST } // removing trailing ", " if (parametersInfo.length() > 2) { parametersInfo.delete(parametersInfo.length()-2, parametersInfo.length()); } // extract return parameter info String returnParameterType = getReturnParameterType(method); String returnParametersInfo = formatParameter("response", returnParameterType); // "in message" way of presenting parameters : //parametersInfo.insert(0, operationConsumes + ": "); // "out message" way of presenting return : //returnParametersInfo = operationProduces + ": " + returnParametersInfo; //TODO LATER also bare signature (or method.getName() ?) : //String signature = method.getCallSignature(); //TODO lATER better as for JAXWS : operationMap.put(method.getName(), new OperationInformation(operationName... operations.add(new OperationInformation(operationName, parametersInfo.toString(), returnParametersInfo, formatDoc(method), operationConsumes, operationProduces)); } } serviceImpl.setOperations(operations); if (this.codeDiscovery.isDiscoverImplementations()) { discoveredNodes.add(serviceImpl); } } } } } } return discoveredNodes; } private String extractRestParamName(JavaParameter parameter) { String paramName = getParamName(parameter, ANN_PATH_PARAM, "path"); if (paramName == null) { paramName = getParamName(parameter, ANN_QUERY_PARAM, "query"); } if (paramName == null) { paramName = getParamName(parameter, ANN_FORM_PARAM, "form"); } if (paramName == null) { paramName = getParamName(parameter, ANN_HEADER_PARAM, "header"); } if (paramName == null) { paramName = getParamName(parameter, ANN_COOKIE_PARAM, "cookie"); } return paramName; } private String getParamName(JavaParameter parameter, String paramAnnotation, String paramKind) { String paramName = ParsingUtils.getAnnotationPropertyString(parameter, paramAnnotation, "value"); if (paramName != null) { return paramKind + ':' + paramName; } return null; } }