package aQute.bnd.maven.indexer.plugin;
import static aQute.bnd.maven.lib.resolve.LocalURLs.ALLOWED;
import static aQute.bnd.maven.lib.resolve.LocalURLs.REQUIRED;
import static org.apache.maven.plugins.annotations.LifecyclePhase.PACKAGE;
import static org.apache.maven.plugins.annotations.ResolutionScope.TEST;
import java.io.File;
import java.io.FileNotFoundException;
import java.net.URI;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.maven.RepositoryUtils;
import org.apache.maven.artifact.DefaultArtifact;
import org.apache.maven.artifact.handler.DefaultArtifactHandler;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.repository.metadata.io.MetadataReader;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.MavenProjectHelper;
import org.apache.maven.project.ProjectDependenciesResolver;
import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.artifact.ArtifactProperties;
import org.eclipse.aether.resolution.ArtifactResult;
import org.osgi.service.repository.ContentNamespace;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import aQute.bnd.maven.lib.resolve.DependencyResolver;
import aQute.bnd.maven.lib.resolve.LocalURLs;
import aQute.bnd.maven.lib.resolve.RemotePostProcessor;
import aQute.bnd.osgi.repository.ResourcesRepository;
import aQute.bnd.osgi.repository.XMLResourceGenerator;
import aQute.bnd.osgi.resource.CapabilityBuilder;
import aQute.bnd.osgi.resource.ResourceBuilder;
import aQute.lib.io.IO;
import aQute.libg.cryptography.SHA256;
/**
* Exports project dependencies to OSGi R5 index format.
*/
@Mojo(name = "index", defaultPhase = PACKAGE, requiresDependencyResolution = TEST)
public class IndexerMojo extends AbstractMojo {
private static final Logger logger = LoggerFactory.getLogger(IndexerMojo.class);
@Parameter(defaultValue = "${project}", readonly = true, required = true)
private MavenProject project;
@Parameter(defaultValue = "${repositorySystemSession}", readonly = true, required = true)
private RepositorySystemSession session;
@Parameter(property = "bnd.indexer.output.file", defaultValue = "${project.build.directory}/index.xml")
private File outputFile;
@Parameter(property = "bnd.indexer.localURLs", defaultValue = "FORBIDDEN")
private LocalURLs localURLs;
@Parameter(property = "bnd.indexer.includeTransitive", defaultValue = "true")
private boolean includeTransitive;
@Parameter(property = "bnd.indexer.includeJar", defaultValue = "false")
private boolean includeJar;
@Parameter(property = "bnd.indexer.add.mvn.urls", defaultValue = "false")
private boolean addMvnURLs;
@Parameter(property = "bnd.indexer.scopes", readonly = true)
private List<String> scopes;
@Parameter(property = "bnd.indexer.include.gzip", defaultValue = "true")
private boolean includeGzip;
@Parameter(property = "bnd.indexer.skip", defaultValue = "false")
private boolean skip;
@Component
private RepositorySystem system;
@Component
private ProjectDependenciesResolver resolver;
@Component
private MetadataReader metadataReader;
@Component
private MavenProjectHelper projectHelper;
private boolean fail;
public void execute() throws MojoExecutionException, MojoFailureException {
if ( skip ) {
logger.debug("skip project as configured");
return;
}
if (scopes == null || scopes.isEmpty()) {
scopes = Arrays.asList("compile", "runtime");
}
logger.debug("Indexing dependencies with scopes: {}", scopes);
logger.debug("Including Transitive dependencies: {}", includeTransitive);
logger.debug("Local file URLs permitted: {}", localURLs);
logger.debug("Adding mvn: URLs as alternative content: {}", addMvnURLs);
DependencyResolver dependencyResolver = new DependencyResolver(project, session, resolver, system, scopes,
includeTransitive, new RemotePostProcessor(session, system, metadataReader, localURLs));
Map<File,ArtifactResult> dependencies = dependencyResolver.resolve();
Map<String,ArtifactRepository> repositories = new HashMap<>();
for (ArtifactRepository artifactRepository : project.getRemoteArtifactRepositories()) {
logger.debug("Located an artifact repository {}", artifactRepository.getId());
repositories.put(artifactRepository.getId(), artifactRepository);
}
ArtifactRepository deploymentRepo = project.getDistributionManagementArtifactRepository();
if (deploymentRepo != null) {
logger.debug("Located a deployment repository {}", deploymentRepo.getId());
if (repositories.get(deploymentRepo.getId()) == null) {
repositories.put(deploymentRepo.getId(), deploymentRepo);
} else {
logger.info(
"The configured deployment repository {} has the same id as one of the remote artifact repositories. It is assumed that these repositories are the same.",
deploymentRepo.getId());
}
}
RepositoryURLResolver repositoryURLResolver = new RepositoryURLResolver(repositories);
MavenURLResolver mavenURLResolver = new MavenURLResolver();
ResourcesRepository resourcesRepository = new ResourcesRepository();
XMLResourceGenerator xmlResourceGenerator = new XMLResourceGenerator();
logger.debug("Indexing artifacts: {}", dependencies.keySet());
try {
IO.mkdirs(outputFile.getParentFile());
for (Entry<File,ArtifactResult> entry : dependencies.entrySet()) {
File file = entry.getKey();
ResourceBuilder resourceBuilder = new ResourceBuilder();
resourceBuilder.addFile(entry.getKey(), repositoryURLResolver.resolver(file, entry.getValue()));
if (addMvnURLs) {
CapabilityBuilder c = new CapabilityBuilder(ContentNamespace.CONTENT_NAMESPACE);
c.addAttribute(ContentNamespace.CONTENT_NAMESPACE, SHA256.digest(file).asHex());
c.addAttribute(ContentNamespace.CAPABILITY_URL_ATTRIBUTE,
mavenURLResolver.resolver(file, entry.getValue()));
c.addAttribute(ContentNamespace.CAPABILITY_SIZE_ATTRIBUTE, file.length());
c.addAttribute(ContentNamespace.CAPABILITY_MIME_ATTRIBUTE, MavenURLResolver.MIME);
resourceBuilder.addCapability(c);
}
resourcesRepository.add(resourceBuilder.build());
}
if (includeJar && project.getPackaging().equals("jar")) {
File current = new File(project.getBuild().getDirectory(), project.getBuild().getFinalName() + ".jar");
if (current.exists()) {
ResourceBuilder resourceBuilder = new ResourceBuilder();
resourceBuilder.addFile(current, current.toURI());
resourcesRepository.add(resourceBuilder.build());
}
}
xmlResourceGenerator.repository(resourcesRepository).save(outputFile);
} catch (Exception e) {
throw new MojoExecutionException(e.getMessage(), e);
}
if (fail) {
throw new MojoExecutionException("One or more URI lookups failed");
}
attach(outputFile, "osgi-index", "xml");
if (includeGzip) {
File gzipOutputFile = new File(outputFile.getPath() + ".gz");
try {
xmlResourceGenerator.save(gzipOutputFile);
} catch (Exception e) {
throw new MojoExecutionException("Unable to create the gzipped output file");
}
attach(gzipOutputFile, "osgi-index", "xml.gz");
}
}
private void attach(File file, String type, String extension) {
DefaultArtifactHandler handler = new DefaultArtifactHandler(type);
handler.setExtension(extension);
DefaultArtifact artifact = new DefaultArtifact(project.getGroupId(), project.getArtifactId(),
project.getVersion(), null, type, null, handler);
artifact.setFile(file);
project.addAttachedArtifact(artifact);
}
class MavenURLResolver {
public static final String MIME = "application/zip";
public URI resolver(File file, ArtifactResult artifactResult) throws Exception {
try {
Artifact artifact = artifactResult.getArtifact();
StringBuilder sb = new StringBuilder("mvn://");
sb.append(artifact.getGroupId()).append("/").append(artifact.getArtifactId()).append("/");
if (artifact.getVersion() != null) {
sb.append(artifact.getVersion());
}
sb.append("/");
String type = artifact.getProperty(ArtifactProperties.TYPE, artifact.getExtension());
if (type != null) {
sb.append(type);
}
sb.append("/");
if (artifact.getClassifier() != null) {
sb.append(artifact.getClassifier());
}
return URI.create(sb.toString()).normalize();
} catch (Exception e) {
fail = true;
logger.error("Failed to determine the artifact URI", e);
throw e;
}
}
}
class RepositoryURLResolver {
private final Map<String,ArtifactRepository> repositories;
public RepositoryURLResolver(Map<String,ArtifactRepository> repositories) {
this.repositories = repositories;
}
public URI resolver(File file, ArtifactResult artifactResult) throws Exception {
try {
if (localURLs == REQUIRED) {
return file.toURI();
}
Artifact artifact = artifactResult.getArtifact();
ArtifactRepository repo = repositories.get(artifactResult.getRepository().getId());
if (repo == null) {
if (localURLs == ALLOWED) {
logger.info(
"The Artifact {} could not be found in any repository, returning the local location",
artifact);
return file.toURI();
}
throw new FileNotFoundException("The repository " + artifactResult.getRepository().getId()
+ " is not known to this resolver");
}
String baseUrl = repo.getUrl();
if (baseUrl.startsWith("file:")) {
// File URLs on Windows are nasty, so send them via a file
baseUrl = new File(baseUrl.substring(5)).toURI().normalize().toString();
}
// The base URL must always point to a directory
if (!baseUrl.endsWith("/")) {
baseUrl = baseUrl + "/";
}
String artifactPath = repo.getLayout().pathOf(RepositoryUtils.toArtifact(artifact));
// The artifact path should never be absolute, it is always
// relative to the repo URL
while (artifactPath.startsWith("/")) {
artifactPath = artifactPath.substring(1);
}
// As we have sorted the trailing and leading / characters
// resolve should do the rest!
return URI.create(baseUrl).resolve(artifactPath).normalize();
} catch (Exception e) {
fail = true;
logger.error("Failed to determine the artifact URI", e);
throw e;
}
}
}
}