Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Added support for REPLs into Tomcat web applications by discovering a…

…lternate classloaders to start the repl in.

Removed repl-caught hack
  • Loading branch information...
commit 4bb056a219805fd0fc058428b477d755d9169335 1 parent e6f2ee7
David Powell djpowell authored
111 liverepl-agent/src/net/djpowell/liverepl/agent/Agent.java
... ... @@ -1,25 +1,102 @@
1 1 package net.djpowell.liverepl.agent;
2 2
3 3 import java.io.File;
  4 +import java.io.IOException;
  5 +import java.io.PrintStream;
4 6 import java.lang.instrument.Instrumentation;
5 7 import java.lang.reflect.Method;
6   -import java.net.InetAddress;
7   -import java.net.URL;
8   -import java.net.URLClassLoader;
9   -import java.util.ArrayList;
10   -import java.util.List;
11   -import java.util.StringTokenizer;
  8 +import java.net.*;
  9 +import java.util.*;
  10 +import java.util.concurrent.atomic.AtomicBoolean;
12 11 import java.util.logging.Logger;
13 12
14 13 import net.djpowell.liverepl.client.Main;
  14 +import net.djpowell.liverepl.discovery.ClassLoaderInfo;
  15 +import net.djpowell.liverepl.discovery.Discovery;
