package aQute.remote.agent; import java.io.IOException; import java.io.PrintStream; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Arrays; import java.util.List; import org.apache.felix.service.command.CommandProcessor; import org.apache.felix.service.command.CommandSession; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceReference; import org.osgi.util.tracker.ServiceTracker; /** * Create a new Gogo Shell Command Session if there is a Gogo Command Processor * service present. If no Command Processor is present we will idle (a service * tracker is used that will handle multiple and switches). * <p> * There is a bit of a class space problem since the agent can be started on the * framework side of the class path. For this reason, we carry a copy of the * Gogo API classes and we will use proxies to use them. This leaves the Gogo * API unconstrained. */ public class GogoRedirector implements Redirector { private AgentServer agentServer; private ServiceTracker<CommandProcessor,CommandProcessor> tracker; private CommandProcessor processor; private CommandSession session; private Shell stdin; private RedirectOutput stdout; /** * Create a redirector * * @param agentServer the server * @param context the context, needed to get the */ public GogoRedirector(AgentServer agentServer, BundleContext context) { this.agentServer = agentServer; tracker = new ServiceTracker<CommandProcessor,CommandProcessor>(context, CommandProcessor.class.getName(), null) { @Override public CommandProcessor addingService(ServiceReference<CommandProcessor> reference) { CommandProcessor cp = proxy(CommandProcessor.class, super.addingService(reference)); if (processor == null) openSession(cp); return cp; } @Override public void removedService(ServiceReference<CommandProcessor> reference, CommandProcessor service) { super.removedService(reference, service); if (service == processor) { closeSession(service); CommandProcessor replacement = getService(); if (replacement != null) { openSession(replacement); } } } }; tracker.open(); } void closeSession(CommandProcessor service) { if (session != null) { session.close(); processor = null; } } synchronized void openSession(CommandProcessor replacement) { processor = replacement; List<AgentServer> agents = Arrays.asList(agentServer); stdout = new RedirectOutput(agents, null, false); stdin = new Shell(); session = processor.createSession(stdin, stdout, stdout); stdin.open(session); } /* * Create a proxy on a class. This is to prevent class cast exceptions. We * get our Gogo likely from another class loader since the agent can reside * on the framework side and we can't force Gogo to import our classes (nor * should we). */ @SuppressWarnings("unchecked") <T> T proxy(final Class<T> clazz, final Object target) { final Class< ? > targetClass = target.getClass(); // // We could also be in the same class space, in that case we // can just return the value // if (targetClass == clazz) return clazz.cast(target); return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class< ? >[] { clazz }, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Method targetMethod = targetClass.getMethod(method.getName(), method.getParameterTypes()); Object result = targetMethod.invoke(target, args); if (result != null && method.getReturnType().isInterface() && targetMethod.getReturnType() != method.getReturnType()) try { return proxy(method.getReturnType(), result); } catch (Exception e) {} return result; } }); } @Override public void close() throws IOException { closeSession(processor); } @Override public int getPort() { return -1; } @Override public void stdin(String s) throws Exception { stdin.add(s); } @Override public PrintStream getOut() throws Exception { return stdout; } }