Java tutorial
/* File: $Id$ * Date: $Date$ * Revision: $Revision$ * Author: $Author$ * * The Netarchive Suite - Software to harvest and preserve websites * Copyright 2004-2012 The Royal Danish Library, the Danish State and * University Library, the National Library of France and the Austrian * National Library. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 * USA */ package dk.netarkivet.archive.bitarchive.distribute; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Observable; import java.util.Observer; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import dk.netarkivet.archive.ArchiveSettings; import dk.netarkivet.archive.bitarchive.BitarchiveMonitor; import dk.netarkivet.archive.checksum.distribute.CorrectMessage; import dk.netarkivet.archive.checksum.distribute.GetAllChecksumsMessage; import dk.netarkivet.archive.checksum.distribute.GetAllFilenamesMessage; import dk.netarkivet.archive.checksum.distribute.GetChecksumMessage; import dk.netarkivet.archive.distribute.ArchiveMessageHandler; import dk.netarkivet.common.CommonSettings; import dk.netarkivet.common.distribute.Channels; import dk.netarkivet.common.distribute.JMSConnection; import dk.netarkivet.common.distribute.JMSConnectionFactory; import dk.netarkivet.common.distribute.NetarkivetMessage; import dk.netarkivet.common.distribute.RemoteFile; import dk.netarkivet.common.distribute.RemoteFileFactory; import dk.netarkivet.common.exceptions.ArgumentNotValid; import dk.netarkivet.common.exceptions.IOFailure; import dk.netarkivet.common.exceptions.UnknownID; import dk.netarkivet.common.utils.CleanupIF; import dk.netarkivet.common.utils.FileUtils; import dk.netarkivet.common.utils.KeyValuePair; import dk.netarkivet.common.utils.NotificationType; import dk.netarkivet.common.utils.NotificationsFactory; import dk.netarkivet.common.utils.Settings; import dk.netarkivet.common.utils.batch.ChecksumJob; import dk.netarkivet.common.utils.batch.FileBatchJob; import dk.netarkivet.common.utils.batch.FileListJob; /** * Class representing message handling for the monitor for bitarchives. The * monitor is used for sending out and combining the results of executing batch * jobs. * * Batch jobs are received on the BAMON-channel, and resent to all bitarchives, * that are considered live by the bitarchive monitor. * * Lets the bitarchive monitor handle batch replies from the bitarchives, and * observes it for when the batch job is done. Then constructs a reply from the * data given, and sends it back to the originator. * * Also registers signs of life from the bitarchives in the bitarchive monitor. */ public class BitarchiveMonitorServer extends ArchiveMessageHandler implements Observer, CleanupIF { /** * The unique instance of this class. */ private static BitarchiveMonitorServer instance; /** * Logger. */ private static Log log = LogFactory.getLog(BitarchiveMonitorServer.class); /** * The jms connection used. */ private final JMSConnection con = JMSConnectionFactory.getInstance(); /** * Object that handles logical operations. */ private BitarchiveMonitor bamon; /** * Map for managing the messages, which are made into batchjobs. * The String is the ID of the message. */ private Map<String, NetarkivetMessage> batchConversions = new HashMap<String, NetarkivetMessage>(); /** * Map for containing the batch-message-ids and the batchjobs, for * the result files to be post-processed before returned back. */ private Map<String, FileBatchJob> batchjobs = new HashMap<String, FileBatchJob>(); /** * The map for managing the CorrectMessages. This involves three stages. * * In the first, a RemoveAndGetFileMessage is sent, and then the * CorrectMessage is put in the map along the ID of the * RemoveAndGetFileMessage. * * In the second stage, the reply of the RemoveAndGetFileMessage is used to * extract the CorrectMessage from the Map. The CorrectMessage is then * updated with the results from the RemoveAndGetFileMessage. Then an * UploadMessage is send with the 'correct' file, where the ID of the * UploadMessage is put into the map along the CorrectMessage. * * In the third stage, the reply of the UploadMessage is used to extract * the CorrectMessage from the map again, and the results of the * UploadMessage is used to update the UploadMessage, which is then * returned. */ private Map<String, CorrectMessage> correctMessages = new HashMap<String, CorrectMessage>(); /** * Creates an instance of a BitarchiveMonitorServer. * * @throws IOFailure - if an error with the JMSConnection occurs */ protected BitarchiveMonitorServer() throws IOFailure { bamon = BitarchiveMonitor.getInstance(); bamon.addObserver(this); con.setListener(Channels.getTheBamon(), this); log.info( "BitarchiveMonitorServer instantiated. " + "Listening to queue: '" + Channels.getTheBamon() + "'."); } /** * Returns the unique instance of a BitarchiveMonitorServer. * * @return the instance * @throws IOFailure - if an error with the JMSConnection occurs */ public static synchronized BitarchiveMonitorServer getInstance() throws IOFailure { if (instance == null) { instance = new BitarchiveMonitorServer(); } return instance; } /** * This is the message handling method for BatchMessages. * * A new BatchMessage is created with the same Job as the incoming * BatchMessage and sent off to all live bitarchives. * * The incoming and outgoing batch messages are then registered at the * bitarchive monitor. * * @param inbMsg The message received * @throws ArgumentNotValid If the BatchMessage is null. */ public void visit(BatchMessage inbMsg) throws ArgumentNotValid { ArgumentNotValid.checkNotNull(inbMsg, "BatchMessage inbMsg"); log.info("Received BatchMessage\n" + inbMsg.toString()); try { BatchMessage outbMsg = new BatchMessage(Channels.getAllBa(), inbMsg.getJob(), Settings.get(CommonSettings.USE_REPLICA_ID)); con.send(outbMsg); long batchTimeout = inbMsg.getJob().getBatchJobTimeout(); // if batch time out is not a positive number, then use settings. if (batchTimeout <= 0) { batchTimeout = Settings.getLong(ArchiveSettings.BITARCHIVE_BATCH_JOB_TIMEOUT); } bamon.registerBatch(inbMsg.getID(), inbMsg.getReplyTo(), outbMsg.getID(), batchTimeout); batchjobs.put(inbMsg.getID(), inbMsg.getJob()); } catch (Exception e) { log.warn("Trouble while handling batch request '" + inbMsg + "'", e); } } /** * This is the message handling method for BatchEndedMessages. * * This delegates the handling of the reply to the bitarchive monitor, which * will notify us if the batch job is now done. * * @param beMsg The BatchEndedMessage to be handled. * @throws ArgumentNotValid If the BatchEndedMessage is null. */ public void visit(final BatchEndedMessage beMsg) throws ArgumentNotValid { ArgumentNotValid.checkNotNull(beMsg, "BatchEndedMessage beMsg"); log.debug("Received batch ended from bitarchive '" + beMsg.getBitarchiveID() + "': " + beMsg); bamon.signOfLife(beMsg.getBitarchiveID()); try { new Thread() { public void run() { // retrieve the error messages. String errorMessages = null; if (!beMsg.isOk()) { errorMessages = beMsg.getErrMsg(); } // send reply to the bitarchive. bamon.bitarchiveReply(beMsg.getOriginatingBatchMsgID(), beMsg.getBitarchiveID(), beMsg.getNoOfFilesProcessed(), beMsg.getFilesFailed(), beMsg.getRemoteFile(), errorMessages, beMsg.getExceptions()); } }.start(); } catch (Exception e) { log.warn("Trouble while handling bitarchive reply '" + beMsg + "'", e); } } /** * This is the message handling method for HeartBeatMessages. * * Registers a sign of life from a bitarchive. * * @param hbMsg the message that represents the sign of life * @throws ArgumentNotValid If the HeartBeatMessage is null. */ public void visit(HeartBeatMessage hbMsg) throws ArgumentNotValid { ArgumentNotValid.checkNotNull(hbMsg, "HeartBeatMessage hbMsg"); try { bamon.signOfLife(hbMsg.getBitarchiveID()); } catch (Exception e) { log.warn("Trouble while handling bitarchive sign of life '" + hbMsg + "'", e); } } /** * This is the first step in correcting a bad entry. * * In the first stage, a RemoveAndGetFileMessage is sent, and then the * CorrectMessage is put in the map along the ID of the * RemoveAndGetFileMessage. * * See the correctMessages Map. * * @param cm The CorrectMessage to handle. * @throws ArgumentNotValid If the CorrectMessage is null. */ public void visit(CorrectMessage cm) throws ArgumentNotValid { ArgumentNotValid.checkNotNull(cm, "CorrectMessage cm"); log.info("Receiving CorrectMessage: " + cm); try { // Create the RemoveAndGetFileMessage for removing the file. RemoveAndGetFileMessage ragfm = new RemoveAndGetFileMessage(Channels.getAllBa(), Channels.getTheBamon(), cm.getArcfileName(), cm.getReplicaId(), cm.getIncorrectChecksum(), cm.getCredentials()); // Send the message. con.send(ragfm); log.info("Step 1 of handling CorrectMessage. Sending " + "RemoveAndGetFileMessage: " + ragfm); // Put the CorrectMessage into the map along the id of the // RemoveAndGetFileMessage correctMessages.put(ragfm.getID(), cm); } catch (Exception e) { String errMsg = "An error occurred during step 1 of handling " + " the CorrectMessage: sending RemoveAndGetFileMessage"; log.warn(errMsg, e); cm.setNotOk(e); con.reply(cm); } } /** * This is the second step in correcting a bad entry. * * In the second stage, the reply of the RemoveAndGetFileMessage is used * to extract the CorrectMessage from the Map. The CorrectMessage is then * updated with the results from the RemoveAndGetFileMessage. Then an * UploadMessage is send with the 'correct' file, where the ID of the * UploadMessage is put into the map along the CorrectMessage. * * See the correctMessages Map. * * @param msg The RemoteAndGetFileMessage. * @throws ArgumentNotValid If the RemoveAndGetFileMessage is null. */ public void visit(RemoveAndGetFileMessage msg) throws ArgumentNotValid { ArgumentNotValid.checkNotNull(msg, "RemoveAndGetFileMessage msg"); log.info("Receiving RemoveAndGetFileMessage (presumably reply): " + msg); // Retrieve the correct message CorrectMessage cm = correctMessages.remove(msg.getID()); // If the RemoveAndGetFileMessage has failed, then the CorrectMessage // has also failed, and should be returned as a fail. if (!msg.isOk()) { String errMsg = "The RemoveAndGetFileMessage has returned the " + "error: '" + msg.getErrMsg() + "'. Reply to CorrectMessage " + "with the same error."; log.warn(errMsg); cm.setNotOk(errMsg); con.reply(cm); return; } try { // update the correct message. cm.setRemovedFile(msg.getRemoteFile()); // Create the upload message, send it. UploadMessage um = new UploadMessage(Channels.getAllBa(), Channels.getTheBamon(), cm.getCorrectFile()); con.send(um); log.info("Step 2 of handling CorrectMessage. Sending UploadMessage: " + um); // Store the CorrectMessage along with the ID of the UploadMessage. correctMessages.put(um.getID(), cm); } catch (Exception e) { String errMsg = "An error occurred during step 2 of handling " + " the CorrectMessage: sending UploadMessage"; log.warn(errMsg, e); cm.setNotOk(e); con.reply(cm); } } /** * This is the third step in correcting a bad entry. * * In the third stage, the reply of the UploadMessage is used to extract * the CorrectMessage from the map again, and the results of the * UploadMessage is used to update the UploadMessage, which is then * returned. * * See the correctMessages Map. * * @param msg The reply of the UploadMessage. * @throws ArgumentNotValid If the UploadMessage is null. */ public void visit(UploadMessage msg) throws ArgumentNotValid { ArgumentNotValid.checkNotNull(msg, "UploadMessage msg"); log.info("Receiving a reply to an UploadMessage: " + msg); // retrieve the CorrectMessage. CorrectMessage cm = correctMessages.remove(msg.getID()); // handle potential errors. if (!msg.isOk()) { cm.setNotOk(msg.getErrMsg()); } // reply to the correct message. con.reply(cm); log.info("Step 3 of handling CorrectMessage. Sending reply for " + "CorrectMessage: '" + cm + "'"); } /** * Method for handling the GetAllChecksumsMessage. * This message will be made into a batchjob, which will executed on the * bitarchives. The reply to the batchjob will be handled and uses as reply * to the GetAllChecksumsMessage. * * @param msg The GetAllChecksumsMessage, which will be made into a batchjob * and sent to the bitarchives. * @throws ArgumentNotValid If the GetAllChecksumsMessage is null. */ public void visit(GetAllChecksumsMessage msg) throws ArgumentNotValid { ArgumentNotValid.checkNotNull(msg, "GetAllChecksumsMessage msg"); log.info("Receiving GetAllChecksumsMessage '" + msg + "'"); // Create batchjob for the GetAllChecksumsMessage. ChecksumJob cj = new ChecksumJob(); // Execute the batchjob. executeConvertedBatch(cj, msg); } /** * Method for handling the GetAllFilenamesMessage. * The GetAllFilenamesMessage will be made into a filelist batchjob, which * will be sent to the bitarchives. The reply to the batchjob will then be * used as reply to the GetAllFilenamesMessage. * * @param msg The GetAllFilenamesMessage, which will be made into a batchjob * and sent to the bitarchives. * @throws ArgumentNotValid If the GetAllFilenamesMessage is null. */ public void visit(GetAllFilenamesMessage msg) throws ArgumentNotValid { ArgumentNotValid.checkNotNull(msg, "GetAllFilenamesMessage msg"); log.info("Receiving GetAllFilenamesMessage '" + msg + "'"); // Create batchjob for the GetAllChecksumsMessage. FileListJob flj = new FileListJob(); // Execute the batchjob. executeConvertedBatch(flj, msg); } /** * Method for handling the GetChecksumMessage. * This is made into the batchjob ChecksumsJob which will be limitted to * the specific filename. The batchjob will be executed on the bitarchives * and the reply to the batchjob will be used as reply to the * GetChecksumMessage. * * @param msg The GetAllChecksumsMessage, which will be made into a batchjob * and sent to the bitarchives. * @throws ArgumentNotValid If the GetChecksumMessage is null. */ public void visit(GetChecksumMessage msg) throws ArgumentNotValid { ArgumentNotValid.checkNotNull(msg, "GetChecksumMessage msg"); log.info("Receiving GetChecksumsMessage '" + msg + "'"); // Create batchjob for the GetAllChecksumsMessage. ChecksumJob cj = new ChecksumJob(); cj.processOnlyFileNamed(msg.getArcfileName()); cj.setBatchJobTimeout(Settings.getLong(ArchiveSettings.SINGLE_CHECKSUM_TIMEOUT)); // Execute the batchjob. executeConvertedBatch(cj, msg); } /** * Method for executing messages converted into batchjobs. * * @param job The job to execute. * @param msg The message which is converted into the batchjob. */ private void executeConvertedBatch(FileBatchJob job, NetarkivetMessage msg) { try { BatchMessage outbMsg = new BatchMessage(Channels.getAllBa(), job, Settings.get(CommonSettings.USE_REPLICA_ID)); con.send(outbMsg); long batchTimeout = job.getBatchJobTimeout(); // if batch time out is not a positive number, then use settings. if (batchTimeout <= 0) { batchTimeout = Settings.getLong(ArchiveSettings.BITARCHIVE_BATCH_JOB_TIMEOUT); } bamon.registerBatch(msg.getID(), msg.getReplyTo(), outbMsg.getID(), batchTimeout); batchjobs.put(msg.getID(), job); // Remember that the message is a batch conversion. log.info(outbMsg); batchConversions.put(msg.getID(), msg); } catch (Throwable e) { log.warn("Unable to handle batch '" + msg + "'request due to " + "unexpected exception", e); msg.setNotOk(e); con.reply(msg); } } /** * Handles notifications from the bitarchive monitor, that a batch job is * complete. * * Spawns a new thread in which all the results are wrapped and sent * back in a reply to the originator of this batch request. * * @param o the observable object. Should always be the bitarchive * monitor. If it isn't, this notification will be logged and * ignored. * @param arg an argument passed from the bitarchive monitor. This should * always be a batch status object indicating the end of that * batchjob. If it isn't, this notification will be logged and * ignored. */ public void update(Observable o, final Object arg) { if (o != bamon) { log.warn("Received unexpected notification from '" + o + "'"); return; } if (arg == null) { log.warn("Received unexpected notification with no argument"); return; } if (!(arg instanceof dk.netarkivet.archive.bitarchive.BitarchiveMonitor.BatchJobStatus)) { log.warn("Received notification with incorrect argument type " + arg.getClass() + ":'" + arg + "''"); return; } new Thread() { public void run() { // convert the input argument. BitarchiveMonitor.BatchJobStatus bjs = (BitarchiveMonitor.BatchJobStatus) arg; // Check whether converted message or actual batchjob. if (batchConversions.containsKey(bjs.originalRequestID)) { replyConvertedBatch(bjs); } else { doBatchReply(bjs); } } }.start(); } /** * This method sends a reply based on the information from bitarchives * received and stored in the given batch job status. * * It will concatenate the results from all the bitarchives in one file, and * construct a reply to the originating requester with all information. * * @param bjs Status of received messages from bitarchives. */ private void doBatchReply(BitarchiveMonitor.BatchJobStatus bjs) { RemoteFile resultsFile = null; try { // Post process the file. File postFile = File.createTempFile("post", "batch", FileUtils.getTempDir()); try { // retrieve the batchjob FileBatchJob bj = batchjobs.remove(bjs.originalRequestID); if (bj == null) { throw new UnknownID("Only knows: " + batchjobs.keySet()); } log.info("Post processing batchjob results for '" + bj.getClass().getName() + "' with id '" + bjs.originalRequestID + "'"); // perform the post process, and handle whether it succeeded. if (bj.postProcess(new FileInputStream(bjs.batchResultFile), new FileOutputStream(postFile))) { log.debug("Post processing finished."); } else { log.debug("No post processing. Using concatenated file."); tryAndDeleteTemporaryFile(postFile); postFile = bjs.batchResultFile; } } catch (Exception e) { log.warn("Exception caught during post processing batchjob. " + "Concatenated file used instead.", e); tryAndDeleteTemporaryFile(postFile); postFile = bjs.batchResultFile; } //Get remote file for batch result resultsFile = RemoteFileFactory.getMovefileInstance(postFile); } catch (Exception e) { log.warn("Make remote file from " + bjs.batchResultFile, e); bjs.appendError("Could not append batch results: " + e); } // Make batch reply message BatchReplyMessage brMsg = new BatchReplyMessage(bjs.originalRequestReplyTo, Channels.getTheBamon(), bjs.originalRequestID, bjs.noOfFilesProcessed, bjs.filesFailed, resultsFile); if (bjs.errorMessages != null) { brMsg.setNotOk(bjs.errorMessages); } // Send the batch reply message. con.send(brMsg); log.info("BatchReplyMessage: '" + brMsg + "' sent from BA monitor " + "to queue: '" + brMsg.getTo() + "'"); } /** Helper method to delete temporary files. * Logs at level debug, if it couldn't delete the file. * @param tmpFile the tmpFile that needs to be deleted. */ private void tryAndDeleteTemporaryFile(File tmpFile) { boolean deleted = tmpFile.delete(); if (!deleted) { log.debug("Failed to delete temporary file '" + tmpFile.getAbsolutePath() + "'"); } else { log.trace("Deleted temporary file '" + tmpFile.getAbsolutePath() + "' successfully"); } } /** * Uses the batchjobstatus on the message converted batchjob to reply on * the original message. * * @param bjs The status of the batchjob. */ private void replyConvertedBatch(BitarchiveMonitor.BatchJobStatus bjs) { // Retrieve the message corresponding to the converted batchjob. NetarkivetMessage msg = batchConversions.remove(bjs.originalRequestID); log.info("replying to converted batchjob message : " + msg); if (msg instanceof GetAllChecksumsMessage) { replyToGetAllChecksumsMessage(bjs, (GetAllChecksumsMessage) msg); } else if (msg instanceof GetAllFilenamesMessage) { replyToGetAllFilenamesMessage(bjs, (GetAllFilenamesMessage) msg); } else if (msg instanceof GetChecksumMessage) { replyToGetChecksumMessage(bjs, (GetChecksumMessage) msg); } else /* unhandled message type. */ { String errMsg = "The message cannot be handled '" + msg + "'"; log.error(errMsg); msg.setNotOk(errMsg); con.reply(msg); } } /** * Method for replying to a GetAllChecksumsMessage. * It uses the reply from the batchjob to make a proper reply to the * GetAllChecksumsMessage. * * @param bjs The BatchJobStatus used to reply to the * GetAllChecksumsMessage. * @param msg The GetAllChecksumsMessage to reply to. */ private void replyToGetAllChecksumsMessage(BitarchiveMonitor.BatchJobStatus bjs, GetAllChecksumsMessage msg) { try { // Set the resulting file. msg.setFile(bjs.batchResultFile); // record any errors. if (bjs.errorMessages != null) { msg.setNotOk(bjs.errorMessages); } } catch (Throwable e) { msg.setNotOk(e); log.warn("An error occurred during the handling of the " + "GetAllChecksumsMessage", e); } finally { // reply log.info("Replying to GetAllChecksumsMessage '" + msg + "'"); con.reply(msg); } } /** * Method for replying to a GetAllFilenamesMessage. * It uses the reply from the batchjob to make a proper reply to the * GetAllFilenamesMessage. * * @param bjs The BatchJobStatus used to reply to the * GetAllFilenamesMessage. * @param msg The GetAllFilenamesMessage to reply to. */ private void replyToGetAllFilenamesMessage(BitarchiveMonitor.BatchJobStatus bjs, GetAllFilenamesMessage msg) { try { // Set the resulting file. msg.setFile(bjs.batchResultFile); // record any errors. if (bjs.errorMessages != null) { msg.setNotOk(bjs.errorMessages); } } catch (Throwable e) { msg.setNotOk(e); log.warn("An error occurred during the handling of the " + "GetAllFilenamesMessage", e); } finally { // reply log.info("Replying to GetAllFilenamesMessage '" + msg + "'"); con.reply(msg); } } /** * Method for replying to a GetChecksumMessage. * It uses the reply from the batchjob to make a proper reply to the * GetChecksumMessage. * * @param bjs The BatchJobStatus to be used for the reply. * @param msg The GetChecksumMessage to reply to. */ private void replyToGetChecksumMessage(BitarchiveMonitor.BatchJobStatus bjs, GetChecksumMessage msg) { try { // Fetch the content of the batchresultfile. List<String> output = FileUtils.readListFromFile(bjs.batchResultFile); if (output.size() < 1) { String errMsg = "The batchjob did not find the file '" + msg.getArcfileName() + "' within the " + "archive."; log.warn(errMsg); throw new IOFailure(errMsg); } if (output.size() > 1) { // Log that duplicates have been found. log.warn("The file '" + msg.getArcfileName() + "' was found " + output.size() + " times in " + "the archive. Using the first found '" + output.get(0) + "' out of '" + output + "'"); // check if any different values. String firstVal = output.get(0); for (int i = 1; i < output.size(); i++) { if (!output.get(i).equals(firstVal)) { String errorString = "Replica '" + msg.getReplicaId() + "' has unidentical duplicates: '" + firstVal + "' and '" + output.get(i) + "'."; log.warn(errorString); NotificationsFactory.getInstance().notify(errorString, NotificationType.WARNING); } else { log.debug("Replica '" + msg.getReplicaId() + "' has identical duplicates: '" + firstVal + "'."); } } } // Extract the filename and checksum of the first result. KeyValuePair<String, String> firstResult = ChecksumJob.parseLine(output.get(0)); // Check that the filename has the expected value (the name of // the requested file). if (!msg.getArcfileName().equals(firstResult.getKey())) { String errMsg = "The first result found the file '" + firstResult.getKey() + "' but should have found '" + msg.getArcfileName() + "'."; log.error(errMsg); throw new IOFailure(errMsg); } // Put the checksum into the reply message, and reply. msg.setChecksum(firstResult.getValue()); // cleanup batchjob file FileUtils.remove(bjs.batchResultFile); } catch (Throwable e) { msg.setNotOk(e); log.warn("An error occurred during the handling of the " + "GetChecksumMessage", e); } finally { log.info("Replying GetChecksumMessage: '" + msg.toString() + "'."); // Reply to the original message (set 'isReply'). msg.setIsReply(); con.reply(msg); } } /** * Close down this BitarchiveMonitor. */ public void close() { log.info("BitarchiveMonitorServer closing down."); cleanup(); log.info("BitarchiveMonitorServer closed down"); } /** * Closes this BitarchiveMonitorServer cleanly. */ public synchronized void cleanup() { if (instance != null) { con.removeListener(Channels.getTheBamon(), this); batchConversions.clear(); instance = null; if (bamon != null) { bamon.cleanup(); bamon = null; } } } }