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 365 out.println("<span id=\"" + updatedId + "\">" + I18N.getString(locale, "last.update.at.0", HTMLUtils.parseDate(activeBitPreservation.getDateForMissingFiles(replica))) + "</span>"); 366 out.println("<br/>"); 367 368 out.println("<a href=\"" + Constants.FILESTATUS_UPDATE_PAGE + "?" + Constants.UPDATE_TYPE_PARAM + "=" 369 + Constants.FIND_MISSING_FILES_OPTION + "&" 370 + (Constants.BITARCHIVE_NAME_PARAM + "=" + HTMLUtils.encodeAndEscapeHTML(replicaName)) + "\">" 371 + I18N.getString(locale, "update.filestatus.for.0", replica.getId()) + "</a>"); 372 out.println("<br/><br/>"); 373 } 374 375 /** 376 * Print HTML formatted state for checksum errors on a given replica in a given locale. 377 * 378 * @param out The writer to write state to. 379 * @param replica The replica to write state for. 380 * @param locale The locale to write state in. 381 * @throws IOException On IO trouble writing state to the writer. 382 */ 383 public static void printChecksumErrorStateForReplica(JspWriter out, Replica replica, Locale locale) 384 throws IOException { 385 ArgumentNotValid.checkNotNull(out, "JspWriter out"); 386 ArgumentNotValid.checkNotNull(replica, "Replica replica"); 387 ArgumentNotValid.checkNotNull(locale, "Locale locale"); 388 ActiveBitPreservation bitPreservation = ActiveBitPreservationFactory.getInstance(); 389 390 // Header 391 out.println(I18N.getString(locale, "checksum.status.for") + " <b>" 392 + HTMLUtils.escapeHtmlValues(replica.getName()) + "</b>"); 393 out.println("<br/>"); 394 395 // Number of changed files 396 long numberOfChangedFiles = bitPreservation.getNumberOfChangedFiles(replica); 397 out.println(I18N.getString(locale, "number.of.files.with.error") + " " 398 + HTMLUtils.localiseLong(numberOfChangedFiles, locale)); 399 400 // Link to fix-page 401 if (numberOfChangedFiles > 0) { 402 out.print(" <a href=\"" + Constants.FILESTATUS_CHECKSUM_PAGE + "?" 403 + (Constants.BITARCHIVE_NAME_PARAM + "=" + HTMLUtils.encodeAndEscapeHTML(replica.getName())) 404 + " \">"); 405 out.print(I18N.getString(locale, "show.files.with.error")); 406 out.print("</a>"); 407 } 408 out.println("<br/>"); 409 410 out.println(I18N.getString(locale, "last.update.at.0", HTMLUtils.parseDate(bitPreservation.getDateForChangedFiles(replica)))); 411 out.println("<br/>"); 412 413 // Link for running a new job 414 out.println("<a href=\"" + Constants.FILESTATUS_UPDATE_PAGE + "?" + Constants.UPDATE_TYPE_PARAM + "=" 415 + Constants.CHECKSUM_OPTION + "&" 416 + (Constants.BITARCHIVE_NAME_PARAM + "=" + HTMLUtils.encodeAndEscapeHTML(replica.getName())) + "\">" 417 + I18N.getString(locale, "update.checksum.and.file.status.for.0", replica.getId()) + "</a>"); 418 419 // Separator 420 out.println("<br/><br/>"); 421 } 422 423 /** 424 * Print a table row with a file name and a checkbox to request more info. 425 * 426 * @param out The stream to print to. 427 * @param filename The name of the file. 428 * @param rowCount The rowcount, used for styling rows. 429 * @param locale The current locale for labels. 430 * @throws IOException On trouble writing to stream. 431 */ 432 public static void printFileName(JspWriter out, String filename, int rowCount, Locale locale) throws IOException { 433 ArgumentNotValid.checkNotNull(out, "JspWriter out"); 434 ArgumentNotValid.checkNotNullOrEmpty(filename, "String filename"); 435 ArgumentNotValid.checkNotNull(locale, "Locale locale"); 436 out.println("<tr class=\"" + HTMLUtils.getRowClass(rowCount) + "\">"); 437 out.println(HTMLUtils.makeTableElement(filename)); 438 out.print("<td>"); 439 out.print(makeCheckbox(Constants.GET_INFO_COMMAND, filename)); 440 out.print(I18N.getString(locale, "get.info")); 441 out.println("</td>"); 442 out.println("</tr>"); 443 } 444 445 /** 446 * Print a file state table for a file. This will present the state of the file in admin data and all bitarchives. 447 * 448 * @param out The stream to print to. 449 * @param fs The file state for the file. 450 * @param locale The locale to print labels in. 451 * @throws IOException On trouble printing to a stream. 452 */ 453 public static void printFileState(JspWriter out, PreservationState fs, Locale locale) throws IOException { 454 ArgumentNotValid.checkNotNull(out, "JspWriter out"); 455 ArgumentNotValid.checkNotNull(fs, "FilePreservationState fs"); 456 ArgumentNotValid.checkNotNull(locale, "Locale locale"); 457 458 // Table headers for info table 459 out.println("<table>"); 460 out.print(HTMLUtils.makeTableRow(HTMLUtils.makeTableHeader(I18N.getString(locale, "replica")), 461 HTMLUtils.makeTableHeader(I18N.getString(locale, "admin.state")), 462 HTMLUtils.makeTableHeader(I18N.getString(locale, "checksum")))); 463 464 // Admin data info 465 printFileStateForAdminData(out, fs, locale); 466 467 // Info for all bitarchives 468 for (Replica l : Replica.getKnown()) { 469 printFileStateForBitarchive(out, l, fs, locale); 470 } 471 out.println("</table>"); 472 } 473 474 /** 475 * Print a table row with current state of a file in admin data. 476 * 477 * @param out The stream to print state to. 478 * @param fs The file preservation state for that file. 479 * @param locale Locale of the labels. 480 * @throws IOException on trouble printing the state. 481 */ 482 private static void printFileStateForAdminData(JspWriter out, PreservationState fs, Locale locale) 483 throws IOException { 484 out.print(HTMLUtils.makeTableRow(HTMLUtils.makeTableElement(I18N.getString(locale, "admin.data")), 485 HTMLUtils.makeTableElement("-"), HTMLUtils.makeTableElement(fs.getAdminChecksum()))); 486 } 487 488 /** 489 * Print a table row with current state of a file in a given bitarchive. 490 * 491 * @param out The stream to print state to. 492 * @param baReplica The replica of the files. 493 * @param fs The file preservation state for that file. 494 * @param locale Locale of the labels. 495 * @throws IOException If an problem occurs when writing to the JspWriter. 496 */ 497 private static void printFileStateForBitarchive(JspWriter out, Replica baReplica, PreservationState fs, 498 Locale locale) throws IOException { 499 log.debug("Printing filestate for bitarchive '" + baReplica.getName() + "'"); 500 out.print(HTMLUtils.makeTableRow(HTMLUtils.makeTableElement(baReplica.getName()), 501 HTMLUtils.makeTableElement(fs.getAdminReplicaState(baReplica)), 502 HTMLUtils.makeTableElement(presentChecksum(fs.getReplicaChecksum(baReplica), locale)))); 503 } 504 505 /** 506 * Print checkboxes for changing state for files. This will print two checkboxes for changing a number of 507 * checkboxes, one for getting more info, one for reestablishing missing files. This method assumes the file 508 * toggleCheckboxes.js to be available in the directory with the page this method is called from. 509 * 510 * @param out The stream to print the checkboxes to. 511 * @param locale The locale of the labels. 512 * @param numberOfMissingCheckboxes The total possible number of missing checkboxes. 513 * @param numberOfUploadableCheckboxes The total possible number of reestablish checkboxes. 514 * @throws IOException On trouble printing the checkboxes. 515 */ 516 public static void printToggleCheckboxes(JspWriter out, Locale locale, int numberOfMissingCheckboxes, 517 int numberOfUploadableCheckboxes) throws IOException { 518 // Print the javascript needed. 519 ArgumentNotValid.checkNotNull(out, "JspWriter out"); 520 ArgumentNotValid.checkNotNull(locale, "Locale locale"); 521 out.println("<script type=\"text/javascript\" language=\"javascript\"" 522 + " src=\"toggleCheckboxes.js\"></script>"); 523 // Add checkbox to toggle multiple "fileinfo" checkboxes 524 printMultipleToggler(out, Constants.GET_INFO_COMMAND, numberOfMissingCheckboxes, "change.infobox.for.0.files", 525 locale); 526 // Add checkbox to toggle multiple "reupload" checkboxes 527 if (numberOfUploadableCheckboxes > 0) { 528 printMultipleToggler(out, Constants.ADD_COMMAND, numberOfUploadableCheckboxes, "change.0.may.be.added", 529 locale); 530 } 531 } 532 533 /** 534 * Print a checkbox that on click will turn a number of checkboxes of a certain type on or off. 535 * 536 * @param out The stream to print the checkbox to. 537 * @param command The type of checkbox. 538 * @param numberOfCheckboxes The total number of checksboxes possible to turn on or off. 539 * @param label The I18N label for the describing text. An input box with the number to change will be added as 540 * parameter {0} in this label. 541 * @param locale The locale for the checkbox. 542 * @throws IOException On trouble printing the checkbox. 543 */ 544 private static void printMultipleToggler(JspWriter out, String command, int numberOfCheckboxes, String label, 545 Locale locale) throws IOException { 546 out.print("<input type=\"checkbox\" id=\"toggle" + command + "\" onclick=\"toggleCheckboxes('" + command 547 + "')\"/>"); 548 out.print(I18N.getString( 549 locale, 550 label, 551 "<input id=\"toggleAmount" + command + "\" value=\"" 552 + Math.min(numberOfCheckboxes, Constants.MAX_TOGGLE_AMOUNT) + "\" />")); 553 out.println("<br/> "); 554 } 555 556 /** 557 * Present a list of checksums in a human-readable form. If size of list is 0, it returns "No checksum". If size of 558 * list is 1, it returns the one available checksum. Otherwise, it returns toString of the list. 559 * 560 * @param csum List of checksum strings 561 * @param locale The given locale. 562 * @return String presenting the checksums. 563 */ 564 public static String presentChecksum(List<String> csum, Locale locale) { 565 ArgumentNotValid.checkNotNull(csum, "List<String> csum"); 566 ArgumentNotValid.checkNotNull(locale, "Locale locale"); 567 String csumString = csum.toString(); 568 if (csum.isEmpty()) { 569 csumString = I18N.getString(locale, "no.checksum"); 570 } else if (csum.size() == 1) { 571 csumString = csum.get(0); 572 } 573 return csumString; 574 } 575}