package org.easysoa.discovery.code.handler; import com.thoughtworks.qdox.model.AbstractInheritableJavaEntity; import com.thoughtworks.qdox.model.DocletTag; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.jar.JarEntry; import java.util.jar.JarFile; import org.apache.maven.artifact.Artifact; import org.apache.maven.plugin.logging.Log; import org.apache.maven.project.MavenProject; import org.easysoa.discovery.code.CodeDiscoveryMojo; import org.easysoa.discovery.code.CodeDiscoveryRegistryClient; import org.easysoa.discovery.code.ParsingUtils; import org.easysoa.discovery.code.handler.consumption.AnnotatedServicesConsumptionFinder; import org.easysoa.discovery.code.handler.consumption.ImportedServicesConsumptionFinder; import org.easysoa.discovery.code.handler.consumption.ServiceConsumptionFinder; import org.easysoa.discovery.code.model.JavaServiceInterfaceInformation; import org.easysoa.registry.rest.SoaNodeInformation; import org.easysoa.registry.rest.client.types.java.MavenDeliverableInformation; import com.thoughtworks.qdox.model.JavaClass; import com.thoughtworks.qdox.model.JavaMethod; import com.thoughtworks.qdox.model.JavaSource; import com.thoughtworks.qdox.model.Type; /** * Provides Java-generic discovery features for interfaces, for now only * detection of Java interface-typed injected (through various methods) members. * * This provides only potential, coarse grain (among a set of deployed deliverables, * any service provider implementation MAY use any service consumer implementation) & * partial (services may also be consumed after being looked up in a registry, not * only through member injection) dependencies between services. * * These dependencies should be refined by providing EasySOA with explicit * architecture information about architectural components (classified deliverable * may already be a good guide there) and business processes. * * LATER Thoughts about more injection strategies : * recursive (including native Java services being injected in one another, but adds complexity), * imports (but qdox can't), non-null assigned variable (but requires almost being runtime) * * @author mdutoo * */ public abstract class AbstractJavaSourceHandler implements SourcesHandler { /* jaxb annotations */ protected static final String ANN_XMLROOTELEMENT = " javax.xml.bind.annotation.XmlRootElement"; /* injection annotations */ protected static final String ANN_INJECT = "javax.inject.Inject"; /* test annotations */ protected static final String ANN_JUNIT_TEST = "org.junit.Test"; protected static final String ANN_TESTNG_TEST = "org.testng.annotations.Test"; // http://testng.org/javadoc/org/testng/annotations/package-summary.html private List<ServiceConsumptionFinder> serviceConsumptionFinders = new ArrayList<ServiceConsumptionFinder>(); private AnnotatedServicesConsumptionFinder annotatedServicesFinder; protected CodeDiscoveryMojo codeDiscovery; /** * * @param codeDiscovery provides access to maven plugin configuration params */ protected AbstractJavaSourceHandler(CodeDiscoveryMojo codeDiscovery) { this.codeDiscovery = codeDiscovery; this.annotatedServicesFinder = new AnnotatedServicesConsumptionFinder(null); this.annotatedServicesFinder.addAnnotationToDetect(ANN_INJECT); // Java 6 this.annotatedServicesFinder.addAnnotationToDetect("org.osoa.sca.annotations.Reference"); this.annotatedServicesFinder.addAnnotationToDetect("org.springframework.beans.factory.annotation.Autowired"); this.annotatedServicesFinder.addAnnotationToDetect("com.google.inject.Inject"); this.annotatedServicesFinder.addAnnotationToDetect("javax.ejb.EJB"); this.serviceConsumptionFinders.add(this.annotatedServicesFinder); this.serviceConsumptionFinders.add(new ImportedServicesConsumptionFinder()); } protected void addAnnotationToDetect(String annotationToDetect) { annotatedServicesFinder.addAnnotationToDetect(annotationToDetect); } protected JavaClass getWsItf(JavaClass c, Map<String, JavaServiceInterfaceInformation> serviceInterfaces) { for (JavaClass itfClass : c.getImplementedInterfaces()) { String itfClassName = itfClass.getFullyQualifiedName(); if (!itfClassName.contains(".")) { itfClassName = c.getPackageName() + "." + itfClassName; itfClass.setName(itfClassName); } if (serviceInterfaces.containsKey(itfClassName)) { return itfClass; } } return null; } public Collection<SoaNodeInformation> handleSources(JavaSource[] sources, MavenDeliverableInformation mavenDeliverable, CodeDiscoveryRegistryClient registryClient, Log log) throws Exception { List<SoaNodeInformation> discoveredNodes = new LinkedList<SoaNodeInformation>(); // Find WS interfaces in sources Map<String, JavaServiceInterfaceInformation> wsInterfaces = new HashMap<String, JavaServiceInterfaceInformation>(); for (JavaSource source : sources) { wsInterfaces.putAll(findWSInterfaces(source, mavenDeliverable, registryClient, log)); } // Find WS interfaces from dependencies if (codeDiscovery.getMavenProject() != null) { // may be null in unit tests MavenProject mavenProject = codeDiscovery.getMavenProject(); for (Object dependencyObject : mavenProject.getDependencyArtifacts()) { Artifact dependency = (Artifact) dependencyObject; if (codeDiscovery.shouldLookupInterfaces(dependency) && dependency.getFile() != null) { // happens in pom projects (but should be skipped before) URLClassLoader jarClassloader = new URLClassLoader( new URL[] { dependency.getFile().toURI().toURL() }, this.getClass().getClassLoader()); JarFile jarFile = new JarFile(dependency.getFile()); Enumeration<JarEntry> jarEntries = jarFile.entries(); while (jarEntries.hasMoreElements()) { JarEntry element = jarEntries.nextElement(); if (element.getName().endsWith(".class")) { String className = element.getName().replace(".class", "").replaceAll("/", "\\."); try { Class<?> candidateClass = jarClassloader.loadClass(className); JavaServiceInterfaceInformation newWSInterface = findWSInterfaceInClasspath(candidateClass, new MavenDeliverableInformation(dependency.getGroupId(), dependency.getArtifactId()), registryClient, log); if (newWSInterface != null) { wsInterfaces.put(newWSInterface.getInterfaceName(), newWSInterface); } } catch (Throwable e) { log.warn("Failed to load class " + className + " to inspect potiential web services: " + e.getClass().getName() + " - " + e.getMessage()); } } } } } } // Find WS implementations Collection<SoaNodeInformation> wsImpls = findWSImplementations(sources, wsInterfaces, mavenDeliverable, registryClient, log); discoveredNodes.addAll(wsImpls); // Find WS consumptions for (JavaSource source : sources) { for (ServiceConsumptionFinder serviceConsumptionFinder : this.serviceConsumptionFinders) { discoveredNodes.addAll(serviceConsumptionFinder.find(source, mavenDeliverable, wsInterfaces)); } } // Additional discovery discoveredNodes.addAll(handleAdditionalDiscovery(sources, wsInterfaces, mavenDeliverable, registryClient, log)); return discoveredNodes; } protected String formatParameter(String parameterName, String parameterType) { return parameterName + "=" + parameterType; } protected boolean isUnitTestingClass(boolean isUnitTestingClass, JavaMethod method) { return isUnitTestingClass || ParsingUtils.hasAnnotation(method, ANN_JUNIT_TEST) || ParsingUtils.hasAnnotation(method, ANN_TESTNG_TEST); // A TestNG class is a Java class that contains at least one TestNG annotation http://testng.org/doc/documentation-main.html } protected String getParameterType(Type parameterType) { String webParameterType = ParsingUtils.getAnnotationPropertyString(parameterType.getJavaClass(), ANN_XMLROOTELEMENT, "name");//TODO ?? if (webParameterType == null) { webParameterType = parameterType.getFullyQualifiedName(); } return webParameterType; } protected String getReturnParameterType(JavaMethod method) { String returnParameterType; Type returnType = method.getReturnType(); // until qdox 1.12 procedure return type was null so nullpointerexception http://jira.codehaus.org/browse/QDOX-214 if (returnType == null) { // complementary patch to qdox 1.12.1 returnType = Type.VOID; // would still nullpointerexception in getParameterType because no javaClassName returnParameterType = returnType.toString(); // NB. a void return returns an empty element } else { returnParameterType = getParameterType(returnType); } return returnParameterType; } protected String getParameterType(Class<?> parameterType) { String webParameterType = ParsingUtils.getAnnotationPropertyString(parameterType, ANN_XMLROOTELEMENT, "name"); if (webParameterType == null) { webParameterType = parameterType.getName(); } return webParameterType; } public abstract Map<String, JavaServiceInterfaceInformation> findWSInterfaces(JavaSource source, MavenDeliverableInformation mavenDeliverable, CodeDiscoveryRegistryClient registryClient, Log log) throws Exception; public abstract JavaServiceInterfaceInformation findWSInterfaceInClasspath(Class<?> candidateClass, MavenDeliverableInformation mavenDeliverable, CodeDiscoveryRegistryClient registryClient, Log log) throws Exception; public abstract Collection<SoaNodeInformation> findWSImplementations(JavaSource[] sources, Map<String, JavaServiceInterfaceInformation> wsInterfaces, MavenDeliverableInformation mavenDeliverable, CodeDiscoveryRegistryClient registryClient, Log log) throws Exception; public Collection<SoaNodeInformation> handleAdditionalDiscovery(JavaSource[] sources, Map<String, JavaServiceInterfaceInformation> wsInterfaces, MavenDeliverableInformation mavenDeliverable, CodeDiscoveryRegistryClient registryClient, Log log) throws Exception { return Collections.emptyList(); } /** * Format the class and method documentation for Nuxeo registry, * especially by adding doclets (eg : @author, @param ...) ommitted by qdox * @param javaEntity Java class or method * @return formatted comment bloc with doctlets tags */ protected String formatDoc(AbstractInheritableJavaEntity javaEntity) { StringBuilder formattedDoc = new StringBuilder(); if(javaEntity.getComment() != null){ formattedDoc.append(javaEntity.getComment()); } for(DocletTag tag : javaEntity.getTags()){ if(formattedDoc.length() > 0){ formattedDoc.append("\n"); } formattedDoc.append("@"); formattedDoc.append(tag.getName()); formattedDoc.append(" "); formattedDoc.append(tag.getValue()); } return formattedDoc.toString(); } }