User Context

One of the new features of Hazelcast 3 is the ability to pass user-context to a HazelcastInstance. This is very useful if you need to access dependencies, but only have a HazelcastInstance available. Imagine that you have some kind of EchoService on each member of your Hazelcast cluster:

public class EchoService{
   public void echo(String msg){
      System.out.println(msg);
   }
}

And imagine that you also have an EchoTask, you are going to submit to a Hazelcast executor, that is going to call this EchoService:

public class EchoTask implements Runnable,Serializable{
   private final String msg;

   public EchoTask(String msg) {
      this.msg = msg;
   }

   @Override
   public void run() {
      ....
   }
}

The big question is how to get access to the EchoService. You can’t inject a local EchoService into the EchoTask, since services typically are objects that don’t like to be sent over the line. Even if it could be sent, it is not desirable because you want to make use of the service on the other side. With Hazelcast 2 it already was possible to let the EchoTask implement the HazelcastInstanceAware interface, so that the HazelcastInstance of the executor running the EchoTask task, is injected:

public class EchoTask implements Runnable, 
   Serializable, HazelcastInstanceAware{
   
   private transient HazelcastInstance hz;
   private final String msg;

   public EchoTask(String msg) {
      this.msg = msg;
   }

   @Override
   public void run() {
      ...
   }

   @Override
   public void setHazelcastInstance(HazelcastInstance hz) {
      this.hz = hz;
   }
}

When the EchoTask is deserialized, Hazelcast checks if it is implementing HazelcastInstanceAware and will inject the HazelcastInstance. The question is how the EchoService can be accessed from the HazelcastInstance. This is where the user-context comes to the rescue. A user-context is a mutable (Concurrent)Map attached to each HazelcastInstance and can be configured from the Hazelcast Config object. To access the EchoService from the user-context of the HazelcastInstance, you need to create it first and put it the user-context:

EchoService echoService = new EchoService();
   Config config = new Config();
   config.getUserContext().put("echoService",echoService);
   HazelcastInstance hz = Hazelcast.newHazelcastInstance(config);

If you are making use of a Hazelcast XML file instead of programmatic configuration, you can do the following:

EchoService echoService = new EchoService();
   Config config = new XmlConfigBuilder().build();
   config.getUserContext().put("echoService",echoService);
   HazelcastInstance hz = Hazelcast.newHazelcastInstance(config);

It isn’t possible to define the user context dependencies in the Hazelcast XML file; Hazelcast XML is not a general purpose object container like Spring. After the EchoService is put in the user-context, you can access the EchoService from the EchoTask like this:

public class EchoTask implements Runnable,
   Serializable, HazelcastInstanceAware{
   
   private transient HazelcastInstance hz;
   private final String msg;

   public EchoTask(String msg) {
      this.msg = msg;
   }

   @Override
   public void run() {
      EchoService echoService = 
         (EchoService)hz.getUserContext().get("echoService");
      echoService.echo(msg);
   }

   @Override
   public void setHazelcastInstance(HazelcastInstance hz) {
      this.hz = hz;
   }
}

If you run this EchoTask, you’ll see that the echoService is located:

public class UserContextExample{
   public static void main(String[] args){
      EchoService echoService = new EchoService();

      Config config = new Config();
      config.getUserContext().put("echoService",echoService);
      HazelcastInstance hz = Hazelcast.newHazelcastInstance(config);

      hz.getExecutorService("echoExecutor").execute(new EchoTask("hello"));
   }
}

It is not only possible to configure user-context on the Config, but you can also directly configure the user-context of the HazelcastInstance. This is practical if you need to add dependencies on the fly. However don’t forget to clean up what you put in the user-context, else you might run into resources problems like an OutOfMemoryError. Perhaps needless to mention; changes made in the user-context are local to a member only. Other members in the cluster are not going to observe changes in the user-context of one member. So if you need to have that EchoService available on each member, you need to add it to the user-context on each member. Important to know is that when a HazelcastInstance is created using some Config instance, a new user-context ConcurrentMap is created and the content of the user-context of the Config copied. So changes made to the user-context of the HazelcastInstance will not reflect on other HazelcastInstance created using the same Config instance.