/**
* Licensed to The Apereo Foundation under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
*
* The Apereo Foundation licenses this file to you under the Educational
* Community 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://opensource.org/licenses/ecl2.txt
*
* 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.opencastproject.message.broker.impl;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.transport.TransportListener;
import org.apache.commons.lang3.StringUtils;
import org.osgi.framework.BundleContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.Date;
import javax.jms.Connection;
import javax.jms.DeliveryMode;
import javax.jms.JMSException;
import javax.jms.MessageProducer;
import javax.jms.Session;
/**
* This is a base facility that handles connections and sessions to an ActiveMQ message broker.
*/
public class MessageBaseFacility {
/** The key to find the URL to connect to the ActiveMQ Message Broker */
protected static final String ACTIVEMQ_BROKER_URL_KEY = "activemq.broker.url";
/** The key to find the username to connect to the ActiveMQ Message Broker */
protected static final String ACTIVEMQ_BROKER_USERNAME_KEY = "activemq.broker.username";
/** The key to find the password to connect to the ActiveMQ Message Broker */
protected static final String ACTIVEMQ_BROKER_PASSWORD_KEY = "activemq.broker.password";
/** Default Broker URL */
private static final String ACTIVEMQ_DEFAULT_URL
= "failover://(tcp://localhost:61616)?initialReconnectDelay=2000&maxReconnectAttempts=2";
/** Minimum time in milliseconds between two connection attempts */
private static final long MIN_RECONNECT_TIME = 60000;
/** The logging facility */
private static final Logger logger = LoggerFactory.getLogger(MessageBaseFacility.class);
/** Create a ConnectionFactory for establishing connections to the Active MQ broker */
private ActiveMQConnectionFactory connectionFactory = null;
/** The connection to the ActiveMQ broker */
private Connection connection = null;
/** Session used to communicate with the ActiveMQ broker */
private Session session = null;
/** The message producer */
private MessageProducer producer = null;
/** Time of last connection attempt */
private long lastConnection = 0;
/** Disabled state of the JMS connection. Used to ignore connection errors. */
protected boolean enabled = false;
/** Connection details */
private String url = ACTIVEMQ_DEFAULT_URL;
private String username = null;
private String password = null;
/** OSGi component activate callback */
public void activate(BundleContext bc) throws Exception {
final String name = this.getClass().getSimpleName();
url = bc.getProperty(ACTIVEMQ_BROKER_URL_KEY);
if (StringUtils.isBlank(url)) {
logger.info("No valid URL found. Using default URL");
url = ACTIVEMQ_DEFAULT_URL;
}
username = bc.getProperty(ACTIVEMQ_BROKER_USERNAME_KEY);
password = bc.getProperty(ACTIVEMQ_BROKER_PASSWORD_KEY);
logger.info("{} is configured to connect with URL {}", name, url);
enable(true);
if (connectMessageBroker()) {
logger.info("{} service successfully started", name);
}
}
/** OSGi component deactivate callback */
public void deactivate() {
logger.info("{} service is stopping...", this.getClass().getSimpleName());
enable(false);
disconnectMessageBroker();
logger.info("{} service successfully stopped", this.getClass().getSimpleName());
}
/** Opens new sessions and connections to the message broker */
protected synchronized boolean connectMessageBroker() {
if (!this.enabled) {
return false;
}
if (isConnected()) {
return true;
}
/* Wait between reconnection attempts */
long time = (new Date()).getTime();
if (time - lastConnection < MIN_RECONNECT_TIME) {
try {
Thread.sleep(MIN_RECONNECT_TIME - time + lastConnection);
} catch (Exception e) {
}
}
lastConnection = (new Date()).getTime();
disconnectMessageBroker(false);
try {
connectionFactory = new ActiveMQConnectionFactory(url);
if (StringUtils.isNotBlank(username) && StringUtils.isNotBlank(password)) {
connectionFactory.setUserName(username);
connectionFactory.setPassword(password);
}
connectionFactory.setTransportListener(new TransportListener() {
@Override
public void transportResumed() {
logger.info("Connection to ActiveMQ is working");
}
@Override
public void transportInterupted() {
logger.error("Connection to ActiveMQ message broker interupted ({}, username: {})",
new Object[] {url, username});
}
@Override
public void onException(IOException ex) {
logger.error("ActiveMQ transport exception: {}", ex.getMessage());
}
@Override
public void onCommand(Object obj) {
logger.trace("ActiveMQ command: {}", obj);
}
});
logger.info("Starting connection to ActiveMQ message broker, waiting until connection is established...");
connection = connectionFactory.createConnection();
connection.start();
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
producer = session.createProducer(null);
producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
} catch (JMSException e) {
logger.error("Failed connecting to ActiveMQ message broker using url '{}'", url);
/* Make sure to set session, etc. to null if connecting failed */
disconnectMessageBroker(false);
return false;
}
logger.info("Connection to ActiveMQ message broker successfully started");
return true;
}
/** Closes all open sessions and connections to the message broker */
protected void disconnectMessageBroker() {
disconnectMessageBroker(true);
}
/** Closes all open sessions and connections to the message broker */
protected void disconnectMessageBroker(final boolean verbose) {
if (producer != null || session != null || connection != null) {
if (verbose) {
logger.info("Stopping connection to ActiveMQ message broker...");
}
try {
if (producer != null) {
producer.close();
}
} catch (JMSException e) {
if (verbose) {
logger.error("Error while trying to close producer: {}", e);
}
}
producer = null;
try {
if (session != null) {
session.close();
}
} catch (JMSException e) {
if (verbose) {
logger.error("Error while trying to close session: {}", e);
}
}
session = null;
try {
if (connection != null) {
connection.close();
}
} catch (JMSException e) {
if (verbose) {
logger.error("Error while trying to close session: {}", e);
}
}
connection = null;
if (verbose) {
logger.info("Connection to ActiveMQ message broker successfully stopped");
}
}
}
/**
* Returns an open session or {@code null} if the facility is not yet connected.
*/
protected Session getSession() {
return session;
}
/**
* Return if there is a connection to the message broker.
*/
public boolean isConnected() {
try {
connection.getMetaData();
return this.enabled;
} catch (Exception e) {
return false;
}
}
/**
* Try reconnecting if there was no reconnection attempt in the last MIN_RECONNECT_TIME.
*/
public boolean reconnect() {
if (isConnected()) {
return true;
}
if ((new Date()).getTime() - lastConnection >= MIN_RECONNECT_TIME) {
return connectMessageBroker();
}
return false;
}
public void enable(final boolean state) {
this.enabled = state;
}
/**
* Returns an anonymous message producer or {@code null} if the facility is not yet connected.
* <p>
* The destination needs to be defined when sending the message ({@code producer.send(destination, message)})
*/
protected MessageProducer getMessageProducer() {
return producer;
}
}