All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.hawkular.bus.common.MessageProcessor Maven / Gradle / Ivy

Go to download

Hawkular Bus Framework Common Library for use with both consumer and producer code.

The newest version!
/*
 * Copyright 2014-2016 Red Hat, Inc. and/or its affiliates
 * and other contributors as indicated by the @author tags.
 *
 * Licensed 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.hawkular.bus.common;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.Map;

import javax.jms.BytesMessage;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.jms.TemporaryQueue;
import javax.jms.TextMessage;

import org.hawkular.bus.common.consumer.AbstractBasicMessageListener;
import org.hawkular.bus.common.consumer.BasicMessageListener;
import org.hawkular.bus.common.consumer.ConsumerConnectionContext;
import org.hawkular.bus.common.consumer.RPCConnectionContext;
import org.hawkular.bus.common.producer.ProducerConnectionContext;
import org.jboss.logging.Logger;

/**
 * Provides some functionality to process messages, both as a producer or consumer.
 *
 * Use {@link ConnectionContextFactory} to create contexts (which create destinations, sessions, and connections for
 * you) that you then use to pass to the listen and send methods in this class.
 */
public class MessageProcessor {

    private final Logger log = Logger.getLogger(MessageProcessor.class);

    public static final String HEADER_BASIC_MESSAGE_CLASS = "basicMessageClassName";

    /**
     * Listens for messages.
     *
     * @param context information that determines where to listen
     * @param listener the listener that processes the incoming messages
     * @throws JMSException any error
     *
     * @see org.hawkular.bus.common.ConnectionContextFactory#createConsumerConnectionContext(Endpoint)
     */
    public  void listen(ConsumerConnectionContext context,
            AbstractBasicMessageListener listener) throws JMSException {
        if (context == null) {
            throw new NullPointerException("context must not be null");
        }
        if (listener == null) {
            throw new NullPointerException("listener must not be null");
        }

        MessageConsumer consumer = context.getMessageConsumer();
        if (consumer == null) {
            throw new NullPointerException("context had a null consumer");
        }

        listener.setConsumerConnectionContext(context);
        consumer.setMessageListener(listener);
    }

    /**
     * Same as {@link #send(ProducerConnectionContext, BasicMessage, Map)} with null headers.
     */
    public MessageId send(ProducerConnectionContext context, BasicMessage basicMessage) throws JMSException {
        return send(context, basicMessage, null);
    }

    /**
     * Send the given message to its destinations across the message bus. Once sent, the message will get assigned a
     * generated message ID. That message ID will also be returned by this method.
     *
     * Since this is fire-and-forget - no response is expected of the remote endpoint.
     *
     * @param context information that determines where the message is sent
     * @param basicMessage the message to send with optional headers included
     * @param headers headers for the JMS transport that will override same-named headers in the basic message
     * @return the message ID
     * @throws JMSException any error
     *
     * @see ConnectionContextFactory#createProducerConnectionContext(Endpoint)
     */
    public MessageId send(ProducerConnectionContext context, BasicMessage basicMessage, Map headers)
            throws JMSException {
        if (context == null) {
            throw new IllegalArgumentException("context must not be null");
        }
        if (basicMessage == null) {
            throw new IllegalArgumentException("message must not be null");
        }

        // create the JMS message to be sent
        Message msg = createMessage(context, basicMessage, headers);

        // if the message is correlated with another, put the correlation ID in the Message to be sent
        if (basicMessage.getCorrelationId() != null) {
            msg.setJMSCorrelationID(basicMessage.getCorrelationId().toString());
        }

        if (basicMessage.getMessageId() != null) {
            log.debugf("Non-null message ID [%s] will be ignored and a new one generated",
                    basicMessage.getMessageId());
            basicMessage.setMessageId(null);
        }

        // now send the message to the broker
        MessageProducer producer = context.getMessageProducer();
        if (producer == null) {
            throw new IllegalStateException("context had a null producer");
        }

        producer.send(msg);

        // put message ID into the message in case the caller wants to correlate it with another record
        MessageId messageId = new MessageId(msg.getJMSMessageID());
        basicMessage.setMessageId(messageId);

        return messageId;
    }

