/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. 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.wso2.carbon.transport.jms.receiver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.wso2.carbon.messaging.CarbonMessageProcessor;
import org.wso2.carbon.transport.jms.exception.JMSConnectorException;
import org.wso2.carbon.transport.jms.factory.JMSConnectionFactory;
import javax.jms.Connection;
import javax.jms.Destination;
import javax.jms.ExceptionListener;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageListener;
import javax.jms.Session;
/**
* The {@link MessageConsumer} used to consume messages in carbon jms transport.
*/
public class JMSMessageConsumer implements MessageConsumer {
private static final Logger logger = LoggerFactory.getLogger(JMSMessageConsumer.class);
/**
* The {@link JMSConnectionFactory} instance which is used to create the consumer.
*/
private JMSConnectionFactory connectionFactory = null;
/**
* The JMS message consumer instance created by the respective JMS client.
*/
private MessageConsumer messageConsumer;
/**
* Tells to use a message receiver instead of a message listener.
*/
private boolean useReceiver = false;
/**
* The {@link Connection} instance represents the jms connection related with this server connector.
*/
private Connection connection;
/**
* The {@link Session} instance represents the jms session related with this server connector.
*/
private Session session;
/**
* The {@link Destination} instance represents a particular jms destination, this server connector listening to.
*/
private Destination destination;
/**
* The service Id which this consumer belongs to.
*/
private String serviceId;
/**
* The {@link CarbonMessageProcessor} instance represents the carbon message processor that handles the incoming
* messages.
*/
private CarbonMessageProcessor carbonMessageProcessor;
/**
* The {@link String} instance represents the jms connection username.
*/
private String username;
/**
* The {@link String} instance represents the jms connection password.
*/
private String password;
/**
* The retry handle which is going to retry connections when failed.
*/
private JMSConnectionRetryHandler retryHandler;
/**
* The retry interval (in milli seconds) if the connection is lost or if the connection cannot be established.
*/
private long retryInterval = 10000;
/**
* The maximum retry count, for retrying to establish a jms connection with the jms provider.
*/
private int maxRetryCount = 5;
/**
* Create a JMS message consumer and start consuming messages.
*
* @param connectionFactory The connection factory to use when creating the connection
* @param useReceiver Whether to use consumer.receive or use a listener when consuming messages
* @param carbonMessageProcessor The message processor who is going to process messages consumed from this
* @param serviceId The service Id which this consumer belongs to
* @param username The username to use when connecting to the JMS provider
* @param password The password to use when connecting to the JMS provider
* @param retryInterval The retry interval in milliseconds to retry connection to JMS provider when failed
* @param maxRetryCount The maximum retry count to retry when connection to the JMS provider fails
* @throws JMSConnectorException
*/
public JMSMessageConsumer(JMSConnectionFactory connectionFactory, boolean useReceiver,
CarbonMessageProcessor carbonMessageProcessor, String serviceId, String username,
String password, long retryInterval, int maxRetryCount) throws JMSConnectorException {
this.connectionFactory = connectionFactory;
this.useReceiver = useReceiver;
this.username = username;
this.password = password;
this.carbonMessageProcessor = carbonMessageProcessor;
this.serviceId = serviceId;
this.retryInterval = retryInterval;
this.maxRetryCount = maxRetryCount;
retryHandler = new JMSConnectionRetryHandler(this, retryInterval, maxRetryCount);
startConsuming();
}
/**
* Create consumers and starting consumer threads.
*
* @throws JMSConnectorException if starting consumer fails due to a JMS layer error.
*/
void startConsuming() throws JMSConnectorException {
try {
if (null != username && null != password) {
connection = connectionFactory.createConnection(username, password);
} else {
connection = connectionFactory.createConnection();
}
ExceptionListener exceptionListener = connection.getExceptionListener();
if (exceptionListener == null) {
ExceptionListener jmsExceptionListener = new JMSExceptionListener(this);
connection.setExceptionListener(jmsExceptionListener);
} else {
if (exceptionListener instanceof JMSExceptionListener) {
((JMSExceptionListener) exceptionListener).addConsumer(this);
} else {
throw new JMSConnectorException(
"This connection is already assigned with an unknown exception handler");
}
}
connectionFactory.start(connection);
session = connectionFactory.createSession(connection);
Destination destination = connectionFactory.getDestination(session);
messageConsumer = connectionFactory.createMessageConsumer(session, destination);
if (useReceiver) {
createMessageReceiver();
} else {
createMessageListener();
}
} catch (JMSException | JMSConnectorException e) {
if (!retryHandler.retry()) {
throw new JMSConnectorException("Connection to JMS server failed and retry was not successful", e);
}
}
}
/**
* Close the consumer by closing the relevant {@link Connection}
*
* @throws JMSConnectorException if closing the connection fails
*/
void closeAll() throws JMSConnectorException {
try {
if (connection != null) {
connectionFactory.closeConnection(connection);
connection = null;
}
} catch (JMSConnectorException e) {
throw new JMSConnectorException("Error closing connection of JMS Service " + serviceId, e);
} finally {
messageConsumer = null;
session = null;
connection = null;
}
}
/**
* Create a message listener to a particular jms destination.
*
* @throws JMSConnectorException JMS Connector exception can be thrown when trying to connect to jms provider
*/
private void createMessageListener() throws JMSConnectorException {
try {
messageConsumer.setMessageListener(new JMSMessageListener(carbonMessageProcessor, serviceId, session));
if (logger.isDebugEnabled()) {
logger.debug("Message listener created for service " + serviceId);
}
} catch (JMSException e) {
throw new JMSConnectorException("Error while initializing message listener", e);
}
}
/**
* Create a message receiver to retrieve messages.
*
* @throws JMSConnectorException Can be thrown when initializing the message handler or receiving messages
*/
private void createMessageReceiver() throws JMSConnectorException {
if (logger.isDebugEnabled()) {
logger.debug("Creating message receiver for service " + serviceId);
}
JMSMessageReceiver messageReceiver =
new JMSMessageReceiver(carbonMessageProcessor, serviceId, session, this);
messageReceiver.receive();
}
void stop() throws JMSConnectorException {
connectionFactory.stop(connection);
}
void start() throws JMSConnectorException {
connectionFactory.start(connection);
}
@Override
public String getMessageSelector() throws JMSException {
return messageConsumer.getMessageSelector();
}
@Override
public MessageListener getMessageListener() throws JMSException {
return messageConsumer.getMessageListener();
}
@Override
public void setMessageListener(MessageListener listener) throws JMSException {
messageConsumer.setMessageListener(listener);
}
@Override
public Message receive() throws JMSException {
return messageConsumer.receive();
}
@Override
public Message receive(long timeout) throws JMSException {
return messageConsumer.receive(timeout);
}
@Override
public Message receiveNoWait() throws JMSException {
return messageConsumer.receiveNoWait();
}
@Override
public void close() throws JMSException {
messageConsumer.close();
}
}
class JMSMessageConsumerBuilder {
/**
* The {@link JMSConnectionFactory} instance which is used to create the consumer.
*/
private JMSConnectionFactory connectionFactory = null;
/**
* Tells to use a message receiver instead of a message listener.
*/
private boolean useReceiver = false;
/**
* The service Id which this consumer belongs to.
*/
private String serviceId;
/**
* The {@link CarbonMessageProcessor} instance represents the carbon message processor that handles the incoming
* messages.
*/
private CarbonMessageProcessor carbonMessageProcessor;
/**
* The {@link String} instance represents the jms connection username.
*/
private String username;
/**
* The {@link String} instance represents the jms connection password.
*/
private String password;
/**
* The retry interval (in milli seconds) if the connection is lost or if the connection cannot be established.
*/
private long retryInterval = 10000;
/**
* The maximum retry count, for retrying to establish a jms connection with the jms provider.
*/
private int maxRetryCount = 5;
/**
* Initialize the builder with mandatory properties.
*
* @param connectionFactory The connection factory to use when creating the JMS connection
* @param carbonMessageProcessor The message processor who is going to process the consumed messages from this
* @param serviceId The service Id which invoked this consumer
*/
public JMSMessageConsumerBuilder(JMSConnectionFactory connectionFactory,
CarbonMessageProcessor carbonMessageProcessor, String serviceId) {
this.connectionFactory = connectionFactory;
this.carbonMessageProcessor = carbonMessageProcessor;
this.serviceId = serviceId;
}
public JMSMessageConsumerBuilder setUseReceiver(boolean useReceiver) {
this.useReceiver = useReceiver;
return this;
}
public JMSMessageConsumerBuilder setUsername(String username) {
this.username = username;
return this;
}
public JMSMessageConsumerBuilder setPassword(String password) {
this.password = password;
return this;
}
public JMSMessageConsumerBuilder setRetryInterval(long retryInterval) {
this.retryInterval = retryInterval;
return this;
}
public JMSMessageConsumerBuilder setMaxRetryCount(int maxRetryCount) {
this.maxRetryCount = maxRetryCount;
return this;
}
/**
* Build the {@link JMSMessageConsumer} with the given data.
* @return the JMS consumer initialized with all required data
*
* @throws JMSConnectorException If initializing the consumer fails
*/
public JMSMessageConsumer build() throws JMSConnectorException {
return new JMSMessageConsumer(connectionFactory, useReceiver, carbonMessageProcessor, serviceId, username,
password, retryInterval, maxRetryCount);
}
}