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 */ 023 024package dk.netarkivet.archive.webinterface; 025 026import java.io.IOException; 027import java.util.ArrayList; 028import java.util.Arrays; 029import java.util.Date; 030import java.util.HashMap; 031import java.util.List; 032import java.util.Locale; 033import java.util.Map; 034 035import javax.servlet.ServletRequest; 036import javax.servlet.jsp.JspWriter; 037import javax.servlet.jsp.PageContext; 038 039import org.slf4j.Logger; 040import org.slf4j.LoggerFactory; 041 042import dk.netarkivet.archive.arcrepository.bitpreservation.ActiveBitPreservation; 043import dk.netarkivet.archive.arcrepository.bitpreservation.ActiveBitPreservationFactory; 044import dk.netarkivet.archive.arcrepository.bitpreservation.PreservationState; 045import dk.netarkivet.common.distribute.arcrepository.Replica; 046import dk.netarkivet.common.exceptions.ArgumentNotValid; 047import dk.netarkivet.common.exceptions.ForwardedToErrorPage; 048import dk.netarkivet.common.utils.I18n; 049import dk.netarkivet.common.utils.StringUtils; 050import dk.netarkivet.common.webinterface.HTMLUtils; 051 052/** 053 * Class encapsulating methods for handling web requests for ActiveBitPreservation. 054 */ 055@SuppressWarnings({"unchecked"}) 056public class BitpreserveFileState { 057 /** Internationalisation object. */ 058 private static final I18n I18N = new I18n(dk.netarkivet.archive.Constants.TRANSLATIONS_BUNDLE); 059 /** The logger for this class. */ 060 //private static Log log = LogFactory.getLog(BitpreserveFileState.class); 061 protected static final Logger log = LoggerFactory.getLogger(BitpreserveFileState.class); 062 063 /** 064 * Private constructor to avoid instantiation of this class. 065 */ 066 private BitpreserveFileState() { 067 } 068 069 /** 070 * Extract the name of the replica (parameter Constants.BITARCHIVE_NAME_PARAM) and the type of update requested 071 * (parameter Constants.UPDATE_TYPE_PARAM). The latter is set to to Constants.FIND_MISSING_FILES_OPTION if the 072 * request is to update missing files, or to Constants.CHECKSUM_OPTION if the request is to update the checksum 073 * information. 074 * 075 * @param context the current JSP context 076 * @return an I18N string telling which type of update has just been initiated. 077 * @throws ForwardedToErrorPage if an unknown bitarchive or update type is posted, or one of the two required 078 * parameters are missing. 079 * @throws ArgumentNotValid If the context is null. 080 */ 081 public static String processUpdateRequest(PageContext context) throws ArgumentNotValid, ForwardedToErrorPage { 082 ArgumentNotValid.checkNotNull(context, "PageContext context"); 083 ServletRequest request = context.getRequest(); 084 085 String bitarchiveName = request.getParameter(Constants.BITARCHIVE_NAME_PARAM); 086 if (bitarchiveName == null) { 087 HTMLUtils.forwardWithErrorMessage(context, I18N, "errormsg;missing.parameter.0", 088 Constants.BITARCHIVE_NAME_PARAM); 089 090 throw new ForwardedToErrorPage("Parameter '" + Constants.BITARCHIVE_NAME_PARAM + "' not set"); 091 } 092 093 String updateTypeRequested = request.getParameter(Constants.UPDATE_TYPE_PARAM); 094 if (updateTypeRequested == null) { 095 HTMLUtils.forwardWithErrorMessage(context, I18N, "errormsg;missing.parameter.0", 096 Constants.UPDATE_TYPE_PARAM); 097 098 throw new ForwardedToErrorPage("Parameter '" + Constants.UPDATE_TYPE_PARAM + "' not set"); 099 } 100 101 if (!Replica.isKnownReplicaName(bitarchiveName)) { 102 HTMLUtils.forwardWithErrorMessage(context, I18N, "errormsg;unknown.bitarchive.0", bitarchiveName); 103 throw new ForwardedToErrorPage("Unknown replica: " + bitarchiveName); 104 } 105 106 Replica bitarchive = Replica.getReplicaFromName(bitarchiveName); 107 108 Locale l = context.getResponse().getLocale(); 109 String statusmessage = HTMLUtils.escapeHtmlValues(I18N.getString(l, "initiating;update.of.0.for.replica.1", 110 updateTypeRequested, bitarchiveName)); 111 if (updateTypeRequested.equalsIgnoreCase(Constants.FIND_MISSING_FILES_OPTION)) { 112 // Start new thread for findmissing files action. 113 new BitpreservationUpdateThread(bitarchive, BitpreservationUpdateType.FINDMISSING).start(); 114 return statusmessage; 115 116 } else if (updateTypeRequested.equalsIgnoreCase(Constants.CHECKSUM_OPTION)) { 117 // Start new thread for finding corrupt files action. 118 new BitpreservationUpdateThread(bitarchive, BitpreservationUpdateType.CHECKSUM).start(); 119 return statusmessage; 120 } else { 121 HTMLUtils.forwardWithErrorMessage(context, I18N, "errormsg;unknown.filestatus.update.type.0", 122 updateTypeRequested); 123 throw new ForwardedToErrorPage("Unknown filestatus update type: " + bitarchiveName); 124 } 125 } 126 127 /** 128 * Processes a missingFiles request. 129 * <p> 130 * Parameters of the form Constants.ADD_COMMAND=<bitarchive>##<filename> causes the file to be added to 131 * that bitarchive, if it is missing. 132 * <p> 133 * Parameters of the form Constants.GET_INFO_COMMAND=<filename> causes checksums to be computed for the file 134 * in all bitarchives and the information to be shown in the next update (notice that this information disappears 135 * when the page is next reloaded). 136 * 137 * @param context the current JSP context. 138 * @param res the result object. This is updated with result information, and expected to be printed to the 139 * resulting page. 140 * @return A map of info gathered for files as requested. 141 * @throws ArgumentNotValid If the context or res is null. 142 * @throws ForwardedToErrorPage if the commands have the wrong number of arguments. 143 */ 144 public static Map<String, PreservationState> processMissingRequest(PageContext context, StringBuilder res) 145 throws ArgumentNotValid, ForwardedToErrorPage { 146 ArgumentNotValid.checkNotNull(context, "PageContext context"); 147 ArgumentNotValid.checkNotNull(res, "StringBuilder res"); 148 Map<String, String[]> params = context.getRequest().getParameterMap(); 149 HTMLUtils.forwardOnMissingParameter(context, Constants.BITARCHIVE_NAME_PARAM); 150 String bitarchiveName = params.get(Constants.BITARCHIVE_NAME_PARAM)[0]; 151 if (!Replica.isKnownReplicaName(bitarchiveName)) { 152 List<String> names = new ArrayList<String>(); 153 HTMLUtils.forwardOnIllegalParameter(context, Constants.BITARCHIVE_NAME_PARAM, 154 StringUtils.conjoin(", ", names.toArray(Replica.getKnownNames()))); 155 } 156 ActiveBitPreservation preserve = ActiveBitPreservationFactory.getInstance(); 157 Locale l = context.getResponse().getLocale(); 158 if (params.containsKey(Constants.ADD_COMMAND)) { 159 String[] adds = params.get(Constants.ADD_COMMAND); 160 for (String s : adds) { 161 String[] parts = s.split(Constants.STRING_FILENAME_SEPARATOR); 162 checkArgs(context, parts, Constants.ADD_COMMAND, "bitarchive name", "filename"); 163 final Replica ba = Replica.getReplicaFromName(parts[0]); 164 final String filename = parts[1]; 165 try { 166 preserve.uploadMissingFiles(ba, filename); 167 res.append(HTMLUtils.escapeHtmlValues(I18N.getString(l, "file.0.has.been.restored.in.replica.on.1", 168 filename, ba.getName()))); 169 res.append("<br/>"); 170 } catch (Exception e) { 171 res.append(I18N.getString(l, "errormsg;attempt.at.restoring.0.in.replica" + ".at.1.failed", 172 filename, ba)); 173 res.append("<br/>"); 174 res.append(e.getMessage()); 175 res.append("<br/>"); 176 log.warn("Could not restore file '" + filename + "' in bitarchive '" + ba + "'", e); 177 } 178 } 179 } 180 // A map ([filename] -> [preservationstate]) to contain 181 // preservationstates for all files retrieved from the 182 // parameter Constants.GET_INFO_COMMAND. 183 // This map is an empty map, if this parameter is undefined. 184 Map<String, PreservationState> infoMap; 185 // Do this at the end so that the info reflects the current state. 186 if (params.containsKey(Constants.GET_INFO_COMMAND)) { 187 String[] getInfos = params.get(Constants.GET_INFO_COMMAND); 188 infoMap = preserve.getPreservationStateMap(getInfos); 189 } else { 190 infoMap = new HashMap<String, PreservationState>(); 191 } 192 193 return infoMap; 194 } 195 196 /** 197 * Check that an array of strings has the arguments corresponding to a command. 198 * 199 * @param context the JSP context to forward to error to. 200 * @param parts Array of arguments given by user. 201 * @param cmd The command to match. 202 * @param argnames The names of the expected arguments. 203 * @throws ForwardedToErrorPage if the parts are not exactly as many as the arguments. 204 */ 205 private static void checkArgs(PageContext context, String[] parts, String cmd, String... argnames) 206 throws ForwardedToErrorPage { 207 if (argnames.length != parts.length) { 208 HTMLUtils.forwardWithErrorMessage(context, I18N, "errormsg;argument.mismatch.command.needs" 209 + ".arguments.0.but.got.1", Arrays.asList(argnames), Arrays.asList(parts)); 210 211 throw new ForwardedToErrorPage("Command " + cmd + " needs arguments " + Arrays.asList(argnames) 212 + ", but got '" + Arrays.asList(parts) + "'"); 213 } 214 } 215 216 /** 217 * Processes a checksum request. 218 * <p> 219 * The name of a bitarchive must always be given in parameter Constants.BITARCHIVE_NAME_PARAM. 220 * <p> 221 * If parameter Constants.FILENAME_PARAM is given, file info for that file will be returned, and all actions will 222 * work on that file. 223 * <p> 224 * If parameter Constants.FIX_ADMIN_CHECKSUM_PARAM is given, the admin data checksum will be fixed for the file. 225 * <p> 226 * If parameter Constants.CREDENTIALS and Constants.CHECKSUM_PARAM is given, removes and reuploads a file with that 227 * checksum in the given bitarchive, using the credentials for authorisation. 228 * 229 * @param res the result object. This is updated with result information, and expected to be printed to the 230 * resulting page. 231 * @param context the current JSP pagecontext. 232 * @return The file preservation state for a file, if a filename is given in the request. Null otherwise. 233 * @throws ArgumentNotValid If the context or res is null. 234 */ 235 public static PreservationState processChecksumRequest(StringBuilder res, PageContext context) 236 throws ArgumentNotValid { 237 ArgumentNotValid.checkNotNull(res, "StringBuilder res"); 238 ArgumentNotValid.checkNotNull(context, "PageContext context"); 239 ServletRequest request = context.getRequest(); 240 Locale l = context.getResponse().getLocale(); 241 HTMLUtils.forwardOnIllegalParameter(context, Constants.BITARCHIVE_NAME_PARAM, Replica.getKnownNames()); 242 String bitarchiveName = request.getParameter(Constants.BITARCHIVE_NAME_PARAM); 243 Replica bitarchive = Replica.getReplicaFromName(bitarchiveName); 244 String filename = request.getParameter(Constants.FILENAME_PARAM); 245 String fixadminchecksum = request.getParameter(Constants.FIX_ADMIN_CHECKSUM_PARAM); 246 String credentials = request.getParameter(Constants.CREDENTIALS_PARAM); 247 String checksum = request.getParameter(Constants.CHECKSUM_PARAM); 248 249 // Parameter validation. Get filename. Complain about missing filename 250 // if we are trying to do actions. 251 if (filename == null) { // param "file" not set - no action to take 252 if (fixadminchecksum != null || credentials != null || checksum != null) { 253 // Only if an action was intended do we complain about 254 // a missing file. 255 res.append(I18N.getString(l, "errormsg;lack.name.for.file.to.be.corrected.in.0", bitarchiveName)); 256 } 257 return null; 258 } 259 260 // At this point we know that the parameter filename is given. 261 // Now we check for actions. 262 ActiveBitPreservation preserve = ActiveBitPreservationFactory.getInstance(); 263 if (fixadminchecksum != null) { 264 // Action to fix admin.data checksum. 265 preserve.changeStateForAdminData(filename); 266 res.append(I18N.getString(l, "file.0.now.has.correct.checksum.in.admin.data", filename)); 267 res.append("<br/>"); 268 } else if (checksum != null || credentials != null) { 269 // Action to replace a broken file with a correct file. 270 // Both parameters must be given. 271 if (checksum == null) { // param CHECKSUM_PARAM not set 272 res.append(I18N.getString(l, "errormsg;lack.checksum.for.corrupted.file.0", filename)); 273 res.append("<br/>"); 274 } else if (credentials == null) { // param CREDENTIALS_PARAM not set 275 res.append(I18N.getString(l, "errormsg;lacking.privileges.to.correct.in.replica")); 276 res.append("<br/>"); 277 } else { 278 // Parameters are correct. Fix the file and report result. 279 try { 280 preserve.replaceChangedFile(bitarchive, filename, credentials, checksum); 281 res.append(I18N.getString(l, "file.0.has.been.replaced.in.1", filename, bitarchive)); 282 res.append("<br/>"); 283 } catch (Exception e) { 284 res.append(I18N.getString(l, "errormsg;attempt.at.restoring.0.in.replica" + ".at.1.failed", 285 filename, bitarchive)); 286 res.append("<br/>"); 287 res.append(e.getMessage()); 288 res.append("<br/>"); 289 log.warn("Attempt at restoring '" + filename + "' in bitarchive on replica '" + bitarchive 290 + "' failed", e); 291 } 292 } 293 } 294 295 return preserve.getPreservationState(filename); 296 } 297 298 /** 299 * Create a generic checkbox as used by processMissingRequest. 300 * 301 * @param command The name of the command 302 * @param args Arguments to the command 303 * @return A checkbox with the command and arguments in correct format and with HTML stuff escaped. 304 */ 305 public static String makeCheckbox(String command, String... args) { 306 ArgumentNotValid.checkNotNull(command, "command"); 307 ArgumentNotValid.checkNotNull(args, "args"); 308 StringBuilder res = new StringBuilder(); 309 for (String arg : args) { 310 if (res.length() == 0) { 311 res.append(" value=\""); 312 } else { 313 res.append(Constants.STRING_FILENAME_SEPARATOR); 314 } 315 res.append(HTMLUtils.escapeHtmlValues(arg)); 316 } 317 if (res.length() != 0) { 318 res.append("\""); 319 } 320 return ("<input type=\"checkbox\" name=\"" + command + "\"" + res.toString() + " />"); 321 } 322 323 /** 324 * Print HTML formatted state for missing files on a given replica in a given locale. 325 * 326 * @param out The writer to write state to. 327 * @param replica The replica to write state for. 328 * @param locale The locale to write state in. 329 * @throws IOException On IO trouble writing state to the writer. 330 */ 331 public static void printMissingFileStateForReplica(JspWriter out, Replica replica, Locale locale) 332 throws IOException { 333 ArgumentNotValid.checkNotNull(out, "JspWriter out"); 334 ArgumentNotValid.checkNotNull(replica, "Replica replica"); 335 ArgumentNotValid.checkNotNull(locale, "Locale locale"); 336 ActiveBitPreservation activeBitPreservation = ActiveBitPreservationFactory.getInstance(); 337 338 //element id's 339 final String replicaName = replica.getName(); 340 final String numberId = replicaName + "_number"; 341 final String missingId = replicaName + "_missing"; 342 final String updatedId = replicaName + "_updated"; 343 //Header 344 out.println(I18N.getString(locale, "filestatus.for") + " <b>" 345 + HTMLUtils.escapeHtmlValues(replicaName) + "</b>"); 346 out.println("<br/>"); 347 348 // Number of files, and number of files missing 349 out.println(I18N.getString(locale, "number.of.files") + " <span id=\"" + numberId + "\">" 350 + HTMLUtils.localiseLong(activeBitPreservation.getNumberOfFiles(replica), locale)+ "</span>"); 351 out.println("<br/>"); 352 long numberOfMissingFiles = activeBitPreservation.getNumberOfMissingFiles(replica); 353 out.println(I18N.getString(locale, "missing.files") + " <span id=\"" + missingId + "\">" 354 + HTMLUtils.localiseLong(numberOfMissingFiles, locale) + "</span>"); 355 356 if (numberOfMissingFiles > 0) { 357 out.print(" <a href=\"" + Constants.FILESTATUS_MISSING_PAGE + "?" 358 + (Constants.BITARCHIVE_NAME_PARAM + "=" + HTMLUtils.encodeAndEscapeHTML(replicaName)) 359 + " \">"); 360 out.print(I18N.getString(locale, "show.missing.files")); 361 out.print("</a>"); 362 } 363 out.println("<br/>"); 364 Date lastMissingFilesupdate = activeBitPreservation.getDateForMissingFiles(replica); 365 if (lastMissingFilesupdate == null) { 366 lastMissingFilesupdate = new Date(0); 367 } 368 out.println("<span id=\"" + updatedId + "\">" + I18N.getString(locale, "last.update.at.0", lastMissingFilesupdate) + "</span>"); 369 out.println("<br/>"); 370 371 out.println("<a href=\"" + Constants.FILESTATUS_UPDATE_PAGE + "?" + Constants.UPDATE_TYPE_PARAM + "=" 372 + Constants.FIND_MISSING_FILES_OPTION + "&" 373 + (Constants.BITARCHIVE_NAME_PARAM + "=" + HTMLUtils.encodeAndEscapeHTML(replicaName)) + "\">" 374 + I18N.getString(locale, "update.filestatus.for.0", replica.getId()) + "</a>"); 375 out.println("<br/><br/>"); 376 } 377 378 /** 379 * Print HTML formatted state for checksum errors on a given replica in a given locale. 380 * 381 * @param out The writer to write state to. 382 * @param replica The replica to write state for. 383 * @param locale The locale to write state in. 384 * @throws IOException On IO trouble writing state to the writer. 385 */ 386 public static void printChecksumErrorStateForReplica(JspWriter out, Replica replica, Locale locale) 387 throws IOException { 388 ArgumentNotValid.checkNotNull(out, "JspWriter out"); 389 ArgumentNotValid.checkNotNull(replica, "Replica replica"); 390 ArgumentNotValid.checkNotNull(locale, "Locale locale"); 391 ActiveBitPreservation bitPreservation = ActiveBitPreservationFactory.getInstance(); 392 393 // Header 394 out.println(I18N.getString(locale, "checksum.status.for") + " <b>" 395 + HTMLUtils.escapeHtmlValues(replica.getName()) + "</b>"); 396 out.println("<br/>"); 397 398 // Number of changed files 399 long numberOfChangedFiles = bitPreservation.getNumberOfChangedFiles(replica); 400 out.println(I18N.getString(locale, "number.of.files.with.error") + " " 401 + HTMLUtils.localiseLong(numberOfChangedFiles, locale)); 402 403 // Link to fix-page 404 if (numberOfChangedFiles > 0) { 405 out.print(" <a href=\"" + Constants.FILESTATUS_CHECKSUM_PAGE + "?" 406 + (Constants.BITARCHIVE_NAME_PARAM + "=" + HTMLUtils.encodeAndEscapeHTML(replica.getName())) 407 + " \">"); 408 out.print(I18N.getString(locale, "show.files.with.error")); 409 out.print("</a>"); 410 } 411 out.println("<br/>"); 412 413 // Time for last update 414 Date lastChangedFilesupdate = bitPreservation.getDateForChangedFiles(replica); 415 if (lastChangedFilesupdate == null) { 416 lastChangedFilesupdate = new Date(0); 417 } 418 out.println(I18N.getString(locale, "last.update.at.0", lastChangedFilesupdate)); 419 out.println("<br/>"); 420 421 // Link for running a new job 422 out.println("<a href=\"" + Constants.FILESTATUS_UPDATE_PAGE + "?" + Constants.UPDATE_TYPE_PARAM + "=" 423 + Constants.CHECKSUM_OPTION + "&" 424 + (Constants.BITARCHIVE_NAME_PARAM + "=" + HTMLUtils.encodeAndEscapeHTML(replica.getName())) + "\">" 425 + I18N.getString(locale, "update.checksum.and.file.status.for.0", replica.getId()) + "</a>"); 426 427 // Separator 428 out.println("<br/><br/>"); 429 } 430 431 /** 432 * Print a table row with a file name and a checkbox to request more info. 433 * 434 * @param out The stream to print to. 435 * @param filename The name of the file. 436 * @param rowCount The rowcount, used for styling rows. 437 * @param locale The current locale for labels. 438 * @throws IOException On trouble writing to stream. 439 */ 440 public static void printFileName(JspWriter out, String filename, int rowCount, Locale locale) throws IOException { 441 ArgumentNotValid.checkNotNull(out, "JspWriter out"); 442 ArgumentNotValid.checkNotNullOrEmpty(filename, "String filename"); 443 ArgumentNotValid.checkNotNull(locale, "Locale locale"); 444 out.println("<tr class=\"" + HTMLUtils.getRowClass(rowCount) + "\">"); 445 out.println(HTMLUtils.makeTableElement(filename)); 446 out.print("<td>"); 447 out.print(makeCheckbox(Constants.GET_INFO_COMMAND, filename)); 448 out.print(I18N.getString(locale, "get.info")); 449 out.println("</td>"); 450 out.println("</tr>"); 451 } 452 453 /** 454 * Print a file state table for a file. This will present the state of the file in admin data and all bitarchives. 455 * 456 * @param out The stream to print to. 457 * @param fs The file state for the file. 458 * @param locale The locale to print labels in. 459 * @throws IOException On trouble printing to a stream. 460 */ 461 public static void printFileState(JspWriter out, PreservationState fs, Locale locale) throws IOException { 462 ArgumentNotValid.checkNotNull(out, "JspWriter out"); 463 ArgumentNotValid.checkNotNull(fs, "FilePreservationState fs"); 464 ArgumentNotValid.checkNotNull(locale, "Locale locale"); 465 466 // Table headers for info table 467 out.println("<table>"); 468 out.print(HTMLUtils.makeTableRow(HTMLUtils.makeTableHeader(I18N.getString(locale, "replica")), 469 HTMLUtils.makeTableHeader(I18N.getString(locale, "admin.state")), 470 HTMLUtils.makeTableHeader(I18N.getString(locale, "checksum")))); 471 472 // Admin data info 473 printFileStateForAdminData(out, fs, locale); 474 475 // Info for all bitarchives 476 for (Replica l : Replica.getKnown()) { 477 printFileStateForBitarchive(out, l, fs, locale); 478 } 479 out.println("</table>"); 480 } 481 482 /** 483 * Print a table row with current state of a file in admin data. 484 * 485 * @param out The stream to print state to. 486 * @param fs The file preservation state for that file. 487 * @param locale Locale of the labels. 488 * @throws IOException on trouble printing the state. 489 */ 490 private static void printFileStateForAdminData(JspWriter out, PreservationState fs, Locale locale) 491 throws IOException { 492 out.print(HTMLUtils.makeTableRow(HTMLUtils.makeTableElement(I18N.getString(locale, "admin.data")), 493 HTMLUtils.makeTableElement("-"), HTMLUtils.makeTableElement(fs.getAdminChecksum()))); 494 } 495 496 /** 497 * Print a table row with current state of a file in a given bitarchive. 498 * 499 * @param out The stream to print state to. 500 * @param baReplica The replica of the files. 501 * @param fs The file preservation state for that file. 502 * @param locale Locale of the labels. 503 * @throws IOException If an problem occurs when writing to the JspWriter. 504 */ 505 private static void printFileStateForBitarchive(JspWriter out, Replica baReplica, PreservationState fs, 506 Locale locale) throws IOException { 507 log.debug("Printing filestate for bitarchive '" + baReplica.getName() + "'"); 508 out.print(HTMLUtils.makeTableRow(HTMLUtils.makeTableElement(baReplica.getName()), 509 HTMLUtils.makeTableElement(fs.getAdminReplicaState(baReplica)), 510 HTMLUtils.makeTableElement(presentChecksum(fs.getReplicaChecksum(baReplica), locale)))); 511 } 512 513 /** 514 * Print checkboxes for changing state for files. This will print two checkboxes for changing a number of 515 * checkboxes, one for getting more info, one for reestablishing missing files. This method assumes the file 516 * toggleCheckboxes.js to be available in the directory with the page this method is called from. 517 * 518 * @param out The stream to print the checkboxes to. 519 * @param locale The locale of the labels. 520 * @param numberOfMissingCheckboxes The total possible number of missing checkboxes. 521 * @param numberOfUploadableCheckboxes The total possible number of reestablish checkboxes. 522 * @throws IOException On trouble printing the checkboxes. 523 */ 524 public static void printToggleCheckboxes(JspWriter out, Locale locale, int numberOfMissingCheckboxes, 525 int numberOfUploadableCheckboxes) throws IOException { 526 // Print the javascript needed. 527 ArgumentNotValid.checkNotNull(out, "JspWriter out"); 528 ArgumentNotValid.checkNotNull(locale, "Locale locale"); 529 out.println("<script type=\"text/javascript\" language=\"javascript\"" 530 + " src=\"toggleCheckboxes.js\"></script>"); 531 // Add checkbox to toggle multiple "fileinfo" checkboxes 532 printMultipleToggler(out, Constants.GET_INFO_COMMAND, numberOfMissingCheckboxes, "change.infobox.for.0.files", 533 locale); 534 // Add checkbox to toggle multiple "reupload" checkboxes 535 if (numberOfUploadableCheckboxes > 0) { 536 printMultipleToggler(out, Constants.ADD_COMMAND, numberOfUploadableCheckboxes, "change.0.may.be.added", 537 locale); 538 } 539 } 540 541 /** 542 * Print a checkbox that on click will turn a number of checkboxes of a certain type on or off. 543 * 544 * @param out The stream to print the checkbox to. 545 * @param command The type of checkbox. 546 * @param numberOfCheckboxes The total number of checksboxes possible to turn on or off. 547 * @param label The I18N label for the describing text. An input box with the number to change will be added as 548 * parameter {0} in this label. 549 * @param locale The locale for the checkbox. 550 * @throws IOException On trouble printing the checkbox. 551 */ 552 private static void printMultipleToggler(JspWriter out, String command, int numberOfCheckboxes, String label, 553 Locale locale) throws IOException { 554 out.print("<input type=\"checkbox\" id=\"toggle" + command + "\" onclick=\"toggleCheckboxes('" + command 555 + "')\"/>"); 556 out.print(I18N.getString( 557 locale, 558 label, 559 "<input id=\"toggleAmount" + command + "\" value=\"" 560 + Math.min(numberOfCheckboxes, Constants.MAX_TOGGLE_AMOUNT) + "\" />")); 561 out.println("<br/> "); 562 } 563 564 /** 565 * Present a list of checksums in a human-readable form. If size of list is 0, it returns "No checksum". If size of 566 * list is 1, it returns the one available checksum. Otherwise, it returns toString of the list. 567 * 568 * @param csum List of checksum strings 569 * @param locale The given locale. 570 * @return String presenting the checksums. 571 */ 572 public static String presentChecksum(List<String> csum, Locale locale) { 573 ArgumentNotValid.checkNotNull(csum, "List<String> csum"); 574 ArgumentNotValid.checkNotNull(locale, "Locale locale"); 575 String csumString = csum.toString(); 576 if (csum.isEmpty()) { 577 csumString = I18N.getString(locale, "no.checksum"); 578 } else if (csum.size() == 1) { 579 csumString = csum.get(0); 580 } 581 return csumString; 582 } 583}