/*******************************************************************************
* Copyright (c) 2006-2010 eBay Inc. All Rights Reserved.
* Licensed 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
*******************************************************************************/
package org.ebayopensource.turmeric.plugins.maven.resources;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarException;
import java.util.jar.JarFile;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.model.Resource;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.util.StringUtils;
public class ResourceLocator {
public static class Location {
private URI uri;
private File file;
private boolean local;
private boolean live;
/**
* A reference to a resource
*
* @param uri
* the uri to the resource
* @param project
* the maven project to work off of
* @throws IOException
*/
public Location(URI uri, MavenProject project) throws MojoExecutionException {
this.uri = uri;
this.live = "file".equals(uri.getScheme());
if (this.live) {
this.file = new File(uri);
this.local = this.file.getAbsolutePath().startsWith(
project.getBasedir().getAbsolutePath());
} else {
this.local = false;
String filename = FilenameUtils.getName(uri.toASCIIString());
File targetDir = new File(project.getBuild().getDirectory());
this.file = new File(targetDir, filename);
try {
FileUtils.copyURLToFile(uri.toURL(), this.file);
} catch (IOException e) {
throw new MojoExecutionException(
"Unable to copy resource URI [" + uri
+ "] to temp location [" + file + "]", e);
}
}
}
/**
* A reference to a resource
*
* @param file
* the file reference to the resource
* @param project
* the maven project to work off of
*/
public Location(File file, MavenProject project) {
this.uri = file.toURI();
this.live = true;
if (this.live) {
this.file = new File(uri);
this.local = this.file.getAbsolutePath().startsWith(
project.getBasedir().getAbsolutePath());
}
}
@Override
public String toString() {
StringBuilder msg = new StringBuilder();
msg.append("Location: ").append(uri.toString());
msg.append(" [local:").append(local);
msg.append(", live:").append(live);
msg.append(", file:").append(file);
msg.append("]");
return msg.toString();
}
/**
* Get a file reference to the desired resource.
* <p>
* May or may not be the live file.
* <p>
* A requested resource that exist in the project's own tree will be referenced directly.<br>
* {@link #isLocalToProject()} will be true.<br>
* {@link #isLiveFile()} will be true.
* <p>
* A requested resource that exists outside of the project's own tree that can be referenced directly, will be.
* (This is the case for m2eclipse dependency references with "Workspace Resolution" enabled)
* {@link #isLocalToProject()} will be false.<br>
* {@link #isLiveFile()} will be true.
* <p>
* A requested resource found in a project dependency's artifact in the local repository, a copy of that desired
* resource will be copied into the project's own <code>/target/turmeric-maven-plugin-located-resources/</code>
* directory tree before a file reference to this copied resource is returned. {@link #isLocalToProject()} will
* be true.<br>
* {@link #isLiveFile()} will be false.<br>
* {@link #getUri()} will point to where the resource was fetched from.
*
* @return the file from the live location, or from inside of the project target directory.
*/
public File getFile() {
return file;
}
public URI getUri() {
return uri;
}
public boolean isLocalToProject() {
return local;
}
public boolean isLiveFile() {
return live;
}
}
private Log log;
private MavenProject project;
public ResourceLocator(Log log, MavenProject project) {
this.log = log;
this.project = project;
}
/**
* Attempt to find a resource in the project's configuration, and then the classpath.
* <p>
* Search Order:<br>
* <ol>
* <li>Project Defined Resource Directories</li>
* <li>${project.build.outputDirectory}</li>
* <li>Classpath</li>
* </ol>
*
* @param pathref
* the path to the resource desired. In the same format as you would use for
* {@link ClassLoader#getResource(String)}
* @return the Location of the found resource, or null if not found.
* @throws MojoExecutionException
* if unable to process resource loookup
*/
public Location findResource(String pathref) throws MojoExecutionException {
if (StringUtils.isBlank(pathref)) {
log.warn("Unable to lookup resource for null pathref: " + pathref);
return null;
}
Location location = null;
location = lookInProject(pathref);
if (location != null) {
return location;
}
location = lookInOutputDirectory(pathref);
if (location != null) {
return location;
}
location = lookInProjectCompileArtifacts(pathref);
if (location != null) {
return location;
}
location = lookInClasspath(pathref);
if (location != null) {
return location;
}
log.debug("NOT FOUND: resource: " + pathref);
return null;
}
private Location lookInProjectCompileArtifacts(String pathref) throws MojoExecutionException {
log.debug("Looking for resource in project compile artifacts: " + pathref);
@SuppressWarnings("unchecked")
List<Artifact> arts = project.getCompileArtifacts();
if(arts == null) {
log.debug("NOT FOUND in project compile artifacts: <no artifacts declared to look in>");
return null;
}
if(log.isDebugEnabled()) {
StringBuilder dbg = new StringBuilder();
dbg.append("Project Compile Artifacts:");
for(Artifact arti: arts) {
dbg.append("\n ");
dbg.append(arti.getFile());
}
log.debug(dbg.toString());
}
URI uri = null;
for(Artifact arti: arts) {
uri = findFileResource(arti.getFile(), pathref);
if(uri != null) {
return new Location(uri, project);
}
}
log.debug("NOT FOUND in project compile artifacts");
return null;
}
private URI findFileResource(File path, String pathref) {
if(path.isFile()) {
// Assume its an archive.
String extn = FilenameUtils.getExtension(path.getName());
if(StringUtils.isBlank(extn)) {
log.debug("No extension found for file: " + path);
return null;
}
extn = extn.toLowerCase();
if("jar".equals(extn)) {
log.debug("looking inside of jar file: " + path);
JarFile jar = null;
try {
jar = new JarFile(path);
JarEntry entry = jar.getJarEntry(pathref);
if(entry == null) {
log.debug("JarEntry not found: " + pathref);
return null;
}
String uripath = String.format("jar:%s!/%s", path.toURI(), entry.getName());
try {
return new URI(uripath);
} catch (URISyntaxException e) {
log.debug("Unable to create URI reference: " + path, e);
return null;
}
} catch (JarException e) {
log.debug("Unable to open archive: " + path, e);
return null;
} catch (IOException e) {
log.debug("Unable to open archive: " + path, e);
return null;
} finally {
if (jar != null) {
try {
jar.close();
} catch (IOException e) {
log.debug("Unable to close jar: " + path, e);
}
}
}
}
log.debug("Unsupported archive file: " + path);
return null;
}
if (path.isDirectory()) {
File testFile = new File(path, FilenameUtils.separatorsToSystem(pathref));
if (testFile.exists() && testFile.isFile()) {
return testFile.toURI();
}
log.debug("Not found in directory: " + testFile);
return null;
}
log.debug("Unable to handle non-file, non-directory " + File.class.getName() + " objects: " + path);
return null;
}
private Location lookInClasspath(String pathref) throws MojoExecutionException {
log.debug("Looking for resource in project classpath: " + pathref);
if (log.isDebugEnabled()) {
StringBuilder dbg = new StringBuilder();
dbg.append("System.getProperty('java.class.path')=");
String rawcp = System.getProperty("java.class.path");
for (String cp : rawcp.split(File.pathSeparator)) {
dbg.append("\n ").append(cp);
}
log.debug(dbg.toString());
ClassLoader cl = this.getClass().getClassLoader();
if (cl instanceof URLClassLoader) {
dbg = new StringBuilder();
dbg.append("URLClassLoader(");
dbg.append(cl.getClass().getName());
dbg.append("):");
URLClassLoader ucl = (URLClassLoader) cl;
for (URL url : ucl.getURLs()) {
dbg.append("\n ").append(url.toExternalForm());
}
log.debug(dbg.toString());
}
}
List<URL> resources = new ArrayList<URL>();
try {
Enumeration<URL> enurls = ClassLoader.getSystemResources(pathref);
if (enurls != null) {
while (enurls.hasMoreElements()) {
URL url = enurls.nextElement();
if(!resources.contains(url)) {
resources.add(url);
}
}
}
addFoundResource(resources, pathref, Thread.currentThread().getContextClassLoader());
addFoundResource(resources, pathref, this.getClass().getClassLoader());
if (resources.isEmpty()) {
log.debug("NOT FOUND in project classpath");
return null;
}
if (resources.size() > 1) {
log.warn("Found more than 1 classpath entry for: " + pathref);
for (URL url : resources) {
log.warn(" + " + url.toExternalForm());
}
}
URI uri = resources.get(0).toURI();
log.debug("FOUND resource in project classpath: " + uri);
return new Location(uri, project);
} catch (IOException e) {
throw new MojoExecutionException("Unable to process resource lookup in project classpath: " + e.getMessage(), e);
} catch (URISyntaxException e) {
throw new MojoExecutionException("Unable to process resource lookup in project classpath: " + e.getMessage(), e);
}
}
private void addFoundResource(List<URL> resources, String pathref, ClassLoader cl) throws IOException {
log.debug("Looking in ClassLoader: " + cl);
Enumeration<URL> enurls = cl.getResources(pathref);
if (enurls != null) {
while (enurls.hasMoreElements()) {
URL url = enurls.nextElement();
if(!resources.contains(url)) {
resources.add(url);
}
}
}
}
private Location lookInOutputDirectory(String pathref) {
File outputDir = new File(project.getBuild().getOutputDirectory());
log.debug("Looking for resource in project output directory ["
+ outputDir + "]: " + pathref);
File testFile = new File(outputDir, toOS(pathref));
if (testFile.exists()) {
log.debug("FOUND resource in project output directory: " + testFile);
return new Location(testFile, project);
}
log.debug("NOT FOUND in project output directory");
return null;
}
private Location lookInProject(String pathref) {
List<?> resources = project.getBuild().getResources();
log.debug("Looking for resource in [" + resources.size()
+ "] directories: " + pathref);
Iterator<?> iter = resources.iterator();
while (iter.hasNext()) {
Resource resource = (Resource) iter.next();
String resDir = resource.getDirectory();
if (StringUtils.isBlank(resDir)) {
log.warn("Blank resources directory: " + resource);
continue;
}
File dir = new File(resDir);
log.debug("Testing for resource in dir: " + dir);
File possiblePath = new File(dir, pathref);
if (possiblePath.exists()) {
log.debug("FOUND resource: " + possiblePath);
return new Location(possiblePath, project);
}
}
log.debug("NOT FOUND in project resource directories");
return null;
}
private String toOS(String path) {
return FilenameUtils.separatorsToSystem(path);
}
}