JMX Server lifecycle events.

Version 3

    With WildFly 11 you can now listen to JMX notifications on a server lifecycle events.

    A server will now send AttributeChangeNotifications for the attributes RuntimeConfigurationState and RunningState on the object named "jboss.root:type=state".

     

    What does this mean to me ?

     

    There are 2 kinds of events on a WildFly server lifecyle :

    * those that concern the state of the model configuration compared to the running configuration : restart-required, reload-required, starting, stopping, stopped and ok.

    * those that concern the running state of a server : stopped, starting, pre-suspended, suspended, stopping, suspending, admin_only and normal. Please note that you can only suspend and resume standalone or managed servers instance.

     

    So now you can simply register a javax.management.NotificationListener on "jboss.root:type=state" and get all those notifications.

    You can filter on the attribute name to get only those that you are interested in.

     

    Some code

     

    Let's create a simple javax.management.NotificationListener

    public class StateNotificationListener implements NotificationListener {
    
        public static final String RUNTIME_CONFIGURATION_FILENAME = "runtime-configuration-notifications.txt";
        public static final String RUNNING_FILENAME = "running-notifications.txt";
        private final Path targetFile;
    
        public ControlledStateNotificationListener() {
            this.targetFile = Paths.get("notifications/data").toAbsolutePath();
            init(targetFile);
        }
    
        protected Path getRuntimeConfigurationTargetFile() {
            return this.targetFile.resolve(RUNTIME_CONFIGURATION_FILENAME);
        }
    
        protected Path getRunningConfigurationTargetFile() {
            return this.targetFile.resolve(RUNNING_FILENAME);
        }
    
        protected final void init(Path targetFile) {
            if (!Files.exists(targetFile)) {
                try {
                   Files.createDirectories(targetFile);
                   if (!Files.exists(targetFile.resolve(RUNTIME_CONFIGURATION_FILENAME))) {
                        Files.createFile(targetFile.resolve(RUNTIME_CONFIGURATION_FILENAME));
                   }
                   if (!Files.exists(targetFile.resolve(RUNNING_FILENAME))) {
                        Files.createFile(targetFile.resolve(RUNNING_FILENAME));
                   }
                } catch (IOException ex) {
                    Logger.getLogger(StateNotificationListener.class).error("Problem handling JMX Notification", ex);
                }
            }
        }
    
        @Override
        public void handleNotification(Notification notification, Object handback) {
            AttributeChangeNotification attributeChangeNotification = (AttributeChangeNotification) notification;
            if ("RuntimeConfigurationState".equals(attributeChangeNotification.getAttributeName())) {
                writeNotification(attributeChangeNotification, getRuntimeConfigurationTargetFile());
            } else {
                writeNotification(attributeChangeNotification, getRunningConfigurationTargetFile());
            }
        }
    
        private void writeNotification(AttributeChangeNotification notification, Path path) {
            try (BufferedWriter in = Files.newBufferedWriter(path, StandardCharsets.UTF_8, StandardOpenOption.APPEND)) {
                in.write(String.format("%s %s %s %s", notification.getType(), notification.getSequenceNumber(), notification.getSource().toString(), notification.getMessage()));
                in.newLine();
                in.flush();
            } catch (IOException ex) {
                Logger.getLogger(StateNotificationListener.class).error("Problem handling JMX Notification", ex);
            }
        }
    
    }
    

     

    Now we can just deploy it using a SAR and registering our listener :

     

    mbeanServer.addNotificationListener(new ObjectName("jboss.root:type=state"), new StateNotificationListener(), null, null);
    

     

    and here we are, ready to listen to all those events.

    Using a Java Agent

     

    Another way to register such a listener is to use a Java agent. This requires us to write such an agent like this ( you may find the whole agent source code attached) :

     

    public class WildFlyStateAgent {
    
        public static void premain(String agentArgs) {
            try {
                final Path runningTargetFile = new File("notifications/data/running-states.txt").toPath();
                final Path runtimeConfigurationTargetFile = new File("notifications/data/runtime-configuration-states.txt").toPath();
                Files.createDirectories(runningTargetFile.getParent());
                Files.deleteIfExists(runningTargetFile);
                Files.createFile(runningTargetFile);
                Files.deleteIfExists(runtimeConfigurationTargetFile);
                Files.createFile(runtimeConfigurationTargetFile);
                final ObjectName name = new ObjectName("jboss.root:type=state");
                final MBeanServer server = ManagementFactory.getPlatformMBeanServer();
                MBeanServerNotificationFilter filter = new MBeanServerNotificationFilter();
                filter.enableAllObjectNames();
                server.addNotificationListener(MBeanServerDelegate.DELEGATE_NAME, new NotificationListener() {
                    @Override
                    public void handleNotification(Notification notification, Object handback) {
                        MBeanServerNotification mbs = (MBeanServerNotification) notification;
                        if (MBeanServerNotification.REGISTRATION_NOTIFICATION.equals(mbs.getType()) && name.equals(mbs.getMBeanName())) {
                            try {
                                server.addNotificationListener(name, new NotificationListener() {
                                    @Override
                                    public void handleNotification(Notification notification, Object handback) {
                                        AttributeChangeNotification attributeChangeNotification = (AttributeChangeNotification) notification;
                                        if ("RunningState".equals(attributeChangeNotification.getAttributeName())) {
                                            try (BufferedWriter in = Files.newBufferedWriter(runningTargetFile, StandardCharsets.UTF_8, StandardOpenOption.APPEND)) {
                                                in.write(String.format("%s %s %s %s", notification.getType(), notification.getSequenceNumber(), notification.getSource().toString(), notification.getMessage()));
                                                in.newLine();
                                                in.flush();
                                            } catch (IOException ex) {
                                                throw new RuntimeException(ex);
                                            }
                                        }
                                        else if ("RuntimeConfigurationState".equals(attributeChangeNotification.getAttributeName())) {
                                            try (BufferedWriter in = Files.newBufferedWriter(runtimeConfigurationTargetFile, StandardCharsets.UTF_8, StandardOpenOption.APPEND)) {
                                                in.write(String.format("%s %s %s %s", notification.getType(), notification.getSequenceNumber(), notification.getSource().toString(), notification.getMessage()));
                                                in.newLine();
                                                in.flush();
                                            } catch (IOException ex) {
                                                throw new RuntimeException(ex);
                                            }
                                        }
                                    }
                                }, null, null);
                            } catch (InstanceNotFoundException ex) {
                                throw new RuntimeException(ex);
                            }
                        }
                    }
                }, filter, null);
            } catch (MalformedObjectNameException | InstanceNotFoundException | IOException ex) {
                throw new RuntimeException(ex);
            }
        }
    }
    

     

    Now we just have to edit the standalone.conf to start our agent when starting the WildFly server instance by changing the $JBOSS_MODULES_SYSTEM_PKGS and adding some $JAVA_OPTS like this :

     

    if [ "x$JBOSS_MODULES_SYSTEM_PKGS" = "x" ]; then
       JBOSS_MODULES_SYSTEM_PKGS="org.jboss.byteman,org.jboss.logmanager,org.wildfly.sample.jmxagent"
    fi
    
    JAVA_OPTS="$JAVA_OPTS -Djava.util.logging.manager=org.jboss.logmanager.LogManager -Xbootclasspath/p:$JBOSS_HOME/modules/system/layers/base/org/jboss/logmanager/main/jboss-logmanager-2.0.4.Final.jar -javaagent:/home/ehsavoie/NetBeansProjects/jmxagent/target/jmxagent.jar"