/*
* Copyright (c) NASK, NCSC
*
* This file is part of HoneySpider Network 2.1.
*
* This is a free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package pl.nask.hsn2.framework.bus;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import pl.nask.hsn2.bus.api.BusException;
import pl.nask.hsn2.bus.api.endpoint.ConsumeEndPoint;
import pl.nask.hsn2.bus.api.endpoint.ConsumeEndPointHandler;
import pl.nask.hsn2.bus.api.endpoint.ObservableConsumeEndPointHandler;
import pl.nask.hsn2.bus.api.endpoint.observer.ContentTypeObserver;
import pl.nask.hsn2.bus.api.endpoint.observer.RetryDeliveryObserver;
import pl.nask.hsn2.bus.connector.CommandExecutionConnector;
import pl.nask.hsn2.bus.connector.framework.DefaultJobEventsNotifier;
import pl.nask.hsn2.bus.connector.framework.JobEventsNotifier;
import pl.nask.hsn2.bus.connector.objectstore.DefaultObjectStoreConnector;
import pl.nask.hsn2.bus.connector.objectstore.ObjectStoreConnector;
import pl.nask.hsn2.bus.connector.process.DefaultProcessConnector;
import pl.nask.hsn2.bus.connector.process.ProcessConnector;
import pl.nask.hsn2.bus.dispatcher.CachableCommandFactory;
import pl.nask.hsn2.bus.dispatcher.CommandDispatcher;
import pl.nask.hsn2.bus.dispatcher.DefaultCommandDispatcher;
import pl.nask.hsn2.bus.dispatcher.ReflectionCommandFactory;
import pl.nask.hsn2.bus.operations.JobStatus;
import pl.nask.hsn2.bus.operations.Operation;
import pl.nask.hsn2.bus.rabbitmq.RbtDestination;
import pl.nask.hsn2.bus.rabbitmq.RbtUtils;
import pl.nask.hsn2.bus.rabbitmq.endpoint.RbtEndPointFactory;
import pl.nask.hsn2.bus.recovery.Recoverable;
import pl.nask.hsn2.bus.serializer.MessageSerializer;
import pl.nask.hsn2.bus.serializer.protobuf.ProtoBufMessageSerializer;
import pl.nask.hsn2.framework.core.WorkflowManager;
public final class RbtFrameworkBus implements FrameworkBus, Recoverable {
private static final Logger LOGGER = LoggerFactory.getLogger(RbtFrameworkBus.class);
private static final String SERVICE_QUEUE_PERFIX = "srv-";
private static final String SERVICE_LOW_PRIORITY_SUFFIX = ":l";
private static final String SERVICE_HIGH_PRIORITY_SUFFIX = ":h";
private static final long ONE_MINUTE = 60 * 1000;
private static final MessageSerializer<Operation> DEFAULT_SERIALIZER = new ProtoBufMessageSerializer();
private static final CommandDispatcher DEFAULT_COMMAND_DISPATCHER = new DefaultCommandDispatcher(
new CachableCommandFactory(
new ReflectionCommandFactory(
new String[] { "pl.nask.hsn2.bus.commands",
"pl.nask.hsn2.framework.commands"
})));
private ProcessConnector servicesConnector = null;
private ObjectStoreConnector objectStoreConnector = null;
private ConsumeEndPoint lowConsumeEndPoint = null;
private ConsumeEndPoint highConsumeEndPoint = null;
private JobEventsNotifier jobEventsNotifier = null;
private RbtBusConfiguration config = null;
private RbtEndPointFactory endPointFactory = null;
private boolean running = false;
private final Object mutex;
private final List<JobReminderData> jobReminders = Collections.synchronizedList(new ArrayList<JobReminderData>());
/**
* Default constructor.
*
* @param config Configuration for the bus.
*/
public RbtFrameworkBus(RbtBusConfiguration config) throws BusException {
this.config = config;
this.endPointFactory = new RbtEndPointFactory();
this.mutex = this;
setup(true);
}
private void setup(boolean purgeQueues) throws BusException {
this.endPointFactory
.setNumberOfconsumerThreads(config.getAmqpConsumersNumber())
.setServerAddress(config.getAMQPServerAddress())
.reconnect();
List<String> queues = new LinkedList<String>();
// Declarations of exchanges and bindings between them
String exchangeMonitoringName = config.getAmqpExchangeMonitoringName();
String exchangeCommonName = config.getAmqpExchangeCommonName();
String exchangeServicesName = config.getAmqpExchangeServicesName();
LOGGER.info("Creating exchanges:{}",new Object[]{exchangeCommonName,exchangeMonitoringName,exchangeServicesName});
RbtUtils.createExchanges(endPointFactory.getConnection(), exchangeCommonName, exchangeServicesName, exchangeMonitoringName);
// services queues
for (String sName : config.getServicesNames()) {
queues.add(SERVICE_QUEUE_PERFIX + sName + SERVICE_HIGH_PRIORITY_SUFFIX);
queues.add(SERVICE_QUEUE_PERFIX + sName + SERVICE_LOW_PRIORITY_SUFFIX);
}
// Object Store queues
queues.add(config.getOsHiQueueName());
queues.add(config.getOsLowQueueName());
// Framework queues
queues.add(config.getFrameworkHighQueue());
queues.add(config.getFrameworkLowQueue());
// deadletter queue
queues.add("deadletter");
LOGGER.info("Creating queues ({}): {}", purgeQueues?"purging":"queues data preserved", queues);
RbtUtils.createQueues(endPointFactory.getConnection(), exchangeServicesName, queues.toArray(new String[queues.size()]), purgeQueues);
}
@Override
public void start() throws BusException {
synchronized(mutex) {
// do not start if bus is running
if (running) {
return;
}
LOGGER.debug("Starting RbtBus");
initOutgoingConnectors();
// initiate message consumer with command dispatcher
ConsumeEndPointHandler commandConsumerHandler = new CommandExecutionConnector(
DEFAULT_SERIALIZER,
endPointFactory.createFireAndForgetEndPoint(),
DEFAULT_COMMAND_DISPATCHER);
ObservableConsumeEndPointHandler observable = new ObservableConsumeEndPointHandler(
commandConsumerHandler);
observable.addDeliveryObserver(new ContentTypeObserver());
observable.addDeliveryObserver(new RetryDeliveryObserver(
endPointFactory.createFireAndForgetEndPoint()));
LOGGER.info("Starting consumer endpoints for the bus...");
lowConsumeEndPoint = endPointFactory.createConsumeEndPoint(
observable, config.getFrameworkLowQueue());
highConsumeEndPoint = endPointFactory.createConsumeEndPoint(
observable, config.getFrameworkHighQueue());
this.running = true;
}
}
@Override
public ObjectStoreConnector getObjectStoreConnector() {
return objectStoreConnector;
}
@Override
public ProcessConnector getProcessConnector() {
return servicesConnector;
}
@Override
public void stop() {
synchronized(mutex) {
if (!running) {
return;
}
try {
lowConsumeEndPoint.close();
} catch (BusException e) {
// problem with closing endpoint, ignoring
}
lowConsumeEndPoint = null;
try {
highConsumeEndPoint.close();
} catch (BusException e) {
// problem with closing endpoint, ignoring
}
highConsumeEndPoint = null;
try {
RbtUtils.closeConnection(endPointFactory.getConnection());
} catch (BusException e) {
// problem with closing connection, ignoring
}
objectStoreConnector.releaseResources();
objectStoreConnector = null;
servicesConnector.releaseResources();
servicesConnector = null;
jobEventsNotifier.releaseResources();
jobEventsNotifier = null;
running = false;
}
}
@Override
public boolean isRunning() {
synchronized(mutex) {return this.running;}
}
@Override
public void recovery() {
boolean recover = false;
try {
if (!endPointFactory.isConnectionValid()) {
stop();
if (!recover) {
LOGGER.error("Connection failure. Cannot reliable recover.");
LOGGER.error("Unfinished jobs:{}.shutting down.",WorkflowManager.getInstance().getWorkflowJobs()) ;
System.exit(1);
} else {
LOGGER.info("Bus problem occured, trying to recover...");
}
setup(false);
start();
}
} catch(BusException ex) {
LOGGER.error("Bus recovery failed.", ex);
}
}
@Override
public void jobStarted(long jobId) {
if (jobEventsNotifier != null) {
jobEventsNotifier.jobStarted(jobId);
} else {
LOGGER.debug("Job events notifier not enabled, will not inform...");
}
}
@Override
public void jobFinished(long jobId, JobStatus status) {
if (jobEventsNotifier != null) {
jobEventsNotifier.jobFinished(jobId, status);
} else {
LOGGER.debug("Job events notifier not enabled, will not inform...");
}
}
public void initOutgoingConnectors() throws BusException {
if (servicesConnector == null) {
servicesConnector = new DefaultProcessConnector(
DEFAULT_SERIALIZER,
endPointFactory,
new RbtDestination(config.getAmqpExchangeCommonName(), config.getFrameworkLowQueue()),
getServicesDestinations(config.getAmqpExchangeCommonName(), config.getServicesNames(), SERVICE_QUEUE_PERFIX, SERVICE_LOW_PRIORITY_SUFFIX));
}
if (objectStoreConnector == null) {
objectStoreConnector = new DefaultObjectStoreConnector(
DEFAULT_SERIALIZER,
new RbtDestination(config.getAmqpExchangeCommonName(), config.getOsHiQueueName()),
endPointFactory.createThreadSafeRequestResponseEndPoint(),
endPointFactory.createFireAndForgetEndPoint());
}
if (jobEventsNotifier == null) {
jobEventsNotifier = new DefaultJobEventsNotifier(
DEFAULT_SERIALIZER, endPointFactory,
new RbtDestination(config.getAmqpExchangeMonitoringName(), ""));
}
LOGGER.info("Initialized outgoing connectors:\n\t{},\n\t{},\n\t{}",new Object[]{servicesConnector,objectStoreConnector,jobEventsNotifier});
}
private Map<String, RbtDestination> getServicesDestinations(String exchange, String[] serviceNames, String prefix, String suffix) {
HashMap<String, RbtDestination> servicesDestinations = new HashMap<String, RbtDestination>();
String routingKey;
for (String s : serviceNames) {
routingKey = new StringBuilder(prefix).append(s).append(suffix).toString();
servicesDestinations.put(s, new RbtDestination(exchange, routingKey));
}
return servicesDestinations;
}
@Override
public void jobFinishedReminder(long jobId, JobStatus status, int offendingTask) {
if (jobEventsNotifier == null) {
LOGGER.debug("Job events notifier not enabled, will not inform. (jobId={}, taskId={})", jobId, offendingTask);
return;
}
synchronized (jobReminders) {
// Cut off old reminders.
long now = System.currentTimeMillis();
int index = Collections.binarySearch(jobReminders, now - ONE_MINUTE);
if (index < 0) {
index = -index - 1;
} else {
index++;
}
jobReminders.subList(0, index).clear();
// Now only reminders from last minute left. Search for job id.
JobReminderData reminderFound = null;
for (JobReminderData tempReminder : jobReminders) {
if (tempReminder.getJobId() == jobId) {
reminderFound = tempReminder;
break;
}
}
// Proceed with reminder sending if needed.
if (reminderFound == null) {
// Reminder not found, sending new one.
jobReminders.add(new JobReminderData(jobId, now));
LOGGER.debug("Reminder not found, sending new one. (jobId={}, taskId={})", jobId, offendingTask);
jobEventsNotifier.jobFinishedReminder(jobId, status, offendingTask);
} else {
// Reminder found, ignore current one.
LOGGER.debug("Reminder found, ignore current one. (jobId={}, taskId={})", jobId, offendingTask);
}
}
}
@Override
public void releaseResources() {
stop();
}
}