diff --git a/joram/joram/tools/jmscheck/pom.xml b/joram/joram/tools/jmscheck/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..2920753574307f6db3e2d78eb2725132285b9433
--- /dev/null
+++ b/joram/joram/tools/jmscheck/pom.xml
@@ -0,0 +1,100 @@
+
+
+
+ 4.0.0
+ joram-tools-jmscheck
+ bundle
+ JORAM :: joram :: tools :: jmscheck
+ JMS Health-Check bundle.
+
+
+ org.ow2.joram
+ joram-tools
+ 5.17.6-SNAPSHOT
+
+
+
+
+
+ org.apache.felix
+ maven-bundle-plugin
+ ${maven.bundle.plugin.version}
+ true
+
+
+ ${project.artifactId}
+ org.ow2.joram.tools.jmscheck.Activator
+
+ fr.dyade.aaa.common,
+ fr.dyade.aaa.util.management,
+ fr.dyade.aaa.agent,
+ javax.jms,
+ javax.naming,
+ fr.dyade.aaa.jndi2.client,
+ javax.transaction.xa,
+ org.osgi.framework,
+ org.objectweb.util.monolog,
+ org.objectweb.util.monolog.api,
+ org.objectweb.joram.client.jms,
+ org.objectweb.joram.client.jms.tcp,
+ org.objectweb.joram.client.jms.local
+
+
+ *
+
+
+
+
+ maven-assembly-plugin
+
+
+
+ attached
+
+ package
+
+
+ jar-with-dependencies
+
+
+
+ org.ow2.joram.tools.jmscheck.JMSCheck
+
+
+
+
+
+
+
+
+
+
+
+ org.ow2.spec.ee
+ ow2-jms-2.0-spec
+
+
+ org.ow2.joram
+ a3-common
+ ${project.version}
+
+
+ org.ow2.joram
+ a3-rt
+ ${project.version}
+
+
+ org.ow2.joram
+ joram-client-jms
+ ${project.version}
+
+
+ org.ow2.joram
+ jndi-client
+ ${project.version}
+
+
+
+
+
+
diff --git a/joram/joram/tools/jmscheck/src/main/java/org/ow2/joram/tools/jmscheck/Activator.java b/joram/joram/tools/jmscheck/src/main/java/org/ow2/joram/tools/jmscheck/Activator.java
new file mode 100644
index 0000000000000000000000000000000000000000..2faf4cf4211a3fda6a4e4628790095fc725756a0
--- /dev/null
+++ b/joram/joram/tools/jmscheck/src/main/java/org/ow2/joram/tools/jmscheck/Activator.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2020 ScalAgent Distributed Technologies
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA.
+ *
+ * Initial developer(s): ScalAgent Distributed Technologies
+ * Contributor(s):
+ */
+package org.ow2.joram.tools.jmscheck;
+
+
+import javax.naming.InitialContext;
+
+import org.objectweb.util.monolog.api.BasicLevel;
+import org.objectweb.util.monolog.api.Logger;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+
+import fr.dyade.aaa.agent.AgentServer;
+import fr.dyade.aaa.common.Debug;
+
+public class Activator implements BundleActivator {
+ public static final Logger logger = Debug.getLogger(Activator.class.getName());
+
+ public final static String CHECK_PREFIX = "org.ow2.joram.check.";
+
+ public final static String GLOBAL_PERIOD = CHECK_PREFIX + "period";
+ public final static String GLOBAL_TIMEOUT = CHECK_PREFIX + "timeout";
+
+ public final static String DUMP_FILE = CHECK_PREFIX + "dump.file";
+ public final static String DUMP_THRESHOLD = CHECK_PREFIX + "dump.threshold";
+ public final static String DUMP_DELAY = CHECK_PREFIX + "dump.delay";
+
+ public final static String JNDI_FILE = CHECK_PREFIX + "jndi.file";
+ public final static String JNDI_FACTORY = CHECK_PREFIX + "jndi.factory";
+ public final static String JNDI_HOST = CHECK_PREFIX + "jndi.host";
+ public final static String JNDI_PORT = CHECK_PREFIX + "jndi.port";
+
+ public static final String CF_NAME = CHECK_PREFIX + "cf";
+
+ public static final String USER_NAME = CHECK_PREFIX + "user";
+ public static final String PASSWORD = CHECK_PREFIX + "password";
+
+ public static final String QUEUE_NAME = CHECK_PREFIX + "queue";
+
+ private static BundleContext context;
+
+ public Activator() {
+ }
+
+ int period, timeout;
+ JMSStatus jmsStatus;
+
+ @Override
+ public void start(BundleContext context) throws Exception {
+ Activator.context = context;
+
+ // Gets the global period.
+ period = JMSStatus.DFLT_PERIOD;
+ String value = context.getProperty(GLOBAL_PERIOD);
+ if (value != null) {
+ try {
+ period = Integer.parseInt(value);
+ } catch (NumberFormatException exc) {
+ logger.log(BasicLevel.WARN,
+ "MqttCheckActivator.start: bad value for property " + GLOBAL_PERIOD + ", set to default.");
+ period = JMSStatus.DFLT_PERIOD;
+ }
+ }
+ if (period <= 0) return;
+
+ // Gets the global timeout.
+ timeout = JMSStatus.DFLT_TIMEOUT;
+ value = context.getProperty(GLOBAL_TIMEOUT);
+ if (value != null) {
+ try {
+ timeout = Integer.parseInt(value);
+ } catch (NumberFormatException exc) {
+ logger.log(BasicLevel.WARN,
+ "MqttCheckActivator.start: bad value for property " + GLOBAL_TIMEOUT + ", set to default.");
+ timeout = JMSStatus.DFLT_TIMEOUT;
+ }
+ }
+
+ // Gets the pathname of the dump file.
+ String dumpFilePath = context.getProperty(DUMP_FILE);
+ if (dumpFilePath == null) {
+ dumpFilePath = JMSStatus.DFLT_DUMP_FILE;
+ }
+
+ // Gets the threshold to generate a dump file.
+ int threshold = JMSStatus.DFLT_THRESHOLD;
+ value = context.getProperty(DUMP_THRESHOLD);
+ if (value != null) {
+ try {
+ threshold = Integer.parseInt(value);
+ } catch (NumberFormatException exc) {
+ logger.log(BasicLevel.WARN,
+ "MqttCheckActivator.start: bad value for property " + DUMP_THRESHOLD + ", set to default.");
+ threshold = JMSStatus.DFLT_THRESHOLD;
+ }
+ }
+
+ // Gets the minimal delay before to generate anew a dump file.
+ int delay = JMSStatus.DFLT_DELAY;
+ value = context.getProperty(DUMP_DELAY);
+ if (value != null) {
+ try {
+ delay = Integer.parseInt(value);
+ } catch (NumberFormatException exc) {
+ logger.log(BasicLevel.WARN,
+ "MqttCheckActivator.start: bad value for property " + DUMP_DELAY + ", set to default.");
+ delay = JMSStatus.DFLT_DELAY;
+ }
+ }
+
+ jmsStatus = new JMSStatus("Joram#" + AgentServer.getServerId(), period, timeout, dumpFilePath, threshold, delay);
+
+ String jndiFile = context.getProperty(JNDI_FILE);
+ String jndiFactory = context.getProperty(JNDI_FACTORY);
+ String jndiHost = context.getProperty(JNDI_HOST);
+ String jndiPort = context.getProperty(JNDI_PORT);
+
+ InitialContext ictx = jmsStatus.getInitialContext(jndiFile, jndiFactory, jndiHost, jndiPort);
+
+ createConnector(jmsStatus, "", ictx);
+ for (int i=1;; i++) {
+ if (!createConnector(jmsStatus, "." + i, ictx)) break;
+ }
+
+ jmsStatus.start();
+ }
+
+
+// public synchronized Object lookup(String name) throws NamingException {
+// if (logger.isLoggable(BasicLevel.DEBUG))
+// logger.log(BasicLevel.DEBUG, "Helper.lookup " + name);
+//
+// ClassLoader originalContextClassLoader = Thread.currentThread().getContextClassLoader();
+// try {
+// Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
+//
+// if (ictx == null)
+// ictx = new InitialContext(jndiProps);
+// return ictx.lookup(name);
+// } finally {
+// Thread.currentThread().setContextClassLoader(originalContextClassLoader);
+// }
+// }
+
+ /**
+ * Adds a new check component using the related properties.
+ *
+ * @param check The root component.
+ * @param suffix The property suffix.
+ * @param ictx The InitialContext to use.
+ * @return True if the component is successively created.
+ */
+ private boolean createConnector(JMSStatus check, String suffix, InitialContext ictx) {
+ String cfname = context.getProperty(CF_NAME + suffix);
+ if (cfname != null) {
+ String user = context.getProperty(USER_NAME + suffix);
+ String pass = context.getProperty(PASSWORD + suffix);
+ String qname = context.getProperty(QUEUE_NAME + suffix);
+
+ // TODO (AF): Allow to override period and timeout.
+ check.addConnectorStatus(cfname, ictx, user, pass, qname, period, timeout);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void stop(BundleContext context) throws Exception {
+ context = null;
+ jmsStatus.stop();
+ }
+}
diff --git a/joram/joram/tools/jmscheck/src/main/java/org/ow2/joram/tools/jmscheck/JMSCheck.java b/joram/joram/tools/jmscheck/src/main/java/org/ow2/joram/tools/jmscheck/JMSCheck.java
new file mode 100644
index 0000000000000000000000000000000000000000..85d1519d4434b53ebfda1895fa8895e4430fd196
--- /dev/null
+++ b/joram/joram/tools/jmscheck/src/main/java/org/ow2/joram/tools/jmscheck/JMSCheck.java
@@ -0,0 +1,98 @@
+package org.ow2.joram.tools.jmscheck;
+
+
+import javax.naming.InitialContext;
+
+import org.objectweb.util.monolog.api.BasicLevel;
+import org.objectweb.util.monolog.api.Logger;
+
+import fr.dyade.aaa.common.Debug;
+
+public class JMSCheck {
+ public static final Logger logger = Debug.getLogger(JMSCheck.class.getName());
+
+ public final static String GLOBAL_PERIOD = "period";
+ public final static String GLOBAL_TIMEOUT = "timeout";
+
+ public final static String JNDI_FILE = "jndi.file";
+ public final static String JNDI_FACTORY = "jndi.factory";
+ public final static String JNDI_HOST = "jndi.host";
+ public final static String JNDI_PORT = "jndi.port";
+
+ public static final String CF_NAME = "cf";
+
+ public static final String USER_NAME = "user";
+ public static final String PASSWORD = "password";
+
+ public static final String QUEUE_NAME = "queue";
+
+ static int period, timeout;
+ static JMSStatus jmsStatus;
+
+ public static void main(String[] args) throws Exception {
+ // Gets the global period.
+ period = JMSStatus.DFLT_PERIOD;
+ String value = System.getProperty(GLOBAL_PERIOD);
+ if (value != null) {
+ try {
+ period = Integer.parseInt(value);
+ } catch (NumberFormatException exc) {
+ logger.log(BasicLevel.WARN,
+ "MqttCheckActivator.start: bad value for property " + GLOBAL_PERIOD + ", set to default.");
+ period = JMSStatus.DFLT_PERIOD;
+ }
+ }
+ if (period <= 0) return;
+
+ // Gets the global timeout.
+ timeout = JMSStatus.DFLT_TIMEOUT;
+ value = System.getProperty(GLOBAL_TIMEOUT);
+ if (value != null) {
+ try {
+ timeout = Integer.parseInt(value);
+ } catch (NumberFormatException exc) {
+ logger.log(BasicLevel.WARN,
+ "MqttCheckActivator.start: bad value for property " + GLOBAL_TIMEOUT + ", set to default.");
+ timeout = JMSStatus.DFLT_TIMEOUT;
+ }
+ }
+
+ jmsStatus = new JMSStatus("Joram", period, timeout, null, 0, 0);
+
+ String jndiFile = System.getProperty(JNDI_FILE);
+ String jndiFactory = System.getProperty(JNDI_FACTORY);
+ String jndiHost = System.getProperty(JNDI_HOST);
+ String jndiPort = System.getProperty(JNDI_PORT);
+
+ InitialContext ictx = jmsStatus.getInitialContext(jndiFile, jndiFactory, jndiHost, jndiPort);
+
+ createConnector(jmsStatus, "", ictx);
+ for (int i=1;; i++) {
+ if (!createConnector(jmsStatus, "." + i, ictx)) break;
+ }
+
+ jmsStatus.start();
+ }
+
+ /**
+ * Adds a new check component using the related properties.
+ *
+ * @param check The root component.
+ * @param suffix The property suffix.
+ * @param ictx The InitialContext to use.
+ * @return True if the component is successively created.
+ */
+ private static boolean createConnector(JMSStatus jmsStatus, String suffix, InitialContext ictx) {
+ String cfname = System.getProperty(CF_NAME + suffix);
+ if (cfname != null) {
+ String user = System.getProperty(USER_NAME + suffix);
+ String pass = System.getProperty(PASSWORD + suffix);
+ String qname = System.getProperty(QUEUE_NAME + suffix);
+
+ // TODO (AF): Allow to override period and timeout.
+ jmsStatus.addConnectorStatus(cfname, ictx, user, pass, qname, period, timeout);
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/joram/joram/tools/jmscheck/src/main/java/org/ow2/joram/tools/jmscheck/JMSConnectorStatus.java b/joram/joram/tools/jmscheck/src/main/java/org/ow2/joram/tools/jmscheck/JMSConnectorStatus.java
new file mode 100644
index 0000000000000000000000000000000000000000..114d1a5b532637ae1451746172ec3f5546b162d2
--- /dev/null
+++ b/joram/joram/tools/jmscheck/src/main/java/org/ow2/joram/tools/jmscheck/JMSConnectorStatus.java
@@ -0,0 +1,399 @@
+/*
+ * Copyright (C) 2020 ScalAgent Distributed Technologies
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA.
+ *
+ * Initial developer(s): ScalAgent Distributed Technologies
+ * Contributor(s):
+ */
+package org.ow2.joram.tools.jmscheck;
+
+import java.text.DateFormat;
+import java.util.Date;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+import javax.jms.Connection;
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageProducer;
+import javax.jms.Session;
+import javax.jms.TextMessage;
+import javax.naming.InitialContext;
+import javax.naming.NamingException;
+
+import org.objectweb.joram.client.jms.ConnectionFactory;
+import org.objectweb.joram.client.jms.Queue;
+import org.objectweb.util.monolog.api.BasicLevel;
+import org.objectweb.util.monolog.api.Logger;
+
+import fr.dyade.aaa.common.Debug;
+import fr.dyade.aaa.util.management.MXWrapper;
+
+public class JMSConnectorStatus implements JMSConnectorStatusMBean {
+ public static Logger logger = Debug.getLogger(JMSConnectorStatus.class.getName());
+
+ public static final int RUNNING = 0;
+ public static final int UNREACHABLE = 1;
+ public static String[] info = { "RUNNING", "UNREACHABLE" };
+
+ /** retryStatusCount == 0 => RUNNING, retryStatusCount > 0 => UNREACHABLE */
+ private int retryStatusCount = UNREACHABLE;
+
+ @Override
+ public int getStatus() {
+ return retryStatusCount;
+ }
+
+ @Override
+ public String getStatusInfo() {
+ if (retryStatusCount > 0)
+ return info[1] + '(' + retryStatusCount + ')';
+ return info[0];
+ }
+
+ void setStatus(int retry) {
+ JMSStatus.checkDump(cfname, retry);
+ this.retryStatusCount = retry;
+ }
+
+ private int nbtry = 0;
+
+ @Override
+ public int getNbTry() {
+ return nbtry;
+ }
+
+ private transient int failures = 0;
+
+ @Override
+ public int getNbFailures() {
+ return failures;
+ }
+
+ private String errorMsg;
+
+ @Override
+ public String getErrorMsg() {
+ return errorMsg;
+ }
+
+ private long latencyConnect = -1;
+
+ @Override
+ public long getLatencyConnect() {
+ return latencyConnect;
+ }
+
+ private void setLatencyConnect(long latencyConnect) {
+ this.latencyConnect = latencyConnect;
+ }
+
+ private String lastConnectTime = "-";
+
+ @Override
+ public String getLastConnectTime() {
+ return lastConnectTime;
+ }
+
+ private long latencyPubSub = -1;
+
+ @Override
+ public long getLatencyPubSub() {
+ return latencyPubSub;
+ }
+
+ private void setLatencyPubSub(long latencyPubSub) {
+ this.latencyPubSub = latencyPubSub;
+ }
+
+ /** The period of time between 2 checks. */
+ private int period = 60; // in seconds
+
+ @Override
+ public int getPeriod() {
+ return period;
+ }
+
+ @Override
+ public void setPeriod(int period) {
+ try {
+ if (period < 5)
+ period = 5;
+ if (period < (2* timeout))
+ period = 2* timeout;
+ this.period = period;
+ schedule();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ /** The maximum time waiting for connection. */
+ private int timeout = 10; // in seconds
+
+ @Override
+ public int getTimeOut() {
+ return timeout;
+ }
+
+ @Override
+ public void setTimeOut(int timeOut) {
+ if (timeOut < 5)
+ timeOut = 5;
+ if (period < (2* timeOut))
+ period = 2* timeOut;
+ this.timeout = timeOut;
+ schedule();
+ }
+
+ /** Root component */
+ JMSStatus jmsStatus;
+
+ /** JNDI Name of the ConnectionFactory to use. */
+ private String cfname;
+
+ @Override
+ public String getName() {
+ return cfname;
+ }
+
+ /* User name and password for authentication */
+ private String user;
+ private String pass;
+
+ /* Internal name of the queue, be careful this name is not a JNDI name. */
+ private String qname;
+
+ private InitialContext ictx;
+
+ private ConnectionFactory cf;
+ private Queue queue;
+
+ ScheduledFuture> callableHandle;
+
+ /**
+ *
+ * @param jmsStatus Root component.
+ * @param cfname JNDI name of the ConnectionFactory to use.
+ * @param ictx JNDI context allowing to get the ConnectionFactory.
+ * @param user User name for authentication, if null uses the ConnectioNfactory default.
+ * @param pass Password for authentication, if null uses the ConnectioNfactory default.
+ * @param qname Name of the queue used to check the JMS behavior. It is an internal not a JNDI name, this destination is created by the
+ * module.
+ * @param period Period (in seconds) between 2 attempts of check.
+ * @param timeOut Maximum amount of time to wait either the connection or the message.
+ */
+ public JMSConnectorStatus(JMSStatus jmsStatus,
+ String cfname,
+ InitialContext ictx,
+ String user, String pass,
+ String qname,
+ int period, int timeOut) {
+ this.jmsStatus = jmsStatus;
+
+ this.cfname = cfname;
+ this.ictx = ictx;
+ this.user = user;
+ this.pass = pass;
+
+ this.qname = qname;
+
+ this.period = period;
+ this.timeout = timeOut;
+
+ mbeanName = JMSStatus.mbeanName + ",cf=" + cfname;
+ }
+
+ private void getConnectionFactory() throws NamingException {
+ if (cf != null) return;
+
+ ClassLoader originalContextClassLoader = Thread.currentThread().getContextClassLoader();
+ try {
+ Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
+ cf = (ConnectionFactory) ictx.lookup(cfname);
+ } catch (NamingException exc) {
+ if (logger.isLoggable(BasicLevel.DEBUG))
+ logger.log(BasicLevel.WARN, "JMSConnectorStatus(" + cfname + "): Cannot get ConnectionFactory.", exc);
+ else
+ logger.log(BasicLevel.WARN, "JMSConnectorStatus(" + cfname + "): Cannot get ConnectionFactory.");
+ throw exc;
+ } finally {
+ Thread.currentThread().setContextClassLoader(originalContextClassLoader);
+ }
+ }
+
+ // --------------------------------------------------------------------------------
+ // JMS Health-check
+ // --------------------------------------------------------------------------------
+
+ private void schedule() {
+ logger.log(BasicLevel.WARN, "JMSConnectorStatus(" + cfname + ").schedule #1");
+ if (callableHandle != null && !callableHandle.isCancelled()) {
+ callableHandle.cancel(true);
+ }
+ if (period > 0) {
+ logger.log(BasicLevel.WARN, "JMSConnectorStatus(" + cfname + ").schedule #2");
+ callableHandle = JMSStatus.scheduler.scheduleAtFixedRate(new Runnable() {
+ public void run() {
+ try {
+ call();
+ } catch (Throwable t) {
+ logger.log(BasicLevel.WARN, "JMSConnectorStatus(" + cfname + ").schedule", t);
+ }
+ }
+ }, period, period, TimeUnit.SECONDS);
+ }
+ logger.log(BasicLevel.WARN, "JMSConnectorStatus(" + cfname + ").schedule #3");
+ }
+
+ public void call() {
+ logger.log(BasicLevel.WARN, "JMSConnectorStatus(" + cfname + ").call #1");
+ nbtry += 1;
+
+ try {
+ logger.log(BasicLevel.WARN, "JMSConnectorStatus(" + cfname + ").call #2 -> " + cf);
+ getConnectionFactory();
+ logger.log(BasicLevel.WARN, "JMSConnectorStatus(" + cfname + ").call #3 -> " + cf);
+ } catch (NamingException exc) {
+ failures += 1;
+ setStatus(getStatus()+1);
+ setLatencyConnect(-1L);
+ setLatencyPubSub(-1L);
+ errorMsg = "Cannot get ConnectionFactory";
+ if (logger.isLoggable(BasicLevel.DEBUG))
+ logger.log(BasicLevel.WARN, "JMSConnectorStatus.check(" + cfname + ")<: Cannot get ConnectionFactory.", exc);
+ else
+ logger.log(BasicLevel.WARN, "JMSConnectorStatus.check(" + cfname + "): Cannot get ConnectionFactory.");
+ return;
+ }
+
+ Connection cnx = null;
+ try {
+ long start = System.nanoTime();
+ cf.getParameters().connectingTimer = timeout;
+ logger.log(BasicLevel.WARN, "JMSConnectorStatus(" + cfname + ").call #4");
+ if ((user == null) || (pass == null))
+ cnx = cf.createConnection();
+ else
+ cnx = cf.createConnection(user, pass);
+ logger.log(BasicLevel.WARN, "JMSConnectorStatus(" + cfname + ").call #5");
+ long dt = System.nanoTime() - start;
+ setLatencyConnect(dt / 1000000L);
+ lastConnectTime = DateFormat.getDateTimeInstance().format(new Date());
+ } catch (JMSException exc) {
+ failures += 1;
+ setStatus(getStatus()+1);
+ setLatencyConnect(-1L);
+ setLatencyPubSub(-1L);
+ errorMsg = "Cannot connect";
+ if (logger.isLoggable(BasicLevel.DEBUG))
+ logger.log(BasicLevel.WARN, "JMSConnectorStatus.check(" + cfname + "): Cannot connect.", exc);
+ else
+ logger.log(BasicLevel.WARN, "JMSConnectorStatus.check(" + cfname + "): Cannot connect.");
+ return;
+ }
+
+ try {
+ logger.log(BasicLevel.WARN, "JMSConnectorStatus(" + cfname + ").call #6");
+ Session sess = cnx.createSession(false, Session.AUTO_ACKNOWLEDGE);
+ logger.log(BasicLevel.WARN, "JMSConnectorStatus(" + cfname + ").call #7");
+ if (queue == null)
+ queue = (Queue) sess.createQueue(qname);
+ logger.log(BasicLevel.WARN, "JMSConnectorStatus(" + cfname + ").call #8");
+ MessageProducer producer = sess.createProducer(queue);
+ MessageConsumer cons = sess.createConsumer(queue);
+ cnx.start();
+
+ logger.log(BasicLevel.WARN, "JMSConnectorStatus(" + cfname + ").call #9");
+ TextMessage sent = sess.createTextMessage("Test number " + nbtry);
+ long start = System.nanoTime();
+ producer.send(sent, Message.DEFAULT_DELIVERY_MODE, Message.DEFAULT_PRIORITY, timeout *1000);
+
+ logger.log(BasicLevel.WARN, "JMSConnectorStatus(" + cfname + ").call #10");
+ Message received = cons.receive(timeout *1000);
+ logger.log(BasicLevel.WARN, "JMSConnectorStatus(" + cfname + ").call #11 -> " + received);
+ long dt = System.nanoTime() - start;
+ if (received == null) {
+ failures += 1;
+ setStatus(getStatus()+1);
+ setLatencyPubSub(-1L);
+ errorMsg = "Message not received";
+ logger.log(BasicLevel.WARN, "JMSConnectorStatus.check(" + cfname + ") message not received." + dt);
+ } else {
+ retryStatusCount = 0;
+ setLatencyPubSub(dt / 1000000L);
+ errorMsg = null;
+ }
+ } catch (JMSException exc) {
+ failures += 1;
+ setStatus(getStatus()+1);
+ setLatencyPubSub(-1L);
+ errorMsg = "Error during message send/receive.";
+ if (logger.isLoggable(BasicLevel.DEBUG))
+ logger.log(BasicLevel.WARN, "JMSConnectorStatus.check(" + cfname + "): Error during message send/receive.", exc);
+ else
+ logger.log(BasicLevel.WARN, "JMSConnectorStatus.check(" + cfname + "): Error during message send/receive.");
+ } finally {
+ try {
+ cnx.close();
+ } catch (JMSException exc) {
+ if (logger.isLoggable(BasicLevel.DEBUG))
+ logger.log(BasicLevel.WARN, "JMSConnectorStatus.check(" + cfname + "): Error closing connection.", exc);
+ else
+ logger.log(BasicLevel.WARN, "JMSConnectorStatus.check(" + cfname + "): Error closing connection.");
+ }
+ }
+ }
+
+ // --------------------------------------------------------------------------------
+ // Life cycle
+ // --------------------------------------------------------------------------------
+
+ void start() {
+ schedule();
+ registerMBean();
+ }
+
+ void stop() {
+ unregisterMBean();
+ if (callableHandle != null)
+ callableHandle.cancel(true);
+ }
+
+ // --------------------------------------------------------------------------------
+ // JMX handling
+ // --------------------------------------------------------------------------------
+
+ private String mbeanName;
+
+ private void registerMBean() {
+ try {
+ MXWrapper.registerMBean(this, jmsStatus.getName(), mbeanName);
+ } catch (Exception e) {
+ logger.log(BasicLevel.WARN, "registerMBean: " + mbeanName, e);
+ }
+ }
+
+ private void unregisterMBean() {
+ try {
+ MXWrapper.unregisterMBean(jmsStatus.getName(), mbeanName);
+ } catch (Exception e) {
+ logger.log(BasicLevel.WARN, "unregisterMBean: " + mbeanName, e);
+ }
+ }
+}
diff --git a/joram/joram/tools/jmscheck/src/main/java/org/ow2/joram/tools/jmscheck/JMSConnectorStatusMBean.java b/joram/joram/tools/jmscheck/src/main/java/org/ow2/joram/tools/jmscheck/JMSConnectorStatusMBean.java
new file mode 100644
index 0000000000000000000000000000000000000000..4de5e4cda7524b4ceb262855cf92da77dad5ba26
--- /dev/null
+++ b/joram/joram/tools/jmscheck/src/main/java/org/ow2/joram/tools/jmscheck/JMSConnectorStatusMBean.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2020 ScalAgent Distributed Technologies
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA.
+ *
+ * Initial developer(s): ScalAgent Distributed Technologies
+ * Contributor(s):
+ */
+package org.ow2.joram.tools.jmscheck;
+
+public interface JMSConnectorStatusMBean {
+ String getName();
+
+ /**
+ * Returns the period of time between 2 checks.
+ * @return the period of time between 2 checks.
+ */
+ int getPeriod();
+
+ /**
+ * Sets the period of time between 2 checks.
+ * @param period the period of time between 2 checks.
+ */
+ void setPeriod(int period);
+
+ /**
+ * Returns the maximum time waiting for connection.
+ * @return the maximum time waiting for connection.
+ */
+ int getTimeOut();
+
+ /**
+ * Sets the maximum time waiting for connection.
+ * @param timeOut the maximum time waiting for connection.
+ */
+ void setTimeOut(int timeOut);
+
+ /**
+ * Returns the status of the connector: 0 => RUNNING, >0 => UNREACHABLE.
+ *
+ * @return the number of unreachable connection
+ */
+ int getStatus();
+
+ String getStatusInfo();
+
+ String getLastConnectTime();
+
+ int getNbTry();
+
+ int getNbFailures();
+
+ long getLatencyConnect();
+
+ long getLatencyPubSub();
+
+ String getErrorMsg();
+}
diff --git a/joram/joram/tools/jmscheck/src/main/java/org/ow2/joram/tools/jmscheck/JMSStatus.java b/joram/joram/tools/jmscheck/src/main/java/org/ow2/joram/tools/jmscheck/JMSStatus.java
new file mode 100644
index 0000000000000000000000000000000000000000..1e96f03158f434225497a5f08e10f62e21b3fcaf
--- /dev/null
+++ b/joram/joram/tools/jmscheck/src/main/java/org/ow2/joram/tools/jmscheck/JMSStatus.java
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2020 ScalAgent Distributed Technologies
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA.
+ *
+ * Initial developer(s): ScalAgent Distributed Technologies
+ * Contributor(s):
+ */
+package org.ow2.joram.tools.jmscheck;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Properties;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+
+import javax.naming.InitialContext;
+import javax.naming.NamingException;
+
+import org.objectweb.util.monolog.api.BasicLevel;
+import org.objectweb.util.monolog.api.Logger;
+
+import fr.dyade.aaa.agent.AgentServer;
+import fr.dyade.aaa.agent.SCServer;
+import fr.dyade.aaa.common.Debug;
+import fr.dyade.aaa.util.management.MXWrapper;
+
+public class JMSStatus implements JMSStatusMBean {
+ public static final Logger logger = Debug.getLogger(JMSStatus.class.getName());
+
+ @Override
+ public int getStatus() {
+ for (JMSConnectorStatus connector : connectors.values()) {
+ if (connector.getStatus() != 0)
+ return JMSConnectorStatus.UNREACHABLE;
+ }
+ return JMSConnectorStatus.RUNNING;
+ }
+
+ @Override
+ public String getStatusInfo() {
+ return JMSConnectorStatus.info[getStatus()];
+ }
+
+ static final int DFLT_PERIOD = 60; // in seconds
+ private int globalPeriod = DFLT_PERIOD; // in seconds
+
+ @Override
+ public int getPeriod() {
+ return globalPeriod;
+ }
+
+ @Override
+ public void setPeriod(int period) {
+ this.globalPeriod = period;
+ for (JMSConnectorStatus connector : connectors.values()) {
+ connector.setPeriod(period);
+ }
+ }
+
+ static final int DFLT_TIMEOUT = 10; // in seconds
+ private int globalTimeout = DFLT_TIMEOUT; // in seconds
+
+ @Override
+ public int getTimeout() {
+ return globalTimeout;
+ }
+
+ @Override
+ public void setTimeout(int timeout) {
+ this.globalTimeout = timeout;
+ }
+
+ static final int DFLT_THRESHOLD = 5;
+ static int threshold = DFLT_THRESHOLD;
+
+ @Override
+ public int getThreshold() {
+ return threshold;
+ }
+
+ @Override
+ public void setThreshold(int threshold) {
+ this.threshold = threshold;
+ }
+
+ static final int DFLT_DELAY = 600;
+ static int delay = DFLT_DELAY;
+
+ @Override
+ public int getDelay() {
+ return delay;
+ }
+
+ @Override
+ public void setDelay(int delay) {
+ this.delay = delay;
+ }
+
+ private HashMap connectors;
+
+// public void add(String name, JMSConnectorStatus connector) {
+// connectors.put(name, connector);
+// }
+
+ public void addConnectorStatus(String cfname, InitialContext ictx,
+ String user, String pass, String qname,
+ int period, int timeout) {
+ JMSConnectorStatus connector = new JMSConnectorStatus(this, cfname, ictx, user, pass, qname, period, timeout);
+ connectors.put(cfname, connector);
+ }
+
+ static final String DFLT_DUMP_FILE = "joram.dump";
+
+ static String dumpFilePath = DFLT_DUMP_FILE;
+
+ static long lastDump = 0L;
+
+ public static void checkDump(String name, int retry) {
+ if ((dumpFilePath == null) || (threshold <= 0) || (delay <= 0))
+ return;
+
+ long current = System.currentTimeMillis();
+ if ((retry >= threshold) && ((current - lastDump) > delay)) {
+ JMSStatus.dumpServerState(dumpFilePath + '.' + current,
+ "Fail to connect to " + name + " after " + retry + " attempts.");
+ JMSStatus.lastDump = current;
+ }
+ }
+
+ public static final String JNDI_FACTORY_DFLT = "fr.dyade.aaa.jndi2.client.NamingContextFactory";
+ public static final String JNDI_HOST_DFLT = "localhost";
+ public static final String JNDI_PORT_DFLT = "16400";
+
+ static ScheduledExecutorService scheduler;
+ private static final int DFLT_THREAD_POOL_SIZE = 1;
+
+ private String name;
+
+ public String getName() {
+ return name;
+ }
+
+ /**
+ *
+ * @param name Name of component, used to define the domain of the JMX MBeans.
+ * @param period Default period of activation for checks (seconds).
+ * @param timeout Default timeout for checks (seconds).
+ * @param dumpFilePath Pathname for server dump, the current time is aggregated at the end of the file name.
+ * @param threshold Number of consecutive failures needed to trigger a dump.
+ * @param delay Minimal delay between 2 dumps (seconds).
+ */
+ public JMSStatus(String name, int period, int timeout, String dumpFilePath, int threshold, int delay) {
+ this.name = name;
+ this.globalPeriod = period;
+ this.globalTimeout = timeout;
+
+ this.dumpFilePath = dumpFilePath;
+ this.threshold = threshold;
+ this.delay = delay;
+
+ connectors = new HashMap();
+ }
+
+ /**
+ *
+ * @param jndiFile Path of JNDI properties file. If not defined, Joram's default are used. "fr.dyade.aaa.jndi2.client.NamingContextFactory" for
+ * JNDI Factory, "localhost", and 16400 for host and port. These values can be overloaded by specific properties below.
+ * @param jndiFactory Classname of the JNDI factory (see "java.naming.factory.initial" property).
+ * @param jndiHost Hostname ou IP address of JNDI server.
+ * @param jndiPort Listening port of JNDI server.
+ * @return
+ * @throws IOException
+ * @throws NamingException
+ */
+ InitialContext getInitialContext(String jndiFile,
+ String jndiFactory,
+ String jndiHost,
+ String jndiPort) throws IOException, NamingException {
+ Properties jndiProps = new Properties();
+ if (jndiFile != null) {
+ jndiProps.load(new FileInputStream(jndiFile));
+ } else {
+ jndiProps.setProperty("java.naming.factory.initial", JNDI_FACTORY_DFLT);
+ jndiProps.setProperty("java.naming.factory.host", JNDI_HOST_DFLT);
+ jndiProps.setProperty("java.naming.factory.port", JNDI_PORT_DFLT);
+ }
+
+ if (jndiFactory != null)
+ jndiProps.setProperty("java.naming.factory.initial", jndiFactory);
+
+ if (jndiHost != null)
+ jndiProps.setProperty("java.naming.factory.host", jndiHost);
+
+ if (jndiPort != null)
+ jndiProps.setProperty("java.naming.factory.port", jndiPort);
+
+ ClassLoader originalContextClassLoader = Thread.currentThread().getContextClassLoader();
+ try {
+ Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
+
+ InitialContext ictx = new InitialContext(jndiProps);
+ return ictx;
+ } finally {
+ Thread.currentThread().setContextClassLoader(originalContextClassLoader);
+ }
+ }
+
+ // --------------------------------------------------------------------------------
+ // Life cycle
+ // --------------------------------------------------------------------------------
+
+ @Override
+ public void start() throws Exception {
+ if (scheduler == null || scheduler.isTerminated())
+ scheduler = Executors.newScheduledThreadPool(DFLT_THREAD_POOL_SIZE);
+
+ // start task
+ for (JMSConnectorStatus status : connectors.values()) {
+ status.start();
+ }
+ registerMBean();
+ }
+
+ @Override
+ public void stop() throws Exception {
+ for (JMSConnectorStatus status : connectors.values()) {
+ try {
+ status.stop();
+ } catch (Exception e) { }
+ }
+ scheduler.shutdown();
+ unregisterMBean();
+ }
+
+ // --------------------------------------------------------------------------------
+ // JMX handling
+ // --------------------------------------------------------------------------------
+
+ static String mbeanName = "type=healthcheck";
+
+ public void registerMBean() {
+ try {
+ MXWrapper.registerMBean(this, name, mbeanName);
+ } catch (Exception e) {
+ logger.log(BasicLevel.WARN, "registerMBean: " + mbeanName, e);
+ }
+ }
+
+ public void unregisterMBean() {
+ try {
+ MXWrapper.unregisterMBean(name, mbeanName);
+ } catch (Exception e) {
+ logger.log(BasicLevel.WARN, "unregisterMBean: " + mbeanName, e);
+ }
+ }
+
+ @Override
+ public void dumpServerState(String path) {
+ dumpServerState(path, null);
+ }
+
+ public static void dumpServerState(String path, String cause) {
+ SCServer.dumpServerState(path, cause);
+ }
+}
diff --git a/joram/joram/tools/jmscheck/src/main/java/org/ow2/joram/tools/jmscheck/JMSStatusMBean.java b/joram/joram/tools/jmscheck/src/main/java/org/ow2/joram/tools/jmscheck/JMSStatusMBean.java
new file mode 100644
index 0000000000000000000000000000000000000000..bfcb456aa2466141344c17719ec90f4043315ae4
--- /dev/null
+++ b/joram/joram/tools/jmscheck/src/main/java/org/ow2/joram/tools/jmscheck/JMSStatusMBean.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2020 ScalAgent Distributed Technologies
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA.
+ *
+ * Initial developer(s): ScalAgent Distributed Technologies
+ * Contributor(s):
+ */
+package org.ow2.joram.tools.jmscheck;
+
+public interface JMSStatusMBean {
+
+ /**
+ * Starts the JMS healthcheck component.
+ *
+ * @throws Exception
+ */
+ void start() throws Exception;
+
+ /**
+ * Stops the JMS healthcheck component.
+ *
+ * @throws Exception
+ */
+ void stop() throws Exception;
+
+ int getStatus();
+
+ String getStatusInfo();
+
+ int getPeriod();
+
+ void setPeriod(int period);
+
+ int getTimeout();
+
+ void setTimeout(int timeout);
+
+ int getThreshold();
+
+ void setThreshold(int threshold);
+
+ int getDelay();
+
+ void setDelay(int delay);
+
+ void dumpServerState(String path);
+}
diff --git a/joram/joram/tools/pom.xml b/joram/joram/tools/pom.xml
index caadb4a4a8f0f49539a11679468641e1a5756898..59cd588d24bb6d77a0b05aabefdcdd52eac2ba67 100644
--- a/joram/joram/tools/pom.xml
+++ b/joram/joram/tools/pom.xml
@@ -17,5 +17,6 @@
jasp
rest
monitoring
+ jmscheck
\ No newline at end of file