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:
parent
305a8ac45f
commit
565fa6f9b1
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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 "My Protocol";
|
||||
* }
|
||||
|
@ -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. */
|
||||
|
|
Loading…
Reference in New Issue