/*
* Copyright (c) MuleSoft, Inc. All rights reserved. http://www.mulesoft.com
* The software in this package is published under the terms of the CPAL v1.0
* license, a copy of which has been included with this distribution in the
* LICENSE.txt file.
*/
package org.mule.test.runner.classification;
import static java.util.Collections.emptyList;
import static org.apache.commons.io.FileUtils.toFile;
import static org.mule.runtime.api.util.Preconditions.checkNotNull;
import org.mule.test.runner.api.WorkspaceLocationResolver;
import org.mule.test.runner.maven.MavenModelFactory;
import java.io.File;
import java.net.URL;
import java.nio.file.Path;
import java.util.List;
import java.util.Optional;
import org.apache.maven.model.Model;
import org.apache.maven.model.Plugin;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.repository.WorkspaceReader;
import org.eclipse.aether.repository.WorkspaceRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* {@link WorkspaceReader} that resolves artifacts using the IDE workspace or Maven multi-module reactor.
*
* @since 4.0
*/
public class DefaultWorkspaceReader implements WorkspaceReader {
private static final String WORKSPACE = "workspace";
private static final String MAVEN_SHADE_PLUGIN_ARTIFACT_ID = "maven-shade-plugin";
private static final String ORG_APACHE_MAVEN_PLUGINS_GROUP_ID = "org.apache.maven.plugins";
private static final String REDUCED_POM_XML = "dependency-reduced-pom.xml";
private static final String POM = "pom";
private static final String POM_XML = POM + ".xml";
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final WorkspaceRepository workspaceRepository = new WorkspaceRepository(WORKSPACE);
private final WorkspaceLocationResolver workspaceLocationResolver;
private final List<URL> classPath;
/**
* Creates and instance of the workspace reader for the given classPath and workspaceLocationResolver.
*
* @param classPath {@link URL}s to find the artifact's {@link URL}
* @param workspaceLocationResolver {@link WorkspaceLocationResolver} to retrieve the workspace reference for a given
* {@link Artifact}
*/
public DefaultWorkspaceReader(List<URL> classPath, WorkspaceLocationResolver workspaceLocationResolver) {
checkNotNull(classPath, "classPath cannot be null");
checkNotNull(workspaceLocationResolver, "workspaceLocationResolver cannot be null");
this.classPath = classPath;
this.workspaceLocationResolver = workspaceLocationResolver;
}
/**
* Looks for a matching {@link URL} for a workspace {@link Artifact}. It also supports to look for jars or classes depending if
* the artifacts were packaged or not.
*
* @param artifact to be used in order to find the {@link URL} in list of urls
* @param classPath a list of {@link URL} obtained from the classPath
* @return {@link File} that represents the {@link Artifact} passed or null
*/
public static File findClassPathURL(final Artifact artifact, final File workspaceArtifactPath, final List<URL> classPath) {
final Path moduleFolder = new File(workspaceArtifactPath.getAbsoluteFile(), "target").toPath();
// Fix to handle when running test during an install phase due to maven builds the classPath pointing out to packaged files
// instead of classes folders.
final StringBuilder explodedUrlSuffix = new StringBuilder();
final StringBuilder packagedUrlSuffix = new StringBuilder();
if (isTestArtifact(artifact)) {
explodedUrlSuffix.append("test-classes");
packagedUrlSuffix.append(".*-tests.jar");
} else {
explodedUrlSuffix.append("classes");
packagedUrlSuffix.append("^(?!.*?(?:-tests.jar)).*.jar");
}
final Optional<URL> localFile = classPath.stream().filter(url -> {
Path path = toFile(url).toPath();
if (path.startsWith(moduleFolder)) {
String file = path.getFileName().toFile().getName();
return file.matches(explodedUrlSuffix.toString()) || file.matches(packagedUrlSuffix.toString());
}
return false;
}).findFirst();
if (!localFile.isPresent()) {
return null;
}
return toFile(localFile.get());
}
/**
* Determines whether the specified artifact refers to test classes.
*
* @param artifact The artifact to check, must not be {@code null}.
* @return {@code true} if the artifact refers to test classes, {@code false} otherwise.
*/
public static boolean isTestArtifact(Artifact artifact) {
return ("test-jar".equals(artifact.getProperty("type", "")))
|| ("jar".equals(artifact.getExtension()) && "tests".equals(artifact.getClassifier()));
}
/**
* {@inheritDoc}
*/
@Override
public WorkspaceRepository getRepository() {
return workspaceRepository;
}
@Override
public File findArtifact(Artifact artifact) {
File workspaceArtifactPath = workspaceLocationResolver.resolvePath(artifact.getArtifactId());
if (workspaceArtifactPath == null) {
// Cannot be resolved in workspace so delegate its resolution to the Maven local repository
return null;
}
File artifactFile;
if (artifact.getExtension().equals(POM)) {
artifactFile = findPomFile(artifact, workspaceArtifactPath);
} else {
artifactFile = findClassPathURL(artifact, workspaceArtifactPath, classPath);
}
if (artifactFile != null && artifactFile.exists()) {
return artifactFile.getAbsoluteFile();
}
return null;
}
/**
* Not need to specify the versions here.
*
* @param artifact to look for its versions
* @return an empty {@link List}
*/
@Override
public List<String> findVersions(Artifact artifact) {
return emptyList();
}
/**
* Resolves the location of the {@value #POM_XML} {@link File} taking into account {@value #MAVEN_SHADE_PLUGIN_ARTIFACT_ID}
* plugin.
*
* @param artifact {@link Artifact} to get its {@value #POM_XML}
* @param workspacePath {@link File} referencing the location of the {@link Artifact} in the workspace
* @return {@link File} to the {@value #POM_XML} of the artifact from the workspace path
*/
private File findPomFile(Artifact artifact, File workspacePath) {
Plugin shadeMavenPlugin =
searchForMavenShadePlugin(MavenModelFactory.createMavenProject(new File(workspacePath, POM_XML)));
if (shadeMavenPlugin != null) {
// TODO (gfernandes) MULE-10485 - add support for reading the plugin configuration using Xpp3 Maven API
// MavenXpp3Reader.parsePluginConfiguration(...)
File reducedPom = new File(workspacePath, REDUCED_POM_XML);
if (!reducedPom.exists()) {
throw new IllegalStateException(artifact + " has in its build configure the " + shadeMavenPlugin + " but default "
+ REDUCED_POM_XML
+ " is not present. Run the plugin first.");
}
logger.debug("Using {} for artifact {}", reducedPom, artifact);
return reducedPom;
} else {
return new File(workspacePath, POM_XML);
}
}
/**
* Searches the Maven {@link Model} for the {@url https://maven.apache.org/plugins/maven-shade-plugin/}. maven-shared-plugin
* would have to be configure in the pom directly and not inherited from a parent pom due to it is not supported by this
* implementation.
*
* @param model Maven {@link Model} to look for the Maven Shade Plugin
* @return {@link Plugin} model for Maven Shade Plugin if found.
*/
private Plugin searchForMavenShadePlugin(Model model) {
if (model.getBuild() != null) {
for (Plugin plugin : model.getBuild().getPlugins()) {
if (plugin.getGroupId().equals(ORG_APACHE_MAVEN_PLUGINS_GROUP_ID)
&& plugin.getArtifactId().equals(MAVEN_SHADE_PLUGIN_ARTIFACT_ID)) {
return plugin;
}
}
}
return null;
}
}