    /**
     * Same as {@link #sendWithBinaryData(ProducerConnectionContext, BasicMessage, InputStream, Map)} with
     * null headers.
     */
    public MessageId sendWithBinaryData(ProducerConnectionContext context, BasicMessage basicMessage,
            InputStream inputStream) throws JMSException {
        return sendWithBinaryData(context, basicMessage, inputStream, null);
    }

    /**
     * Same as {@link #sendWithBinaryData(ProducerConnectionContext, BasicMessage, File, Map)} with null
     * headers.
     *
     * @throws FileNotFoundException if the file does not exist
     */
    public MessageId sendWithBinaryData(ProducerConnectionContext context, BasicMessage basicMessage, File file)
            throws JMSException, FileNotFoundException {
        return sendWithBinaryData(context, basicMessage, new FileInputStream(file), null);
    }

    /**
     * Same as {@link #sendWithBinaryData(ProducerConnectionContext, BasicMessage, InputStream, Map)} with the input
     * stream being a stream to read the file.
     *
     * @throws FileNotFoundException if the file does not exist
     */
    public MessageId sendWithBinaryData(ProducerConnectionContext context, BasicMessage basicMessage, File file,
            Map headers) throws JMSException, FileNotFoundException {
        return sendWithBinaryData(context, basicMessage, new FileInputStream(file), headers);
    }

    /**
     * If the given {@code message.getBinaryData()} is not {@code null} delegates to
     * {@link #sendWithBinaryData(ProducerConnectionContext, BasicMessage, InputStream, Map)} otherwise delegates to
     * {@link #send(ProducerConnectionContext, BasicMessageWithExtraData, Map)}
     *
     * @param context information that determines where the message is sent
     * @param message the message to send
     * @param headers headers for the JMS transport that will override same-named headers in the basic message
     * @return the message ID
     * @throws JMSException any error
     */
    public  MessageId send(ProducerConnectionContext context,
            BasicMessageWithExtraData message, Map headers) throws JMSException {
        if (message.getBinaryData() == null) {
            return send(context, message.getBasicMessage(), headers);
        } else {
            return sendWithBinaryData(context, message.getBasicMessage(), message.getBinaryData(), headers);
        }
    }

    /**
     * Send the given message along with the stream of binary data to its destinations across the message bus. Once
     * sent, the message will get assigned a generated message ID. That message ID will also be returned by this method.
     *
     * Since this is fire-and-forget - no response is expected of the remote endpoint.
     *
     * @param context information that determines where the message is sent
     * @param basicMessage the message to send with optional headers included
     * @param inputStream binary data that will be sent with the message
     * @param headers headers for the JMS transport that will override same-named headers in the basic message
     * @return the message ID
     * @throws JMSException any error
     *
     * @see ConnectionContextFactory#createProducerConnectionContext(Endpoint)
     */
    public MessageId sendWithBinaryData(ProducerConnectionContext context, BasicMessage basicMessage,
            InputStream inputStream, Map headers) throws JMSException {
        if (context == null) {
            throw new IllegalArgumentException("context must not be null");
        }
        if (basicMessage == null) {
            throw new IllegalArgumentException("message must not be null");
        }
        if (inputStream == null) {
            throw new IllegalArgumentException("binary data must not be null");
        }

        // create the JMS message to be sent
        Message msg = createMessageWithBinaryData(context, basicMessage, inputStream, headers);

        // if the message is correlated with another, put the correlation ID in the Message to be sent
        if (basicMessage.getCorrelationId() != null) {
            msg.setJMSCorrelationID(basicMessage.getCorrelationId().toString());
        }

        if (basicMessage.getMessageId() != null) {
            log.debugf("Non-null message ID [%s] will be ignored and a new one generated",
                    basicMessage.getMessageId());
            basicMessage.setMessageId(null);
        }

        // now send the message to the broker
        MessageProducer producer = context.getMessageProducer();
        if (producer == null) {
            throw new IllegalStateException("context had a null producer");
        }

        producer.send(msg);

        // put message ID into the message in case the caller wants to correlate it with another record
        MessageId messageId = new MessageId(msg.getJMSMessageID());
        basicMessage.setMessageId(messageId);

        return messageId;
    }

