├── .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 |
--------------------------------------------------------------------------------