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"
Comments