/*
* 2012-3 Red Hat Inc. and/or its affiliates and other contributors.
*
* 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.overlord.rtgov.epn.jms;
import java.text.MessageFormat;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageListener;
import javax.jms.MessageProducer;
import javax.jms.ObjectMessage;
import javax.jms.Session;
import javax.naming.InitialContext;
import org.overlord.commons.services.ServiceClose;
import org.overlord.commons.services.ServiceInit;
import org.overlord.rtgov.common.util.RTGovProperties;
import org.overlord.rtgov.epn.AbstractEPNManager;
import org.overlord.rtgov.epn.Channel;
import org.overlord.rtgov.epn.EPNContainer;
import org.overlord.rtgov.epn.EventList;
import org.overlord.rtgov.epn.Network;
import org.overlord.rtgov.epn.Node;
/**
* This class provides the JMS implementation of
* the EPN Manager.
*
*/
public class JMSEPNManagerImpl extends AbstractEPNManager implements JMSEPNManager {
private ConnectionFactory _connectionFactory;
private Destination _epnEventsDestination;
private Destination _epnNotificationsDestination;
/** The subscription subjects. **/
public static final String EPN_SUBJECTS = "EPNSubjects";
/** The EPN Network Name. **/
public static final String EPN_NETWORK = "EPNNetwork";
/** The EPN Version. **/
public static final String EPN_VERSION = "EPNVersion";
/** The EPN Destination Node Names. **/
public static final String EPN_DESTINATION_NODES = "EPNDestinationNodes";
/** The EPN Source Name. **/
public static final String EPN_SOURCE = "EPNSource";
/** The EPN Number of Retries Left. **/
public static final String EPN_RETRIES_LEFT = "EPNRetriesLeft";
/** The EPN notification type. **/
public static final String EPN_NOTIFY_TYPE = "EPNNotifyType";
private boolean _initConsumers=false;
private String _epnEventsDestinationName=null;
private String _epnNotificationsDestinationName=null;
private String _username=RTGovProperties.getProperty("JMSEPNManager.username");
private String _password=RTGovProperties.getProperty("JMSEPNManager.password");
private Connection _connection=null;
private Session _session=null;
private MessageProducer _eventsProducer=null;
private MessageProducer _notificationsProducer=null;
private MessageConsumer _eventsConsumer=null;
private MessageConsumer _notificationsConsumer=null;
private EPNContainer _container=new JMSEPNContainer();
private boolean _initialized=false;
private static final Logger LOG=Logger.getLogger(JMSEPNManagerImpl.class.getName());
/**
* {@inheritDoc}
*/
protected boolean isManaged() {
return (true);
}
/**
* {@inheritDoc}
*/
protected EPNContainer getContainer() {
return (_container);
}
/**
* This method sets the connection factory.
*
* @param cf The connection factory
*/
public void setConnectionFactory(ConnectionFactory cf) {
_connectionFactory = cf;
}
/**
* This method returns the connection factory.
*
* @return The connection factory
*/
public ConnectionFactory getConnectionFactory() {
return (_connectionFactory);
}
/**
* This method sets the events destination.
*
* @param dest The destination
*/
public void setEventsDestination(Destination dest) {
_epnEventsDestination = dest;
}
/**
* This method returns the events destination.
*
* @return The events destination
*/
public Destination getEventsDestination() {
return (_epnEventsDestination);
}
/**
* This method sets the notifications destination.
*
* @param dest The destination
*/
public void setNotificationsDestination(Destination dest) {
_epnNotificationsDestination = dest;
}
/**
* This method returns the notifications destination.
*
* @return The notifications destination
*/
public Destination getNotificationsDestination() {
return (_epnNotificationsDestination);
}
/**
* This method sets the events destination name.
*
* @param dest The destination name
*/
public void setEventsDestinationName(String dest) {
_epnEventsDestinationName = dest;
}
/**
* This method returns the events destination name.
*
* @return The events destination name
*/
public String getEventsDestinationName() {
return (_epnEventsDestinationName);
}
/**
* This method sets the notifications destination name.
*
* @param dest The destination name
*/
public void setNotificationsDestinationName(String dest) {
_epnNotificationsDestinationName = dest;
}
/**
* This method returns the notifications destination name.
*
* @return The notifications destination name
*/
public String getNotificationsDestinationName() {
return (_epnNotificationsDestinationName);
}
/**
* This method determines whether consumers should be initialized.
* If false, then it means JMS messages will be consumed externally
* and supplied to this EPNManager for processing.
*
* @return Whether to initialize consumers
*/
public boolean getInitConsumers() {
return (_initConsumers);
}
/**
* This method determines whether consumers should be initialized.
* If false, then it means JMS messages will be consumed externally
* and supplied to this EPNManager for processing.
*
* @param initConsumers Whether to initialize consumers
*/
public void setInitConsumers(boolean initConsumers) {
_initConsumers = initConsumers;
}
/**
* This method sets the username.
*
* @param username The username
*/
public void setUsername(String username) {
_username = username;
}
/**
* This method returns the username.
*
* @return The username
*/
public String getUsername() {
return (_username);
}
/**
* This method sets the password.
*
* @param password The password
*/
public void setPassword(String password) {
_password = password;
}
/**
* This method returns the password.
*
* @return The password
*/
public String getPassword() {
return (_password);
}
/**
* The initialize method.
*/
@ServiceInit
public void init() {
super.init();
initJMS(true);
}
/**
* This method initializes the JMS connection and destinations.
*
* @param onInit Whether being called on service initializatipn
*/
protected synchronized void initJMS(boolean onInit) {
if (LOG.isLoggable(Level.FINEST)) {
LOG.finest("Initialise JMS (onInit="+onInit+", initialized="+_initialized+")");
}
if (_initialized) {
return;
}
// Check if connection factory initialized
if (_connectionFactory == null) {
try {
InitialContext context=new InitialContext();
_connectionFactory = (ConnectionFactory)context.lookup("java:/JmsXA");
if (LOG.isLoggable(Level.FINEST)) {
LOG.finest("Initialised connection factory="+_connectionFactory);
}
} catch (Exception e) {
LOG.log((onInit ? Level.FINEST : Level.SEVERE), "Failed to initialize JMS connection factory", e);
return;
}
}
if (_connectionFactory != null && _connection == null) {
try {
if (_username != null) {
_connection = _connectionFactory.createConnection(_username, _password);
} else {
_connection = _connectionFactory.createConnection();
}
if (LOG.isLoggable(Level.FINEST)) {
LOG.finest("Initialised connection="+_connection);
}
} catch (Exception e) {
LOG.log(Level.SEVERE, "Failed to create connection", e);
}
}
if (_connection != null && _session == null) {
try {
// TODO: Issue - must be non-transacted, to enable arquillian test
// to work, but ideally needs to be transacted in production to
// ensure transactional consistency
_session = _connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
if (LOG.isLoggable(Level.FINEST)) {
LOG.finest("Initialised session="+_session);
}
if (_epnEventsDestination == null && _epnEventsDestinationName != null) {
_epnEventsDestination = _session.createQueue(_epnEventsDestinationName);
if (LOG.isLoggable(Level.FINEST)) {
LOG.finest("Initialised events destination("+_epnEventsDestinationName+")="
+_epnEventsDestination);
}
}
if (_epnNotificationsDestination == null && _epnNotificationsDestinationName != null) {
_epnNotificationsDestination = _session.createTopic(_epnNotificationsDestinationName);
if (LOG.isLoggable(Level.FINEST)) {
LOG.finest("Initialised notifications destination("+_epnNotificationsDestinationName+")="
+_epnNotificationsDestination);
}
}
} catch (Exception e) {
LOG.log(Level.SEVERE, "Failed to create session or destinations", e);
}
}
if (_epnEventsDestination == null) {
try {
InitialContext context=new InitialContext();
_epnEventsDestination = (Destination)context.lookup("java:/EPNEvents");
if (LOG.isLoggable(Level.FINEST)) {
LOG.finest("Initialised events destination="+_epnEventsDestination);
}
} catch (Exception e) {
LOG.log((onInit ? Level.FINEST : Level.SEVERE), "Failed to initialize JMS events destination", e);
return;
}
}
if (_epnNotificationsDestination == null) {
try {
InitialContext context=new InitialContext();
_epnNotificationsDestination = (Destination)context.lookup("java:/EPNNotifications");
if (LOG.isLoggable(Level.FINEST)) {
LOG.finest("Initialised notifications destination="+_epnNotificationsDestination);
}
} catch (Exception e) {
LOG.log((onInit ? Level.FINEST : Level.SEVERE), "Failed to initialize JMS notifications destination", e);
return;
}
}
if (_session != null && _epnEventsDestination != null && _epnNotificationsDestination != null) {
try {
_eventsProducer = _session.createProducer(_epnEventsDestination);
_notificationsProducer = _session.createProducer(_epnNotificationsDestination);
if (LOG.isLoggable(Level.FINEST)) {
LOG.finest("Initialised producers, events="+_eventsProducer+" notifications="+_notificationsProducer);
}
if (_initConsumers) {
_eventsConsumer = _session.createConsumer(_epnEventsDestination);
_notificationsConsumer = _session.createConsumer(_epnNotificationsDestination);
_eventsConsumer.setMessageListener(new MessageListener() {
public void onMessage(Message message) {
try {
handleEventsMessage(message);
} catch (Exception e) {
LOG.log(Level.SEVERE, java.util.PropertyResourceBundle.getBundle(
"epn-jms.Messages").getString("EPN-JMS-7"), e);
}
}
});
_notificationsConsumer.setMessageListener(new MessageListener() {
public void onMessage(Message message) {
try {
handleNotificationsMessage(message);
} catch (Exception e) {
LOG.log(Level.SEVERE, java.util.PropertyResourceBundle.getBundle(
"epn-jms.Messages").getString("EPN-JMS-8"), e);
}
}
});
if (LOG.isLoggable(Level.FINEST)) {
LOG.finest("Initialised consumers, events="+_eventsConsumer+" notifications="+_notificationsConsumer);
}
}
_connection.start();
_initialized = true;
if (LOG.isLoggable(Level.FINEST)) {
LOG.finest("Connection started");
}
} catch (Exception e) {
LOG.log(Level.SEVERE, java.util.PropertyResourceBundle.getBundle(
"epn-jms.Messages").getString("EPN-JMS-1"), e);
}
setUsePrePostEventListProcessing(true);
}
}
/**
* {@inheritDoc}
*/
public void publish(String subject, java.util.List<? extends java.io.Serializable> events) throws Exception {
// Check if need to explicitly initialize
if (!_initialized) {
initJMS(false);
}
if (LOG.isLoggable(Level.FINEST)) {
LOG.finest("Publish "+events+" to subject '"+subject+"'");
}
javax.jms.ObjectMessage mesg=_session.createObjectMessage(new EventList(events));
mesg.setStringProperty(JMSEPNManagerImpl.EPN_SUBJECTS, subject);
_eventsProducer.send(mesg);
}
/**
* This method handles an events message received via JMS.
*
* @param message The JMS message carrying the events
* @throws Exception Failed to handle message
*/
public void handleEventsMessage(Message message) throws Exception {
// Check if need to explicitly initialize
if (!_initialized) {
initJMS(false);
}
if (message instanceof ObjectMessage) {
if (LOG.isLoggable(Level.FINEST)) {
LOG.finest("EPNManager("+this+"): Received event batch: "+message);
}
EventList events=(EventList)((ObjectMessage)message).getObject();
if (message.propertyExists(EPN_SUBJECTS)) {
dispatchToSubjects(message.getStringProperty(EPN_SUBJECTS), events);
}
if (message.propertyExists(EPN_NETWORK)) {
String network=message.getStringProperty(JMSEPNManagerImpl.EPN_NETWORK);
String version=message.getStringProperty(JMSEPNManagerImpl.EPN_VERSION);
String node=message.getStringProperty(JMSEPNManagerImpl.EPN_DESTINATION_NODES);
String source=message.getStringProperty(JMSEPNManagerImpl.EPN_SOURCE);
int retriesLeft=message.getIntProperty(JMSEPNManagerImpl.EPN_RETRIES_LEFT);
dispatchToNodes(network, version, node, source, events, retriesLeft);
}
} else {
LOG.severe(MessageFormat.format(java.util.PropertyResourceBundle.getBundle(
"epn-jms.Messages").getString("EPN-JMS-2"), message));
}
}
/**
* This method dispatches the events to any network that has subscribed to any one of
* the supplied list of subjects.
*
* @param subjectList The subject list
* @param events The list of events
* @throws Exception Failed to dispatch events to the networks associated with the
* supplied list of subjects
*/
protected void dispatchToSubjects(String subjectList, EventList events) throws Exception {
if (LOG.isLoggable(Level.FINEST)) {
LOG.finest("Dispatch to subjects="+subjectList);
}
String[] subjects=subjectList.split(",");
long timestamp=System.currentTimeMillis();
for (int i=0; i < subjects.length; i++) {
String subject=subjects[i];
java.util.List<Network> networks=getNetworksForSubject(subject);
if (networks == null) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("No networks exist for subject="+subject);
}
} else {
for (int j=0; j < networks.size(); j++) {
Network network=networks.get(j);
java.util.List<Node> nodes=network.getNodesForSubject(subject);
if (nodes != null) {
preProcessEvents(events, network);
for (int k=0; k < nodes.size(); k++) {
dispatch(network, nodes.get(k), subject, events, -1);
}
postProcessEvents(events);
setLastAccessed(network, timestamp);
}
}
}
}
}
/**
* This method dispatches the events to the list of nodes that are associated with
* the named network.
*
* @param networkName The name of the network
* @param version The version, or null for current
* @param nodeList The list of nodes
* @param source The source node, or null if sending to root
* @param events The list of events to be processed
* @param retriesLeft The number of retries left, or -1 if should be max value
* @throws Exception Failed to dispatch the events for processing
*/
protected void dispatchToNodes(String networkName, String version, String nodeList, String source,
EventList events, int retriesLeft) throws Exception {
if (LOG.isLoggable(Level.FINEST)) {
LOG.finest("Dispatch to network="+networkName+" and nodes="+nodeList);
}
Network network=getNetwork(networkName, version);
if (network == null) {
String mesg=MessageFormat.format(java.util.PropertyResourceBundle.getBundle(
"epn-jms.Messages").getString("EPN-JMS-3"),
networkName, version);
LOG.severe(mesg);
throw new IllegalArgumentException(mesg);
} else {
preProcessEvents(events, network);
String[] nodes=nodeList.split(",");
for (int i=0; i < nodes.length; i++) {
Node node=network.getNode(nodes[i]);
dispatch(network, node, source, events, retriesLeft);
}
postProcessEvents(events);
setLastAccessed(network, System.currentTimeMillis());
}
}
/**
* This method handles a notification message received via JMS.
*
* @param message The JMS message carrying the notification
* @throws Exception Failed to handle notification
*/
public void handleNotificationsMessage(Message message) throws Exception {
// Check if need to explicitly initialize
if (!_initialized) {
initJMS(false);
}
if (message instanceof ObjectMessage) {
if (LOG.isLoggable(Level.FINEST)) {
LOG.finest("EPNManager("+this+"): Received notification batch: "+message);
}
EventList events=(EventList)((ObjectMessage)message).getObject();
String subject=message.getStringProperty(JMSEPNManagerImpl.EPN_SUBJECTS);
dispatchNotificationToListeners(subject, events);
} else {
LOG.severe(MessageFormat.format(java.util.PropertyResourceBundle.getBundle(
"epn-jms.Messages").getString("EPN-JMS-2"), message));
}
}
/**
* This method dispatches a set of events directly to the supplied
* network and node.
*
* @param network The network
* @param node The node
* @param source The source node/subject
* @param events The list of events to be processed
* @param retriesLeft The number of retries left, or -1 if should be max value
* @throws Exception Failed to dispatch the events for processing
*/
protected void dispatch(Network network, Node node,
String source, EventList events,
int retriesLeft) throws Exception {
if (retriesLeft == -1) {
retriesLeft = node.getMaxRetries();
}
if (LOG.isLoggable(Level.FINEST)) {
LOG.finest("Dispatch "+network.getName()+"/"+network.getVersion()
+"/"+node.getName()+" ("+node
+") events="+events+" retriesLeft="+retriesLeft);
}
EventList retries=process(network, node,
source, events, retriesLeft);
if (retries != null) {
retry(network.getName(), network.getVersion(), node.getName(),
source, retries, retriesLeft-1);
}
}
/**
* This method handles retrying the supplied set of events, if the number of
* retries left is greater than 0.
*
* @param networkName The name of the network
* @param version The version
* @param nodeName The optional node name, or root node if not specified
* @param source The source
* @param events The events
* @param retriesLeft The number of retries now remaining after this failure to process them
* @throws Exception Failed to retry the events processing
*/
protected void retry(String networkName, String version, String nodeName,
String source, EventList events, int retriesLeft) throws Exception {
if (LOG.isLoggable(Level.FINEST)) {
LOG.finest("Retry "+networkName+"/"+nodeName+" events="+events+" retriesLeft="+retriesLeft);
}
if (retriesLeft >= 0) {
javax.jms.ObjectMessage mesg=_session.createObjectMessage(events);
mesg.setStringProperty(JMSEPNManagerImpl.EPN_NETWORK, networkName);
mesg.setStringProperty(JMSEPNManagerImpl.EPN_VERSION, version);
mesg.setStringProperty(JMSEPNManagerImpl.EPN_DESTINATION_NODES, nodeName);
mesg.setStringProperty(JMSEPNManagerImpl.EPN_SOURCE, source);
mesg.setIntProperty(JMSEPNManagerImpl.EPN_RETRIES_LEFT, retriesLeft);
_eventsProducer.send(mesg);
} else {
// Events failed to be processed
// TODO: Should this be reported via the manager?
LOG.severe(MessageFormat.format(java.util.PropertyResourceBundle.getBundle(
"epn-jms.Messages").getString("EPN-JMS-4"),
networkName, version, nodeName));
}
}
/**
* {@inheritDoc}
*/
@Override
protected void notifyListeners(String subject, EventList events) throws Exception {
if (LOG.isLoggable(Level.FINEST)) {
LOG.finest("Notify subject="+subject+" events="+events);
}
javax.jms.ObjectMessage mesg=_session.createObjectMessage(events);
mesg.setStringProperty(JMSEPNManagerImpl.EPN_SUBJECTS, subject);
_notificationsProducer.send(mesg);
}
/**
* {@inheritDoc}
*/
@ServiceClose
public void close() throws Exception {
super.close();
LOG.info("Closing JMS EPN Manager");
try {
if (_session != null) {
_session.close();
}
if (_connection != null) {
_connection.close();
}
_session = null;
_connection = null;
} catch (Exception e) {
LOG.log(Level.SEVERE, java.util.PropertyResourceBundle.getBundle(
"epn-jms.Messages").getString("EPN-JMS-5"), e);
}
}
/**
* This class provides the JMS implementation of the EPN container.
*
*/
protected class JMSEPNContainer implements EPNContainer {
/**
* {@inheritDoc}
*/
public Channel getChannel(Network network, String source, String dest)
throws Exception {
return new JMSChannel(_session, _eventsProducer, network, source, dest);
}
/**
* {@inheritDoc}
*/
public Channel getNotificationChannel(Network network, String subject)
throws Exception {
return new JMSChannel(_session, _eventsProducer, network, subject, true);
}
/**
* {@inheritDoc}
*/
public Channel getChannel(String subject) throws Exception {
// Create internal pub/sub channel
return new JMSChannel(_session, _eventsProducer, null, subject, false);
}
/**
* {@inheritDoc}
*/
public void send(EventList events, List<Channel> channels)
throws Exception {
if (channels.size() > 0) {
javax.jms.ObjectMessage mesg=_session.createObjectMessage(events);
String subjects=null;
String destNodes=null;
String networkName=null;
String version=null;
String sourceNode=null;
for (int i=0; i < channels.size(); i++) {
Channel channel=channels.get(i);
if (channel instanceof JMSChannel) {
JMSChannel jmsc=(JMSChannel)channel;
// Check if notification channel
if (jmsc.isNotificationChannel()) {
// Send immediately as no details
// need to be aggregated
notifyListeners(jmsc.getSubject(), events);
// Check if channel has internal pub/sub subjects
} else if (jmsc.getSubject() != null) {
if (subjects == null) {
subjects = jmsc.getSubject();
} else {
subjects += ","+jmsc.getSubject();
}
} else {
// Channel is point to point, to destination nodes(s)
if (destNodes == null) {
destNodes = jmsc.getDestinationNode();
networkName = jmsc.getNetworkName();
version = jmsc.getVersion();
sourceNode = jmsc.getSourceNode();
} else {
destNodes += ","+jmsc.getDestinationNode();
}
}
} else {
LOG.severe(MessageFormat.format(java.util.PropertyResourceBundle.getBundle(
"epn-jms.Messages").getString("EPN-JMS-6"),
channel));
}
}
boolean sendResults=false;
if (subjects != null) {
mesg.setStringProperty(JMSEPNManagerImpl.EPN_SUBJECTS, subjects);
sendResults = true;
}
if (destNodes != null) {
mesg.setStringProperty(JMSEPNManagerImpl.EPN_NETWORK, networkName);
mesg.setStringProperty(JMSEPNManagerImpl.EPN_VERSION, version);
mesg.setStringProperty(JMSEPNManagerImpl.EPN_DESTINATION_NODES, destNodes);
mesg.setStringProperty(JMSEPNManagerImpl.EPN_SOURCE, sourceNode);
mesg.setIntProperty(JMSEPNManagerImpl.EPN_RETRIES_LEFT, -1);
sendResults = true;
}
if (sendResults) {
if (LOG.isLoggable(Level.FINEST)) {
LOG.finest("Send events network="+networkName
+" version="+version
+" sourceNode="+sourceNode
+" nodes="+destNodes
+" subjects="+subjects+" events="+events);
}
_eventsProducer.send(mesg);
}
}
}
}
}