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.arcrepositoryadmin; 024 025import java.sql.Connection; 026import java.sql.Date; 027import java.sql.PreparedStatement; 028import java.sql.ResultSet; 029import java.sql.SQLException; 030import java.sql.Statement; 031import java.util.ArrayList; 032import java.util.Calendar; 033import java.util.HashMap; 034import java.util.HashSet; 035import java.util.List; 036import java.util.Map; 037import java.util.Set; 038 039import org.slf4j.Logger; 040import org.slf4j.LoggerFactory; 041 042import dk.netarkivet.common.distribute.arcrepository.Replica; 043import dk.netarkivet.common.distribute.arcrepository.ReplicaStoreState; 044import dk.netarkivet.common.distribute.arcrepository.ReplicaType; 045import dk.netarkivet.common.exceptions.IOFailure; 046import dk.netarkivet.common.exceptions.IllegalState; 047import dk.netarkivet.common.utils.DBUtils; 048import dk.netarkivet.common.utils.NotificationType; 049import dk.netarkivet.common.utils.NotificationsFactory; 050 051/** 052 * Helper methods used by {@link ReplicaCacheDatabase}. 053 */ 054public final class ReplicaCacheHelpers { 055 056 /** The log. */ 057 protected static Logger log = LoggerFactory.getLogger(ReplicaCacheHelpers.class); 058 059 /** Private constructor to avoid instantiation. */ 060 private ReplicaCacheHelpers() { 061 } 062 063 /** 064 * Method for checking whether a replicafileinfo is in the database. 065 * 066 * @param fileid The id of the file. 067 * @param replicaID The id of the replica. 068 * @param con An open connection to the archive database 069 * @return Whether the replicafileinfo was there or not. 070 * @throws IllegalState If more than one copy of the replicafileinfo is placed in the database. 071 */ 072 protected static boolean existsReplicaFileInfoInDB(long fileid, String replicaID, Connection con) 073 throws IllegalState { 074 // retrieve the amount of times this replicafileinfo 075 // is within the database. 076 String sql = "SELECT COUNT(*) FROM replicafileinfo WHERE file_id = ? AND replica_id = ?"; 077 int count = DBUtils.selectIntValue(con, sql, fileid, replicaID); 078 079 // Handle the different cases for count. 080 switch (count) { 081 case 0: 082 return false; 083 case 1: 084 return true; 085 default: 086 throw new IllegalState("Cannot handle " + count + " replicafileinfo entries with the id '" + fileid + "'."); 087 } 088 } 089 090 /** 091 * Method for inserting a Replica into the replica table. The replica_guid is automatically given by the database, 092 * and the values in the fields replica_id, replica_name and replica_type is created from the replica argument. 093 * 094 * @param rep The Replica to insert into the replica table. 095 * @param con An open connection to the archive database 096 * @throws IOFailure If a SQLException is caught. 097 */ 098 protected static void insertReplicaIntoDB(Replica rep, Connection con) throws IOFailure { 099 PreparedStatement statement = null; 100 try { 101 // Make the SQL statement for putting the replica into the database 102 // and insert the variables for the entry to the replica table. 103 statement = con.prepareStatement("INSERT INTO replica (replica_id, replica_name, replica_type) " 104 + "(SELECT ?,?,? from replica WHERE replica_id=? HAVING count(*) = 0)"); 105 statement.setString(1, rep.getId()); 106 statement.setString(2, rep.getName()); 107 statement.setInt(3, rep.getType().ordinal()); 108 statement.setString(4, rep.getId()); 109 log.debug("Executing insert, conditional on {} not already existing in the database.", rep.getId()); 110 int result = statement.executeUpdate(); 111 log.debug("Insert statement for {} returned {}", rep.getId(), result); 112 con.commit(); 113 } catch (SQLException e) { 114 throw new IOFailure("Cannot add replica '" + rep + "'to the database.", e); 115 } finally { 116 DBUtils.closeStatementIfOpen(statement); 117 } 118 } 119 120 /** 121 * Method to create a new entry in the file table in the database. The file_id is automatically created by the 122 * database, and the argument is used for the filename for this new entry to the table. This will also create a 123 * replicafileinfo entry for each replica. 124 * 125 * @param filename The filename for the new entry in the file table. 126 * @param connection An open connection to the archive database 127 * @return created file_id for the new entry. 128 * @throws IllegalState If the file cannot be inserted into the database. 129 */ 130 protected static long insertFileIntoDB(String filename, Connection connection) throws IllegalState { 131 log.debug("Insert file '{}' into database", filename); 132 PreparedStatement statement = null; 133 try { 134 // Make the SQL statement for putting the replica into the database 135 // and insert the variables for the entry to the replica table. 136 statement = connection.prepareStatement("INSERT INTO file (filename) VALUES ( ? )", 137 Statement.RETURN_GENERATED_KEYS); 138 statement.setString(1, filename); 139 140 // execute the SQL statement 141 statement.executeUpdate(); 142 // Retrieve the fileId for the just inserted file. 143 ResultSet resultset = statement.getGeneratedKeys(); 144 resultset.next(); 145 long fileId = resultset.getLong(1); 146 connection.commit(); 147 148 // Create replicafileinfo for each replica. 149 createReplicaFileInfoEntriesInDB(fileId, connection); 150 log.debug("Insert file '{}' into database completed. Assigned fileID={}", filename, fileId); 151 return fileId; 152 } catch (SQLException e) { 153 throw new IllegalState("Cannot add file '" + filename + "' to the database.", e); 154 } finally { 155 DBUtils.closeStatementIfOpen(statement); 156 } 157 } 158 159 /** 160 * When a new file is inserted into the database, each replica gets a new entry in the replicafileinfo table for 161 * this file. The fields for this new entry are set to the following: - file_id = argument. - replica_id = The id of 162 * the current replica. - filelist_status = NO_FILELIST_STATUS. - checksum_status = UNKNOWN. - upload_status = 163 * NO_UPLOAD_STATUS. 164 * <p> 165 * The replicafileinfo_guid is automatically created by the database, and the dates are set to null. 166 * 167 * @param fileId The id for the file. 168 * @param con An open connection to the archive database 169 * @throws IllegalState If the file could not be entered into the database. 170 */ 171 protected static void createReplicaFileInfoEntriesInDB(long fileId, Connection con) throws IllegalState { 172 PreparedStatement statement = null; 173 try { 174 // init variables 175 List<String> repIds = ReplicaCacheHelpers.retrieveIdsFromReplicaTable(con); 176 177 // Make a entry for each replica. 178 for (String repId : repIds) { 179 // create if it does not exists already. 180 if (!existsReplicaFileInfoInDB(fileId, repId, con)) { 181 // Insert with the known values (no dates). 182 statement = DBUtils.prepareStatement(con, 183 "INSERT INTO replicafileinfo (file_id, replica_id, filelist_status, checksum_status, " 184 + "upload_status ) VALUES ( ?, ?, ?, ?, ? )", fileId, repId, 185 FileListStatus.NO_FILELIST_STATUS.ordinal(), ChecksumStatus.UNKNOWN.ordinal(), 186 ReplicaStoreState.UNKNOWN_UPLOAD_STATE.ordinal()); 187 188 // execute the SQL statement 189 statement.executeUpdate(); 190 con.commit(); 191 statement.close(); // Important to cleanup! 192 } 193 } 194 } catch (SQLException e) { 195 throw new IllegalState("Cannot add replicafileinfo to the database.", e); 196 } finally { 197 DBUtils.closeStatementIfOpen(statement); 198 } 199 } 200 201 /** 202 * Method for retrieving the replica IDs within the database. 203 * 204 * @param con An open connection to the archive database 205 * @return The list of replicaIds from the replica table in the database. 206 */ 207 protected static List<String> retrieveIdsFromReplicaTable(Connection con) { 208 // Make SQL statement for retrieving the replica_ids in the 209 // replica table. 210 final String sql = "SELECT replica_id FROM replica"; 211 212 // execute the SQL statement and return the results. 213 return DBUtils.selectStringList(con, sql); 214 } 215 216 /** 217 * Method for retrieving all the file IDs within the database. 218 * 219 * @param con An open connection to the archive database 220 * @return The list of fileIds from the file table in the database. 221 */ 222 protected static List<String> retrieveIdsFromFileTable(Connection con) { 223 // Make SQL statement for retrieving the file_ids in the 224 // file table. 225 final String sql = "SELECT file_id FROM file"; 226 227 // execute the SQL statement and return the results. 228 return DBUtils.selectStringList(con, sql); 229 } 230 231 /** 232 * Method for retrieving all the ReplicaFileInfo GUIDs within the database. 233 * 234 * @param con An open connection to the archive database 235 * @return A list of the replicafileinfo_guid for all entries in the replicafileinfo table. 236 */ 237 protected static List<String> retrieveIdsFromReplicaFileInfoTable(Connection con) { 238 // Make SQL statement for retrieving the replicafileinfo_guids in the 239 // replicafileinfo table. 240 final String sql = "SELECT replicafileinfo_guid FROM replicafileinfo"; 241 242 // execute the SQL statement and return the results. 243 return DBUtils.selectStringList(con, sql); 244 } 245 246 /** 247 * Retrieves the file_id for the corresponding filename. An error is thrown if no such file_id is found in the file 248 * table, and if more than one instance with the given name is found, then a warning is issued. If more than one is 249 * found, then it is logged and only the first is returned. 250 * 251 * @param filename The entry in the filename list where the corresponding file_id should be found. 252 * @param con An open connection to the archive database 253 * @return The file_id for the file, or -1 if the file was not found. 254 */ 255 protected static long retrieveIdForFile(String filename, Connection con) { 256 // retrieve the file_id of the entry in the file table with the 257 // filename filename. 258 final String sql = "SELECT file_id FROM file WHERE filename = ?"; 259 List<Long> files = DBUtils.selectLongList(con, sql, filename); 260 261 switch (files.size()) { 262 // if no such file within the database, then return negative value. 263 case 0: 264 return -1; 265 case 1: 266 return files.get(0); 267 // if more than one file, then log it and return the first found. 268 default: 269 log.warn("Only one entry in the file table for the name '{}' was expected, but {} was found. " 270 + "The first element is returned.", filename, files.size()); 271 return files.get(0); 272 } 273 } 274 275 /** 276 * Method for retrieving the replicafileinfo_guid for a specific instance defined from the fileId and the replicaId. 277 * If more than one is found, then it is logged and only the first is returned. 278 * 279 * @param fileId The identifier for the file. 280 * @param replicaId The identifier for the replica. 281 * @param con An open connection to the archive database 282 * @return The identifier for the replicafileinfo, or -1 if not found. 283 */ 284 protected static long retrieveReplicaFileInfoGuid(long fileId, String replicaId, Connection con) { 285 // sql for retrieving the replicafileinfo_guid. 286 final String sql = "SELECT replicafileinfo_guid FROM replicafileinfo WHERE file_id = ? AND replica_id = ?"; 287 List<Long> result = DBUtils.selectLongList(con, sql, fileId, replicaId); 288 289 // Handle the different cases for count. 290 switch (result.size()) { 291 case 0: 292 return -1; 293 case 1: 294 return result.get(0); 295 default: 296 log.warn( 297 "More than one replicafileinfo with the file id '{}' from replica '{}': {}. The first result returned.", 298 fileId, replicaId, result); 299 return result.get(0); 300 } 301 } 302 303 /** 304 * Method for retrieving the list of all the replicafileinfo_guids for a specific replica. 305 * 306 * @param replicaId The id for the replica to contain the files. 307 * @param con An open connection to the archiveDatabase. 308 * @return The list of all the replicafileinfo_guid. 309 */ 310 protected static Set<Long> retrieveReplicaFileInfoGuidsForReplica(String replicaId, Connection con) { 311 // sql for retrieving the replicafileinfo_guids for the replica. 312 final String sql = "SELECT replicafileinfo_guid FROM replicafileinfo " 313 + "WHERE replica_id = ? ORDER BY replicafileinfo_guid"; 314 return DBUtils.selectLongSet(con, sql, replicaId); 315 } 316 317 /** 318 * Method for retrieving the replica type for a specific replica. 319 * 320 * @param replicaId The id of the replica. 321 * @param con An open connection to the archiveDatabase. 322 * @return The type of the replica. 323 */ 324 protected static ReplicaType retrieveReplicaType(String replicaId, Connection con) { 325 // The SQL statement for retrieving the replica_type of a replica with 326 // the given replica id. 327 final String sql = "SELECT replica_type FROM replica WHERE replica_id = ?"; 328 return ReplicaType.fromOrdinal(DBUtils.selectIntValue(con, sql, replicaId)); 329 } 330 331 /** 332 * Method for retrieving the list of replica, where the file with the given name has the checksum_status 'OK'. 333 * 334 * @param filename The name of the file. 335 * @param con An open connection to the archive database 336 * @return The list of replicas where the status for the checksum of the file is OK. 337 */ 338 protected static List<String> retrieveReplicaIdsWithOKChecksumStatus(String filename, Connection con) { 339 // The SQL statement to retrieve the replica_id for the entries in the 340 // replicafileinfo table for the given fileId and checksum_status = OK 341 // FIXME Use joins 342 final String sql = "SELECT replica_id FROM replicafileinfo, file " 343 + "WHERE replicafileinfo.file_id = file.file_id AND file.filename = ? AND checksum_status = ?"; 344 return DBUtils.selectStringList(con, sql, filename, ChecksumStatus.OK.ordinal()); 345 } 346 347 /** 348 * Method for retrieving the filename from the entry in the file table which has the fileId as file_id. 349 * 350 * @param fileId The file_id of the entry in the file table for which to retrieve the filename. 351 * @param con An open connection to the archive database 352 * @return The filename corresponding to the fileId in the file table. 353 */ 354 protected static String retrieveFilenameForFileId(long fileId, Connection con) { 355 // The SQL statement to retrieve the filename for a given file_id 356 final String sql = "SELECT filename FROM file WHERE file_id = ?"; 357 return DBUtils.selectStringValue(con, sql, fileId); 358 } 359 360 /** 361 * Method for retrieving the filelist_status for the entry in the replicafileinfo table associated with the given 362 * filename for the replica identified with a given id. 363 * 364 * @param filename the filename of the file for which you want a status. 365 * @param replicaId The identifier of the replica 366 * @param con An open connection to the archive database 367 * @return The above mentioned filelist_status of the file 368 */ 369 protected static int retrieveFileListStatusFromReplicaFileInfo(String filename, String replicaId, Connection con) { 370 // The SQL statement to retrieve the filelist_status for the given 371 // entry in the replica fileinfo table. 372 // FIXME Use joins 373 final String sql = "SELECT filelist_status FROM replicafileinfo, file " 374 + "WHERE file.file_id = replicafileinfo.file_id AND file.filename=? AND replica_id=?"; 375 return DBUtils.selectIntValue(con, sql, filename, replicaId); 376 } 377 378 /** 379 * This is used for updating a replicafileinfo instance based on the results of a checksumjob. Updates the following 380 * fields for the entry in the replicafileinfo: <br/> 381 * - checksum = checksum argument. <br/> 382 * - upload_status = completed. <br/> 383 * - filelist_status = ok. <br/> 384 * - checksum_status = UNKNOWN. <br/> 385 * - checksum_checkdatetime = now. <br/> 386 * - filelist_checkdatetime = now. 387 * 388 * @param replicafileinfoId The unique id for the replicafileinfo. 389 * @param checksum The new checksum for the entry. 390 * @param con An open connection to the archive database 391 */ 392 protected static void updateReplicaFileInfoChecksum(long replicafileinfoId, String checksum, Connection con) { 393 PreparedStatement statement = null; 394 try { 395 // The SQL statement 396 final String sql = "UPDATE replicafileinfo SET checksum = ?, upload_status = ?, filelist_status = ?," 397 + " checksum_status = ?, checksum_checkdatetime = ?, filelist_checkdatetime = ? " 398 + "WHERE replicafileinfo_guid = ?"; 399 400 Date now = new Date(Calendar.getInstance().getTimeInMillis()); 401 402 // complete the SQL statement. 403 statement = DBUtils.prepareStatement(con, sql, checksum, ReplicaStoreState.UPLOAD_COMPLETED.ordinal(), 404 FileListStatus.OK.ordinal(), ChecksumStatus.UNKNOWN.ordinal(), now, now, replicafileinfoId); 405 406 // execute the SQL statement 407 statement.executeUpdate(); 408 con.commit(); 409 } catch (Exception e) { 410 String msg = "Problems updating the replicafileinfo."; 411 log.warn(msg); 412 throw new IOFailure(msg, e); 413 } finally { 414 DBUtils.closeStatementIfOpen(statement); 415 } 416 } 417 418 /** 419 * Method for updating the filelist of a replicafileinfo instance. Updates the following fields for the entry in the 420 * replicafileinfo: <br/> 421 * filelist_status = OK. <br/> 422 * filelist_checkdatetime = current time. 423 * 424 * @param replicafileinfoId The id of the replicafileinfo. 425 * @param con An open connection to the archive database 426 */ 427 protected static void updateReplicaFileInfoFilelist(long replicafileinfoId, Connection con) { 428 PreparedStatement statement = null; 429 try { 430 // The SQL statement 431 final String sql = "UPDATE replicafileinfo SET filelist_status = ?, filelist_checkdatetime = ? " 432 + "WHERE replicafileinfo_guid = ?"; 433 434 Date now = new Date(Calendar.getInstance().getTimeInMillis()); 435 436 // complete the SQL statement. 437 statement = DBUtils.prepareStatement(con, sql, FileListStatus.OK.ordinal(), now, replicafileinfoId); 438 439 // execute the SQL statement 440 statement.executeUpdate(); 441 con.commit(); 442 } catch (Exception e) { 443 String msg = "Problems updating the replicafileinfo."; 444 log.warn(msg); 445 throw new IOFailure(msg, e); 446 } finally { 447 DBUtils.closeStatementIfOpen(statement); 448 } 449 } 450 451 /** 452 * Method for updating the filelist of a replicafileinfo instance. Updates the following fields for the entry in the 453 * replicafileinfo: <br/> 454 * filelist_status = missing. <br/> 455 * filelist_checkdatetime = current time. 456 * <p> 457 * The replicafileinfo is in the filelist. 458 * 459 * @param replicafileinfoId The id of the replicafileinfo. 460 * @param con An open connection to the archive database 461 */ 462 protected static void updateReplicaFileInfoMissingFromFilelist(long replicafileinfoId, Connection con) { 463 PreparedStatement statement = null; 464 try { 465 // The SQL statement 466 final String sql = "UPDATE replicafileinfo " 467 + "SET filelist_status = ?, filelist_checkdatetime = ?, upload_status = ? " 468 + "WHERE replicafileinfo_guid = ?"; 469 470 Date now = new Date(Calendar.getInstance().getTimeInMillis()); 471 472 // complete the SQL statement. 473 statement = DBUtils.prepareStatement(con, sql, FileListStatus.MISSING.ordinal(), now, 474 ReplicaStoreState.UPLOAD_FAILED.ordinal(), replicafileinfoId); 475 476 // execute the SQL statement 477 statement.executeUpdate(); 478 con.commit(); 479 } catch (Exception e) { 480 String msg = "Problems updating the replicafileinfo."; 481 log.warn(msg); 482 throw new IOFailure(msg, e); 483 } finally { 484 DBUtils.closeStatementIfOpen(statement); 485 } 486 } 487 488 /** 489 * Method for updating the checksum status of a replicafileinfo instance. Updates the following fields for the entry 490 * in the replicafileinfo: <br/> 491 * checksum_status = CORRUPT. <br/> 492 * checksum_checkdatetime = current time. 493 * <p> 494 * The replicafileinfo is in the filelist. 495 * 496 * @param replicafileinfoId The id of the replicafileinfo. 497 * @param con An open connection to the archive database 498 */ 499 protected static void updateReplicaFileInfoChecksumCorrupt(long replicafileinfoId, Connection con) { 500 PreparedStatement statement = null; 501 try { 502 // The SQL statement 503 final String sql = "UPDATE replicafileinfo " 504 + "SET checksum_status = ?, checksum_checkdatetime = ?, upload_status = ? " 505 + "WHERE replicafileinfo_guid = ?"; 506 507 Date now = new Date(Calendar.getInstance().getTimeInMillis()); 508 509 // complete the SQL statement. 510 statement = DBUtils.prepareStatement(con, sql, ChecksumStatus.CORRUPT.ordinal(), now, 511 ReplicaStoreState.UPLOAD_FAILED.ordinal(), replicafileinfoId); 512 513 // execute the SQL statement 514 statement.executeUpdate(); 515 con.commit(); 516 } catch (Exception e) { 517 String msg = "Problems updating the replicafileinfo."; 518 log.warn(msg); 519 throw new IOFailure(msg, e); 520 } finally { 521 DBUtils.closeStatementIfOpen(statement); 522 } 523 } 524 525 /** 526 * Retrieve the guid stored for a filename on a given replica. 527 * 528 * @param filename a given filename 529 * @param replicaId An identifier for a replica. 530 * @param con An open connection to the archive database 531 * @return the abovementioned guid. 532 */ 533 protected static long retrieveGuidForFilenameOnReplica(String filename, String replicaId, Connection con) { 534 // sql for retrieving the replicafileinfo_guid. 535 // FIXME Use joins 536 final String sql = "SELECT replicafileinfo_guid FROM replicafileinfo, file " 537 + "WHERE replicafileinfo.file_id = file.file_id AND file.filename = ? AND replica_id = ?"; 538 List<Long> result = DBUtils.selectLongList(con, sql, filename, replicaId); 539 return result.get(0); 540 } 541 542 /** 543 * Method for updating the checksum status of a replicafileinfo instance. Updates the following fields for the entry 544 * in the replicafileinfo: <br/> 545 * checksum_status = UNKNOWN. <br/> 546 * checksum_checkdatetime = current time. 547 * <p> 548 * The replicafileinfo is in the filelist. 549 * 550 * @param replicafileinfoId The id of the replicafileinfo. 551 * @param con An open connection to the archive database 552 */ 553 protected static void updateReplicaFileInfoChecksumUnknown(long replicafileinfoId, Connection con) { 554 PreparedStatement statement = null; 555 try { 556 // The SQL statement 557 final String sql = "UPDATE replicafileinfo SET checksum_status = ?, checksum_checkdatetime = ? " 558 + "WHERE replicafileinfo_guid = ?"; 559 560 Date now = new Date(Calendar.getInstance().getTimeInMillis()); 561 562 // complete the SQL statement. 563 statement = DBUtils.prepareStatement(con, sql, ChecksumStatus.UNKNOWN.ordinal(), now, replicafileinfoId); 564 565 // execute the SQL statement 566 statement.executeUpdate(); 567 con.commit(); 568 } catch (Exception e) { 569 String msg = "Problems updating the replicafileinfo."; 570 log.warn(msg); 571 throw new IOFailure(msg, e); 572 } finally { 573 DBUtils.closeStatementIfOpen(statement); 574 } 575 } 576 577 /** 578 * Method for updating the checksum status of a replicafileinfo instance. Updates the following fields for the entry 579 * in the replicafileinfo: <br/> 580 * checksum_status = OK. <br/> 581 * upload_status = UPLOAD_COMLPETE. <br/> 582 * checksum_checkdatetime = current time. <br/> 583 * <br/> 584 * The file is required to exist in the replica. 585 * 586 * @param replicafileinfoId The id of the replicafileinfo. 587 * @param con An open connection to the archive database 588 */ 589 protected static void updateReplicaFileInfoChecksumOk(long replicafileinfoId, Connection con) { 590 PreparedStatement statement = null; 591 try { 592 // The SQL statement 593 final String sql = "UPDATE replicafileinfo " 594 + "SET checksum_status = ?, checksum_checkdatetime = ?, upload_status = ? " 595 + "WHERE replicafileinfo_guid = ?"; 596 Date now = new Date(Calendar.getInstance().getTimeInMillis()); 597 598 // complete the SQL statement. 599 statement = DBUtils.prepareStatement(con, sql, ChecksumStatus.OK.ordinal(), now, 600 ReplicaStoreState.UPLOAD_COMPLETED.ordinal(), replicafileinfoId); 601 602 // execute the SQL statement 603 statement.executeUpdate(); 604 con.commit(); 605 } catch (Exception e) { 606 String msg = "Problems updating the replicafileinfo."; 607 log.warn(msg); 608 throw new IOFailure(msg, e); 609 } finally { 610 DBUtils.closeStatementIfOpen(statement); 611 } 612 } 613 614 /** 615 * Method for updating the checksum_updated field for a given replica in the replica table. This is called when a 616 * checksum_job has been handled. 617 * <p> 618 * The following fields for the entry in the replica table: <br/> 619 * checksum_updated = now. 620 * 621 * @param rep The replica which has just been updated. 622 * @param con An open connection to the archive database 623 */ 624 protected static void updateChecksumDateForReplica(Replica rep, Connection con) { 625 PreparedStatement statement = null; 626 try { 627 Date now = new Date(Calendar.getInstance().getTimeInMillis()); 628 final String sql = "UPDATE replica SET checksum_updated = ? WHERE replica_id = ?"; 629 statement = DBUtils.prepareStatement(con, sql, now, rep.getId()); 630 statement.executeUpdate(); 631 con.commit(); 632 } catch (Exception e) { 633 String msg = "Cannot update the checksum_updated for replica '" + rep + "'."; 634 log.warn(msg); 635 throw new IOFailure(msg, e); 636 } finally { 637 DBUtils.closeStatementIfOpen(statement); 638 } 639 } 640 641 /** 642 * Method for updating the filelist_updated field for a given replica in the replica table. This is called when a 643 * filelist_job or a checksum_job has been handled. 644 * <p> 645 * The following fields for the entry in the replica table: <br/> 646 * filelist_updated = now. 647 * 648 * @param rep The replica which has just been updated. 649 * @param connection An open connection to the archive database 650 */ 651 protected static void updateFilelistDateForReplica(Replica rep, Connection connection) { 652 PreparedStatement statement = null; 653 try { 654 Date now = new Date(Calendar.getInstance().getTimeInMillis()); 655 final String sql = "UPDATE replica SET filelist_updated = ? WHERE replica_id = ?"; 656 statement = DBUtils.prepareStatement(connection, sql, now, rep.getId()); 657 statement.executeUpdate(); 658 connection.commit(); 659 } catch (Exception e) { 660 String msg = "Cannot update the filelist_updated for replica '" + rep + "'."; 661 log.warn(msg); 662 throw new IOFailure(msg, e); 663 } finally { 664 DBUtils.closeStatementIfOpen(statement); 665 } 666 } 667 668 /** 669 * Method for setting the filelist_updated field for a given replica in the replica table to a specified value. This 670 * is only called when the admin.data is converted. 671 * <p> 672 * The following fields for the entry in the replica table: <br/> 673 * filelist_updated = date. 674 * 675 * @param rep The replica which has just been updated. 676 * @param date The date for the last filelist update. 677 * @param con An open connection to the archive database 678 */ 679 protected static void setFilelistDateForReplica(Replica rep, Date date, Connection con) { 680 PreparedStatement statement = null; 681 try { 682 final String sql = "UPDATE replica SET filelist_updated = ? WHERE replica_id = ?"; 683 statement = DBUtils.prepareStatement(con, sql, date, rep.getId()); 684 statement.executeUpdate(); 685 con.commit(); 686 } catch (Exception e) { 687 String msg = "Cannot update the filelist_updated for replica '" + rep + "'."; 688 log.warn(msg); 689 throw new IOFailure(msg, e); 690 } finally { 691 DBUtils.closeStatementIfOpen(statement); 692 } 693 } 694 695 /** 696 * Method for setting the checksum_updated field for a given replica in the replica table to a specified value. This 697 * is only called when the admin.data is converted. 698 * <p> 699 * The following fields for the entry in the replica table: <br/> 700 * checksum_updated = date. 701 * 702 * @param rep The replica which has just been updated. 703 * @param date The date for the last checksum update. 704 * @param con An open connection to the archive database 705 */ 706 protected static void setChecksumlistDateForReplica(Replica rep, Date date, Connection con) { 707 PreparedStatement statement = null; 708 try { 709 final String sql = "UPDATE replica SET checksum_updated = ? WHERE replica_id = ?"; 710 statement = DBUtils.prepareStatement(con, sql, date, rep.getId()); 711 statement.executeUpdate(); 712 con.commit(); 713 } catch (Exception e) { 714 String msg = "Cannot update the filelist_updated for replica '" + rep + "'."; 715 log.warn(msg); 716 throw new IOFailure(msg, e); 717 } finally { 718 DBUtils.closeStatementIfOpen(statement); 719 } 720 } 721 722 /** 723 * Method for testing whether a replica already is within the database. 724 * 725 * @param rep The replica to find in the database. 726 * @param con An open connection to the archive database 727 * @return Whether the replica is found in the database. 728 */ 729 protected static boolean existsReplicaInDB(Replica rep, Connection con) { 730 // retrieve the amount of times this replica is within the database. 731 final String sql = "SELECT COUNT(*) FROM replica WHERE replica_id = ?"; 732 int count = DBUtils.selectIntValue(con, sql, rep.getId()); 733 734 // Handle the different cases for count. 735 switch (count) { 736 case 0: 737 return false; 738 case 1: 739 return true; 740 default: 741 throw new IOFailure("Cannot handle " + count + " replicas " + "with id '" + rep.getId() + "'."); 742 } 743 } 744 745 /** 746 * Method for retrieving ReplicaFileInfo entry in the database. 747 * 748 * @param replicaFileInfoGuid The guid for the specific replicafileinfo. 749 * @param con An open connection to the archive database 750 * @return The replicafileinfo. 751 */ 752 protected static ReplicaFileInfo getReplicaFileInfo(long replicaFileInfoGuid, Connection con) { 753 // retrieve all 754 final String sql = "SELECT replicafileinfo_guid, replica_id, file_id, segment_id, checksum, upload_status, " 755 + "filelist_status, checksum_status, filelist_checkdatetime, checksum_checkdatetime " 756 + "FROM replicafileinfo WHERE replicafileinfo_guid = ?"; 757 758 PreparedStatement s = null; 759 760 try { 761 s = DBUtils.prepareStatement(con, sql, replicaFileInfoGuid); 762 ResultSet res = s.executeQuery(); 763 res.next(); 764 765 // return the corresponding replica file info. 766 return new ReplicaFileInfo(res); 767 } catch (SQLException e) { 768 final String message = "SQL error while selecting ResultsSet by executing statement '" + sql + "'."; 769 log.warn(message, e); 770 throw new IOFailure(message, e); 771 } finally { 772 DBUtils.closeStatementIfOpen(s); 773 } 774 } 775 776 /** 777 * Method for retrieving the data for the wanted entries in the replicafileinfo table. All the replicafileinfo 778 * entries with no checksum defined is ignored. 779 * 780 * @param rfiGuids The list of guids for the entries in the replicafileinfo table which is wanted. 781 * @param con An open connection to the archive database 782 * @return The complete data for these entries in the replicafileinfo table. 783 */ 784 protected static List<ReplicaFileInfo> retrieveReplicaFileInfosWithChecksum(List<Long> rfiGuids, Connection con) { 785 ArrayList<ReplicaFileInfo> result = new ArrayList<ReplicaFileInfo>(); 786 787 // Extract all the replicafileinfos, but only put the entries with a 788 // non-empty checksum into the result list. 789 for (long rfiGuid : rfiGuids) { 790 ReplicaFileInfo rfi = getReplicaFileInfo(rfiGuid, con); 791 if (rfi.getChecksum() != null && !rfi.getChecksum().isEmpty()) { 792 result.add(rfi); 793 } 794 } 795 796 return result; 797 } 798 799 /** 800 * Method for updating an entry in the replicafileinfo table. This method does not update the 801 * 'checksum_checkdatetime' and 'filelist_checkdatetime'. 802 * 803 * @param replicafileinfoGuid The guid to update. 804 * @param checksum The new checksum for the entry. 805 * @param state The state for the upload. 806 * @param con An open connection to the archive database 807 * @throws IOFailure If an error occurs in the database connection. 808 */ 809 protected static void updateReplicaFileInfo(long replicafileinfoGuid, String checksum, ReplicaStoreState state, 810 Connection con) throws IOFailure { 811 PreparedStatement statement = null; 812 try { 813 final String sql = "UPDATE replicafileinfo " 814 + "SET checksum = ?, upload_status = ?, filelist_status = ?, checksum_status = ? " 815 + "WHERE replicafileinfo_guid = ?"; 816 817 FileListStatus fls; 818 ChecksumStatus cs; 819 820 if (state == ReplicaStoreState.UPLOAD_COMPLETED) { 821 fls = FileListStatus.OK; 822 cs = ChecksumStatus.OK; 823 } else if (state == ReplicaStoreState.UPLOAD_FAILED) { 824 fls = FileListStatus.MISSING; 825 cs = ChecksumStatus.UNKNOWN; 826 } else { 827 fls = FileListStatus.NO_FILELIST_STATUS; 828 cs = ChecksumStatus.UNKNOWN; 829 } 830 831 // complete the SQL statement. 832 statement = DBUtils.prepareStatement(con, sql, checksum, state.ordinal(), fls.ordinal(), cs.ordinal(), 833 replicafileinfoGuid); 834 statement.executeUpdate(); 835 con.commit(); 836 } catch (Exception e) { 837 String errMsg = "Problems with updating a ReplicaFileInfo"; 838 log.warn(errMsg); 839 throw new IOFailure(errMsg, e); 840 } finally { 841 DBUtils.closeStatementIfOpen(statement); 842 } 843 } 844 845 /** 846 * Method for updating an entry in the replicafileinfo table. This method updates the 'checksum_checkdatetime' and 847 * 'filelist_checkdatetime' with the given date argument. 848 * 849 * @param replicafileinfoGuid The guid to update. 850 * @param checksum The new checksum for the entry. 851 * @param date The date for the update. 852 * @param state The status for the upload. 853 * @param con An open connection to the archive database 854 * @throws IOFailure If an error occurs in the connection to the database. 855 */ 856 protected static void updateReplicaFileInfo(long replicafileinfoGuid, String checksum, Date date, 857 ReplicaStoreState state, Connection con) throws IOFailure { 858 PreparedStatement statement = null; 859 try { 860 final String sql = "UPDATE replicafileinfo " 861 + "SET checksum = ?, upload_status = ?, filelist_status = ?, checksum_status = ?, " 862 + "checksum_checkdatetime = ?, " + "filelist_checkdatetime = ? WHERE replicafileinfo_guid = ?"; 863 864 FileListStatus fls; 865 ChecksumStatus cs; 866 867 if (state == ReplicaStoreState.UPLOAD_COMPLETED) { 868 fls = FileListStatus.OK; 869 cs = ChecksumStatus.OK; 870 } else if (state == ReplicaStoreState.UPLOAD_FAILED) { 871 fls = FileListStatus.MISSING; 872 cs = ChecksumStatus.UNKNOWN; 873 } else { 874 fls = FileListStatus.NO_FILELIST_STATUS; 875 cs = ChecksumStatus.UNKNOWN; 876 } 877 878 // complete the SQL statement. 879 statement = DBUtils.prepareStatement(con, sql, checksum, state.ordinal(), fls.ordinal(), cs.ordinal(), 880 date, date, replicafileinfoGuid); 881 statement.executeUpdate(); 882 con.commit(); 883 } catch (Throwable e) { 884 String errMsg = "Problems with updating a ReplicaFileInfo"; 885 log.warn(errMsg); 886 throw new IOFailure(errMsg); 887 } finally { 888 DBUtils.closeStatementIfOpen(statement); 889 } 890 } 891 892 /** 893 * Retrieves the UploadStatus for a specific entry in the replicafileinfo table identified by the file guid and the 894 * replica id. 895 * 896 * @param fileGuid The id of the file. 897 * @param repId The id of the replica. 898 * @param con An open connection to the archive database 899 * @return The upload status of the corresponding replicafileinfo entry. 900 */ 901 protected static ReplicaStoreState retrieveUploadStatus(long fileGuid, String repId, Connection con) { 902 // sql query for retrieval of upload status for a specific entry. 903 final String sql = "SELECT upload_status FROM replicafileinfo WHERE file_id = ? AND replica_id = ?"; 904 int us = DBUtils.selectIntValue(con, sql, fileGuid, repId); 905 return ReplicaStoreState.fromOrdinal(us); 906 } 907 908 /** 909 * Retrieves the checksum for a specific entry in the replicafileinfo table identified by the file guid and the 910 * replica id. 911 * 912 * @param fileGuid The guid of the file in the file table. 913 * @param repId The id of the replica. 914 * @param con An open connection to the archive database 915 * @return The checksum of the corresponding replicafileinfo entry. 916 */ 917 protected static String retrieveChecksumForReplicaFileInfoEntry(long fileGuid, String repId, Connection con) { 918 // sql query for retrieval of checksum value for an specific entry. 919 final String sql = "SELECT checksum FROM replicafileinfo WHERE file_id = ? AND replica_id = ?"; 920 return DBUtils.selectStringValue(con, sql, fileGuid, repId); 921 } 922 923 /** 924 * Retrieves the checksum status for a specific entry in the replicafileinfo table identified by the file guid and 925 * the replica id. 926 * 927 * @param fileGuid The guid of the file in the file table. 928 * @param repId The id of the replica. 929 * @param con An open connection to the archive database 930 * @return The checksum status of the corresponding replicafileinfo entry. 931 */ 932 protected static ChecksumStatus retrieveChecksumStatusForReplicaFileInfoEntry(long fileGuid, String repId, 933 Connection con) { 934 // sql query for retrieval of checksum value for an specific entry. 935 String sql = "SELECT checksum_status FROM replicafileinfo WHERE file_id = ? AND replica_id = ?"; 936 // retrieve the ordinal for the checksum status. 937 int statusOrdinal = DBUtils.selectIntValue(con, sql, fileGuid, repId); 938 // return the checksum corresponding to the ordinal. 939 return ChecksumStatus.fromOrdinal(statusOrdinal); 940 } 941 942 /** 943 * Method for finding the checksum which is present most times in the list. 944 * 945 * @param checksums The list of checksum to vote about. 946 * @return The most common checksum, or null if several exists. 947 */ 948 protected static String vote(List<String> checksums) { 949 log.debug("voting for checksums: {}", checksums.toString()); 950 951 // count the occurrences of each unique checksum. 952 Map<String, Integer> csMap = new HashMap<String, Integer>(); 953 for (String cs : checksums) { 954 if (csMap.containsKey(cs)) { 955 // count one more! 956 Integer count = csMap.get(cs) + 1; 957 csMap.put(cs, count); 958 } else { 959 csMap.put(cs, 1); 960 } 961 } 962 963 // find the checksum with the largest count. 964 int largestCount = -1; 965 boolean unique = false; 966 String checksum = null; 967 for (Map.Entry<String, Integer> entry : csMap.entrySet()) { 968 if (entry.getValue() > largestCount) { 969 largestCount = entry.getValue(); 970 checksum = entry.getKey(); 971 unique = true; 972 } else if (entry.getValue() == largestCount) { 973 unique = false; 974 } 975 } 976 977 // if not unique, then log an error and return null! 978 if (!unique) { 979 log.error("No checksum has the most occurrences in '{}'. A null has been returned!", csMap); 980 return null; 981 } 982 983 return checksum; 984 } 985 986 /** 987 * The method for voting about the checksum of a file. <br/> 988 * Each entry in the replicafileinfo table containing the file is retrieved. All the unique checksums are retrieved, 989 * e.g. if a checksum is found more than one, then it is ignored. <br/> 990 * If only one unique checksum is found, then if must be the correct one, and all the replicas with this file will 991 * have their checksum_status set to 'OK'. <br/> 992 * If more than one checksum is found, then a vote for the correct checksum is performed. This is done by counting 993 * the amount of time each of the unique checksum is found among the replicafileinfo entries for the current file. 994 * The checksum with most votes is chosen as the correct one, and the checksum_status for all the replicafileinfo 995 * entries with this checksum is set to 'OK', whereas the replicafileinfo entries with a different checksum is set 996 * to 'CORRUPT'. <br/> 997 * If no winner is found then a warning and a notification is issued, and the checksum_status for all the 998 * replicafileinfo entries with for the current file is set to 'UNKNOWN'. <br/> 999 * 1000 * @param fileId The id for the file to vote about. 1001 * @param con An open connection to the archive database 1002 */ 1003 protected static void fileChecksumVote(long fileId, Connection con) { 1004 // Get all the replicafileinfo instances for the fileid, though 1005 // only the ones which have a valid checksum. 1006 // Check the checksums against each other if they differ, 1007 // then set to CORRUPT. 1008 final String sql = "SELECT replicafileinfo_guid FROM replicafileinfo WHERE file_id = ?"; 1009 List<Long> rfiGuids = DBUtils.selectLongList(con, sql, fileId); 1010 1011 List<ReplicaFileInfo> rfis = retrieveReplicaFileInfosWithChecksum(rfiGuids, con); 1012 1013 // handle the case, when no replicas has a checksum of the file. 1014 if (rfis.size() == 0) { 1015 // issue a warning. 1016 log.warn("No replicas contains a valid version of the file '{}'.", retrieveFilenameForFileId(fileId, con)); 1017 1018 return; 1019 } 1020 1021 // Put all the checksums into a hash set to obtain a set of 1022 // unique checksums. 1023 Set<String> hs = new HashSet<String>(rfis.size()); 1024 for (ReplicaFileInfo rfi : rfis) { 1025 // only accept those files which can be found. 1026 if (rfi.getFileListState() == FileListStatus.OK) { 1027 hs.add(rfi.getChecksum()); 1028 } 1029 } 1030 1031 // handle the unlikely case, where the file is missing from everywhere! 1032 if (hs.size() == 0) { 1033 String errorMsg = "The file '" + retrieveFilenameForFileId(fileId, con) + "' is missing in all replicas"; 1034 log.warn(errorMsg); 1035 NotificationsFactory.getInstance().notify(errorMsg, NotificationType.WARNING); 1036 1037 return; 1038 } 1039 1040 // if at exactly one unique checksum is found, then no irregularities 1041 // among the checksums are found. 1042 if (hs.size() == 1) { 1043 log.trace("No irregularities found for the file with id '{}'.", fileId); 1044 1045 // Tell all the replicafileinfo entries that their checksum 1046 // is ok 1047 for (ReplicaFileInfo rfi : rfis) { 1048 // only set OK for those replica where the file is. 1049 if (rfi.getFileListState() == FileListStatus.OK) { 1050 updateReplicaFileInfoChecksumOk(rfi.getGuid(), con); 1051 } 1052 } 1053 1054 // go to next entry in the file table. 1055 return; 1056 } 1057 1058 // Make a list of the checksums for voting. 1059 List<String> checksums = new ArrayList<String>(); 1060 for (ReplicaFileInfo rfi : rfis) { 1061 if (rfi.getFileListState() == FileListStatus.OK) { 1062 checksums.add(rfi.getChecksum()); 1063 } 1064 } 1065 1066 // vote to find the unique most common checksum (null if no unique). 1067 String uniqueChecksum = vote(checksums); 1068 1069 if (uniqueChecksum != null) { 1070 // change checksum_status to CORRUPT for the replicafileinfo 1071 // which 1072 // does not have the chosen checksum. 1073 // Set the others replicafileinfo entries to OK. 1074 for (ReplicaFileInfo rfi : rfis) { 1075 if (!rfi.getChecksum().equals(uniqueChecksum)) { 1076 updateReplicaFileInfoChecksumCorrupt(rfi.getGuid(), con); 1077 } else { 1078 updateReplicaFileInfoChecksumOk(rfi.getGuid(), con); 1079 } 1080 } 1081 } else { 1082 // Handle the case, when no checksum has most votes. 1083 String errMsg = "There is no winner of the votes between the replicas for the checksum of file '" 1084 + retrieveFilenameForFileId(fileId, con) + "'."; 1085 log.warn(errMsg); 1086 1087 // send a notification 1088 NotificationsFactory.getInstance().notify(errMsg, NotificationType.WARNING); 1089 1090 // set all replicafileinfo entries to unknown 1091 for (ReplicaFileInfo rfi : rfis) { 1092 updateReplicaFileInfoChecksumUnknown(rfi.getGuid(), con); 1093 } 1094 } 1095 } 1096 1097 /** 1098 * Add information about one file in a given replica. 1099 * 1100 * @param file The name of a file 1101 * @param replica A replica 1102 * @param con An open connection to the ArchiveDatabase 1103 * @return the ReplicaFileInfo ID for the given filename and replica in the database 1104 */ 1105 protected static long addFileInformation(String file, Replica replica, Connection con) { 1106 // retrieve the file_id for the file. 1107 long fileId = ReplicaCacheHelpers.retrieveIdForFile(file, con); 1108 // If not found, log and create the file in the database. 1109 if (fileId < 0) { 1110 log.info("The file '{}' was not found in the database. Thus creating entry for the file.", file); 1111 // insert the file and retrieve its file_id. 1112 fileId = ReplicaCacheHelpers.insertFileIntoDB(file, con); 1113 } 1114 1115 // retrieve the replicafileinfo_guid for this entry. 1116 long rfiId = ReplicaCacheHelpers.retrieveReplicaFileInfoGuid(fileId, replica.getId(), con); 1117 // if not found log and create the replicafileinfo in the database. 1118 if (rfiId < 0) { 1119 log.warn("Cannot find the file '{}' for replica '{}'. Thus creating missing entry before updating.", file, 1120 replica.getId()); 1121 ReplicaCacheHelpers.createReplicaFileInfoEntriesInDB(fileId, con); 1122 } 1123 1124 // update the replicafileinfo of this file: 1125 // filelist_checkdate, filelist_status, upload_status 1126 ReplicaCacheHelpers.updateReplicaFileInfoFilelist(rfiId, con); 1127 1128 return rfiId; 1129 } 1130 1131 /** 1132 * Process checksum information about one file in a given replica. and update the database accordingly. 1133 * 1134 * @param filename The name of a file 1135 * @param checksum The checksum of that file. 1136 * @param replica A replica 1137 * @param con An open connection to the ArchiveDatabase 1138 * @return the ReplicaFileInfo ID for the given filename and replica in the database 1139 */ 1140 public static long processChecksumline(String filename, String checksum, Replica replica, Connection con) { 1141 1142 // The ID for the file. 1143 long fileid = -1; 1144 1145 // If the file is not within DB, then insert it. 1146 int count = DBUtils.selectIntValue(con, "SELECT COUNT(*) FROM file WHERE filename = ?", filename); 1147 1148 if (count == 0) { 1149 log.info("Inserting the file '{}' into the database.", filename); 1150 fileid = ReplicaCacheHelpers.insertFileIntoDB(filename, con); 1151 } else { 1152 fileid = ReplicaCacheHelpers.retrieveIdForFile(filename, con); 1153 } 1154 1155 // If the file does not already exists in the database, create it 1156 // and retrieve the new ID. 1157 if (fileid < 0) { 1158 log.warn("Inserting the file '{}' into the database, again: This should never happen!!!", filename); 1159 fileid = ReplicaCacheHelpers.insertFileIntoDB(filename, con); 1160 } 1161 1162 // Retrieve the replicafileinfo for the file at the replica. 1163 long rfiId = ReplicaCacheHelpers.retrieveReplicaFileInfoGuid(fileid, replica.getId(), con); 1164 1165 // Check if there already is an entry in the replicafileinfo table. 1166 // rfiId is negative if no entry was found. 1167 if (rfiId < 0) { 1168 // insert the file into the table. 1169 ReplicaCacheHelpers.createReplicaFileInfoEntriesInDB(fileid, con); 1170 log.info("Inserted file '{}' for replica '{}' into replicafileinfo.", filename, replica.toString()); 1171 } 1172 1173 // Update this table 1174 ReplicaCacheHelpers.updateReplicaFileInfoChecksum(rfiId, checksum, con); 1175 log.trace("Updated file '{}' for replica '{}' into replicafileinfo.", filename, replica.toString()); 1176 1177 return rfiId; 1178 } 1179 1180}