package com.revolsys.maven; import java.io.File; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.net.URLStreamHandler; import java.net.URLStreamHandlerFactory; import java.util.Collection; import java.util.Collections; import java.util.Map; import java.util.Set; import java.util.WeakHashMap; import com.revolsys.collection.map.MapEx; import com.revolsys.logging.Logs; import com.revolsys.record.io.format.xml.Xml; import com.revolsys.spring.resource.DefaultResourceLoader; import com.revolsys.spring.resource.FileSystemResource; import com.revolsys.spring.resource.Resource; import com.revolsys.util.Pair; import com.revolsys.util.Property; import com.revolsys.util.Strings; public class MavenRepository implements URLStreamHandlerFactory { public static String getMavenId(final String groupId, final String artifactId, final String type, final String classifier, final String version, final String scope) { return Strings.toString(":", groupId, artifactId, type, classifier, version, scope); } public static String getPath(final String groupId, final String artifactId, final String version, final String type, final String classifier, final String specificVersion, final String algorithm) { final StringBuilder path = new StringBuilder(); path.append('/'); path.append(groupId.replace('.', '/')); path.append('/'); path.append(artifactId); path.append('/'); path.append(version); path.append('/'); path.append(artifactId); path.append('-'); path.append(specificVersion); if (Property.hasValue(classifier)) { path.append('-'); path.append(classifier); } path.append('.'); path.append(type); if (Property.hasValue(algorithm)) { path.append('.'); path.append(algorithm); } return path.toString(); } private Resource root; private final URLStreamHandler urlHandler = new MavenUrlStreamHandler(this); private final Map<String, MavenPom> pomCache = new WeakHashMap<>(); public MavenRepository() { this(null); } /** * Root resource must end in a / * * @param root */ public MavenRepository(final Resource root) { setRoot(root); } @Override public URLStreamHandler createURLStreamHandler(final String protocol) { return this.urlHandler; } public MapEx getMavenMetadata(final String groupId, final String artifactId, final String version) { final String mavenMetadataPath = "/" + Strings.toString("/", groupId.replace('.', '/'), artifactId, version, "maven-metadata.xml"); final Resource resource = this.root; final Resource mavenMetadataResource = resource.newChildResource(mavenMetadataPath); if (mavenMetadataResource.exists()) { try { return Xml.toMap(mavenMetadataResource); } catch (final RuntimeException e) { Logs.error(this, "Error loading maven resource" + mavenMetadataResource, e); if (mavenMetadataResource instanceof FileSystemResource) { try { final File file = mavenMetadataResource.getFile(); if (file.delete()) { Logs.error(this, "Deleting corrupt maven resource" + mavenMetadataResource, e); } } catch (final Throwable ioe) { } } throw e; } } else { return MapEx.EMPTY; } } public String getPath(final String id) { final String[] parts = id.split(":"); final String groupId = parts[0]; final String artifactId = parts[1]; final String type = parts[2]; String version; String classifier = null; if (parts.length == 5) { version = parts[3]; } else { classifier = parts[3]; version = parts[4]; } return getPath(groupId, artifactId, version, type, classifier, version, null); } public MavenPom getPom(final Resource resource) { if (resource.exists()) { final MapEx map = Xml.toMap(resource); final MavenPom mavenPom = new MavenPom(this, map); final String groupArtifactVersion = mavenPom.getGroupArtifactVersion(); this.pomCache.put(groupArtifactVersion, mavenPom); return mavenPom; } else { throw new IllegalArgumentException("Pom does not exist for " + resource); } } public MavenPom getPom(final String groupArtifactVersion) { MavenPom pom = this.pomCache.get(groupArtifactVersion); if (pom == null) { final String[] parts = groupArtifactVersion.split(":"); if (parts.length < 3) { throw new IllegalArgumentException(groupArtifactVersion + " is not a valid Maven identifier. Should be in the format: <groupId>:<artifactId>:<version>."); } final String groupId = parts[0]; final String artifactId = parts[1]; String version; if (parts.length == 5) { version = parts[3]; } else if (parts.length == 6) { version = parts[4]; } else { version = parts[2]; } pom = getPom(groupId, artifactId, version); if (pom == null) { Logs.error(this, "Maven pom not found for " + groupArtifactVersion); } } return pom; } public MavenPom getPom(final String groupId, final String artifactId, final String version) { final String groupArtifactVersion = groupId + ":" + artifactId + ":" + version; MavenPom pom = this.pomCache.get(groupArtifactVersion); if (pom == null) { final Resource resource = getResource(groupId, artifactId, "pom", version); if (resource.exists()) { final MapEx map = Xml.toMap(resource); pom = new MavenPom(this, map); this.pomCache.put(groupArtifactVersion, pom); } else { return null; } } return pom; } public Resource getResource(String id) { id = id.replace('/', ':'); final String[] parts = id.split(":"); final String groupId = parts[0]; final String artifactId = parts[1]; final String type = parts[2]; if (parts.length < 6) { final String version = parts[3]; return getResource(groupId, artifactId, type, version); } else { final String classifier = parts[3]; final String version = parts[4]; return getResource(groupId, artifactId, type, classifier, version); } } public Resource getResource(final String groupId, final String artifactId, final String type, final String version) { return getResource(groupId, artifactId, type, null, version); } public Resource getResource(final String groupId, final String artifactId, final String type, final String classifier, final String version) { return getResource(groupId, artifactId, type, classifier, version, null); } public Resource getResource(final String groupId, final String artifactId, final String type, final String classifier, final String version, final String algorithm) { final String specificVersion = getSpecificVersion(groupId, artifactId, version, type, classifier, algorithm); return getResource(groupId, artifactId, version, type, classifier, specificVersion, algorithm); } private Resource getResource(final String groupId, final String artifactId, final String version, final String type, final String classifier, final String specificVersion, final String algorithm) { final String path = getPath(groupId, artifactId, version, type, classifier, specificVersion, algorithm); final Resource artifactResource = this.root.newChildResource(path); if (!artifactResource.exists()) { return handleMissingResource(artifactResource, groupId, artifactId, specificVersion, type, classifier, specificVersion, algorithm); } return artifactResource; } public Resource getRoot() { return this.root; } public String getSha1(final String groupId, final String artifactId, final String version, final String type, final String classifier, final String specificVersion, final String algorithm) { if (!Property.hasValue(algorithm)) { final Resource digestResource = getResource(groupId, artifactId, version, type, classifier, specificVersion, "sha1"); if (digestResource.exists()) { String digestContents = null; try { digestContents = digestResource.contentsAsString(); return digestContents.trim().substring(0, 40); } catch (final Throwable e) { if (digestContents == null) { Logs.error(this, "Error downloading: " + digestResource, e); } else { Logs.error(this, "Error in SHA-1 checksum " + digestContents + " for " + digestResource, e); } } } } return null; } protected Pair<Long, String> getSnapshotVersion(final String groupId, final String artifactId, final String version, final String type, final String classifier, final String algorithm) { final MapEx mavenMetadata = getMavenMetadata(groupId, artifactId, version); if (mavenMetadata == MapEx.EMPTY) { return null; } else { String specificVersion = version; long time = 0; final MapEx versioning = mavenMetadata.getValue("versioning"); if (versioning != null) { final MapEx snapshot = versioning.getValue("snapshot"); time = versioning.getLong("lastUpdated", 0); if (snapshot != null) { final String timestamp = snapshot.getString("timestamp"); if (Property.hasValue(timestamp)) { final String buildNumber = snapshot.getString("buildNumber"); final StringBuilder specificVersionBuilder = new StringBuilder( version.substring(0, version.length() - 8)); specificVersionBuilder.append(timestamp); specificVersionBuilder.append('-'); if (Property.hasValue(buildNumber)) { specificVersionBuilder.append(buildNumber); } else { specificVersionBuilder.append('1'); } specificVersion = specificVersionBuilder.toString(); } } } final Pair<Long, String> timeAndVersion = new Pair<>(time, specificVersion); return timeAndVersion; } } private String getSpecificVersion(final String groupId, final String artifactId, final String version, final String type, final String classifier, final String algorithm) { if (version != null && version.endsWith("-SNAPSHOT")) { final Pair<Long, String> timeAndVersion = getSnapshotVersion(groupId, artifactId, version, type, classifier, algorithm); if (timeAndVersion != null) { return timeAndVersion.getValue2(); } } return version; } public URL getURL(final String id) { final String[] parts = id.split(":"); parts[2] = "jar"; final String path = Strings.toString("/", parts); try { return new URL("mvn", "", -1, path, this.urlHandler); } catch (final MalformedURLException e) { throw new IllegalArgumentException("Not a valid maven identifier " + id, e); } } protected Resource handleMissingResource(final Resource resource, final String groupId, final String artifactId, final String version, final String type, final String classifier, final String specificVersion, final String algorithm) { return resource; } public URLClassLoader newClassLoader(final String id) { final Set<String> exclusionIds = Collections.emptySet(); return newClassLoader(id, exclusionIds); } public URLClassLoader newClassLoader(final String mavenId, final Collection<String> exclusionIds) { final MavenPom pom = getPom(mavenId); final Set<String> dependencies = pom.getDependencyIds(exclusionIds); final URL[] urls = new URL[dependencies.size() + 1]; urls[0] = getURL(pom.getMavenId()); int i = 1; for (final String dependencyId : dependencies) { final URL dependencyUrl = getURL(dependencyId); urls[i++] = dependencyUrl; } final ClassLoader parentClassLoader = getClass().getClassLoader(); return new URLClassLoader(urls, parentClassLoader, this); } public void setRoot(final Resource root) { if (root == null) { this.root = new FileSystemResource(System.getProperty("user.home") + "/.m2/repository/"); } else { try { String url = root.getURL().toExternalForm(); url = url.replaceAll("([^(:/{2,3)])//+", "$1/"); if (!url.endsWith("/")) { url += '/'; } this.root = new DefaultResourceLoader().getResource(url); } catch (final Throwable e) { this.root = root; } } } @Override public String toString() { return this.root.toString(); } }