/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.nifi.processors.standard.util; import static org.apache.nifi.processors.standard.util.JmsProperties.ACKNOWLEDGEMENT_MODE; import static org.apache.nifi.processors.standard.util.JmsProperties.ACK_MODE_AUTO; import static org.apache.nifi.processors.standard.util.JmsProperties.ACTIVEMQ_PROVIDER; import static org.apache.nifi.processors.standard.util.JmsProperties.CLIENT_ID_PREFIX; import static org.apache.nifi.processors.standard.util.JmsProperties.DESTINATION_NAME; import static org.apache.nifi.processors.standard.util.JmsProperties.DESTINATION_TYPE; import static org.apache.nifi.processors.standard.util.JmsProperties.DESTINATION_TYPE_QUEUE; import static org.apache.nifi.processors.standard.util.JmsProperties.DESTINATION_TYPE_TOPIC; import static org.apache.nifi.processors.standard.util.JmsProperties.DURABLE_SUBSCRIPTION; import static org.apache.nifi.processors.standard.util.JmsProperties.JMS_PROVIDER; import static org.apache.nifi.processors.standard.util.JmsProperties.MESSAGE_SELECTOR; import static org.apache.nifi.processors.standard.util.JmsProperties.PASSWORD; import static org.apache.nifi.processors.standard.util.JmsProperties.SSL_CONTEXT_SERVICE; import static org.apache.nifi.processors.standard.util.JmsProperties.TIMEOUT; import static org.apache.nifi.processors.standard.util.JmsProperties.URL; import static org.apache.nifi.processors.standard.util.JmsProperties.USERNAME; import java.io.IOException; import java.io.ObjectOutputStream; import java.net.URI; import java.net.URISyntaxException; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.UUID; import java.util.concurrent.TimeUnit; import javax.jms.BytesMessage; import javax.jms.Connection; import javax.jms.ConnectionFactory; 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.ObjectMessage; import javax.jms.Queue; import javax.jms.Session; import javax.jms.StreamMessage; import javax.jms.TextMessage; import javax.jms.Topic; import org.apache.activemq.ActiveMQConnectionFactory; import org.apache.activemq.ActiveMQSslConnectionFactory; import org.apache.activemq.command.ActiveMQQueue; import org.apache.activemq.command.ActiveMQTopic; import org.apache.activemq.util.URISupport; import org.apache.activemq.util.URISupport.CompositeData; import org.apache.nifi.processor.ProcessContext; import org.apache.nifi.ssl.SSLContextService; import org.apache.nifi.stream.io.ByteArrayOutputStream; public class JmsFactory { public static final boolean DEFAULT_IS_TRANSACTED = false; public static final String ATTRIBUTE_PREFIX = "jms."; public static final String ATTRIBUTE_TYPE_SUFFIX = ".type"; public static final String CLIENT_ID_FIXED_PREFIX = "NiFi-"; // JMS Metadata Fields public static final String JMS_MESSAGE_ID = "JMSMessageID"; public static final String JMS_DESTINATION = "JMSDestination"; public static final String JMS_REPLY_TO = "JMSReplyTo"; public static final String JMS_DELIVERY_MODE = "JMSDeliveryMode"; public static final String JMS_REDELIVERED = "JMSRedelivered"; public static final String JMS_CORRELATION_ID = "JMSCorrelationID"; public static final String JMS_TYPE = "JMSType"; public static final String JMS_TIMESTAMP = "JMSTimestamp"; public static final String JMS_EXPIRATION = "JMSExpiration"; public static final String JMS_PRIORITY = "JMSPriority"; // JMS Property Types. public static final String PROP_TYPE_STRING = "string"; public static final String PROP_TYPE_INTEGER = "integer"; public static final String PROP_TYPE_OBJECT = "object"; public static final String PROP_TYPE_BYTE = "byte"; public static final String PROP_TYPE_DOUBLE = "double"; public static final String PROP_TYPE_FLOAT = "float"; public static final String PROP_TYPE_LONG = "long"; public static final String PROP_TYPE_SHORT = "short"; public static final String PROP_TYPE_BOOLEAN = "boolean"; public static Connection createConnection(final ProcessContext context) throws JMSException { return createConnection(context, createClientId(context)); } public static Connection createConnection(final ProcessContext context, final String clientId) throws JMSException { Objects.requireNonNull(context); Objects.requireNonNull(clientId); final ConnectionFactory connectionFactory = createConnectionFactory(context); final String username = context.getProperty(USERNAME).getValue(); final String password = context.getProperty(PASSWORD).getValue(); final Connection connection = (username == null && password == null) ? connectionFactory.createConnection() : connectionFactory.createConnection(username, password); connection.setClientID(clientId); connection.start(); return connection; } public static Connection createConnection(final String url, final String jmsProvider, final String username, final String password, final int timeoutMillis) throws JMSException { final ConnectionFactory connectionFactory = createConnectionFactory(url, timeoutMillis, jmsProvider); return (username == null && password == null) ? connectionFactory.createConnection() : connectionFactory.createConnection(username, password); } public static String createClientId(final ProcessContext context) { final String clientIdPrefix = context.getProperty(CLIENT_ID_PREFIX).getValue(); return CLIENT_ID_FIXED_PREFIX + (clientIdPrefix == null ? "" : clientIdPrefix) + "-" + UUID.randomUUID().toString(); } public static boolean clientIdPrefixEquals(final String one, final String two) { if (one == null) { return two == null; } else if (two == null) { return false; } int uuidLen = UUID.randomUUID().toString().length(); if (one.length() <= uuidLen || two.length() <= uuidLen) { return false; } return one.substring(0, one.length() - uuidLen).equals(two.substring(0, two.length() - uuidLen)); } public static byte[] createByteArray(final Message message) throws JMSException { if (message instanceof TextMessage) { return getMessageBytes((TextMessage) message); } else if (message instanceof BytesMessage) { return getMessageBytes((BytesMessage) message); } else if (message instanceof StreamMessage) { return getMessageBytes((StreamMessage) message); } else if (message instanceof MapMessage) { return getMessageBytes((MapMessage) message); } else if (message instanceof ObjectMessage) { return getMessageBytes((ObjectMessage) message); } return new byte[0]; } private static byte[] getMessageBytes(TextMessage message) throws JMSException { return (message.getText() == null) ? new byte[0] : message.getText().getBytes(); } private static byte[] getMessageBytes(BytesMessage message) throws JMSException { final long byteCount = message.getBodyLength(); if (byteCount > Integer.MAX_VALUE) { throw new JMSException("Incoming message cannot be written to a FlowFile because its size is " + byteCount + " bytes, and the maximum size that this processor can handle is " + Integer.MAX_VALUE); } byte[] bytes = new byte[(int) byteCount]; message.readBytes(bytes); return bytes; } private static byte[] getMessageBytes(StreamMessage message) throws JMSException { final ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] byteBuffer = new byte[4096]; int byteCount; while ((byteCount = message.readBytes(byteBuffer)) != -1) { baos.write(byteBuffer, 0, byteCount); } try { baos.close(); } catch (final IOException ioe) { } return baos.toByteArray(); } @SuppressWarnings("rawtypes") private static byte[] getMessageBytes(MapMessage message) throws JMSException { Map<String, String> map = new HashMap<>(); Enumeration elements = message.getMapNames(); while (elements.hasMoreElements()) { String key = (String) elements.nextElement(); map.put(key, message.getString(key)); } return map.toString().getBytes(); } private static byte[] getMessageBytes(ObjectMessage message) throws JMSException { try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); // will fail if Object is not Serializable try (ObjectOutputStream oos = new ObjectOutputStream(baos)) { // will fail if Object is not Serializable oos.writeObject(message.getObject()); oos.flush(); } return baos.toByteArray(); } catch (IOException e) { return new byte[0]; } } public static Session createSession(final ProcessContext context, final Connection connection, final boolean transacted) throws JMSException { final String configuredAckMode = context.getProperty(ACKNOWLEDGEMENT_MODE).getValue(); return createSession(connection, configuredAckMode, transacted); } public static Session createSession(final Connection connection, final String configuredAckMode, final boolean transacted) throws JMSException { final int ackMode; if (configuredAckMode == null) { ackMode = Session.AUTO_ACKNOWLEDGE; } else { ackMode = configuredAckMode.equalsIgnoreCase(ACK_MODE_AUTO) ? Session.AUTO_ACKNOWLEDGE : Session.CLIENT_ACKNOWLEDGE; } final Session session = connection.createSession(transacted, ackMode); return session; } public static WrappedMessageConsumer createQueueMessageConsumer(final ProcessContext context) throws JMSException { Connection connection = null; Session jmsSession = null; try { connection = JmsFactory.createConnection(context); jmsSession = JmsFactory.createSession(context, connection, DEFAULT_IS_TRANSACTED); final String messageSelector = context.getProperty(MESSAGE_SELECTOR).getValue(); final Destination destination = createQueue(context); final MessageConsumer messageConsumer = jmsSession.createConsumer(destination, messageSelector, false); return new WrappedMessageConsumer(connection, jmsSession, messageConsumer); } catch (JMSException e) { if (jmsSession != null) { jmsSession.close(); } if (connection != null) { connection.close(); } throw e; } } public static WrappedMessageConsumer createTopicMessageConsumer(final ProcessContext context) throws JMSException { return createTopicMessageConsumer(context, createClientId(context)); } public static WrappedMessageConsumer createTopicMessageConsumer(final ProcessContext context, final String clientId) throws JMSException { Objects.requireNonNull(context); Objects.requireNonNull(clientId); Connection connection = null; Session jmsSession = null; try { connection = JmsFactory.createConnection(context, clientId); jmsSession = JmsFactory.createSession(context, connection, DEFAULT_IS_TRANSACTED); final String messageSelector = context.getProperty(MESSAGE_SELECTOR).getValue(); final Topic topic = createTopic(context); final MessageConsumer messageConsumer; if (context.getProperty(DURABLE_SUBSCRIPTION).asBoolean()) { messageConsumer = jmsSession.createDurableSubscriber(topic, clientId, messageSelector, false); } else { messageConsumer = jmsSession.createConsumer(topic, messageSelector, false); } return new WrappedMessageConsumer(connection, jmsSession, messageConsumer); } catch (JMSException e) { if (jmsSession != null) { jmsSession.close(); } if (connection != null) { connection.close(); } throw e; } } private static Destination getDestination(final ProcessContext context) throws JMSException { final String destinationType = context.getProperty(DESTINATION_TYPE).getValue(); switch (destinationType) { case DESTINATION_TYPE_TOPIC: return createTopic(context); case DESTINATION_TYPE_QUEUE: default: return createQueue(context); } } public static WrappedMessageProducer createMessageProducer(final ProcessContext context) throws JMSException { return createMessageProducer(context, false); } public static WrappedMessageProducer createMessageProducer(final ProcessContext context, final boolean transacted) throws JMSException { Connection connection = null; Session jmsSession = null; try { connection = JmsFactory.createConnection(context); jmsSession = JmsFactory.createSession(context, connection, transacted); final Destination destination = getDestination(context); final MessageProducer messageProducer = jmsSession.createProducer(destination); return new WrappedMessageProducer(connection, jmsSession, messageProducer); } catch (JMSException e) { if (connection != null) { connection.close(); } if (jmsSession != null) { jmsSession.close(); } throw e; } } public static Destination createQueue(final ProcessContext context) { return createQueue(context, context.getProperty(DESTINATION_NAME).getValue()); } public static Queue createQueue(final ProcessContext context, final String queueName) { return createQueue(context.getProperty(JMS_PROVIDER).getValue(), queueName); } public static Queue createQueue(final String jmsProvider, final String queueName) { switch (jmsProvider) { case ACTIVEMQ_PROVIDER: default: return new ActiveMQQueue(queueName); } } private static Topic createTopic(final ProcessContext context) { final String topicName = context.getProperty(DESTINATION_NAME).getValue(); switch (context.getProperty(JMS_PROVIDER).getValue()) { case ACTIVEMQ_PROVIDER: default: return new ActiveMQTopic(topicName); } } private static ConnectionFactory createConnectionFactory(final ProcessContext context) throws JMSException { final URI uri; try { uri = new URI(context.getProperty(URL).getValue()); } catch (URISyntaxException e) { // Should not happen - URI was validated throw new IllegalArgumentException("Validated URI [" + context.getProperty(URL) + "] was invalid", e); } final int timeoutMillis = context.getProperty(TIMEOUT).asTimePeriod(TimeUnit.MILLISECONDS).intValue(); final String provider = context.getProperty(JMS_PROVIDER).getValue(); if (isSSL(uri)) { final SSLContextService sslContextService = context.getProperty(SSL_CONTEXT_SERVICE).asControllerService(SSLContextService.class); if (sslContextService == null) { throw new IllegalArgumentException("Attempting to initiate SSL JMS connection and SSL Context is not set."); } return createSslConnectionFactory(uri, timeoutMillis, provider, sslContextService.getKeyStoreFile(), sslContextService.getKeyStorePassword(), sslContextService.getTrustStoreFile(), sslContextService.getTrustStorePassword()); } else { return createConnectionFactory(uri, timeoutMillis, provider); } } private static boolean isSSL(URI uri) { try { CompositeData compositeData = URISupport.parseComposite(uri); if ("ssl".equals(compositeData.getScheme())) { return true; } for(URI component : compositeData.getComponents()){ if ("ssl".equals(component.getScheme())) { return true; } } } catch (URISyntaxException e) { throw new IllegalArgumentException("Attempting to initiate JMS with invalid composite URI [" + uri + "]", e); } return false; } public static ConnectionFactory createConnectionFactory(final URI uri, final int timeoutMillis, final String jmsProvider) throws JMSException { return createConnectionFactory(uri.toString(), timeoutMillis, jmsProvider); } public static ConnectionFactory createConnectionFactory(final String url, final int timeoutMillis, final String jmsProvider) throws JMSException { switch (jmsProvider) { case ACTIVEMQ_PROVIDER: { final ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(url); factory.setSendTimeout(timeoutMillis); return factory; } default: throw new IllegalArgumentException("Unknown JMS Provider: " + jmsProvider); } } public static ConnectionFactory createSslConnectionFactory(final URI uri, final int timeoutMillis, final String jmsProvider, final String keystore, final String keystorePassword, final String truststore, final String truststorePassword) throws JMSException { return createSslConnectionFactory(uri.toString(), timeoutMillis, jmsProvider, keystore, keystorePassword, truststore, truststorePassword); } public static ConnectionFactory createSslConnectionFactory(final String url, final int timeoutMillis, final String jmsProvider, final String keystore, final String keystorePassword, final String truststore, final String truststorePassword) throws JMSException { switch (jmsProvider) { case ACTIVEMQ_PROVIDER: { final ActiveMQSslConnectionFactory factory = new ActiveMQSslConnectionFactory(url); try { factory.setKeyStore(keystore); } catch (Exception e) { throw new JMSException("Problem Setting the KeyStore: " + e.getMessage()); } factory.setKeyStorePassword(keystorePassword); try { factory.setTrustStore(truststore); } catch (Exception e) { throw new JMSException("Problem Setting the TrustStore: " + e.getMessage()); } factory.setTrustStorePassword(truststorePassword); factory.setSendTimeout(timeoutMillis); return factory; } default: throw new IllegalArgumentException("Unknown JMS Provider: " + jmsProvider); } } public static Map<String, String> createAttributeMap(final Message message) throws JMSException { final Map<String, String> attributes = new HashMap<>(); final Enumeration<?> enumeration = message.getPropertyNames(); while (enumeration.hasMoreElements()) { final String propName = (String) enumeration.nextElement(); final Object value = message.getObjectProperty(propName); if (value == null) { attributes.put(ATTRIBUTE_PREFIX + propName, ""); attributes.put(ATTRIBUTE_PREFIX + propName + ATTRIBUTE_TYPE_SUFFIX, "Unknown"); continue; } final String valueString = value.toString(); attributes.put(ATTRIBUTE_PREFIX + propName, valueString); final String propType; if (value instanceof String) { propType = PROP_TYPE_STRING; } else if (value instanceof Double) { propType = PROP_TYPE_DOUBLE; } else if (value instanceof Float) { propType = PROP_TYPE_FLOAT; } else if (value instanceof Long) { propType = PROP_TYPE_LONG; } else if (value instanceof Integer) { propType = PROP_TYPE_INTEGER; } else if (value instanceof Short) { propType = PROP_TYPE_SHORT; } else if (value instanceof Byte) { propType = PROP_TYPE_BYTE; } else if (value instanceof Boolean) { propType = PROP_TYPE_BOOLEAN; } else { propType = PROP_TYPE_OBJECT; } attributes.put(ATTRIBUTE_PREFIX + propName + ATTRIBUTE_TYPE_SUFFIX, propType); } if (message.getJMSCorrelationID() != null) { attributes.put(ATTRIBUTE_PREFIX + JMS_CORRELATION_ID, message.getJMSCorrelationID()); } if (message.getJMSDestination() != null) { String destinationName; if (message.getJMSDestination() instanceof Queue) { destinationName = ((Queue) message.getJMSDestination()).getQueueName(); } else { destinationName = ((Topic) message.getJMSDestination()).getTopicName(); } attributes.put(ATTRIBUTE_PREFIX + JMS_DESTINATION, destinationName); } if (message.getJMSMessageID() != null) { attributes.put(ATTRIBUTE_PREFIX + JMS_MESSAGE_ID, message.getJMSMessageID()); } if (message.getJMSReplyTo() != null) { attributes.put(ATTRIBUTE_PREFIX + JMS_REPLY_TO, message.getJMSReplyTo().toString()); } if (message.getJMSType() != null) { attributes.put(ATTRIBUTE_PREFIX + JMS_TYPE, message.getJMSType()); } attributes.put(ATTRIBUTE_PREFIX + JMS_DELIVERY_MODE, String.valueOf(message.getJMSDeliveryMode())); attributes.put(ATTRIBUTE_PREFIX + JMS_EXPIRATION, String.valueOf(message.getJMSExpiration())); attributes.put(ATTRIBUTE_PREFIX + JMS_PRIORITY, String.valueOf(message.getJMSPriority())); attributes.put(ATTRIBUTE_PREFIX + JMS_REDELIVERED, String.valueOf(message.getJMSRedelivered())); attributes.put(ATTRIBUTE_PREFIX + JMS_TIMESTAMP, String.valueOf(message.getJMSTimestamp())); return attributes; } }