Archiv für den Monat: Januar 2014

Remote Shut down Java clients

From time to time I have to update the application of our clients – no surprise. At most client sites the software is installed on a network drive and started from that location. In order to update one or moreJar files, every application has to be closed.

Usually, all users are informed in advance that they have to close their clients for the update. And in most cases some clients just log off and don’t close the application. In this case it is more work for me to perform the update.

I was looking for a solution to close all client application remotely using Java technology. What came to mind was to use Java Massage Service (JMS) to inform all connected clients that they have to terminate (now).

It’s been a while (EJB 2.x) since the last time I used JMS so I searched the internet for the APIs and some useful information about where and how to get started. My first starting point was the JEE tutorial provided by Oracle. It’s a big document (over 1000 pages) and seems to have all information I need. Unfortunately, the document assumes that Netbeans is used and I don’t want to install Netbeans and instead use my known IDE Eclipse. So I searched again and found the blog from Umoh Bassey-Duke which has a nice step by step introduction into using JMS the first time.

The solution I had in mind was to create a “shutdown”-topic where every client has to register during the starting phase. So when ever somebody sends a message to the “shutdown”-topic all registered clients now, that they have to perform a System.exit(0) now.

To prove my concept I created an application with a simple user interface. In the user interface I can send messages to a topic and display messages from the topic.

Here is what is required:

The installation of JEE, GlassFish, Eclipse and the plugin is not described here. Please consult the web if you need help here.

Step 1 – Configure the Shutdown topic in GlassFish

The configuration of the JMS related information/resources is done with the admin console.

If you don’t know how to do this please have a look into the blog of Umoh Bassey-Duke (http://www.greenkode.com/2011/09/using-glassfish-and-ejb-3-0-message-driven-beans-for-java-messaging/).

Step 2 – Create the client

As a client I created a class called MessageReceiverClient. The client is connecting to the application server (GlassFish in my case) and is registering itself as a Topic subscriber. While the client is registered every time a new message is posted to the specified topic he gets a notification and can process the message.

The magic of connecting to the application server is done in the method initConnection2AS(). Here is the code with a description what is to do:

private void initConnection2AS() {
  try {
    // Get the JNDI Context
    jndiContext = new InitialContext();
    // Lookup the topic
    topic = (Topic) jndiContext.lookup(CONNECTION_TOPIC);
    // Lookup the Connection Factory
    connectionFactory = (TopicConnectionFactory) jndiContext.lookup(CONNECTION_FACTORY);
    connection = connectionFactory.createTopicConnection();
    // Create a Session
    TopicSession topicSession = connection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);
    // Create a subscriper for the topic
    TopicSubscriber topicSubscriber = topicSession.createSubscriber(topic);
    // Register this client as a subscriber to the topic
    topicSubscriber.setMessageListener(this);
    // Start the subscription
    connection.start();
  } catch (NamingException e) {
    e.printStackTrace();
  } catch (JMSException e) {
    e.printStackTrace();
  }
}

The processing of the messages posted to the topic is done in the method onMessage(Message). The method is part of the interface MessageListener which my class has to implement to be able to connect to a JMS topic. To start slowly in the initial step only the received message will be posted in a simple status bar of the GUI and in the console.

/** (non-Javadoc)
 *
 * @see javax.jms.MessageListener#onMessage(javax.jms.Message)
 */
@Override
public void onMessage(Message message) {
  TextMessage tm = (TextMessage) message;
  try {
    System.out.println(getClass().getName() + ": " + tm.getText());
    messageLog.setText("Received: " + tm.getText());
  } catch (JMSException e) {
    e.printStackTrace();
  }
}

The next part is posting any messages to the topic. In the client this is done in the method sendMessageToTopic()

protected void sendMessageToTopic() {
  try {
    // Create the session
    Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
    // Create Message Producer for the known topic
    MessageProducer producer = session.createProducer(topic);
    // Create and send a TextMessage
    TextMessage textMessage = session.createTextMessage();
    textMessage.setText("Message send at: " + new Date());
    producer.send(textMessage);
  } catch (JMSException e) {
    e.printStackTrace();
    messageLog.setText(e.getMessage());
  }
}

Finally I have to make sure that closing clients unregister from the topic. To get a save closing I register a shutdown hook to the runtime and close any connection to the application server.

// To close the connections during exit of the application, I register a shutdown hook.
Runtime.getRuntime().addShutdownHook(new Thread() {
  @Override
  public void run() {
    try {
      if (connection != null) {
        connection.close();
      }
    } catch (JMSException e) {
      e.printStackTrace();
    }
  }
});

My first shot of this class created the GUI first and afterwards the connection to the application. But I fail to use it in the first run and I got a NullPointerExcetion when I hit the button “send message”. A first analysis showed that connecting to the application server take some seconds and I was too fast in using the client. So I switch the initialization steps and connect first to the application server and create the GUI afterwards. This works fine.

The full code of the client can be found my GIT repository (see links at the end of this post).

