/** * */ package com.mtvi.plateng.hudson; import hudson.Extension; import static hudson.Util.fixEmpty; import hudson.Launcher; import hudson.model.AbstractBuild; import hudson.model.AbstractProject; import hudson.model.BuildListener; import hudson.model.Result; import hudson.tasks.BuildStepDescriptor; import hudson.tasks.BuildStepMonitor; import hudson.tasks.Notifier; import hudson.tasks.Publisher; import hudson.util.FormValidation; import java.io.IOException; import java.io.PrintStream; import java.net.MalformedURLException; import javax.management.MBeanServerConnection; import javax.management.MalformedObjectNameException; import javax.management.ObjectName; import javax.management.remote.JMXConnector; import javax.management.remote.JMXConnectorFactory; import javax.management.remote.JMXServiceURL; import net.sf.json.JSONObject; import org.apache.commons.lang.StringUtils; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.QueryParameter; import org.kohsuke.stapler.StaplerRequest; /** * Hudson plugin that invokes a JMX operation upon a successful build. * * @author edelsonj * @plugin */ public class JMXInvokerPublisher extends Notifier { private JMXServiceURL mbeanServerUrl; private ObjectName objectName; private String operationName; private String password; private String username; /** * Create a new JMX Publisher instance, converting the mbeanServerURL and * objectName parameters to the appropriate JMX types * * @throws MalformedURLException if the mbeanServerUrl isn't valid * @throws MalformedObjectNameException if the objectName isn't valid. * {@stapler-constructor} */ @DataBoundConstructor public JMXInvokerPublisher(String mbeanServerUrl, String objectName, String operationName, String username, String password) throws MalformedURLException, MalformedObjectNameException { this.mbeanServerUrl = createJMXServiceURL(mbeanServerUrl); this.objectName = new ObjectName(objectName); this.operationName = operationName; this.username = username; this.password = password; } /** * Create a JMXServiceURL object, converting the short <host>:<port> form * of a JMX Service URL to the full form if necessary. * * @param value either a host and port or a full JMX URL * @return the JMX Service URL * @throws MalformedURLException if the URL isn't valid */ private static JMXServiceURL createJMXServiceURL(String value) throws MalformedURLException { if (StringUtils.countMatches(value, ":") == 1) { value = String.format("service:jmx:rmi:///jndi/rmi://%s/jmxrmi", value); } return new JMXServiceURL(value); } public String getMbeanServerUrl() { return mbeanServerUrl.toString(); } public String getObjectName() { return objectName.toString(); } public String getOperationName() { return operationName; } public String getPassword() { return password; } public String getUsername() { return username; } public BuildStepMonitor getRequiredMonitorService() { return BuildStepMonitor.STEP; } @Override public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException { PrintStream logger = listener.getLogger(); if (build.getResult() == Result.SUCCESS) { // JMXServiceURL url = new // JMXServiceURL("service:jmx:rmi:///jndi/rmi://:9999/jmxrmi"); JMXConnector jmxc = JMXConnectorFactory.connect(mbeanServerUrl, null); MBeanServerConnection mbsc = jmxc.getMBeanServerConnection(); try { logger.println(String.format("Invoking %s on %s", operationName, objectName)); mbsc.invoke(objectName, operationName, new Object[0], new String[0]); } catch (Exception e) { logger.println(String.format("Unable to invoke %s on MBean %s: ", operationName, objectName, e.getMessage())); return false; } finally { jmxc.close(); } } return true; } @Extension public static final class DescriptorImpl extends BuildStepDescriptor<Publisher> { public DescriptorImpl() { super(JMXInvokerPublisher.class); load(); } /** * Performs on-the-fly validation of the mbean server url field. */ public FormValidation doCheckMBeanServerUrl(@QueryParameter String value) { if (fixEmpty(value) == null) { return FormValidation.error("MBean Server URL cannot be empty"); } else { JMXServiceURL url = null; try { url = createJMXServiceURL(value); } catch (MalformedURLException e) { return FormValidation.error("Invalid MBean Server URL - %s", e.getMessage()); } return FormValidation.ok(); // JMXConnector jmxc = null; // try { // jmxc = JMXConnectorFactory.connect(url, null); // } catch (Exception e) { // error("Unable to connect to MBean Server: " + // e.getMessage()); // } finally { // if (jmxc != null) { // jmxc.close(); // } // } } } /** * Performs on-the-fly validation of the object name field. */ public FormValidation doCheckObjectName(@QueryParameter String value) { if (fixEmpty(value) == null) { return FormValidation.error("Object Name cannot be empty"); } else { try { new ObjectName(value); } catch (Exception e) { return FormValidation.error("Invalid object name: %s", e.getMessage()); } } return FormValidation.ok(); } /** * Performs on-the-fly validation of the operation name field. */ public FormValidation doCheckOperationName(@QueryParameter String value) { if (fixEmpty(value) == null) { return FormValidation.error("Operation Name cannot be empty"); } return FormValidation.ok(); } @Override public String getDisplayName() { return "Invoke a JMX Operation"; } @Override public boolean isApplicable(Class<? extends AbstractProject> jobType) { return true; } @Override public Publisher newInstance(StaplerRequest req, JSONObject formData) throws FormException { return req.bindJSON(clazz, formData); } } }