Freitag, 3. Februar 2012

Hessian Wrapper to Enable Context Propagation

One advantage od the hessian protocol (compared e.g. with pure RMI) is that it is able to propagate implicite context information together with a remote call. However there are a few things to prepare in order to use this feature:

First we need an extension of the HessianProxy passing context information to the request header.

public class HessianProxyWithContext extends HessianProxy {
 private static final long serialVersionUID = 1L;
 private Map context = new HashMap();
 public HessianProxyWithContext(URL url, HessianProxyFactory factory, Class type) {
  super(url, factory, type);
 }
 public HessianProxyWithContext(URL url, HessianProxyFactory factory) {
  super(url, factory);
 }
 void setContext(Map context) {
  this.context = context;
 }
 protected void addRequestHeaders(HessianConnection conn) {
  super.addRequestHeaders(conn);
  for (Entry e : context.entrySet()) {
   conn.addHeader(e.getKey(), e.getValue());
  }
 }
}

Then we need an extension of the factory using our new Proxy class:

public class HessianProxyWithContextFactory extends HessianProxyFactory {
 private Map context = new HashMap();
 public Map getContext() {
  return context;
 }
 public String putContext(String key, String value) {
  return context.put(key, value);
 }
 public Object create(Class api, URL url, ClassLoader loader) {
  if (api == null)
   throw new NullPointerException("api must not be null for HessianProxyFactory.create()");
  HessianProxyWithContext handler = new HessianProxyWithContext(url, this, api);
  handler.setContext(context);
  return Proxy.newProxyInstance(loader, new Class[] { api, HessianRemoteObject.class }, handler);
 }
}

Note that the context information can be set at the factory for the next call.
Finally we have to extract the context information from the request header at the server.
For this purpose we need to access the servlet request.
To achive this we use a little extension of the HessianServlet class:

public class HessianServletWithContext extends HessianServlet {
 private static final long serialVersionUID = 1L;
 private ServletRequest request = null;
 @Override
 public void service(ServletRequest req, ServletResponse response) throws IOException, ServletException {
  request = req;
  super.service(req, response);
 }
 public String getContext(String key) {
  String value = null;
  if(request instanceof HttpServletRequest) {
   value = ((HttpServletRequest) request).getHeader(key);
  }
  return value;
 }
}

Basically this was all we need. The following example shows how to use the little wrapper:
public interface TestInterface {
 public String echoContext(String call);
}
public class TestEndpoint extends HessianServletWithContext implements TestInterface {
 private static final long serialVersionUID = 1L;
 public String echoContext(String contextKey) {
  return contextKey +"="+ getContext(contextKey);
 }
}
public class ClientTest {
    private TestInterface test;
    @Before
    public void initProxy() throws MalformedURLException {
        String url = "http://localhost:8080/test/TestInterface";
        HessianProxyWithContextFactory factory = new HessianProxyWithContextFactory();
        factory.putContext("contextKey", "contextValue"); 
        test = (TestInterface) factory.create(TestInterface.class, url);
    }
    @Test
    public void echoContext() {
      System.out.println(test.echoContext("unknown"));
      System.out.println(test.echoContext("contextKey"));
    }
}

Donnerstag, 2. Februar 2012

Firewall Friendly RMI Port Fixing

Although it probably makes little difference its often required to keep the number of ports allowed by a firewall as small as possible. By default RMI determines dynamically which port to use for communication. One way to define the port that shall be used for communication is to define it when exporting the remote object. Another less invasive way is to set a custom RMISocketFactory like the following one:

public class CustomRmiSocketFactory extends RMISocketFactory {
  private static RMISocketFactory delegate = RMISocketFactory.getDefaultSocketFactory();
  public static void register(int customPort) throws IOException {
     RMISocketFactory.setSocketFactory(new CustomRmiSocketFactory(customPort));
  }
  private final int customPort; 
  private CustomRmiSocketFactory(int customPort) {
    this.customPort = customPort;
  }
  public Socket createSocket(String host, int port) throws IOException {
    return delegate.createSocket(host, port);
  }
  public ServerSocket createServerSocket(int port) throws IOException { // 0 means arbitrary port
    return delegate.createServerSocket(port!=0 ? port : customPort); 
  }
  public int hashCode() {
    return customServerSocketPort;
  }
  public boolean equals(Object obj) {
    return (getClass() == obj.getClass() && customPort == ((CustomRmiSocketFactory) obj).customPort);
  }
}


As fas as you ensure the register method is calles as early as possible in your application (e.g. in an static initializer in your main class) this works quite well. All RMI communication uses the predefined (server) port.

One unexpected and nasty thing happens when you use this in connection with JMX. If you start your application with the usual JMX options the JMX agent already initializes RMI and chooses a port befor any class of your application is loaded. In order to keep RMI only taking the defined port you either have to provice jour own JMX agent (wich first sets the factory) or start the JMX agent programatically in jour application (after setting the RMISocketFactory).