blob: d6480ee101972164d9dc06c1a5b615186e3039c8 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.felix.scrplugin.tags;
import java.io.*;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;
import java.util.jar.*;
import org.apache.felix.scrplugin.Constants;
import org.apache.felix.scrplugin.om.Component;
import org.apache.felix.scrplugin.om.Components;
import org.apache.felix.scrplugin.tags.annotation.AnnotationJavaClassDescription;
import org.apache.felix.scrplugin.tags.annotation.AnnotationTagProviderManager;
import org.apache.felix.scrplugin.tags.cl.ClassLoaderJavaClassDescription;
import org.apache.felix.scrplugin.tags.qdox.QDoxJavaClassDescription;
import org.apache.felix.scrplugin.xml.ComponentDescriptorIO;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.ArtifactUtils;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.util.*;
import com.thoughtworks.qdox.JavaDocBuilder;
import com.thoughtworks.qdox.model.JavaClass;
import com.thoughtworks.qdox.model.JavaSource;
/**
* <code>JavaClassDescriptorManager.java</code>...
*
*/
public class JavaClassDescriptorManager {
protected static final String SERVICE_COMPONENT = "Service-Component";
/** The sources read by qdox. */
protected final JavaSource[] sources;
/** The maven log. */
protected final Log log;
/** The classloader used to compile the classes. */
protected final ClassLoader classloader;
/** A cache containing the java class descriptions hashed by classname. */
protected final Map<String, JavaClassDescription> javaClassDescriptions = new HashMap<String, JavaClassDescription>();
/** The component definitions from other bundles hashed by classname. */
protected final Map<String, Component> componentDescriptions = new HashMap<String, Component>();
/** The maven project. */
protected final MavenProject project;
/**
* Supports mapping of built-in and custom java anntoations to {@link JavaTag} implementations.
*/
protected final AnnotationTagProviderManager annotationTagProviderManager;
/** Parse Javadocs? */
protected final boolean parseJavadocs;
/** Process Annotations? */
protected final boolean processAnnotations;
/**
* Construct a new manager.
* @param log
* @param project
* @param annotationTagProviders List of annotation tag providers
* @param excludeString The exclude information for sources
* @param parseJavadocs Should the javadocs be parsed?
* @param processAnnotations Should the annotations be processed?
* @throws MojoFailureException
* @throws MojoExecutionException
*/
public JavaClassDescriptorManager(final Log log,
final MavenProject project,
final String[] annotationTagProviders,
final String excludeString,
final boolean parseJavadocs,
final boolean processAnnotations)
throws MojoFailureException, MojoExecutionException {
this.processAnnotations = processAnnotations;
this.parseJavadocs = parseJavadocs;
this.log = log;
this.project = project;
this.annotationTagProviderManager = new AnnotationTagProviderManager(annotationTagProviders);
this.classloader = this.getCompileClassLoader();
ClassUtil.classLoader = this.classloader;
// get all the class sources through qdox
this.log.debug("Setting up QDox");
JavaDocBuilder builder = new JavaDocBuilder();
builder.getClassLibrary().addClassLoader(this.classloader);
@SuppressWarnings("unchecked")
final Iterator<String> i = project.getCompileSourceRoots().iterator();
// FELIX-509: check for excludes
if ( excludeString != null ) {
final String[] excludes = StringUtils.split(excludeString, ",");
final String[] includes = new String[] {"**/*.java"};
while ( i.hasNext() ) {
final String tree = i.next();
this.log.debug("Scanning source tree " + tree);
final File directory = new File(tree);
final DirectoryScanner scanner = new DirectoryScanner();
scanner.setBasedir( directory );
if ( excludes != null && excludes.length > 0 ) {
scanner.setExcludes( excludes );
}
scanner.addDefaultExcludes();
scanner.setIncludes( includes );
scanner.scan();
final String[] files = scanner.getIncludedFiles();
if ( files != null ) {
for(int m=0; m<files.length; m++) {
this.log.debug("Adding source file " + files[m]);
try {
builder.addSource(new File(directory, files[m]));
} catch (FileNotFoundException e) {
throw new MojoExecutionException("Unable to scan directory.", e);
} catch (IOException e) {
throw new MojoExecutionException("Unable to scan directory.", e);
}
}
}
}
} else {
while ( i.hasNext() ) {
final String tree = i.next();
this.log.debug("Adding source tree " + tree);
final File directory = new File(tree);
builder.addSourceTree(directory);
}
}
this.sources = builder.getSources();
// and now scan artifacts
final List<Component> components = new ArrayList<Component>();
@SuppressWarnings("unchecked")
final Map<String, Artifact> resolved = project.getArtifactMap();
@SuppressWarnings("unchecked")
final Set<Artifact> artifacts = project.getDependencyArtifacts();
final Iterator<Artifact> it = artifacts.iterator();
while ( it.hasNext() ) {
final Artifact declared = it.next();
this.log.debug("Checking artifact " + declared);
if ( this.isJavaArtifact(declared)) {
if (Artifact.SCOPE_COMPILE.equals(declared.getScope())
|| Artifact.SCOPE_RUNTIME.equals(declared.getScope())
|| Artifact.SCOPE_PROVIDED.equals(declared.getScope())) {
this.log.debug("Resolving artifact " + declared);
final Artifact artifact = resolved.get(ArtifactUtils.versionlessKey(declared));
if (artifact != null) {
this.log.debug("Trying to get manifest from artifact " + artifact);
try {
final Manifest manifest = this.getManifest(artifact);
if ( manifest != null ) {
// read Service-Component entry
if ( manifest.getMainAttributes().getValue(JavaClassDescriptorManager.SERVICE_COMPONENT) != null ) {
final String serviceComponent = manifest.getMainAttributes().getValue(JavaClassDescriptorManager.SERVICE_COMPONENT);
this.log.debug("Found Service-Component: " + serviceComponent + " in artifact " + artifact);
final StringTokenizer st = new StringTokenizer(serviceComponent, ",");
while ( st.hasMoreTokens() ) {
final String entry = st.nextToken().trim();
if ( entry.length() > 0 ) {
final Components c = this.readServiceComponentDescriptor(artifact, entry);
if ( c != null ) {
components.addAll(c.getComponents());
}
}
}
} else {
this.log.debug("Artifact has no service component entry in manifest " + artifact);
}
} else {
this.log.debug("Unable to get manifest from artifact " + artifact);
}
} catch (IOException ioe) {
throw new MojoExecutionException("Unable to get manifest from artifact " + artifact, ioe);
}
this.log.debug("Trying to get scrinfo from artifact " + artifact);
// now read the scr private file - components stored there overwrite components already
// read from the service component section.
try {
final File scrInfoFile = this.getFile(artifact, Constants.ABSTRACT_DESCRIPTOR_ARCHIV_PATH);
if ( scrInfoFile != null ) {
components.addAll(this.parseServiceComponentDescriptor(artifact, scrInfoFile).getComponents());
} else {
this.log.debug("Artifact has no scrinfo file (it's optional): " + artifact);
}
} catch (IOException ioe) {
throw new MojoExecutionException("Unable to get scrinfo from artifact " + artifact, ioe);
}
} else {
this.log.debug("Unable to resolve artifact " + declared);
}
} else {
this.log.debug("Artifact " + declared + " has not scope compile or runtime, but " + declared.getScope());
}
} else {
this.log.debug("Artifact " + declared + " is not a java artifact, type is " + declared.getType());
}
}
// now create map with component descriptions
for(final Component component : components) {
this.componentDescriptions.put(component.getImplementation().getClassame(), component);
}
}
/**
* Check if the artifact is a java artifact (jar or bundle)
*/
private boolean isJavaArtifact(Artifact artifact) {
if ( "jar".equals(artifact.getType()) ) {
return true;
}
if ( "bundle".equals(artifact.getType()) ) {
return true;
}
return false;
}
/**
* Return the log.
*/
public Log getLog() {
return this.log;
}
/**
* Return the class laoder.
*/
public ClassLoader getClassLoader() {
return this.classloader;
}
/**
* Return the project.
*/
public MavenProject getProject() {
return this.project;
}
/**
* @return Annotation tag provider manager
*/
public AnnotationTagProviderManager getAnnotationTagProviderManager() {
return this.annotationTagProviderManager;
}
/**
* Read the service component description.
* @param artifact
* @param entry
* @throws IOException
* @throws MojoExecutionException
*/
protected Components readServiceComponentDescriptor(Artifact artifact, String entry) {
this.log.debug("Reading " + entry + " from " + artifact);
try {
final File xml = this.getFile(artifact, entry);
if ( xml == null ) {
throw new MojoExecutionException("Artifact " + artifact + " does not contain declared service component descriptor " + entry);
}
return this.parseServiceComponentDescriptor(artifact, xml);
} catch (IOException mee) {
this.log.warn("Unable to read SCR descriptor file from artifact " + artifact + " at " + entry);
this.log.debug("Exception occurred during reading: " + mee.getMessage(), mee);
} catch (MojoExecutionException mee) {
this.log.warn("Unable to read SCR descriptor file from artifact " + artifact + " at " + entry);
this.log.debug("Exception occurred during reading: " + mee.getMessage(), mee);
}
return null;
}
protected Components parseServiceComponentDescriptor(Artifact artifact, File file)
throws MojoExecutionException {
this.log.debug("Parsing " + file);
final Components list = ComponentDescriptorIO.read(file);
return list;
}
/**
* Get the url for the target directory
*/
protected URL getOutputDirectory()
throws MojoFailureException {
final String targetDirectory = this.getProject().getBuild().getOutputDirectory();
try {
return new File(targetDirectory).toURI().toURL();
} catch (IOException ioe) {
throw new MojoFailureException("Unable to add target directory to classloader.");
}
}
/**
* Create a class loader containing all compile artifacts including
* the target/class directory of the current project
* @return The class loader
* @throws MojoFailureException
*/
protected ClassLoader getCompileClassLoader()
throws MojoFailureException {
@SuppressWarnings("unchecked")
List<Artifact> artifacts = this.getProject().getCompileArtifacts();
URL[] path = new URL[artifacts.size() + 1];
int i = 0;
for (Iterator<Artifact> ai=artifacts.iterator(); ai.hasNext(); ) {
Artifact a = ai.next();
try {
path[i++] = a.getFile().toURI().toURL();
} catch (IOException ioe) {
throw new MojoFailureException("Unable to get compile class loader.");
}
}
path[path.length - 1] = this.getOutputDirectory();
return new URLClassLoader(path, this.getClass().getClassLoader());
}
protected Manifest getManifest(Artifact artifact) throws IOException {
JarFile file = null;
try {
file = new JarFile(artifact.getFile());
return file.getManifest();
} finally {
if (file != null) {
try {
file.close();
} catch (IOException ignore) {
}
}
}
}
protected File getFile(Artifact artifact, String path) throws IOException {
final int pos = path.lastIndexOf('.');
final String suffix = path.substring(pos + 1);
JarFile file = null;
File tmpFile = null;
try {
file = new JarFile(artifact.getFile());
final JarEntry entry = file.getJarEntry(path);
if ( entry != null ) {
tmpFile = File.createTempFile("scrjcdm" + artifact.getArtifactId(), suffix);
tmpFile.deleteOnExit();
final FileOutputStream fos = new FileOutputStream(tmpFile);
IOUtil.copy(file.getInputStream(entry), fos);
IOUtil.close(fos);
return tmpFile;
}
return null;
} finally {
if (file != null) {
try {
file.close();
} catch (IOException ignore) {
}
}
}
}
/**
* Return all source descriptions of this project.
* @return All contained java class descriptions.
*/
public JavaClassDescription[] getSourceDescriptions() throws MojoExecutionException {
final JavaClassDescription[] descs = new JavaClassDescription[this.sources.length];
for(int i=0; i<this.sources.length; i++) {
final String className = this.sources[i].getClasses()[0].getFullyQualifiedName();
descs[i] = this.getJavaClassDescription(className);
}
return descs;
}
/**
* Get a java class description for the class.
* @param className
* @return The java class description.
* @throws MojoExecutionException
*/
public JavaClassDescription getJavaClassDescription(String className)
throws MojoExecutionException {
JavaClassDescription result = this.javaClassDescriptions.get(className);
if ( result == null ) {
this.log.debug("Searching description for: " + className);
int index = 0;
while ( result == null && index < this.sources.length) {
final JavaClass javaClass = this.sources[index].getClasses()[0];
if ( javaClass.getFullyQualifiedName().equals(className) ) {
try {
// check for java annotation descriptions - fallback to QDox if none found
Class<?> clazz = this.classloader.loadClass(className);
if (this.processAnnotations && getAnnotationTagProviderManager().hasScrPluginAnnotation(javaClass)) {
this.log.debug("Found java annotation description for: " + className);
result = new AnnotationJavaClassDescription(clazz, this.sources[index], this);
} else if ( this.parseJavadocs ) {
this.log.debug("Found qdox description for: " + className);
result = new QDoxJavaClassDescription(clazz, this.sources[index], this);
}
} catch (ClassNotFoundException e) {
throw new MojoExecutionException("Unable to load class " + className);
}
} else {
index++;
}
}
if ( result == null ) {
try {
this.log.debug("Generating classloader description for: " + className);
result = new ClassLoaderJavaClassDescription(this.classloader.loadClass(className), this.componentDescriptions.get(className), this);
} catch (ClassNotFoundException e) {
throw new MojoExecutionException("Unable to load class " + className);
}
}
this.javaClassDescriptions.put(className, result);
}
return result;
}
}