Step 3 – Switch from text message to object message

It is a little bit unstable to parse a text message for shutdown related information so I created my own shutdown information class (ShutdownInfo). It contains a text that can be displayed to the user and a shutdown time. At this time the client will be terminated automatically – if not done by the user. The code is very simple and can be found in the GIT repository.

I extend the client with a new input element for a time and a new button to send the shutdown information to the topic.

Here’s the code to send the shutdown information:

protected void createAndSendShutdownMessage() {
  String time = timeTextField.getText();
  String[] timeElements = time.split(":");
  Calendar shutDownCalendar = Calendar.getInstance();
  // if timeElements contains 2 Elements than try to interpret as integer and
  // add this to the current time
  if (timeElements.length == 2) {
    // This should be created more robust
    shutDownCalendar.add(Calendar.HOUR_OF_DAY, Integer.parseInt(timeElements[0]));
    shutDownCalendar.add(Calendar.MINUTE, Integer.parseInt(timeElements[1]));
  }

  ShutdownInfo info = new ShutdownInfo(
    "The client will automatically shutdown\nat "
    + DateFormat.getDateTimeInstance().format(
    shutDownCalendar.getTime()) + ".",
    shutDownCalendar.getTime());

  try {
    // Create the session
    Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
    // Create Message Producer
    MessageProducer producer = session.createProducer(topic);
    // Send ObjectMessage
    ObjectMessage objMessage = session.createObjectMessage();
    objMessage.setObject(info);
    producer.send(objMessage);
  } catch (JMSException e) {
    e.printStackTrace();
    messageLog.setText(e.getMessage());
  }
}

First the shutdown time is calculated (if possible) or it is now. After that a ShutdownInfo object is created with a message to the user and the shutdown date. Finally the information is send with the known mechanism. The only difference to the method sendMessage2Topic() is that I use ObjectMessage and pack the ShutdownInfo with the method setObject(Serializable).

In the method onMessage(Message) I have differentiate now between handling the old messages of type TextMessage and the new message of type ObjectMessage.

@Override
public void onMessage(Message message) {
  try {
    // Handling text messages
    if (message instanceof TextMessage) {
      TextMessage tm = (TextMessage) message;
      System.out.println(getClass().getName() + ": " + tm.getText());
      messageLog.setText("Received: " + tm.getText());
    } else
    // Handling object messages
    if (message instanceof ObjectMessage) {
      ObjectMessage om = (ObjectMessage) message;
      // Checking the type of the object in the message
      if (om.getObject() instanceof ShutdownInfo) {
        ShutdownInfo sdi = (ShutdownInfo) om.getObject();
        System.out.println(sdi.getMessage());
        messageLog.setText(sdi.getMessage());
        // performing the shutdown if time is over
        if (sdi.getShutdownTime().before(new Date()))
          System.exit(0);
      }
    } else {
      // Handling unknown messages
      messageLog.setText("Unknown message type");
    }
  } catch (JMSException e) {
    e.printStackTrace();
  }
}

But what is that? I cannot perform a System.exit(0) in the method on Message(Message)? The GUI is terminating but the JVM is still running. Even if I move the System.exit(0) in a finally block at the end of the method the JVM is not dying.

Maybe the solution is to start a new thread and perform the termination there. Let’s try.

// performing the shutdown
Thread shutdownThread = new Thread() {
  @Override
  public void run() {
    try {
      sleep(1000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    System.exit(0);
  }
};
shutdownThread.start();

Ok, now the JVM is closing down completely.

Step 4 – delay the shutdown and notify the user

It would be nice if the user gets a notification first via a dialog box that the client will shut down automatically in a few minutes. To get this done this thread from the chapter before will be further extended.

First a dialog will be created and then published using the AWTEventQueue, second the delay between now and the desired shutdown time will be calculated. If the delay is less than 10 seconds the delay will be set to 10 seconds.

    // performing the shutdown
    Thread shutdownThread = new Thread() {
      @Override
      public void run() {
        // Create a runnable to display the dialog in the AWTEventQueue
        Runnable dialogRun = new Runnable() {
          @Override
          public void run() {
            JOptionPane.showMessageDialog(null, sdi.getMessage(), "Shutdown of the client",
              JOptionPane.WARNING_MESSAGE);
          }
        };

        SwingUtilities.invokeLater(dialogRun);

        try {
          // Calculate how long to wait before shutdown
          long delay = sdi.getShutdownTime().getTime() - System.currentTimeMillis();
          // we should wait at least 10 seconds
          if (delay < 10000)
            delay = 10000;
          sleep(delay);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
        System.exit(0);
      }
    };

    shutdownThread.start();

Open tasks

What happens to clients that connect to the topic after the shutdown message is send? They will not get the information and therefore not shutdown.

What happens to open transactions when the shutdown is performed? Do they do a automatically rollback?

These questions should be answered in a later blog post.

Links

Source code on Github: https://github.com/7droids/ShutdownClient