    /**
     * Same as {@link #sendAndListen(ProducerConnectionContext, BasicMessage, BasicMessageListener, Map)} with
     * null headers.
     */
    public  RPCConnectionContext sendAndListen(ProducerConnectionContext context,
            BasicMessage basicMessage, BasicMessageListener responseListener) throws JMSException {
        return sendAndListen(context, basicMessage, responseListener, null);
    }

    /**
     * Send the given message to its destinations across the message bus and any response sent back will be passed to
     * the given listener. Use this for request-response messages where you expect to get a non-void response back.
     *
     * The response listener should close its associated consumer since typically there is only a single response that
     * is expected. This is left to the listener to do in case there are special circumstances where the listener does
     * expect multiple response messages.
     *
     * If the caller merely wants to wait for a single response and obtain the response message to process it further,
     * consider using instead the method {@link #sendRPC} and use its returned Future to wait for the response, rather
     * than having to supply your own response listener.
     *
     * @param context information that determines where the message is sent
     * @param basicMessage the request message to send with optional headers included
     * @param responseListener The listener that will process the response of the request. This listener should close
     *            its associated consumer when appropriate.
     * @param headers headers for the JMS transport that will override same-named headers in the basic message
     *
     * @return the RPC context which includes information about the handling of the expected response
     * @throws JMSException any error
     *
     * @see org.hawkular.bus.common.ConnectionContextFactory#createProducerConnectionContext(Endpoint)
     */
    public  RPCConnectionContext sendAndListen(ProducerConnectionContext context,
            BasicMessage basicMessage, BasicMessageListener responseListener, Map headers)
                    throws JMSException {

        if (context == null) {
            throw new IllegalArgumentException("context must not be null");
        }
        if (basicMessage == null) {
            throw new IllegalArgumentException("message must not be null");
        }
        if (responseListener == null) {
            throw new IllegalArgumentException("response listener must not be null");
        }

        // create the JMS message to be sent
        Message msg = createMessage(context, basicMessage, headers);

        // if the message is correlated with another, put the correlation ID in the Message to be sent
        if (basicMessage.getCorrelationId() != null) {
            msg.setJMSCorrelationID(basicMessage.getCorrelationId().toString());
        }

        if (basicMessage.getMessageId() != null) {
            log.debugf("Non-null message ID [%s] will be ignored and a new one generated",
                    basicMessage.getMessageId());
            basicMessage.setMessageId(null);
        }

        MessageProducer producer = context.getMessageProducer();
        if (producer == null) {
            throw new NullPointerException("Cannot send request-response message - the producer is null");
        }

        // prepare for the response prior to sending the request
        Session session = context.getSession();
        if (session == null) {
            throw new NullPointerException("Cannot send request-response message - the session is null");
        }
        TemporaryQueue responseQueue = session.createTemporaryQueue();
        MessageConsumer responseConsumer = session.createConsumer(responseQueue);

        RPCConnectionContext rpcContext = new RPCConnectionContext();
        rpcContext.copy(context);
        rpcContext.setDestination(responseQueue);
        rpcContext.setMessageConsumer(responseConsumer);
        rpcContext.setRequestMessage(msg);
        rpcContext.setResponseListener(responseListener);

        responseListener.setConsumerConnectionContext(rpcContext);
        responseConsumer.setMessageListener(responseListener);

        msg.setJMSReplyTo(responseQueue);

        // now send the message to the broker
        producer.send(msg);

        // put message ID into the message in case the caller wants to correlate it with another record
        MessageId messageId = new MessageId(msg.getJMSMessageID());
        basicMessage.setMessageId(messageId);

        return rpcContext;
    }

    /**
     * Same as {@link #createMessage(ConnectionContext, BasicMessage, Map)} with null headers.
     */
    protected Message createMessage(ConnectionContext context, BasicMessage basicMessage) throws JMSException {
        return createMessage(context, basicMessage, null);
    }

