001/* 002 * #%L 003 * Netarchivesuite - archive 004 * %% 005 * Copyright (C) 2005 - 2014 The Royal Danish Library, the Danish State and University Library, 006 * the National Library of France and the Austrian National Library. 007 * %% 008 * This program is free software: you can redistribute it and/or modify 009 * it under the terms of the GNU Lesser General Public License as 010 * published by the Free Software Foundation, either version 2.1 of the 011 * License, or (at your option) any later version. 012 * 013 * This program is distributed in the hope that it will be useful, 014 * but WITHOUT ANY WARRANTY; without even the implied warranty of 015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 016 * GNU General Lesser Public License for more details. 017 * 018 * You should have received a copy of the GNU General Lesser Public 019 * License along with this program. If not, see 020 * <http://www.gnu.org/licenses/lgpl-2.1.html>. 021 * #L% 022 */ 023package dk.netarkivet.archive.checksum.distribute; 024 025import java.io.File; 026 027import org.slf4j.Logger; 028import org.slf4j.LoggerFactory; 029 030import dk.netarkivet.archive.ArchiveSettings; 031import dk.netarkivet.archive.bitarchive.distribute.UploadMessage; 032import dk.netarkivet.archive.checksum.ChecksumArchive; 033import dk.netarkivet.common.CommonSettings; 034import dk.netarkivet.common.distribute.Channels; 035import dk.netarkivet.common.distribute.JMSConnectionFactory; 036import dk.netarkivet.common.distribute.RemoteFile; 037import dk.netarkivet.common.distribute.RemoteFileFactory; 038import dk.netarkivet.common.exceptions.ArgumentNotValid; 039import dk.netarkivet.common.exceptions.IllegalState; 040import dk.netarkivet.common.exceptions.UnknownID; 041import dk.netarkivet.common.utils.FileUtils; 042import dk.netarkivet.common.utils.NotificationType; 043import dk.netarkivet.common.utils.NotificationsFactory; 044import dk.netarkivet.common.utils.Settings; 045import dk.netarkivet.common.utils.SystemUtils; 046 047/** 048 * The server for the ChecksumFileApplication. Used for communication between the ArcRepository and the checksum 049 * archive. 050 */ 051public class ChecksumFileServer extends ChecksumArchiveServer { 052 053 /** The logger used by this class. */ 054 private static final Logger log = LoggerFactory.getLogger(ChecksumFileServer.class); 055 056 /** The instance of this server. */ 057 protected static ChecksumFileServer instance; 058 059 /** The archive which contain the actual data. */ 060 protected ChecksumArchive cs; 061 062 /** The character to separate the applicationInstanceId and the IP address. */ 063 public static final String APPLICATION_ID_SEPARATOR = "_"; 064 065 /** 066 * Returns the unique instance of this class. 067 * <p> 068 * The server creates an instance of the checksum it creates access to and starts to listen to a JMS messages on the 069 * incoming JMS queue. 070 * <p> 071 * <p> 072 * Should this do the heart beats to a monitor? This would be quite odd, since Checksum does not use a monitor. 073 * 074 * @return This instance. 075 */ 076 public static ChecksumFileServer getInstance() { 077 if (instance == null) { 078 instance = new ChecksumFileServer(); 079 } 080 return instance; 081 } 082 083 /** 084 * Constructor. 085 */ 086 private ChecksumFileServer() { 087 // log that this instance is been invoked. 088 log.info("Initialising the ChecksumFileServer."); 089 090 // get the instance of the checksum archive 091 cs = ChecksumArchiveFactory.getInstance(); 092 093 // initialise the JMSConnection. 094 jmsCon = JMSConnectionFactory.getInstance(); 095 096 // initialise the channel. 097 theCR = Channels.getTheCR(); 098 099 // Start listening to the channel. 100 jmsCon.setListener(theCR, this); 101 102 // create the application identifier 103 checksumAppId = createAppId(); 104 105 // log that this instance has successfully been invoked. 106 log.info("ChecksumFileServer '{}' initialised.", checksumAppId); 107 } 108 109 /** 110 * Method for closing the instance. 111 */ 112 public void close() { 113 log.info("ChecksumFileServer '{}' closing down.", checksumAppId); 114 cleanup(); 115 if (jmsCon != null) { 116 jmsCon.removeListener(theCR, this); 117 jmsCon = null; 118 } 119 log.info("ChecksumFileServer '{}' closed down.", checksumAppId); 120 } 121 122 /** 123 * Method for cleaning up, when closing this instance down. 124 */ 125 public void cleanup() { 126 instance = null; 127 cs.cleanup(); 128 } 129 130 /** 131 * Method for retrieving the identification of this application. 132 * 133 * @return The id of this application. 134 */ 135 public String getAppId() { 136 return checksumAppId; 137 } 138 139 /** 140 * Method for creating the identification for this application. 141 * 142 * @return The id of this application. 143 */ 144 protected String createAppId() { 145 String id; 146 // Create an id with the IP address of this current host 147 id = SystemUtils.getLocalIP(); 148 149 // Append an underscore and APPLICATION_INSTANCE_ID from settings 150 // to the id, if specified in settings. 151 // If no APPLICATION_INSTANCE_ID is found do nothing. 152 try { 153 String applicationInstanceId = Settings.get(CommonSettings.APPLICATION_INSTANCE_ID); 154 if (!applicationInstanceId.isEmpty()) { 155 id += APPLICATION_ID_SEPARATOR + applicationInstanceId; 156 } 157 } catch (UnknownID e) { 158 // Ignore the fact, that there is no APPLICATION_INSTANCE_ID in 159 // settings 160 log.warn("No setting APPLICATION_INSTANCE_ID found in settings: ", e); 161 } 162 return id; 163 } 164 165 /** 166 * The method for uploading arc files. Note that cleanup of the upload file embedded in the message is delegated the 167 * method {@link ChecksumArchive#upload(RemoteFile, String)} 168 * 169 * @param msg The upload message, containing the file to upload. 170 * @throws ArgumentNotValid If the UploadMessage is null. 171 */ 172 public void visit(UploadMessage msg) throws ArgumentNotValid { 173 ArgumentNotValid.checkNotNull(msg, "UploadMessage msg"); 174 log.debug("Receiving UploadMessage: " + msg.toString()); 175 try { 176 try { 177 cs.upload(msg.getRemoteFile(), msg.getArcfileName()); 178 } catch (Throwable e) { 179 log.warn("Cannot process upload message '{}'", msg, e); 180 msg.setNotOk(e); 181 } finally { // check if enough space 182 if (!cs.hasEnoughSpace()) { 183 log.warn("Not enough space any more."); 184 jmsCon.removeListener(theCR, this); 185 } 186 } 187 } catch (Throwable e) { 188 log.warn("Cannnot remove listener after upload message '{}'", msg, e); 189 } finally { 190 log.debug("Replying to UploadMessage: {}", msg.toString()); 191 jmsCon.reply(msg); 192 } 193 } 194 195 /** 196 * Method for correcting an entry in the archive. It start by ensuring that the file exists, then it checks the 197 * credentials. Then it is checked whether the "bad entry" does have the "bad checksum". If no problems occurred, 198 * then the bad entry will be corrected by the archive (the bad entry is removed from the archive file and put into 199 * the "wrong entry" file. Then the new entry is placed in the archive file. 200 * <p> 201 * If it fails in any of the above, then the method fails (throws an exception which is caught and use for replying 202 * NotOk to the message). 203 * 204 * @param msg The message containing the correct instance of the file to correct. 205 * @throws ArgumentNotValid If the correct message is null. 206 */ 207 public void visit(CorrectMessage msg) throws ArgumentNotValid { 208 ArgumentNotValid.checkNotNull(msg, "CorrectMessage msg"); 209 log.debug("Receiving correct message: {}", msg.toString()); 210 // the file for containing the received file from the message. 211 File correctFile = null; 212 try { 213 String filename = msg.getArcfileName(); 214 String currentCs = cs.getChecksum(filename); 215 String incorrectCs = msg.getIncorrectChecksum(); 216 217 // ensure that the entry actually exists. 218 if (currentCs == null) { 219 // This exception is logged later. 220 throw new IllegalState("Cannot correct an entry for the file '" + filename 221 + "', since it is not within the archive."); 222 } 223 224 // Check credentials 225 String credentialsReceived = msg.getCredentials(); 226 if (credentialsReceived == null || credentialsReceived.isEmpty() 227 || !credentialsReceived.equals(Settings.get(ArchiveSettings.ENVIRONMENT_THIS_CREDENTIALS))) { 228 throw new IllegalState("The received credentials '" + credentialsReceived 229 + "' were invalid. The entry of " + "file '" + filename + "' will not be corrected."); 230 } 231 232 // check that the current checksum is incorrect as supposed. 233 if (!currentCs.equals(incorrectCs)) { 234 throw new IllegalState("Wrong checksum for the entry for file '" + filename + "' has the checksum '" 235 + currentCs + "', " + "though it was supposed to have the checksum '" + incorrectCs + "'."); 236 } 237 238 // retrieve the data as a file. 239 correctFile = File.createTempFile("correct", filename, FileUtils.getTempDir()); 240 msg.getData(correctFile); 241 242 // Log and notify 243 String warning = "The record for file '" + filename + "' is being corrected at '" 244 + Settings.get(CommonSettings.USE_REPLICA_ID) + "'"; 245 log.warn(warning); 246 NotificationsFactory.getInstance().notify(warning, NotificationType.WARNING); 247 248 // put the file into the archive. 249 File badFile = cs.correct(filename, correctFile); 250 251 // Send the file containing the removed entry back. 252 msg.setRemovedFile(RemoteFileFactory.getMovefileInstance(badFile)); 253 } catch (Throwable t) { 254 // Handle errors. 255 log.warn("Cannot handle CorrectMessage: '{}'", msg, t); 256 msg.setNotOk(t); 257 } finally { 258 // log and reply at the end. 259 log.info("Replying CorrectMessage: {}", msg.toString()); 260 jmsCon.reply(msg); 261 262 // cleanup the data file 263 if (correctFile != null) { 264 FileUtils.remove(correctFile); 265 } 266 } 267 } 268 269 /** 270 * Method for retrieving the checksum of a record. 271 * 272 * @param msg The GetChecksumMessage which contains the name of the record to have its checksum retrieved. 273 * @throws ArgumentNotValid If the message is null. 274 */ 275 public void visit(GetChecksumMessage msg) throws ArgumentNotValid { 276 ArgumentNotValid.checkNotNull(msg, "GetChecksumMessage msg"); 277 278 log.debug("Receiving GetChecksumMessage: {}", msg.toString()); 279 try { 280 // get the name of the arc file 281 String filename = msg.getArcfileName(); 282 // get the checksum of the arc file 283 String checksum = cs.getChecksum(filename); 284 285 // Check if the checksum was found. If not throw exception. 286 if (checksum == null || checksum.isEmpty()) { 287 // The error is logged, when the exception is caught. 288 throw new IllegalState("Cannot fetch checksum of an entry, " + filename 289 + ", which is not within the archive."); 290 } 291 292 // send the checksum of the arc file. 293 msg.setChecksum(checksum); 294 } catch (Throwable e) { 295 // Handle errors (if the file cannot be found). 296 log.warn("Cannot handle '{}' containing the message: {}", msg.getClass().getName(), msg, e); 297 msg.setNotOk(e); 298 } finally { 299 // TODO this should be set elsewhere. 300 msg.setIsReply(); 301 // log the message and reply. 302 log.info("Replying GetChecksumMessage: {}", msg.toString()); 303 jmsCon.reply(msg); 304 } 305 } 306 307 /** 308 * Method for retrieving all the filenames within the archive. 309 * 310 * @param msg The GetAllFilenamesMessage. 311 * @throws ArgumentNotValid If the GetAllFilenamesMessages is null. 312 */ 313 public void visit(GetAllFilenamesMessage msg) throws ArgumentNotValid { 314 ArgumentNotValid.checkNotNull(msg, "GetAllFilenamesMessage msg"); 315 log.debug("Receiving GetAllFilenamesMessage: {}", msg.toString()); 316 317 try { 318 // get all the file names 319 msg.setFile(cs.getAllFilenames()); 320 } catch (Throwable e) { 321 log.warn("Cannot retrieve the filenames to reply on the {} : {}", msg.getClass().getName(), msg, e); 322 msg.setNotOk(e); 323 } finally { 324 // log the message and reply. 325 log.info("Replying GetAllFilenamesMessage: {}", msg.toString()); 326 jmsCon.reply(msg); 327 } 328 } 329 330 /** 331 * Method for retrieving a map containing all the checksums and their corresponding filenames within the archive. 332 * 333 * @param msg The GetAllChecksumMessage. 334 * @throws ArgumentNotValid If the GetAllChecksumMessage is null. 335 */ 336 public void visit(GetAllChecksumsMessage msg) throws ArgumentNotValid { 337 ArgumentNotValid.checkNotNull(msg, "GetAllChecksumsMessage msg"); 338 log.debug("Receiving GetAllChecksumsMessage: {}", msg.toString()); 339 340 try { 341 msg.setFile(cs.getArchiveAsFile()); 342 } catch (Throwable e) { 343 log.warn("Cannot retrieve all the checksums.", e); 344 msg.setNotOk(e); 345 } finally { 346 // log the message and reply 347 log.info("Replying GetAllChecksumsMessage: {}", msg.toString()); 348 jmsCon.reply(msg); 349 } 350 } 351 352}