/* * Copyright Technophobia Ltd 2012 * * This file is part of Substeps. * * Substeps 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 3 of the License, or * (at your option) any later version. * * Substeps 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 Substeps. If not, see <http://www.gnu.org/licenses/>. */ package com.technophobia.substeps.runner; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.util.List; import java.util.Map; import javax.management.*; import javax.management.remote.JMXConnector; import javax.management.remote.JMXConnectorFactory; import javax.management.remote.JMXServiceURL; import javax.naming.ServiceUnavailableException; import com.technophobia.substeps.execution.ExecutionNodeResult; import org.apache.maven.plugin.MojoExecutionException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.technophobia.substeps.execution.node.RootNode; import com.technophobia.substeps.jmx.SubstepsServerMBean; /** * @author ian * */ public class SubstepsJMXClient implements SubstepsRunner, NotificationListener { Logger log = LoggerFactory.getLogger(SubstepsJMXClient.class); private SubstepsServerMBean mbean; private JMXConnector cntor = null; private MBeanServerConnection mbsc = null; private static final int JMX_CLIENT_TIMEOUT_SECS = 10; public void init(final int portNumber) throws MojoExecutionException { final String url = "service:jmx:rmi:///jndi/rmi://:" + portNumber + "/jmxrmi"; // The address of the connector server try { final JMXServiceURL serviceURL = new JMXServiceURL(url); final Map<String, ?> environment = null; // Create the JMXCconnectorServer this.cntor = getConnector(serviceURL, environment); // Obtain a "stub" for the remote MBeanServer mbsc = this.cntor.getMBeanServerConnection(); final ObjectName objectName = new ObjectName(SubstepsServerMBean.SUBSTEPS_JMX_MBEAN_NAME); this.mbean = MBeanServerInvocationHandler.newProxyInstance(mbsc, objectName, SubstepsServerMBean.class, false); addNotificationListener(objectName); } catch (final IOException e) { throw new MojoExecutionException("Failed to connect to substeps server", e); } catch (final MalformedObjectNameException e) { throw new MojoExecutionException("Failed to connect to substeps server", e); } } protected void addNotificationListener(ObjectName objectName) throws IOException { boolean added = false; int tries = 0; while (!added || tries < 3){ try { tries++; mbsc.addNotificationListener(objectName, this, null, null); added = true; } catch (InstanceNotFoundException e) { log.debug("adding notification InstanceNotFoundException"); } } } protected JMXConnector getConnector(JMXServiceURL serviceURL, Map<String, ?> environment) throws IOException { // Create the JMXCconnectorServer JMXConnector connector = null; long timeout = System.currentTimeMillis() + (JMX_CLIENT_TIMEOUT_SECS * 1000); while (connector == null && System.currentTimeMillis() < timeout ) { try { log.debug("trying to connect to: " + serviceURL); connector = JMXConnectorFactory.connect(serviceURL, environment); log.debug("connected"); } catch (IOException e) { log.debug("e.getCause(): " + e.getCause().getClass()); if (! (e.getCause() instanceof ServiceUnavailableException)){ log.error("not a ServiceUnavailableException", e); break; } log.debug("ConnectException sleeping.."); try { Thread.sleep(500); } catch (InterruptedException e1) { log.debug("InterruptedException:", e1); } } } if (connector == null){ log.error("failed to get the JMXConnector in time"); } return connector; } public RootNode prepareExecutionConfig(final SubstepsExecutionConfig cfg) { return this.mbean.prepareExecutionConfig(cfg); } public List<SubstepExecutionFailure> getFailures() { return this.mbean.getFailures(); } public RootNode run() { return this.mbean.run(); } public void addNotifier(final IExecutionListener listener) { this.mbean.addNotifier(listener); } public boolean shutdown() { boolean successfulShutdown = false; try { this.mbean.shutdown(); successfulShutdown = true; } catch (final RuntimeException re) { this.log.debug("Unable to connect to server to shutdown, it may have already closed"); } return successfulShutdown; } // @Override public void handleNotification(Notification notification, Object handback) { if (notification.getType().compareTo("ExNode")==0) { byte[] rawBytes = (byte[])notification.getUserData(); ExecutionNodeResult result = getFromBytes(rawBytes); this.log.debug("received a JMX event msg: " + notification.getMessage() + " seq: " + notification.getSequenceNumber() + " exec result node id: " + result.getExecutionNodeId()); // notificiationHandler.handleNotification(result); } else if (notification.getType().compareTo("ExecConfigComplete")==0) { // notificiationHandler.handleCompleteMessage(); } else { log.error("unknown notificaion type"); } } protected static <T> T getFromBytes(byte[] bytes) { T rn = null; ByteArrayInputStream bis = new ByteArrayInputStream(bytes); ObjectInputStream ois = null; try { ois = new ObjectInputStream(bis); rn = (T)ois.readObject(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } finally{ try { bis.close(); } catch (IOException e) { e.printStackTrace(); } } return rn; } }