    /**
     * Creates a text message that can be send via a producer that contains the given BasicMessage's JSON encoded data.
     *
     * @param context the context whose session is used to create the message
     * @param basicMessage contains the data that will be JSON-encoded and encapsulated in the created message, with
     *            optional headers included
     * @param headers headers for the Message that will override same-named headers in the basic message
     * @return the message that can be produced
     * @throws JMSException any error
     * @throws NullPointerException if the context is null or the context's session is null
     */
    protected Message createMessage(ConnectionContext context, BasicMessage basicMessage, Map headers)
            throws JMSException {
        if (context == null) {
            throw new IllegalArgumentException("The context is null");
        }
        if (basicMessage == null) {
            throw new IllegalArgumentException("The message is null");
        }

        Session session = context.getSession();
        if (session == null) {
            throw new IllegalArgumentException("The context had a null session");
        }
        TextMessage msg = session.createTextMessage(basicMessage.toJSON());

        setHeaders(basicMessage, headers, msg);

        log.debugf("Created text message [%s] with text [%s]", msg, msg.getText());

        return msg;
    }

    /**
     * First sets the {@link MessageProcessor#HEADER_BASIC_MESSAGE_CLASS} string property of {@code destination} to
     * {@code basicMessage.getClass().getName()}, then copies all headers from {@code basicMessage.getHeaders()} to
     * {@code destination} using {@link Message#setStringProperty(String, String)} and then does the same thing with the
     * supplied {@code headers}.
     *
     * @param basicMessage the {@link BasicMessage} to copy headers from
     * @param headers the headers to copy to {@code destination}
     * @param destination the {@link Message} to copy the headers to
     * @throws JMSException
     */
    protected void setHeaders(BasicMessage basicMessage, Map headers, Message destination)
            throws JMSException {
        log.debugf("Setting [%s] = [%s] on a message of type [%s]", MessageProcessor.HEADER_BASIC_MESSAGE_CLASS,
                basicMessage.getClass().getName(), destination.getClass().getName());
        destination.setStringProperty(MessageProcessor.HEADER_BASIC_MESSAGE_CLASS, basicMessage.getClass().getName());

        // if the basicMessage has headers, use those first
        Map basicMessageHeaders = basicMessage.getHeaders();
        if (basicMessageHeaders != null) {
            for (Map.Entry entry : basicMessageHeaders.entrySet()) {
                destination.setStringProperty(entry.getKey(), entry.getValue());
            }
        }

        // If we were given headers separately, add those now.
        // Notice these will override same-named headers that were found in the basic message itself.
        if (headers != null) {
            for (Map.Entry entry : headers.entrySet()) {
                destination.setStringProperty(entry.getKey(), entry.getValue());
            }
        }
    }

    /**
     * Same as {@link #createMessage(ConnectionContext, BasicMessage, Map)} with null headers.
     */
    protected Message createMessageWithBinaryData(ConnectionContext context, BasicMessage basicMessage,
            InputStream inputStream) throws JMSException {
        return createMessageWithBinaryData(context, basicMessage, inputStream, null);
    }

    /**
     * Creates a blob message that can be send via a producer that contains the given BasicMessage's JSON encoded data
     * along with binary data.
     *
     * @param context the context whose session is used to create the message
     * @param basicMessage contains the data that will be JSON-encoded and encapsulated in the created message, with
     *            optional headers included
     * @param inputStream binary data that will be sent with the message
     * @param headers headers for the Message that will override same-named headers in the basic message
     * @return the message that can be produced
     * @throws JMSException any error
     * @throws NullPointerException if the context is null or the context's session is null
     */
    protected Message createMessageWithBinaryData(ConnectionContext context, BasicMessage basicMessage,
            InputStream inputStream, Map headers) throws JMSException {
        if (context == null) {
            throw new IllegalArgumentException("The context is null");
        }
        if (basicMessage == null) {
            throw new IllegalArgumentException("The message is null");
        }
        if (inputStream == null) {
            throw new IllegalArgumentException("The binary data is null");
        }

        Session session = context.getSession();
        if (session == null) {
            throw new IllegalArgumentException("The context had a null session");
        }

        // we are going to use BinaryData which allows us to prefix the binary data with the JSON message
        BinaryData messagePlusBinaryData = new BinaryData(basicMessage.toJSON().getBytes(), inputStream);

        BytesMessage msg = session.createBytesMessage();
        msg.setObjectProperty("JMS_AMQ_InputStream", messagePlusBinaryData);

        setHeaders(basicMessage, headers, msg);

        log.debugf("Created binary message [%s]", msg);

        return msg;
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy