From 565fa6f9b15da98b9c508ab6129d34a1755acd18 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 7 Mar 2011 15:39:03 -0800 Subject: [PATCH] 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 --- .../org/eclipse/jgit/transport/Transport.java | 87 +++++++++++++++++++ .../jgit/transport/TransportProtocol.java | 14 ++- 2 files changed, 100 insertions(+), 1 deletion(-) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java index 073b1ac1a..f8c4a7bcd 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java @@ -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 catalogs = catalogs(ldr); + while (catalogs.hasMoreElements()) + scan(ldr, catalogs.nextElement()); + } + + private static Enumeration 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().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); + } + } } /** diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportProtocol.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportProtocol.java index 4652e01de..a55f495a6 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportProtocol.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportProtocol.java @@ -58,8 +58,10 @@ * static class members, for example: * *
+ * 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 @@
  * }
  * 
* + *

* 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. + *

+ * 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. */