Register TransportProtocols using services

Use the Java 6 like services approach to find all supported
TransportProtocols within the CLASSPATH and load them all for use.

This allows users to inject additional protocol implementations simply
by putting their JARs on the application CLASSPATH, provided the
protocol author has written the proper services file.

Change-Id: I7a82d8846e4c4ed012c769f03d4bb2461f1bd148
Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
This commit is contained in:
Shawn O. Pearce 2011-03-07 15:39:03 -08:00
parent 305a8ac45f
commit 565fa6f9b1
2 changed files with 100 additions and 1 deletions

View File

@ -46,17 +46,25 @@
package org.eclipse.jgit.transport;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.net.URISyntaxException;
import java.net.URL;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import java.util.concurrent.CopyOnWriteArrayList;
import org.eclipse.jgit.JGitText;
@ -104,6 +112,85 @@ public enum Operation {
register(TransportHttp.PROTO_FTP);
register(TransportHttp.PROTO_HTTP);
register(TransportGitSsh.PROTO_SSH);
registerByService();
}
private static void registerByService() {
ClassLoader ldr = Thread.currentThread().getContextClassLoader();
if (ldr == null)
ldr = Transport.class.getClassLoader();
Enumeration<URL> catalogs = catalogs(ldr);
while (catalogs.hasMoreElements())
scan(ldr, catalogs.nextElement());
}
private static Enumeration<URL> catalogs(ClassLoader ldr) {
try {
String prefix = "META-INF/services/";
String name = prefix + Transport.class.getName();
return ldr.getResources(name);
} catch (IOException err) {
return new Vector<URL>().elements();
}
}
private static void scan(ClassLoader ldr, URL url) {
BufferedReader br;
try {
InputStream urlIn = url.openStream();
br = new BufferedReader(new InputStreamReader(urlIn, "UTF-8"));
} catch (IOException err) {
// If we cannot read from the service list, go to the next.
//
return;
}
try {
String line;
while ((line = br.readLine()) != null) {
if (line.length() > 0 && !line.startsWith("#"))
load(ldr, line);
}
} catch (IOException err) {
// If we failed during a read, ignore the error.
//
} finally {
try {
br.close();
} catch (IOException e) {
// Ignore the close error; we are only reading.
}
}
}
private static void load(ClassLoader ldr, String cn) {
Class<?> clazz;
try {
clazz = Class.forName(cn, false, ldr);
} catch (ClassNotFoundException notBuiltin) {
// Doesn't exist, even though the service entry is present.
//
return;
}
for (Field f : clazz.getDeclaredFields()) {
if ((f.getModifiers() & Modifier.STATIC) == Modifier.STATIC
&& TransportProtocol.class.isAssignableFrom(f.getType())) {
TransportProtocol proto;
try {
proto = (TransportProtocol) f.get(null);
} catch (IllegalArgumentException e) {
// If we cannot access the field, don't.
continue;
} catch (IllegalAccessException e) {
// If we cannot access the field, don't.
continue;
}
if (proto != null)
register(proto);
}
}
}
/**

View File

@ -58,8 +58,10 @@
* static class members, for example:
*
* <pre>
* package com.example.my_transport;
*
* class MyTransport extends Transport {
* static final TransportProtocol PROTO = new TransportProtocol() {
* public static final TransportProtocol PROTO = new TransportProtocol() {
* public String getName() {
* return &quot;My Protocol&quot;;
* }
@ -67,12 +69,22 @@
* }
* </pre>
*
* <p>
* Applications may register additional protocols for use by JGit by calling
* {@link Transport#register(TransportProtocol)}. Because that API holds onto
* the protocol object by a WeakReference, applications must ensure their own
* ClassLoader retains the TransportProtocol for the life of the application.
* Using a static singleton pattern as above will ensure the protocol is valid
* so long as the ClassLoader that defines it remains valid.
* <p>
* Applications may automatically register additional protocols by filling in
* the names of their TransportProtocol defining classes using the services file
* {@code META-INF/services/org.eclipse.jgit.transport.Transport}. For each
* class name listed in the services file, any static fields of type
* {@code TransportProtocol} will be automatically registered. For the above
* example the string {@code com.example.my_transport.MyTransport} should be
* listed in the file, as that is the name of the class that defines the static
* PROTO singleton.
*/
public abstract class TransportProtocol {
/** Fields within a {@link URIish} that a transport uses. */