Java tutorial
package com.redhat.jenkins.plugins.ci.messaging; import static com.redhat.utils.MessageUtils.JSON_TYPE; import hudson.EnvVars; import hudson.model.Result; import hudson.model.TaskListener; import hudson.model.Run; import java.io.StringReader; import java.net.Inet4Address; import java.net.UnknownHostException; import java.sql.Time; import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.UUID; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Pattern; import javax.jms.BytesMessage; import javax.jms.Connection; import javax.jms.DeliveryMode; import javax.jms.Destination; import javax.jms.JMSException; import javax.jms.MapMessage; import javax.jms.Message; import javax.jms.MessageConsumer; import javax.jms.MessageProducer; import javax.jms.Session; import javax.jms.TextMessage; import javax.jms.Topic; import javax.jms.TopicSubscriber; import jenkins.model.Jenkins; import org.apache.activemq.ActiveMQConnectionFactory; import org.apache.commons.lang.text.StrSubstitutor; import org.apache.commons.lang3.StringUtils; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import com.redhat.jenkins.plugins.ci.CIEnvironmentContributingAction; import com.redhat.jenkins.plugins.ci.messaging.checks.MsgCheck; import com.redhat.utils.MessageUtils; /* * The MIT License * * Copyright (c) Red Hat, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ public class ActiveMqMessagingWorker extends JMSMessagingWorker { private static final Logger log = Logger.getLogger(ActiveMqMessagingWorker.class.getName()); private final ActiveMqMessagingProvider provider; private final MessagingProviderOverrides overrides; private Connection connection; private TopicSubscriber subscriber; private String topic; public ActiveMqMessagingWorker(ActiveMqMessagingProvider provider, MessagingProviderOverrides overrides, String jobname) { this.provider = provider; this.overrides = overrides; this.jobname = jobname; } @Override public boolean subscribe(String jobname, String selector) { this.topic = getTopic(); if (this.topic != null) { while (!Thread.currentThread().isInterrupted()) { try { if (!isConnected()) { if (!connect()) { return false; } } if (subscriber == null) { log.info("Subscribing job '" + jobname + "' to '" + this.topic + "' topic."); Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); Topic destination = session.createTopic(this.topic); subscriber = session.createDurableSubscriber(destination, jobname, selector, false); log.info("Successfully subscribed job '" + jobname + "' to '" + this.topic + "' topic with selector: " + selector); } else { log.fine("Already subscribed to '" + this.topic + "' topic with selector: " + selector + " for job '" + jobname); } return true; } catch (JMSException ex) { // Either we were interrupted, or something else went // wrong. If we were interrupted, then we will jump ship // on the next iteration. If something else happened, // then we just unsubscribe here, sleep, so that we may // try again on the next iteration. log.log(Level.SEVERE, "JMS exception raised while subscribing job '" + jobname + "', retrying in " + RETRY_MINUTES + " minutes.", ex); if (!Thread.currentThread().isInterrupted()) { unsubscribe(jobname); try { Thread.sleep(RETRY_MINUTES * 60 * 1000); } catch (InterruptedException ie) { // We were interrupted while waiting to retry. // We will jump ship on the next iteration. // NB: The interrupt flag was cleared when // InterruptedException was thrown. We have to // re-install it to make sure we eventually // leave this thread. Thread.currentThread().interrupt(); } } } } } return false; } @Override public boolean connect() { connection = null; ActiveMQConnectionFactory connectionFactory = provider.getConnectionFactory(); String ip = null; try { ip = Inet4Address.getLocalHost().getHostAddress(); } catch (UnknownHostException e) { log.severe("Unable to get localhost IP address."); } Connection connectiontmp = null; try { connectiontmp = connectionFactory.createConnection(); String url = ""; if (Jenkins.getInstance() != null) { url = Jenkins.getInstance().getRootUrl(); } connectiontmp.setClientID(provider.getName() + "_" + url + "_" + ip + "_" + jobname); connectiontmp.start(); } catch (JMSException e) { log.severe("Unable to connect to " + provider.getBroker() + " " + e.getMessage()); return false; } log.info("Connection started"); connection = connectiontmp; return true; } @Override public void unsubscribe(String jobname) { log.info("Unsubcribing job '" + jobname + "' from the '" + this.topic + "' topic."); disconnect(); if (subscriber != null) { try { subscriber.close(); } catch (Exception se) { } finally { subscriber = null; } } } public static String getMessageBody(Message message) { try { if (message instanceof MapMessage) { MapMessage mm = (MapMessage) message; ObjectMapper mapper = new ObjectMapper(); ObjectNode root = mapper.createObjectNode(); @SuppressWarnings("unchecked") Enumeration<String> e = mm.getMapNames(); while (e.hasMoreElements()) { String field = e.nextElement(); root.set(field, mapper.convertValue(mm.getObject(field), JsonNode.class)); } return mapper.writer().writeValueAsString(root); } else if (message instanceof TextMessage) { TextMessage tm = (TextMessage) message; return tm.getText(); } else if (message instanceof BytesMessage) { BytesMessage bm = (BytesMessage) message; byte[] bytes = new byte[(int) bm.getBodyLength()]; if (bm.readBytes(bytes) == bm.getBodyLength()) { return new String(bytes); } } else { log.log(Level.SEVERE, "Unsupported message type:\n" + formatMessage(message)); } } catch (Exception e) { log.log(Level.SEVERE, "Unhandled exception retrieving message body:\n" + formatMessage(message), e); } return ""; } private boolean verify(Message message, MsgCheck check) { String sVal = ""; if (check.getField().equals(MESSAGECONTENTFIELD)) { try { if (message instanceof TextMessage) { sVal = ((TextMessage) message).getText(); } else if (message instanceof MapMessage) { MapMessage mm = (MapMessage) message; ObjectMapper mapper = new ObjectMapper(); ObjectNode root = mapper.createObjectNode(); @SuppressWarnings("unchecked") Enumeration<String> e = mm.getMapNames(); while (e.hasMoreElements()) { String field = e.nextElement(); root.set(field, mapper.convertValue(mm.getObject(field), JsonNode.class)); } sVal = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(root); } else if (message instanceof BytesMessage) { BytesMessage bm = (BytesMessage) message; bm.reset(); byte[] bytes = new byte[(int) bm.getBodyLength()]; if (bm.readBytes(bytes) == bm.getBodyLength()) { sVal = new String(bytes); } } } catch (JMSException e) { return false; } catch (JsonProcessingException e) { return false; } } else { Enumeration<String> propNames = null; try { propNames = message.getPropertyNames(); while (propNames.hasMoreElements()) { String propertyName = propNames.nextElement(); if (propertyName.equals(check.getField())) { if (message.getObjectProperty(propertyName) != null) { sVal = message.getObjectProperty(propertyName).toString(); break; } } } } catch (JMSException e) { return false; } } String eVal = ""; if (check.getExpectedValue() != null) { eVal = check.getExpectedValue(); } if (Pattern.compile(eVal).matcher(sVal).find()) { return true; } return false; } private void process(String jobname, Message message) { try { Map<String, String> params = new HashMap<String, String>(); params.put("CI_MESSAGE", getMessageBody(message)); @SuppressWarnings("unchecked") Enumeration<String> e = message.getPropertyNames(); while (e.hasMoreElements()) { String s = e.nextElement(); if (message.getStringProperty(s) != null) { params.put(s, message.getObjectProperty(s).toString()); } } super.trigger(jobname, formatMessage(message), params); } catch (Exception e) { log.log(Level.SEVERE, "Unhandled exception processing message:\n" + formatMessage(message), e); } } @Override public void receive(String jobname, List<MsgCheck> checks, long timeoutInMs) { try { Message m = subscriber.receive(timeoutInMs); // In milliseconds! if (m != null) { //check checks here boolean allPassed = true; for (MsgCheck check : checks) { if (!verify(m, check)) { allPassed = false; log.fine("msg check: " + check.toString() + " failed against: " + formatMessage(m)); break; } } if (allPassed) { if (checks.size() > 0) { log.info("All msg checks have passed."); } process(jobname, m); } else { log.info("Some msg checks did not pass."); } } else { log.info("No message received for the past " + timeoutInMs + " ms, re-subscribing job '" + jobname + "'."); unsubscribe(jobname); } } catch (JMSException e) { if (!Thread.currentThread().isInterrupted()) { // Something other than an interrupt causes this. // Unsubscribe, but stay in our loop and try to reconnect.. log.log(Level.WARNING, "JMS exception raised while receiving, going to re-subscribe job '" + jobname + "'.", e); unsubscribe(jobname); // Try again next time. } } } @Override public boolean isConnected() { if (connection == null) { return false; } else { return true; } } @Override public void disconnect() { if (connection != null) { try { connection.close(); } catch (JMSException e) { } finally { connection = null; } } } @Override public boolean sendMessage(Run<?, ?> build, TaskListener listener, MessageUtils.MESSAGE_TYPE type, String props, String content) { Connection connection = null; Session session = null; MessageProducer publisher = null; try { String ltopic = getTopic(); if (provider.getAuthenticationMethod() != null && ltopic != null && provider.getBroker() != null) { ActiveMQConnectionFactory connectionFactory = provider.getConnectionFactory(); connection = connectionFactory.createConnection(); connection.start(); session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); Destination destination = session.createTopic(ltopic); publisher = session.createProducer(destination); TextMessage message; message = session.createTextMessage(""); message.setJMSType(JSON_TYPE); message.setStringProperty("CI_NAME", build.getParent().getName()); message.setStringProperty("CI_TYPE", type.getMessage()); if (!build.isBuilding()) { message.setStringProperty("CI_STATUS", (build.getResult() == Result.SUCCESS ? "passed" : "failed")); } StrSubstitutor sub = new StrSubstitutor(build.getEnvironment(listener)); if (props != null && !props.trim().equals("")) { Properties p = new Properties(); p.load(new StringReader(props)); @SuppressWarnings("unchecked") Enumeration<String> e = (Enumeration<String>) p.propertyNames(); while (e.hasMoreElements()) { String key = e.nextElement(); message.setStringProperty(key, sub.replace(p.getProperty(key))); } } message.setText(sub.replace(content)); publisher.send(message); log.info("Sent " + type.toString() + " message for job '" + build.getParent().getName() + "' to topic '" + ltopic + "':\n" + formatMessage(message)); } else { log.severe("One or more of the following is invalid (null): user, password, topic, broker."); return false; } } catch (Exception e) { log.log(Level.SEVERE, "Unhandled exception in perform.", e); } finally { if (publisher != null) { try { publisher.close(); } catch (JMSException e) { } } if (session != null) { try { session.close(); } catch (JMSException e) { } } if (connection != null) { try { connection.close(); } catch (JMSException e) { } } } return true; } @Override public String waitForMessage(Run<?, ?> build, String selector, String variable, Integer timeout) { String ip = null; try { ip = Inet4Address.getLocalHost().getHostAddress(); } catch (UnknownHostException e) { log.severe("Unable to get localhost IP address."); } String ltopic = getTopic(); if (ip != null && provider.getAuthenticationMethod() != null && ltopic != null && provider.getBroker() != null) { log.info("Waiting for message with selector: " + selector); Connection connection = null; MessageConsumer consumer = null; try { ActiveMQConnectionFactory connectionFactory = provider.getConnectionFactory(); connection = connectionFactory.createConnection(); connection.setClientID(ip + "_" + UUID.randomUUID().toString()); connection.start(); Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); Topic destination = session.createTopic(ltopic); consumer = session.createConsumer(destination, selector); Message message = consumer.receive(timeout * 60 * 1000); if (message != null) { String value = getMessageBody(message); if (build != null) { if (StringUtils.isNotEmpty(variable)) { EnvVars vars = new EnvVars(); vars.put(variable, value); build.addAction(new CIEnvironmentContributingAction(vars)); } } log.info("Received message with selector: " + selector + "\n" + formatMessage(message)); return value; } log.info("Timed out waiting for message!"); } catch (Exception e) { log.log(Level.SEVERE, "Unhandled exception waiting for message.", e); } finally { if (consumer != null) { try { consumer.close(); } catch (Exception e) { } } if (connection != null) { try { connection.close(); } catch (Exception e) { } } } } else { log.severe("One or more of the following is invalid (null): ip, user, password, topic, broker."); } return null; } @Override public void prepareForInterrupt() { } private static String formatHeaders(Message message) { Destination dest = null; int delMode = 0; long expiration = 0; Time expTime = null; int priority = 0; String msgID = null; long timestamp = 0; Time timestampTime = null; String correlID = null; Destination replyTo = null; boolean redelivered = false; String type = null; StringBuilder sb = new StringBuilder(); try { try { dest = message.getJMSDestination(); sb.append(" JMSDestination: "); sb.append(dest); sb.append("\n"); } catch (Exception e) { log.log(Level.WARNING, "Unable to generate JMSDestination header\n", e); } try { delMode = message.getJMSDeliveryMode(); if (delMode == DeliveryMode.NON_PERSISTENT) { sb.append(" JMSDeliveryMode: non-persistent\n"); } else if (delMode == DeliveryMode.PERSISTENT) { sb.append(" JMSDeliveryMode: persistent\n"); } else { sb.append(" JMSDeliveryMode: neither persistent nor non-persistent; error\n"); } } catch (Exception e) { log.log(Level.WARNING, "Unable to generate JMSDeliveryMode header\n", e); } try { expiration = message.getJMSExpiration(); if (expiration != 0) { expTime = new Time(expiration); sb.append(" JMSExpiration: "); sb.append(expTime); sb.append("\n"); } else { sb.append(" JMSExpiration: 0\n"); } } catch (Exception e) { log.log(Level.WARNING, "Unable to generate JMSExpiration header\n", e); } try { priority = message.getJMSPriority(); sb.append(" JMSPriority: "); sb.append(priority); sb.append("\n"); } catch (Exception e) { log.log(Level.WARNING, "Unable to generate JMSPriority header\n", e); } try { msgID = message.getJMSMessageID(); sb.append(" JMSMessageID: "); sb.append(msgID); sb.append("\n"); } catch (Exception e) { log.log(Level.WARNING, "Unable to generate JMSMessageID header\n", e); } try { timestamp = message.getJMSTimestamp(); if (timestamp != 0) { timestampTime = new Time(timestamp); sb.append(" JMSTimestamp: "); sb.append(timestampTime); sb.append("\n"); } else { sb.append(" JMSTimestamp: 0\n"); } } catch (Exception e) { log.log(Level.WARNING, "Unable to generate JMSTimestamp header\n", e); } try { correlID = message.getJMSCorrelationID(); sb.append(" JMSCorrelationID: "); sb.append(correlID); sb.append("\n"); } catch (Exception e) { log.log(Level.WARNING, "Unable to generate JMSCorrelationID header\n", e); } try { replyTo = message.getJMSReplyTo(); sb.append(" JMSReplyTo: "); sb.append(replyTo); sb.append("\n"); } catch (Exception e) { log.log(Level.WARNING, "Unable to generate JMSReplyTo header\n", e); } try { redelivered = message.getJMSRedelivered(); sb.append(" JMSRedelivered: "); sb.append(redelivered); sb.append("\n"); } catch (Exception e) { log.log(Level.WARNING, "Unable to generate JMSRedelivered header\n", e); } try { type = message.getJMSType(); sb.append(" JMSType: "); sb.append(type); sb.append("\n"); } catch (Exception e) { log.log(Level.WARNING, "Unable to generate JMSType header\n", e); } } catch (Exception e) { log.log(Level.WARNING, "Unable to generate JMS headers\n", e); } return sb.toString(); } public static String formatMessage(Message message) { StringBuilder sb = new StringBuilder(); try { String headers = formatHeaders(message); if (headers.length() > 0) { sb.append("Message Headers:\n"); sb.append(headers); } sb.append("Message Properties:\n"); @SuppressWarnings("unchecked") Enumeration<String> propNames = message.getPropertyNames(); while (propNames.hasMoreElements()) { String propertyName = propNames.nextElement(); sb.append(" "); sb.append(propertyName); sb.append(": "); if (message.getObjectProperty(propertyName) != null) { sb.append(message.getObjectProperty(propertyName).toString()); } sb.append("\n"); } sb.append("Message Content:\n"); if (message instanceof TextMessage) { sb.append(((TextMessage) message).getText()); } else if (message instanceof MapMessage) { MapMessage mm = (MapMessage) message; ObjectMapper mapper = new ObjectMapper(); ObjectNode root = mapper.createObjectNode(); @SuppressWarnings("unchecked") Enumeration<String> e = mm.getMapNames(); while (e.hasMoreElements()) { String field = e.nextElement(); root.put(field, mapper.convertValue(mm.getObject(field), JsonNode.class)); } sb.append(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(root)); } else if (message instanceof BytesMessage) { BytesMessage bm = (BytesMessage) message; bm.reset(); byte[] bytes = new byte[(int) bm.getBodyLength()]; if (bm.readBytes(bytes) == bm.getBodyLength()) { sb.append(new String(bytes)); } } else { sb.append(" Unhandled message type: " + message.getJMSType()); } } catch (Exception e) { log.log(Level.SEVERE, "Unable to format message:", e); } return sb.toString(); } private String getTopic() { if (overrides != null && overrides.getTopic() != null && !overrides.getTopic().isEmpty()) { return overrides.getTopic(); } else if (provider.getTopic() != null && !provider.getTopic().isEmpty()) { return provider.getTopic(); } else { return null; } } }