package dk.kb.yggdrasil;
import java.util.HashMap;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import dk.kb.yggdrasil.bitmag.Bitrepository;
import dk.kb.yggdrasil.config.Models;
import dk.kb.yggdrasil.config.RequestHandlerContext;
import dk.kb.yggdrasil.config.YggdrasilConfig;
import dk.kb.yggdrasil.db.StateDatabase;
import dk.kb.yggdrasil.exceptions.ArgumentCheck;
import dk.kb.yggdrasil.exceptions.RabbitException;
import dk.kb.yggdrasil.exceptions.YggdrasilException;
import dk.kb.yggdrasil.messaging.MQ;
import dk.kb.yggdrasil.messaging.MessageRequestHandler;
import dk.kb.yggdrasil.messaging.MqResponse;
import dk.kb.yggdrasil.messaging.RemotePreservationStateUpdater;
import dk.kb.yggdrasil.preservation.PreservationRequestHandler;
import dk.kb.yggdrasil.preservationimport.PreservationImportRequestHandler;
/**
* The class receiving and initiating the workflows for the different kinds of requests.
*/
public class Workflow {
/** The RabbitMQ connection used by this workflow. */
private MQ mq;
/** Logging mechanism. */
private static Logger logger = LoggerFactory.getLogger(Workflow.class.getName());
/** The mapping between message type and message request handlers.*/
private final Map<String, MessageRequestHandler> requestHandlers;
/** Whether or not to shutdown. */
private boolean shutdown = false;
/**
* Constructor for the Workflow class.
* @param rabbitconnector The rabbitmq connector object
* @param states the StateDatabase
* @param bitrepository the interface with bitrepository
* @param config general configuration
* @param models metadatamodelMapper
* @param httpCommunication The httpCommunication.
* @param updater The remote preservation state updater.
*/
public Workflow(MQ rabbitconnector, StateDatabase states, Bitrepository bitrepository, YggdrasilConfig config,
Models models, HttpCommunication httpCommunication, RemotePreservationStateUpdater updater) {
ArgumentCheck.checkNotNull(rabbitconnector, "MQ rabbitconnector");
ArgumentCheck.checkNotNull(states, "StateDatabase states");
ArgumentCheck.checkNotNull(bitrepository, "Bitrepository bitrepository");
ArgumentCheck.checkNotNull(config, "Config config");
ArgumentCheck.checkNotNull(models, "Models models");
this.mq = rabbitconnector;
RequestHandlerContext context = new RequestHandlerContext(bitrepository, config, states,
updater, httpCommunication);
requestHandlers = new HashMap<String, MessageRequestHandler>();
requestHandlers.put(MQ.PRESERVATIONREQUEST_MESSAGE_TYPE.toUpperCase(),
new PreservationRequestHandler(context, models));
requestHandlers.put(MQ.IMPORTREQUEST_MESSAGE_TYPE.toUpperCase(),
new PreservationImportRequestHandler(context));
}
/**
* Run this method infinitely.
* @throws YggdrasilException If a preservation request cannot be handled.
* @throws RabbitException When message queue connection fails.
*/
public void run() throws YggdrasilException, RabbitException {
RequestReceiver requestReceiver = new RequestReceiver(mq.getSettings().getPreservationDestination());
RequestReceiver shutdownReceiver = new RequestReceiver(mq.getSettings().getShutdownDestination());
requestReceiver.run();
shutdownReceiver.run();
while (!shutdown) {
try {
synchronized(this) {
this.wait();
}
} catch (InterruptedException e) {
logger.debug("Ignore interruption.", e);
}
}
synchronized(requestReceiver) {
requestReceiver.notify();
}
synchronized(shutdownReceiver) {
shutdownReceiver.notify();
}
}
/**
* Receiver thread class.
* The receiving from a queue needs to be split into dedicated threads to be able to receive from different queues simultaneous.
*
* TODO: Handle shutdown receiver differently from request-receiver?
*/
class RequestReceiver extends Thread {
/** The queue to listen to. */
final String queue;
/**
* Constructor.
* @param queue The name of the queue.
*/
protected RequestReceiver(String queue) {
this.queue = queue;
}
@Override
public void run() {
while (!shutdown) {
try {
this.handleNextRequest();
} catch (YggdrasilException e) {
logger.error("Caught exception while retrieving message from rabbitmq. Skipping message", e);
continue;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
/**
* Wait until the next request arrives on the queue and handle it responsively.
* @return whether a shutdown message was received.
* @throws YggdrasilException If bad messagetype
* @throws RabbitException When message queue connection fails.
*/
private void handleNextRequest() throws YggdrasilException, RabbitException {
MqResponse requestContent = mq.receiveMessageFromQueue(queue);
String messageType = (requestContent == null ? null : requestContent.getMessageType());
if (requestContent == null || messageType == null) {
throw new YggdrasilException("'null' messagetype is not handled. message ignored ");
} else if (messageType.equalsIgnoreCase(MQ.SHUTDOWN_MESSAGE_TYPE)) {
terminate();
} else if (requestHandlers.containsKey(messageType.toUpperCase())) {
MessageRequestHandler mrh = requestHandlers.get(messageType.toUpperCase());
mrh.handleRequest(mrh.extractRequest(requestContent.getPayload()));
} else {
throw new YggdrasilException("The message type '"
+ messageType + "' is not handled by Yggdrasil.");
}
}
/**
* Shutting down.
*/
private synchronized void terminate() {
logger.warn("Shutdown message received");
shutdown = true;
this.notifyAll();
}
}
}