├── .gitignore ├── README.md ├── pom.xml ├── samples └── joda-time-in-jruby.rb └── src ├── main └── java │ └── com │ └── bigfatgun │ └── MavenClassLoader.java └── test └── java └── com └── bigfatgun └── MavenClassLoaderTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | .m2 2 | target 3 | *.iml 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | maven-classloader 2 | === 3 | 4 | Load Any Class In The (Mavenized) World 5 | --- 6 | 7 | Given a [Maven][1] GAV, the Maven Classloader will resolve and download all dependencies from local and remote maven repositories. 8 | 9 | This library utilizes [Sonatype Aether][2], the library used by Maven to deal with repositories. Aether does all of the heavy lifting, this library aims to be a lightweight shim on top of it to help reduce the friction for a majority of potential uses. 10 | 11 | [1]: http://maven.apache.org/ "Apache Maven" 12 | [2]: http://aether.sonatype.org/ "Sonatype Aether Product Page" 13 | 14 | ### Getting Started (Maven) 15 | 16 | Until this is put into a maven repo, download source and build a `.jar` with Maven: `mvn clean install` 17 | 18 | Add the following to your `pom.xml`: 19 | 20 | 21 | com.bigfatgun 22 | maven-classloader 23 | 1.0-SNAPSHOT 24 | 25 | 26 | Create a ClassLoader: 27 | 28 | ClassLoader classLoader = MavenClassloader.forGAV("junit:junit:4.8.1"); 29 | 30 | You're done! Assuming you wanted the classloader for a reason, such as loading a class, you just use normal reflection: 31 | 32 | Class junitAssertClass = classLoader.loadClass("org.junit.Assert"); 33 | 34 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | maven-classloader 6 | com.bigfatgun 7 | maven-classloader 8 | 1.0-SNAPSHOT 9 | 10 | 1.13.1 11 | 3.0.4 12 | 2.2 13 | 14 | 15 | 16 | com.google.guava 17 | guava 18 | 11.0.2 19 | 20 | 21 | org.sonatype.aether 22 | aether-api 23 | ${aetherVersion} 24 | 25 | 26 | org.sonatype.aether 27 | aether-util 28 | ${aetherVersion} 29 | 30 | 31 | org.sonatype.aether 32 | aether-impl 33 | ${aetherVersion} 34 | 35 | 36 | com.google.collections 37 | google-collections 38 | 39 | 40 | 41 | 42 | org.sonatype.aether 43 | aether-connector-wagon 44 | ${aetherVersion} 45 | 46 | 47 | com.google.collections 48 | google-collections 49 | 50 | 51 | junit 52 | junit 53 | 54 | 55 | 56 | 57 | org.apache.maven 58 | maven-aether-provider 59 | ${mavenVersion} 60 | 61 | 62 | org.apache.maven.wagon 63 | wagon-file 64 | ${wagonVersion} 65 | 66 | 67 | org.apache.maven.wagon 68 | wagon-http-lightweight 69 | ${wagonVersion} 70 | 71 | 72 | commons-logging 73 | commons-logging 74 | 75 | 76 | nekohtml 77 | nekohtml 78 | 79 | 80 | nekohtml 81 | xercesMinimal 82 | 83 | 84 | 85 | 86 | junit 87 | junit 88 | 4.10 89 | test 90 | 91 | 92 | 93 | 94 | 95 | org.apache.maven.plugins 96 | maven-release-plugin 97 | 2.1 98 | 99 | 100 | org.apache.maven.plugins 101 | maven-shade-plugin 102 | 1.4 103 | 104 | 105 | package 106 | 107 | shade 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | scm:git:git@github.com:year2013/maven-classloader.git 125 | scm:git:git@github.com:year2013/maven-classloader.git 126 | scm:git:git@github.com:year2013/maven-classloader.git 127 | 128 | 129 | -------------------------------------------------------------------------------- /samples/joda-time-in-jruby.rb: -------------------------------------------------------------------------------- 1 | include Java 2 | require "../target/maven-classloader-1.0-SNAPSHOT.jar" 3 | include_class Java::com.bigfatgun.MavenClassLoader 4 | 5 | @cl = MavenClassLoader.forGAV("joda-time:joda-time:1.6.2"); 6 | 7 | def dateTime() 8 | newInstanceOf("org.joda.time.DateTime") 9 | end 10 | 11 | def newInstanceOf(clsName) 12 | @cl.loadClass(clsName).newInstance() 13 | end 14 | 15 | # ruby date 16 | ruby_now = Time.now 17 | # joda date 18 | joda_now = dateTime() 19 | 20 | puts "Ruby says it is #{ruby_now} and joda-time says it is #{joda_now}" 21 | 22 | # ruby is duck-typed! 23 | hour_of_day = joda_now.hourOfDay(); 24 | -------------------------------------------------------------------------------- /src/main/java/com/bigfatgun/MavenClassLoader.java: -------------------------------------------------------------------------------- 1 | package com.bigfatgun; 2 | 3 | import com.google.common.base.Function; 4 | import com.google.common.base.Preconditions; 5 | import com.google.common.base.Throwables; 6 | import com.google.common.collect.ImmutableList; 7 | import com.google.common.collect.Lists; 8 | import org.apache.maven.repository.internal.MavenRepositorySystemSession; 9 | import org.codehaus.plexus.DefaultPlexusContainer; 10 | import org.codehaus.plexus.PlexusContainerException; 11 | import org.codehaus.plexus.component.repository.exception.ComponentLookupException; 12 | import org.sonatype.aether.RepositorySystem; 13 | import org.sonatype.aether.RepositorySystemSession; 14 | import org.sonatype.aether.artifact.Artifact; 15 | import org.sonatype.aether.collection.CollectRequest; 16 | import org.sonatype.aether.collection.DependencyCollectionException; 17 | import org.sonatype.aether.graph.Dependency; 18 | import org.sonatype.aether.graph.DependencyNode; 19 | import org.sonatype.aether.repository.LocalRepository; 20 | import org.sonatype.aether.repository.RemoteRepository; 21 | import org.sonatype.aether.resolution.ArtifactResolutionException; 22 | import org.sonatype.aether.util.artifact.DefaultArtifact; 23 | import org.sonatype.aether.util.graph.PreorderNodeListGenerator; 24 | 25 | import java.io.File; 26 | import java.net.MalformedURLException; 27 | import java.net.URL; 28 | import java.net.URLClassLoader; 29 | import java.util.List; 30 | 31 | public final class MavenClassLoader { 32 | 33 | public static class ClassLoaderBuilder { 34 | 35 | private static final String COMPILE_SCOPE = "compile"; 36 | private static final ClassLoader SHARE_NOTHING = null; 37 | 38 | private final ImmutableList repositories; 39 | private final File localRepositoryDirectory; 40 | 41 | private ClassLoaderBuilder(RemoteRepository... repositories) { 42 | Preconditions.checkNotNull(repositories); 43 | Preconditions.checkArgument(repositories.length > 0, "Must specify at least one remote repository."); 44 | 45 | this.repositories = ImmutableList.copyOf(repositories); 46 | this.localRepositoryDirectory = new File(".m2/repository"); 47 | } 48 | 49 | public URLClassLoader forGAV(String gav) { 50 | try { 51 | CollectRequest collectRequest = createCollectRequestForGAV(gav); 52 | List artifacts = collectDependenciesIntoArtifacts(collectRequest); 53 | List urls = Lists.newLinkedList(); 54 | for (Artifact artifact : artifacts) { 55 | urls.add(artifact.getFile().toURI().toURL()); 56 | } 57 | 58 | return new URLClassLoader(urls.toArray(new URL[urls.size()]), SHARE_NOTHING); 59 | } catch (Exception e) { 60 | throw Throwables.propagate(e); 61 | } 62 | } 63 | 64 | private CollectRequest createCollectRequestForGAV(String gav) { 65 | Dependency dependency = new Dependency(new DefaultArtifact(gav), COMPILE_SCOPE); 66 | 67 | CollectRequest collectRequest = new CollectRequest(); 68 | collectRequest.setRoot(dependency); 69 | for (RemoteRepository repository : repositories) { 70 | collectRequest.addRepository(repository); 71 | } 72 | 73 | return collectRequest; 74 | } 75 | 76 | private List collectDependenciesIntoArtifacts(CollectRequest collectRequest) 77 | throws PlexusContainerException, ComponentLookupException, DependencyCollectionException, ArtifactResolutionException { 78 | 79 | RepositorySystem repositorySystem = newRepositorySystem(); 80 | RepositorySystemSession session = newSession(repositorySystem); 81 | DependencyNode node = repositorySystem.collectDependencies(session, collectRequest).getRoot(); 82 | 83 | repositorySystem.resolveDependencies(session, node, null); 84 | 85 | PreorderNodeListGenerator nlg = new PreorderNodeListGenerator(); 86 | node.accept(nlg); 87 | 88 | return nlg.getArtifacts(false); 89 | } 90 | 91 | private RepositorySystem newRepositorySystem() throws PlexusContainerException, ComponentLookupException { 92 | return new DefaultPlexusContainer().lookup(RepositorySystem.class); 93 | } 94 | 95 | private RepositorySystemSession newSession(RepositorySystem system) { 96 | MavenRepositorySystemSession session = new MavenRepositorySystemSession(); 97 | 98 | LocalRepository localRepo = new LocalRepository(localRepositoryDirectory); 99 | session.setLocalRepositoryManager(system.newLocalRepositoryManager(localRepo)); 100 | 101 | return session; 102 | } 103 | } 104 | 105 | /** 106 | * Creates a classloader that will resolve artifacts against the default "central" repository. Throws 107 | * {@link IllegalArgumentException} if the GAV is invalid, {@link NullPointerException} if the GAV is null. 108 | * 109 | * @param gav artifact group:artifact:version, i.e. joda-time:joda-time:1.6.2 110 | * @return a classloader that can be used to load classes from the given artifact 111 | */ 112 | public static URLClassLoader forGAV(String gav) { 113 | return usingCentralRepo().forGAV(Preconditions.checkNotNull(gav)); 114 | } 115 | 116 | public static ClassLoaderBuilder usingCentralRepo() { 117 | RemoteRepository central = new RemoteRepository("central", "default", "http://repo1.maven.org/maven2/"); 118 | return new ClassLoaderBuilder(central); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/test/java/com/bigfatgun/MavenClassLoaderTest.java: -------------------------------------------------------------------------------- 1 | package com.bigfatgun; 2 | 3 | import com.google.common.collect.Multiset; 4 | import org.junit.Test; 5 | 6 | import java.lang.reflect.InvocationTargetException; 7 | import java.lang.reflect.Method; 8 | 9 | import static org.junit.Assert.assertNotNull; 10 | 11 | public class MavenClassLoaderTest { 12 | 13 | @Test 14 | public void jodaTime() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException { 15 | String gav = "joda-time:joda-time:[1.6,)"; 16 | String className = "org.joda.time.chrono.BuddhistChronology"; 17 | ClassLoader loader = MavenClassLoader.forGAV(gav); 18 | assertNotNull(loader); 19 | Class buddhistChronology = loader.loadClass(className); 20 | assertNotNull(buddhistChronology); 21 | Method factoryMethod = buddhistChronology.getMethod("getInstance"); 22 | assertNotNull(factoryMethod.invoke(null)); 23 | } 24 | 25 | @Test(expected = ClassNotFoundException.class) 26 | public void jodaTimeClassLoaderDoesNotHaveMultiset() throws ClassNotFoundException { 27 | String gav = "joda-time:joda-time:[1.6,)"; 28 | ClassLoader loader = MavenClassLoader.forGAV(gav); 29 | assertNotNull(loader); 30 | loader.loadClass(Multiset.class.getName()); 31 | } 32 | 33 | @Test(expected = IllegalArgumentException.class) 34 | public void classLoaderConstructionFailsOnBogusGAV() { 35 | MavenClassLoader.forGAV("this isn't going to work!"); 36 | } 37 | } 38 | --------------------------------------------------------------------------------