/*
* Autopsy Forensic Browser
*
* Copyright 2011-2015 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* 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.sleuthkit.autopsy.events;
import java.net.URISyntaxException;
import java.util.logging.Level;
import javax.jms.Connection;
import javax.jms.DeliveryMode;
import javax.jms.JMSException;
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.jms.Topic;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.sleuthkit.autopsy.coreutils.Logger;
/**
* Provides thread-safe support for publishing events to registered subscribers
* on other Autopsy nodes and for publishing events from other Autopsy nodes.
* Subscribers on this node are constrained to be PropertyChangeListeners to
* integrate with the legacy use of JavaBeans PropertyChangeEvents and
* PropertyChangeListeners as an application event system.
*/
final class RemoteEventPublisher {
private static final Logger logger = Logger.getLogger(RemoteEventPublisher.class.getName());
private static final String ALL_MESSAGE_SELECTOR = "All"; //NON-NLS
private final LocalEventPublisher localPublisher;
private final Connection connection;
private final Session session;
private final MessageProducer producer;
private final MessageConsumer consumer;
private final MessageReceiver receiver;
/**
* Constructs an object for publishing events to registered subscribers on
* other Autopsy nodes and for publishing events from other Autopsy nodes.
*
* @param eventChannelName The name of the event channel to be used for
* communication with other Autopsy nodes.
* @param localPublisher An event publisher that will be used to publish
* events from other Autopsy nodes on this node.
* @param info Connection info for the message service.
*
* @throws URISyntaxException if the URI in the connection info is
* malformed.
* @throws JMSException if the connection to the message service
* cannot be made.
*/
RemoteEventPublisher(String eventChannelName, LocalEventPublisher localPublisher, MessageServiceConnectionInfo info) throws URISyntaxException, JMSException {
try {
this.localPublisher = localPublisher;
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(info.getUserName(), info.getPassword(), info.getURI());
connection = connectionFactory.createConnection();
connection.start();
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Topic topic = session.createTopic(eventChannelName);
producer = session.createProducer(topic);
producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
consumer = session.createConsumer(topic, "events = '" + ALL_MESSAGE_SELECTOR + "'", true); //NON-NLS
receiver = new MessageReceiver();
consumer.setMessageListener(receiver);
} catch (URISyntaxException | JMSException ex) {
logger.log(Level.SEVERE, "Failed to connect to event channel", ex); //NON-NLS
try {
stop();
} catch (JMSException ignored) {
/**
* Not surprising if there is some error here, but it was worth
* trying to clean up.
*/
}
throw ex;
}
}
/**
* Stops this publisher, causing it to disconnect from the message service.
*
* @throws JMSException if there is a problem closing the session or the
* connection.
*/
synchronized void stop() throws JMSException {
if (null != producer) {
producer.close();
}
if (null != consumer) {
consumer.close();
}
if (null != session) {
session.close();
}
if (null != connection) {
connection.close();
}
}
/**
* Sends an event message to the message service.
*
* @param event The event to publish.
*/
synchronized void publish(AutopsyEvent event) throws JMSException {
ObjectMessage message = session.createObjectMessage();
message.setStringProperty("events", ALL_MESSAGE_SELECTOR); //NON-NLS
message.setObject(event);
producer.send(message);
}
/**
* Receives event messages from the message service and publishes them
* locally.
*/
private final class MessageReceiver implements MessageListener {
/**
* Receives an event message from the message service and publishes it
* locally. Called by a JMS thread.
*
* @param message The message.
*/
@Override
public void onMessage(Message message) {
try {
if (message instanceof ObjectMessage) {
ObjectMessage objectMessage = (ObjectMessage) message;
Object object = objectMessage.getObject();
if (object instanceof AutopsyEvent) {
AutopsyEvent event = (AutopsyEvent) object;
event.setSourceType(AutopsyEvent.SourceType.REMOTE);
localPublisher.publish(event);
}
}
} catch (Exception ex) {
logger.log(Level.SEVERE, "Error receiving message", ex); //NON-NLS
}
}
}
}