blob: c268edcb221c7173fe24d1fa59d4049b271328b1 [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.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.IOUtil;
import com.thoughtworks.qdox.JavaDocBuilder;
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 javaClassDescriptions = new HashMap();
/** The component definitions from other bundles hashed by classname. */
protected final Map componentDescriptions = new HashMap();
/** The maven project. */
protected final MavenProject project;
/**
* Construct a new manager.
* @param log
* @param project
* @throws MojoFailureException
* @throws MojoExecutionException
*/
public JavaClassDescriptorManager(final Log log,
final MavenProject project)
throws MojoFailureException, MojoExecutionException {
this.log = log;
this.project = project;
this.classloader = this.getCompileClassLoader(project);
// get all the class sources through qdox
this.log.debug("Setting up QDox");
JavaDocBuilder builder = new JavaDocBuilder();
builder.getClassLibrary().addClassLoader(this.classloader);
final Iterator i = project.getCompileSourceRoots().iterator();
while ( i.hasNext() ) {
final String tree = (String)i.next();
this.log.debug("Adding source tree " + tree);
builder.addSourceTree(new File(tree));
}
this.sources = builder.getSources();
// and now scan artifacts
final List components = new ArrayList();
final Map resolved = project.getArtifactMap();
final Set artifacts = project.getDependencyArtifacts();
final Iterator it = artifacts.iterator();
while ( it.hasNext() ) {
final Artifact declared = (Artifact) it.next();
this.log.debug("Checking artifact " + declared);
if (Artifact.SCOPE_COMPILE.equals(declared.getScope())
|| Artifact.SCOPE_RUNTIME.equals(declared.getScope())) {
this.log.debug("Resolving artifact " + declared);
final Artifact 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 ) {
components.addAll(this.readServiceComponentDescriptor(artifact, entry).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);
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());
}
}
// now create map with component descriptions
final Iterator cI = components.iterator();
while ( cI.hasNext() ) {
final Component component = (Component) cI.next();
this.componentDescriptions.put(component.getImplementation().getClassame(), component);
}
}
/**
* 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;
}
/**
* Read the service component description.
* @param artifact
* @param entry
* @throws IOException
* @throws MojoExecutionException
*/
protected Components readServiceComponentDescriptor(Artifact artifact, String entry)
throws IOException, MojoExecutionException {
this.log.debug("Reading " + entry + " from " + artifact);
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);
}
protected Components parseServiceComponentDescriptor(Artifact artifact, File file)
throws IOException, MojoExecutionException {
this.log.debug("Parsing " + file);
final Components list = ComponentDescriptorIO.read(file);
return list;
}
protected ClassLoader getCompileClassLoader(MavenProject project)
throws MojoFailureException {
List artifacts = project.getCompileArtifacts();
URL[] path = new URL[artifacts.size() + 1];
int i = 0;
for (Iterator ai=artifacts.iterator(); ai.hasNext(); ) {
Artifact a = (Artifact) ai.next();
try {
path[i++] = a.getFile().toURI().toURL();
} catch (IOException ioe) {
throw new MojoFailureException("Unable to get compile class loader.");
}
}
final String targetDirectory = this.getProject().getBuild().getOutputDirectory();
try {
path[path.length - 1] = new File(targetDirectory).toURI().toURL();
} catch (IOException ioe) {
throw new MojoFailureException("Unable to add target directory to classloader.");
}
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();
try {
descs[i] = new QDoxJavaClassDescription(this.classloader.loadClass(className), this.sources[i], this);
} catch (ClassNotFoundException e) {
throw new MojoExecutionException("Unable to load class " + 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 = (JavaClassDescription) this.javaClassDescriptions.get(className);
if ( result == null ) {
this.log.debug("Searching description for: " + className);
int index = 0;
while ( result == null && index < this.sources.length) {
if ( this.sources[index].getClasses()[0].getFullyQualifiedName().equals(className) ) {
try {
this.log.debug("Found qdox description for: " + className);
result = new QDoxJavaClassDescription(this.classloader.loadClass(className), 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), (Component)this.componentDescriptions.get(className), this);
} catch (ClassNotFoundException e) {
throw new MojoExecutionException("Unable to load class " + className);
}
}
this.javaClassDescriptions.put(className, result);
}
return result;
}
}