15 16
16 17 public class Agent {
17   -
18   - private static ClassLoader pushClassLoader(List<URL> urls) {
  18 +
  19 + private static final Discovery discovery = new Discovery();
  20 +
  21 + public static interface ConnectNotifyingTask {
  22 + void run(ServerSocket server, AtomicBoolean connected);
  23 + }
  24 +
  25 + private static Thread startKillerThread(final int connectTimeout, final AtomicBoolean connected, final ServerSocket server) {
  26 + Thread killer = new Thread(new Runnable() {
  27 + public void run() {
  28 + try {
  29 + Thread.sleep(connectTimeout);
  30 + } catch (InterruptedException e) {
  31 + // ignore
  32 + }
  33 + if (!connected.get()) {
  34 + // TRC.fine("Client connect timeout: terminating server");
  35 + try {
  36 + server.close();
  37 + } catch (IOException e) {
  38 + throw new RuntimeException(e);
  39 + }
  40 + }
  41 + }
  42 + }, "Killer Thread");
  43 + killer.start();
  44 + return killer;
  45 + }
  46 +
  47 + public static void runAfterConnect(int port, int connectTimeout, String threadName, final ConnectNotifyingTask task) throws Exception {
  48 + final ServerSocket serverSocket = new ServerSocket(port, 0, Main.LOCALHOST);
  49 + final AtomicBoolean connected = new AtomicBoolean(false);
  50 + Thread taskThread = new Thread(new Runnable() {
  51 + public void run() {
  52 + task.run(serverSocket, connected);
  53 + }
  54 + }, threadName);
  55 + startKillerThread(connectTimeout, connected, serverSocket);
  56 + taskThread.start();
  57 + }
  58 +
  59 + private static void printClassLoaderInfo(int port) {
  60 + try {
  61 + runAfterConnect(port, 5000, "ClassLoaderInfoThread", new ConnectNotifyingTask() {
  62 + public void run(ServerSocket server, AtomicBoolean connected) {
  63 + try {
  64 + Socket socket = server.accept();
  65 + connected.set(true);
  66 + try {
  67 + PrintStream out = new PrintStream(socket.getOutputStream());
  68 + try {
  69 + discovery.dumpList(out);
  70 + } finally {
  71 + out.close();
  72 + }
  73 + } finally {
  74 + socket.close();
  75 + }
  76 + } catch (RuntimeException e) {
  77 + throw e;
  78 + } catch (Exception e) {
  79 + throw new RuntimeException(e);
  80 + }
  81 + }
  82 + });
  83 + } catch (RuntimeException e) {
  84 + throw e;
  85 + } catch (Exception e) {
  86 + throw new RuntimeException(e);
  87 + }
  88 + }
  89 +
  90 + private static ClassLoader pushClassLoader(List<URL> urls, String clId) {
19 91 TRC.fine("Creating new classloader with: " + urls);
20 92 ClassLoader old = Thread.currentThread().getContextClassLoader();
21 93 TRC.fine("Old classloader: " + old);
22   - URLClassLoader withClojure = new URLClassLoader(urls.toArray(new URL[urls.size()]), old); // TODO
  94 + ClassLoaderInfo cli = discovery.findClassLoader(clId);
  95 + if (cli == null) {
  96 + throw new RuntimeException("Unknown class loader: " + clId);
  97 + }
  98 + ClassLoader cl = cli.getClassLoader();
  99 + URLClassLoader withClojure = new URLClassLoader(urls.toArray(new URL[urls.size()]), cl); // TODO
23 100 Thread.currentThread().setContextClassLoader(withClojure);
24 101 return old;
25 102 }
@@ -41,9 +118,9 @@ private static boolean isClojureLoaded() {
41 118
42 119 public static void agentmain(String agentArgs, Instrumentation inst) {
43 120 TRC.fine("Started Attach agent");
44   -
  121 +
45 122 StringTokenizer stok = new StringTokenizer(agentArgs, "\n");
46   - if (stok.countTokens() != 3) {
  123 + if (stok.countTokens() != 4) {
47 124 throw new RuntimeException("Invalid parameters: " + agentArgs);
48 125 }
49 126
@@ -51,7 +128,13 @@ public static void agentmain(String agentArgs, Instrumentation inst) {
51 128 TRC.fine("Port: " + port);
52 129 String clojurePath = stok.nextToken();
53 130 String serverPath = stok.nextToken();
54   -
  131 + String classLoaderId = stok.nextToken();
  132 +
  133 + if ("L".equals(classLoaderId)) {
  134 + printClassLoaderInfo(port);
  135 + return;
  136 + }
  137 +
55 138 boolean clojureLoaded = isClojureLoaded();
56 139 TRC.fine("Clojure is " + (clojureLoaded ? "" : "not ") + "loaded");
57 140
@@ -61,8 +144,8 @@ public static void agentmain(String agentArgs, Instrumentation inst) {
61 144 } else {
62 145 urls = getJarUrls(clojurePath, serverPath);
63 146 }
64   -
65   - ClassLoader old = pushClassLoader(urls);
  147 +
  148 + ClassLoader old = pushClassLoader(urls, classLoaderId);
66 149 try {
67 150 if (!clojureLoaded) { // if clojure wasn't loaded before, print current status
68 151 TRC.fine("Clojure is " + (isClojureLoaded() ? "" : "not ") + "loaded");
9 liverepl-agent/src/net/djpowell/liverepl/client/Console.java
@@ -13,10 +13,11 @@
13 13
14 14 public class Console {
15 15
16   - private static String NEWLINE = System.getProperty("line.separator");
  16 + private static final String NEWLINE = System.getProperty("line.separator");
17 17
18   - public static void main(InetAddress host, int port) throws Exception {
  18 + public static void main(InetAddress host, int port, boolean readInput) throws Exception {
19 19 final BufferedReader cin = new BufferedReader(new InputStreamReader(System.in));
  20 + if (!readInput) cin.close();
20 21 final Writer cout = new OutputStreamWriter(System.out);
21 22 Socket s = new Socket(host, port);
22 23 try {
@@ -42,7 +43,7 @@ public void run() {
42 43 }
43 44 }
44 45 };
45   - ch.start();
  46 + if (readInput) ch.start();
46 47
47 48 Thread sh = new Thread("SocketHandler") {
48 49 @Override
@@ -68,7 +69,7 @@ public void run() {
68 69 // block until both threads finish
69 70 try {
70 71 sh.join();
71   - ch.join();
  72 + if (readInput) ch.join();
72 73 } catch (InterruptedException e) {
73 74 // if either thread dies, then allow the socket to
74 75 // close, terminating the other one
39 liverepl-agent/src/net/djpowell/liverepl/client/Main.java
@@ -26,7 +26,7 @@ private static int getFreePort(InetAddress host) {
26 26 // so that we can tell the server to listen on that port
27 27 // and we will know which port to connect to
28 28 try {
29   - ServerSocket server = new ServerSocket(0, 0, LOCALHOST);
  29 + ServerSocket server = new ServerSocket(0, 0, host);
30 30 int port = server.getLocalPort();
31 31 server.close();
32 32 return port;
@@ -48,8 +48,8 @@ private static void listPids() {
48 48 }
49 49
50 50 public static void main(String[] args) throws Exception {
51   - // Usage: <clojurepath> <agentjarpath> <serverjarpath> <jvmpid>
52   - if (args.length != 4) {
  51 + // Usage: <clojurepath> <agentjarpath> <serverjarpath> <jvmpid> <classloaderid>
  52 + if (args.length < 4) {
53 53 listPids();
54 54 System.exit(0);
55 55 }
@@ -57,19 +57,40 @@ public static void main(String[] args) throws Exception {
57 57 String agentpath = args[1];
58 58 String serverpath = args[2];
59 59 String pid = args[3];
  60 + String classLoaderId;
  61 + if (args.length < 5) {
  62 + classLoaderId = "L";
  63 + System.out.println();
  64 + System.out.println("List of ClassLoaders for process #" + pid);
  65 + } else {
  66 + classLoaderId = args[4];
  67 + }
  68 +
60 69 TRC.fine("Attaching to pid: " + pid);
61   - VirtualMachine vm = VirtualMachine.attach(pid);
  70 + final VirtualMachine vm = VirtualMachine.attach(pid);
62 71
63 72 int port = getFreePort(LOCALHOST);
64 73 // start the agent, which will create the server socket, then return
65   - String agentArgs = String.valueOf(port) + "\n" + clojurepath + "\n" + serverpath;
  74 + String agentArgs = String.valueOf(port) + "\n" + clojurepath + "\n" + serverpath + "\n" + classLoaderId;
66 75 vm.loadAgent(agentpath, agentArgs);
67   -
  76 +
  77 + boolean listClassLoaders = "L".equals(classLoaderId);
68 78 // start the code that will connect to the server socket
69   - Console.main(LOCALHOST, port);
70   -
  79 + Console.main(LOCALHOST, port, !listClassLoaders);
  80 +
71 81 // the server will shutdown when the client disconnects
72   - vm.detach();
  82 + Runtime.getRuntime().addShutdownHook(new Thread() {
  83 + @Override
  84 + public void run() {
  85 + try {
  86 + vm.detach();
  87 + } catch (RuntimeException e) {
  88 + throw e;
  89 + } catch (Exception e) {
  90 + throw new RuntimeException(e);
  91 + }
  92 + }
  93 + });
73 94 }
74 95
75 96 private static final Logger TRC = Logger.getLogger(Main.class.getName());
16 liverepl-agent/src/net/djpowell/liverepl/discovery/ClassLoaderDiscovery.java
... ... @@ -0,0 +1,16 @@
  1 +package net.djpowell.liverepl.discovery;
  2 +
  3 +import java.util.Collection;
  4 +
  5 +/**
  6 + * SPI Interface for providing implementations to discover ClassLoaders.
  7 + */
  8 +public interface ClassLoaderDiscovery {
  9 + /**
  10 + * Return information about the available ClassLoaders.
  11 + * Implementations must register each ClassLoader with the application's ClassLoaderRegistry,
  12 + * and use the id returned by the registry in the ClassLoaderInfo instances returned.
  13 + */
  14 + Collection<ClassLoaderInfo> listClassLoaders();
  15 + String discoveryName();
  16 +}
40 liverepl-agent/src/net/djpowell/liverepl/discovery/ClassLoaderInfo.java
... ... @@ -0,0 +1,40 @@
  1 +package net.djpowell.liverepl.discovery;
  2 +
  3 +import java.lang.ref.WeakReference;
  4 +
  5 +public class ClassLoaderInfo implements Comparable {
  6 +
  7 + public final String id;
  8 + private final WeakReference<ClassLoader> classLoader;
  9 + public final String info;
  10 +
  11 + public ClassLoaderInfo(String id, ClassLoader classLoader, String info) {
  12 + this.id = id;
  13 + this.classLoader = new WeakReference<ClassLoader>(classLoader);
  14 + this.info = info;
  15 + }
  16 +
  17 + public ClassLoader getClassLoader() {
  18 + return classLoader.get();
  19 + }
  20 +
  21 + public String getClassLoaderName() {
  22 + ClassLoader cl = classLoader.get();
  23 + if (cl == null) {
  24 + return "<null>";
  25 + } else {
  26 + return cl.getClass().getSimpleName();
  27 + }
  28 + }
  29 +
  30 + public static final String header = String.format("#%-3s %-20s : %s", "Id", "ClassLoader", "Info");
  31 +
  32 + public String toString() {
  33 + return String.format("#%-3s %-20s : %s", id, getClassLoaderName(), info);
  34 + }
  35 +
  36 + public int compareTo(Object o) {
  37 + ClassLoaderInfo cli = (ClassLoaderInfo)o;
  38 + return id.compareTo(cli.id);
  39 + }
  40 +}
29 liverepl-agent/src/net/djpowell/liverepl/discovery/ClassLoaderRegistry.java
... ... @@ -0,0 +1,29 @@
  1 +package net.djpowell.liverepl.discovery;
  2 +
  3 +import java.util.Map;
  4 +import java.util.WeakHashMap;
  5 +import java.util.concurrent.atomic.AtomicInteger;
  6 +
  7 +/**
  8 + * ClassLoaderDiscovery implementations must register all ClassLoaders with this registry to give them an
  9 + * identifier which can be used to subsequently find the ClassLoader that they user asked for.
  10 + *
  11 + */
  12 +public class ClassLoaderRegistry {
  13 +
  14 + private final AtomicInteger clIdGenerator = new AtomicInteger(0);
  15 + private final Map<ClassLoader, String> clIdMap = new WeakHashMap<ClassLoader, String>();
  16 +
  17 + public String registerClassLoader(ClassLoader classLoader) {
  18 + String id;
  19 + synchronized (clIdMap) {
  20 + id = clIdMap.get(classLoader);
  21 + if (id == null) {
  22 + id = String.valueOf(clIdGenerator.getAndIncrement());
  23 + clIdMap.put(classLoader, id);
  24 + }
  25 + }
  26 + return id;
  27 + }
  28 +
  29 +}
62 liverepl-agent/src/net/djpowell/liverepl/discovery/Discovery.java
... ... @@ -0,0 +1,62 @@
  1 +package net.djpowell.liverepl.discovery;
  2 +
  3 +import net.djpowell.liverepl.discovery.impl.SystemDiscovery;
  4 +import net.djpowell.liverepl.discovery.impl.ThreadDiscovery;
  5 +import net.djpowell.liverepl.discovery.impl.TomcatDiscovery;
  6 +
  7 +import java.io.PrintStream;
  8 +import java.util.ArrayList;
  9 +import java.util.Collection;
  10 +import java.util.List;
  11 +
  12 +/**
  13 + * API for obtaining a list of ClassLoaders by using available implementations of ClassLoaderDiscovery
  14 + */
  15 +public class Discovery implements ClassLoaderDiscovery {
  16 +
  17 + private final List<ClassLoaderDiscovery> impls = new ArrayList<ClassLoaderDiscovery>();
  18 +
  19 + public Discovery() {
  20 + ClassLoaderRegistry registry=new ClassLoaderRegistry();
  21 + impls.add(new SystemDiscovery(registry));
  22 + impls.add(new TomcatDiscovery(registry));
  23 + impls.add(new ThreadDiscovery(registry));
  24 + }
  25 +
  26 + public Collection<ClassLoaderInfo> listClassLoaders() {
  27 + List<ClassLoaderInfo> ret = new ArrayList();
  28 + for (ClassLoaderDiscovery discovery : impls) {
  29 + ret.addAll(discovery.listClassLoaders());
  30 + }
  31 + return ret;
  32 + }
  33 +
  34 + public String discoveryName() {
  35 + return "All ClassLoaders";
  36 + }
  37 +
  38 + public void dumpList(PrintStream out) {
  39 + for (ClassLoaderDiscovery discovery : impls) {
  40 + Collection<ClassLoaderInfo> clis = discovery.listClassLoaders();
  41 + if (!clis.isEmpty()) {
  42 + out.println();
  43 + out.println(discovery.discoveryName() + ":");
  44 + out.println();
  45 + out.println(ClassLoaderInfo.header);
  46 + for (ClassLoaderInfo cli : clis) {
  47 + out.println(cli.toString());
  48 + }
  49 + }
  50 + }
  51 + }
  52 +
  53 + public ClassLoaderInfo findClassLoader(String clId) {
  54 + for (ClassLoaderInfo cli : listClassLoaders()) {
  55 + if (cli.id.equals(clId)) {
  56 + return cli;
  57 + }
  58 + }
  59 + return null;
  60 + }
  61 +
  62 +}
7 liverepl-agent/src/net/djpowell/liverepl/discovery/Function.java
... ... @@ -0,0 +1,7 @@
  1 +package net.djpowell.liverepl.discovery;
  2 +
  3 +public interface Function<R, A> {
  4 +
  5 + R invoke (A arg);
  6 +
  7 +}
67 liverepl-agent/src/net/djpowell/liverepl/discovery/impl/JMXDiscovery.java
... ... @@ -0,0 +1,67 @@
  1 +package net.djpowell.liverepl.discovery.impl;
  2 +
  3 +import net.djpowell.liverepl.discovery.ClassLoaderDiscovery;
  4 +import net.djpowell.liverepl.discovery.ClassLoaderInfo;
  5 +import net.djpowell.liverepl.discovery.ClassLoaderRegistry;
  6 +import net.djpowell.liverepl.discovery.Function;
  7 +
  8 +import javax.management.*;
  9 +import java.lang.management.ManagementFactory;
  10 +import java.util.Collection;
  11 +import java.util.Set;
  12 +import java.util.TreeSet;
  13 +
  14 +/**
  15 + * Helper class to return ClassLoaders using JMX
  16 + */
  17 +public class JMXDiscovery implements ClassLoaderDiscovery {
  18 +
  19 + private final ClassLoaderRegistry registry;
  20 + private final MBeanServer mbs;
  21 + private final String searchName;
  22 + private final String attributeName;
  23 + private final Function<ClassLoader, Object> getClassLoader;
  24 + private final Function<String, ObjectName> getInfo;
  25 + private final String discoveryName;
  26 +
  27 + public JMXDiscovery(ClassLoaderRegistry registry, String searchName, String attributeName, Function<ClassLoader, Object> getClassLoader, Function<String, ObjectName> getInfo, String discoveryName) {
  28 + this.registry = registry;
  29 + this.mbs = ManagementFactory.getPlatformMBeanServer();
  30 + this.searchName = searchName;
  31 + this.attributeName = attributeName;
  32 + this.getClassLoader = getClassLoader;
  33 + this.getInfo = getInfo;
  34 + this.discoveryName = discoveryName;
  35 + }
  36 +
  37 + public Collection<ClassLoaderInfo> listClassLoaders() {
  38 + Set<ClassLoaderInfo> ret = new TreeSet<ClassLoaderInfo>();
  39 + Set<ObjectName> names;
  40 + try {
  41 + names = mbs.queryNames(new ObjectName(searchName), null);
  42 + } catch (MalformedObjectNameException e) {
  43 + throw new RuntimeException(e);
  44 + }
  45 +
  46 + for (ObjectName name : names) {
  47 + Object obj;
  48 + try {
  49 + obj = mbs.getAttribute(name, attributeName);
  50 + } catch (RuntimeException e) {
  51 + throw e;
  52 + } catch (Exception e) {
  53 + throw new RuntimeException(e);
  54 + }
  55 + ClassLoader classLoader = getClassLoader.invoke(obj);
  56 + String id = registry.registerClassLoader(classLoader);
  57 + String info = getInfo.invoke(name);
  58 + ClassLoaderInfo cli = new ClassLoaderInfo(id, classLoader, info);
  59 + ret.add(cli);
  60 + }
  61 + return ret;
  62 + }
  63 +
  64 + public String discoveryName() {
  65 + return discoveryName;
  66 + }
  67 +}
32 liverepl-agent/src/net/djpowell/liverepl/discovery/impl/SystemDiscovery.java
... ... @@ -0,0 +1,32 @@
  1 +package net.djpowell.liverepl.discovery.impl;
  2 +
  3 +import net.djpowell.liverepl.discovery.ClassLoaderDiscovery;
  4 +import net.djpowell.liverepl.discovery.ClassLoaderInfo;
  5 +import net.djpowell.liverepl.discovery.ClassLoaderRegistry;
  6 +
  7 +import java.util.Collection;
  8 +import java.util.Collections;
  9 +
  10 +/**
  11 + * Simple implementation of ClassLoaderDiscovery for returning the SystemClassLoader.
  12 + * This implementation should be registered first with Discovery to ensure that the
  13 + * well-known SystemClassLoader gets assigned 0 as its id.
  14 + */
  15 +public class SystemDiscovery implements ClassLoaderDiscovery {
  16 +
  17 + private final String id;
  18 +
  19 + public SystemDiscovery(ClassLoaderRegistry registry) {
  20 + id = registry.registerClassLoader(ClassLoader.getSystemClassLoader());
  21 + }
  22 +
  23 + public Collection<ClassLoaderInfo> listClassLoaders() {
  24 + ClassLoaderInfo cli = new ClassLoaderInfo(id, ClassLoader.getSystemClassLoader(), "<system>");
  25 + return Collections.singletonList(cli);
  26 + }
  27 +
  28 + public String discoveryName() {
  29 + return "System Class Loader";
  30 + }
  31 +
  32 +}
52 liverepl-agent/src/net/djpowell/liverepl/discovery/impl/ThreadDiscovery.java
... ... @@ -0,0 +1,52 @@
  1 +package net.djpowell.liverepl.discovery.impl;
  2 +
  3 +import net.djpowell.liverepl.discovery.ClassLoaderDiscovery;
  4 +import net.djpowell.liverepl.discovery.ClassLoaderInfo;
  5 +import net.djpowell.liverepl.discovery.ClassLoaderRegistry;
  6 +
  7 +import java.util.*;
  8 +
  9 +/**
  10 + * Implementation of ClassLoaderDiscovery that makes available the Thread Context ClassLoaders
  11 + * associated with currently running threads.
  12 + */
  13 +public class ThreadDiscovery implements ClassLoaderDiscovery {
  14 +
  15 + private final ClassLoaderRegistry registry;
  16 +
  17 + public ThreadDiscovery(ClassLoaderRegistry registry) {
  18 + this.registry = registry;
  19 + }
  20 +
  21 + public Collection<ClassLoaderInfo> listClassLoaders() {
  22 + Collection<ClassLoaderInfo> ret = new ArrayList<ClassLoaderInfo>();
  23 + ClassLoader systemCl = ClassLoader.getSystemClassLoader();
  24 + Collection<Thread> threads = new HashSet<Thread>(Thread.getAllStackTraces().keySet());
  25 + Map<ClassLoader, String> classLoaders = new HashMap<ClassLoader, String>();
  26 + for (Thread thread : threads) {
  27 + ClassLoader classLoader = thread.getContextClassLoader();
  28 + if (classLoader == null) continue;
  29 + if (classLoader == systemCl) continue;
  30 + classLoaders.put(classLoader, thread.getName() + " #" + thread.getId() + " [" + thread.getThreadGroup().getName() + "]");
  31 + }
  32 + TreeSet<Map.Entry<ClassLoader, String>> entries = new TreeSet<Map.Entry<ClassLoader, String>>(
  33 + new Comparator<Map.Entry<ClassLoader, String>>() {
  34 + public int compare(Map.Entry<ClassLoader, String> o1, Map.Entry<ClassLoader, String> o2) {
  35 + return o1.getValue().compareTo(o2.getValue());
  36 + }
  37 + });
  38 + entries.addAll(classLoaders.entrySet());
  39 + for (Map.Entry<ClassLoader, String> entry : entries) {
  40 + ClassLoader classLoader = entry.getKey();
  41 + String threadName = entry.getValue();
  42 + String id = registry.registerClassLoader(classLoader);
  43 + ClassLoaderInfo cli = new ClassLoaderInfo(id, classLoader, threadName);
  44 + ret.add(cli);
  45 + }
  46 + return ret;
  47 + }
  48 +
  49 + public String discoveryName() {
  50 + return "Thread Context Class Loaders";
  51 + }
  52 +}
51 liverepl-agent/src/net/djpowell/liverepl/discovery/impl/TomcatDiscovery.java
... ... @@ -0,0 +1,51 @@
  1 +package net.djpowell.liverepl.discovery.impl;
  2 +
  3 +import net.djpowell.liverepl.discovery.ClassLoaderDiscovery;
  4 +import net.djpowell.liverepl.discovery.ClassLoaderInfo;
  5 +import net.djpowell.liverepl.discovery.ClassLoaderRegistry;
  6 +import net.djpowell.liverepl.discovery.Function;
  7 +
  8 +import javax.management.ObjectName;
  9 +import java.util.Collection;
  10 +
  11 +/**
  12 + * Implementation of ClassLoaderDiscovery which uses JMX to obtain the ClassLoaders associated
  13 + * with Tomcat web applications.
  14 + */
  15 +public class TomcatDiscovery implements ClassLoaderDiscovery {
  16 +
  17 + private final JMXDiscovery jmxDiscovery;
  18 +
  19 + public TomcatDiscovery(ClassLoaderRegistry registry) {
  20 + this.jmxDiscovery = new JMXDiscovery(registry, "Catalina:j2eeType=WebModule,*", "loader",
  21 + new Function<ClassLoader, Object>() {
  22 + public ClassLoader invoke(Object obj) {
  23 + ClassLoader classLoader;
  24 + try {
  25 + classLoader = (ClassLoader) obj.getClass().getMethod("getClassLoader").invoke(obj);
  26 + } catch (RuntimeException e) {
  27 + throw e;
  28 + } catch (Exception e) {
  29 + throw new RuntimeException(e);
  30 + }
  31 + return classLoader;
  32 + }
  33 + },
  34 + new Function<String, ObjectName>() {
  35 + public String invoke(ObjectName arg) {
  36 + String name = arg.getKeyProperty("name");
  37 + int pos = name.lastIndexOf('/');
  38 + if (pos != -1) name = name.substring(pos);
  39 + return name;
  40 + }
  41 + }, "Tomcat Web Applications");
  42 + }
  43 +
  44 + public Collection<ClassLoaderInfo> listClassLoaders() {
  45 + return jmxDiscovery.listClassLoaders();
  46 + }
  47 +
  48 + public String discoveryName() {
  49 + return jmxDiscovery.discoveryName();
  50 + }
  51 +}
9 liverepl-server/src/net/djpowell/liverepl/server/Repl.java
@@ -10,14 +10,13 @@
10 10
11 11 public class Repl {
12 12
13   - final static public Symbol REPL_NS = Symbol.create("net.djpowell.liverepl.server.repl");
14   - final static public Namespace NS = Namespace.findOrCreate(REPL_NS);
15   - final static public Var REQUIRE = Var.intern(RT.CLOJURE_NS, Symbol.create("require"));
16   - final static public Var REPL = Var.intern(NS, Symbol.create("repl"));
  13 + private final static Symbol REPL_NS = Symbol.create("net.djpowell.liverepl.server.repl");
  14 + private final static Namespace NS = Namespace.findOrCreate(REPL_NS);
  15 + private final static Var REQUIRE = Var.intern(RT.CLOJURE_NS, Symbol.create("require"));
  16 + private final static Var REPL = Var.intern(NS, Symbol.create("repl"));
17 17
18 18 public static void main(InetAddress host, int port) throws Exception {
19 19 // not really needed in clojure 1.1, as context class loaders are the default
20   - // required so that clojure can load the repl code
21 20 Var.pushThreadBindings(new PersistentArrayMap(new Object[] {RT.USE_CONTEXT_CLASSLOADER, Boolean.TRUE}));
22 21 try {
23 22 REQUIRE.invoke(REPL_NS);
93 liverepl-server/src/net/djpowell/liverepl/server/repl.clj
... ... @@ -1,50 +1,43 @@
1   -(ns net.djpowell.liverepl.server.repl
2   - (:require clojure.main)
3   - (:import (java.io InputStreamReader BufferedWriter OutputStreamWriter Writer)
4   - (java.net ServerSocket Socket)
5   - (java.util.logging Logger)
6   - (clojure.lang LineNumberingPushbackReader)))
7   -
8   -(def new-line (System/getProperty "line.separator"))
9   -
10   -(defn repl-caught
11   - "Override the default repl-caught to not rely on PrintStream"
12   - [e]
13   - (.write #^Writer *err* (str (clojure.main/repl-exception e) new-line)))
14   -
15   -;; The client should connect immediately after the server starts up,
16   -;; but in case there is a problem, we will kill the serversocket,
17   -;; terminating the server, if the client fails to connect.
18   -(def client-connect-timeout 10000)
19   -
20   -(def #^Logger trc (Logger/getLogger (str *ns*)))
21   -
22   -(defn repl
23   - "Start a single connection repl server"
24   - [port backlog host]
25   - (let [server (ServerSocket. port backlog host)
26   - got-connection (atom false)
27   - killer-thread (Thread.
28   - (fn []
29   - (Thread/sleep client-connect-timeout)
30   - (when-not @got-connection
31   - (.fine trc "Client connect timeout: terminating server")
32   - (.close server)))
33   - "Killer Thread")
34   - repl-thread (Thread.
35   - (fn []
36   - (with-open [server server
37   - socket (.accept server)
38   - in (LineNumberingPushbackReader. (InputStreamReader. (.getInputStream socket)))
39   - out (BufferedWriter. (OutputStreamWriter. (.getOutputStream socket)))]
40   - (swap! got-connection (constantly true))
41   - (binding [*in* in
42   - *out* out
43   - *err* out]
44   - (try
45   - (clojure.main/repl :caught repl-caught :init #(in-ns 'user))
46   - (catch Exception e ; discard errors caused by abrupt disconnection
47   - nil)))))
48   - "Repl Thread")]
49   - (.start killer-thread)
50   - (.start repl-thread)))
  1 +(ns net.djpowell.liverepl.server.repl
  2 + (:require clojure.main)
  3 + (:import (java.io InputStreamReader BufferedWriter OutputStreamWriter Writer)
  4 + (java.net ServerSocket Socket)
  5 + (java.util.logging Logger)
  6 + (clojure.lang LineNumberingPushbackReader)))
  7 +
  8 +;; The client should connect immediately after the server starts up,
  9 +;; but in case there is a problem, we will kill the serversocket,
  10 +;; terminating the server, if the client fails to connect.
  11 +(def client-connect-timeout 10000)
  12 +
  13 +(def #^Logger trc (Logger/getLogger (str *ns*)))
  14 +
  15 +(defn repl
  16 + "Start a single connection repl server"
  17 + [port backlog host]
  18 + (let [server (ServerSocket. port backlog host)
  19 + got-connection (atom false)
  20 + killer-thread (Thread.
  21 + (fn []
  22 + (Thread/sleep client-connect-timeout)
  23 + (when-not @got-connection
  24 + (.fine trc "Client connect timeout: terminating server")
  25 + (.close server)))
  26 + "Killer Thread")
  27 + repl-thread (Thread.
  28 + (fn []
  29 + (with-open [server server
  30 + socket (.accept server)
  31 + in (LineNumberingPushbackReader. (InputStreamReader. (.getInputStream socket)))
  32 + out (BufferedWriter. (OutputStreamWriter. (.getOutputStream socket)))]
  33 + (swap! got-connection (constantly true))
  34 + (binding [*in* in
  35 + *out* out
  36 + *err* out]
  37 + (try
  38 + (clojure.main/repl :init #(do (in-ns 'user) (println "Clojure" (clojure-version))))
  39 + (catch Exception e ; discard errors caused by abrupt disconnection
  40 + nil)))))
  41 + "Repl Thread")]
  42 + (.start killer-thread)
  43 + (.start repl-thread)))
16 liverepl.bat
... ... @@ -1,8 +1,8 @@
1   -@echo off
2   -rem Starter script for Clojure liverepl
3   -
4   -set JDK_HOME=c:\jdk
5   -set LIVEREPL_HOME=%~dp0
6   -set CLOJURE_JAR=%LIVEREPL_HOME%\clojure.jar
7   -
8   -"%JDK_HOME%\bin\java" -cp "%LIVEREPL_HOME%\liverepl-agent.jar";"%JDK_HOME%\lib\tools.jar" net.djpowell.liverepl.client.Main "%CLOJURE_JAR%" "%LIVEREPL_HOME%\liverepl-agent.jar" "%LIVEREPL_HOME%\liverepl-server.jar" %1
  1 +@echo off
  2 +rem Starter script for Clojure liverepl
  3 +
  4 +set JDK_HOME=c:\jdk
  5 +set LIVEREPL_HOME=%~dp0
  6 +set CLOJURE_JAR=%LIVEREPL_HOME%\clojure.jar
  7 +
  8 +"%JDK_HOME%\bin\java" -cp "%LIVEREPL_HOME%\liverepl-agent.jar";"%JDK_HOME%\lib\tools.jar" net.djpowell.liverepl.client.Main "%CLOJURE_JAR%" "%LIVEREPL_HOME%\liverepl-agent.jar" "%LIVEREPL_HOME%\liverepl-server.jar" %*

0 comments on commit 4bb056a

Please sign in to comment.
Something went wrong with that request. Please try again.