001/* 002 * #%L 003 * Netarchivesuite - harvester 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.harvester.webinterface; 024 025import java.text.ParseException; 026import java.text.SimpleDateFormat; 027import java.util.ArrayList; 028import java.util.Date; 029import java.util.HashSet; 030import java.util.List; 031import java.util.Set; 032 033import javax.servlet.ServletRequest; 034 035import dk.netarkivet.common.CommonSettings; 036import dk.netarkivet.common.exceptions.ArgumentNotValid; 037import dk.netarkivet.common.utils.Settings; 038import dk.netarkivet.harvester.datamodel.JobStatus; 039 040/** 041 * Represents a query for a set of jobs. Filtering can be performed on: 042 * <ul> 043 * <li>Job Status (multiple)</li> 044 * <li>Harvest name (single)</li> 045 * <li>Job start date (day of month and year)</li> 046 * <li>Job end date (day of month and year)</li> 047 * </ul> 048 * <p> 049 * The semantics of the date filters is as follows: 050 * <ol> 051 * <li>If only a start date is specified, will fetch jobs whose start date is equal or posterior</li> 052 * <li>If only an end date is specified, will fetch jobs whose end date is equal or anterior</li> 053 * <li>If both are specified, will fetch jobs whose start and end date are equal or comprised between the specified 054 * bounds.</li> 055 * </ol> 056 * <p> 057 * The class enforces that end date is set at a date posterior to start date. 058 * <p> 059 * Additionally a sort order (applied to job IDs) can be set (ascending or descending), and the query can be limited to 060 * a certain row number and a start index. 061 */ 062public class HarvestStatusQuery { 063 064 /** The String code to select all states. */ 065 public static final String JOBSTATUS_ALL = "ALL"; 066 067 /** The String code to select all harvests. */ 068 public static final String HARVEST_NAME_ALL = "ALL"; 069 /** String to check, if there is a wildcard in the harvestname. */ 070 public static final String HARVEST_NAME_WILDCARD = "*"; 071 /** Value used to define page size undefined. */ 072 public static final long PAGE_SIZE_NONE = 0; 073 /** Value used to define date undefined. */ 074 public static final long DATE_NONE = -1L; 075 076 /** 077 * Enum class defining the different sort-orders. 078 */ 079 public static enum SORT_ORDER { 080 /** Ascending mode. From lowest to highest. */ 081 ASC, 082 /** Descending mode. From highest to lowest. */ 083 DESC; 084 085 /** 086 * Parse the given argument and return a sorting order. 087 * 088 * @param order a given sorting order as string 089 * @return a sorting order representing the given string. 090 */ 091 public static SORT_ORDER parse(String order) { 092 for (SORT_ORDER o : values()) { 093 if (o.name().equals(order)) { 094 return o; 095 } 096 } 097 return null; 098 } 099 } 100 101 /** 102 * Defines the UI fields and their default values. 103 */ 104 public static enum UI_FIELD { 105 /** jobstatus with default status ALL. */ 106 JOB_STATUS("ALL"), 107 /** JOB ID order. default is ascending. */ 108 JOB_ID_ORDER("DESC"), 109 /** harvest name. default is ALL (i.e all harvests). */ 110 HARVEST_NAME(HARVEST_NAME_ALL), 111 /** The harvest ID. No default. */ 112 HARVEST_ID(""), 113 /** The harvest Run number. No default. */ 114 HARVEST_RUN(""), 115 /** The harvest start date. No default. */ 116 START_DATE(""), 117 /** The harvest end date. No default. */ 118 END_DATE(""), 119 /** The job id range : list of job ids or range separated by commas, for instance: 2,4,8-14. No default. */ 120 JOB_ID_RANGE(""), 121 122 123 /** 124 * The number of results on each page. The default is read from the setting 125 * {@link CommonSettings#HARVEST_STATUS_DFT_PAGE_SIZE}. 126 */ 127 PAGE_SIZE(Settings.get(CommonSettings.HARVEST_STATUS_DFT_PAGE_SIZE)), 128 /** The starting page. Default is 1. */ 129 START_PAGE_INDEX("1"), 130 /** The number of Jobs to resubmit identified by ID. No default. */ 131 RESUBMIT_JOB_IDS(""); 132 133 /** The default value for this UI-field. */ 134 private final String defaultValue; 135 136 /** 137 * Constructor for the UI_FIELD enum class. 138 * 139 * @param defaultValue the default value of the field. 140 */ 141 UI_FIELD(String defaultValue) { 142 this.defaultValue = defaultValue; 143 } 144 145 /** 146 * Get the values stored in the request for this UI_FIELD. 147 * 148 * @param req the servlet request 149 * @return the values stored in the request for this UI_FIELD as a string array. 150 */ 151 public String[] getValues(ServletRequest req) { 152 String[] values = req.getParameterValues(name()); 153 if (values == null || values.length == 0) { 154 return new String[] {this.defaultValue}; 155 } 156 return values; 157 } 158 159 /** 160 * Extracts the field's value from a servlet request. If the request does not define the paraeter's value, it is 161 * set to the default value. 162 * 163 * @param req a servlet request 164 * @return the field's value 165 */ 166 public String getValue(ServletRequest req) { 167 String value = req.getParameter(name()); 168 if (value == null || value.isEmpty()) { 169 return this.defaultValue; 170 } 171 return value; 172 } 173 } 174 175 /** 176 * The date format used by the calendar widget. It is actually the same format as the one represented by the 177 * DATE_FORMAT. 178 */ 179 public static final String CALENDAR_UI_DATE_FORMAT = "%Y/%m/%d"; 180 181 /** The date format used when returning dates as strings. */ 182 private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy/MM/dd"); 183 184 /** The job states selected in this query. */ 185 private Set<JobStatus> jobStatuses = new HashSet<JobStatus>(); 186 /** The harvestID. */ 187 private Long harvestId; 188 /** The harvest run number. */ 189 private Long harvestRunNumber; 190 /** The harvest name. */ 191 private String harvestName; 192 /** The start date. */ 193 private Date startDate; 194 /** The end date. */ 195 private Date endDate; 196 /** The job id range : list of job ids or range separated by commas, for instance: 2,4,8-14. No default. */ 197 private String jobIdRange; 198 private List<String> jobIdRangeList; 199 /** The sort order. */ 200 private SORT_ORDER sortingOrder; 201 /** The page-size. */ 202 private long pageSize; 203 /** The start page. */ 204 private long startPageIndex; 205 /** Is the harvest name case sensitive. The default is yes. */ 206 private boolean caseSensitiveHarvestName = true; 207 208 /** 209 * Builds a default query that will select all jobs. 210 */ 211 public HarvestStatusQuery() { 212 jobIdRangeList = new ArrayList<String>(); 213 } 214 215 /** 216 * Builds a default query that will find jobs for a given run of a harvest. 217 * 218 * @param harvestId A given harvestId 219 * @param harvestRunNumber a given harvestRunNumber 220 */ 221 public HarvestStatusQuery(long harvestId, long harvestRunNumber) { 222 this.harvestId = harvestId; 223 this.harvestRunNumber = harvestRunNumber; 224 jobIdRangeList = new ArrayList<String>(); 225 } 226 227 /** 228 * Builds a query from a servlet request. Unspecified fields are set to their default value. 229 * 230 * @param req a servlet request 231 */ 232 public HarvestStatusQuery(ServletRequest req) { 233 234 String[] statuses = UI_FIELD.JOB_STATUS.getValues(req); 235 for (String s : statuses) { 236 if (JOBSTATUS_ALL.equals(s)) { 237 this.jobStatuses.clear(); 238 break; 239 } 240 this.jobStatuses.add(JobStatus.parse(s)); 241 } 242 243 this.harvestName = UI_FIELD.HARVEST_NAME.getValue(req); 244 if (HARVEST_NAME_ALL.equalsIgnoreCase(this.harvestName)) { 245 this.harvestName = ""; 246 } 247 248 String harvestIdStr = UI_FIELD.HARVEST_ID.getValue(req); 249 try { 250 this.harvestId = Long.parseLong(harvestIdStr); 251 } catch (NumberFormatException e) { 252 this.harvestId = null; 253 } 254 255 String harvestRunStr = UI_FIELD.HARVEST_RUN.getValue(req); 256 try { 257 this.harvestRunNumber = Long.parseLong(harvestRunStr); 258 } catch (NumberFormatException e) { 259 this.harvestRunNumber = null; 260 } 261 262 String startDateStr = UI_FIELD.START_DATE.getValue(req); 263 if (!startDateStr.isEmpty()) { 264 try { 265 this.startDate = DATE_FORMAT.parse(startDateStr); 266 } catch (ParseException e) { 267 // Should never happen, date comes from a date selector UI 268 throw new ArgumentNotValid("Invalid date specification", e); 269 } 270 } 271 272 String endDateStr = UI_FIELD.END_DATE.getValue(req); 273 if (!endDateStr.isEmpty()) { 274 try { 275 this.endDate = DATE_FORMAT.parse(endDateStr); 276 } catch (ParseException e) { 277 // Should never happen, date comes from a date selector UI 278 throw new ArgumentNotValid("Invalid date specification", e); 279 } 280 } 281 282 if (startDate != null && endDate != null) { 283 if (endDate.before(this.startDate)) { 284 throw new ArgumentNotValid("End date is set after start date!"); 285 } 286 } 287 288 jobIdRangeList = new ArrayList<String>(); 289 String jobIdRange = UI_FIELD.JOB_ID_RANGE.getValue(req); 290 if(!jobIdRange.isEmpty()) { 291 try { 292 String[] splittedRange = jobIdRange.replaceAll("\\s+","").split(","); 293 for(String s : splittedRange) { 294 if(s.contains("-")) { 295 //if it's a range eg 11-27 296 String[] range = s.split("-"); 297 if(range.length != 2) { 298 throw new ArgumentNotValid("Invalid Job IDs range (1-10 or 1,2,3)"); 299 } 300 //check if it's a number 301 Long.parseLong(range[0]); 302 Long.parseLong(range[1]); 303 } else { 304 //check if it's a number 305 Long.parseLong(s); 306 } 307 jobIdRangeList.add(s); 308 } 309 this.jobIdRange = jobIdRange; 310 } catch (NumberFormatException e) { 311 this.jobIdRange = null; 312 throw new ArgumentNotValid("Job IDs must be digits", e); 313 } 314 } 315 316 String orderStr = UI_FIELD.JOB_ID_ORDER.getValue(req); 317 this.sortingOrder = SORT_ORDER.parse(orderStr); 318 if (this.sortingOrder == null) { 319 // Will not happen as value comes from a select 320 throw new ArgumentNotValid("Invalid sort order!"); 321 } 322 323 String pageSizeStr = UI_FIELD.PAGE_SIZE.getValue(req); 324 try { 325 this.pageSize = Long.parseLong(pageSizeStr); 326 } catch (NumberFormatException e) { 327 throw new ArgumentNotValid("Invalid number!", e); 328 } 329 330 String startPageIndexStr = UI_FIELD.START_PAGE_INDEX.getValue(req); 331 try { 332 this.startPageIndex = Long.parseLong(startPageIndexStr); 333 } catch (NumberFormatException e) { 334 throw new ArgumentNotValid("Invalid number!", e); 335 } 336 337 } 338 339 /** 340 * @return the selected job states as an array. 341 */ 342 public JobStatus[] getSelectedJobStatuses() { 343 return jobStatuses.toArray(new JobStatus[jobStatuses.size()]); 344 } 345 346 /** 347 * @return the selected job states as a set.. 348 */ 349 public Set<JobStatus> getSelectedJobStatusesAsSet() { 350 return jobStatuses; 351 } 352 353 /** 354 * @return the harvest name. 355 */ 356 public String getHarvestName() { 357 if (harvestName == null) { 358 return ""; 359 } 360 return harvestName; 361 } 362 363 /** 364 * Set the harvest name. 365 * 366 * @param harvestName The harvest name 367 */ 368 public void setHarvestName(String harvestName) { 369 this.harvestName = harvestName; 370 } 371 372 /** 373 * @return the harvest ID. 374 */ 375 public Long getHarvestId() { 376 return harvestId; 377 } 378 379 /** 380 * @return the harvest run number. 381 */ 382 public Long getHarvestRunNumber() { 383 return harvestRunNumber; 384 } 385 386 /** 387 * @return the start date as milliseconds since Epoch or {@link HarvestStatusQuery#DATE_NONE} if start date is 388 * undefined 389 */ 390 public long getStartDate() { 391 return (startDate == null ? DATE_NONE : startDate.getTime()); 392 } 393 394 /** 395 * @return the end date as milliseconds since Epoch, or {@link HarvestStatusQuery#DATE_NONE} if end date is 396 * undefined 397 */ 398 public long getEndDate() { 399 return (endDate == null ? DATE_NONE : endDate.getTime()); 400 } 401 402 /** 403 * @return the job ids range as String 404 */ 405 public String getJobIdRange() { 406 if(jobIdRange == null) { 407 return ""; 408 } 409 return jobIdRange; 410 } 411 412 /** 413 * return only the ids or only the range 414 * if isRange is true : 2,3,5-9,14-18 -> 5-9,14-18 415 * if isRange is false : 2,3,5-9,14-18 -> 2,3 416 * @return the job ids range as List, only the ids or only the ranges 417 */ 418 public List<String> getPartialJobIdRangeAsList(boolean isRange) { 419 List<String> list = new ArrayList<String>(); 420 for(String s : jobIdRangeList) { 421 if(s.contains("-") == isRange) { 422 list.add(s); 423 } 424 } 425 return list; 426 } 427 428 /** 429 * @return the start date as a string, or an empty string if start date is undefined 430 */ 431 public String getStartDateAsString() { 432 if (startDate == null) { 433 return ""; 434 } 435 return DATE_FORMAT.format(startDate); 436 } 437 438 /** 439 * @return the end date as a string, or an empty string if end date is undefined 440 */ 441 public String getEndDateAsString() { 442 if (endDate == null) { 443 return ""; 444 } 445 return DATE_FORMAT.format(endDate); 446 } 447 448 /** 449 * @return true, if the sorting order is Ascending, otherwise false. 450 */ 451 public boolean isSortAscending() { 452 return SORT_ORDER.ASC.equals(sortingOrder); 453 } 454 455 /** 456 * @return the page size, i.e. the number of results on each page. 457 */ 458 public long getPageSize() { 459 return pageSize; 460 } 461 462 /** 463 * Sets the page size. 464 * 465 * @param pageSize a number > 0. 466 */ 467 public void setPageSize(long pageSize) { 468 ArgumentNotValid.checkNotNegative(pageSize, "pageSize"); 469 this.pageSize = pageSize; 470 } 471 472 /** 473 * @return the start page 474 */ 475 public long getStartPageIndex() { 476 return startPageIndex; 477 } 478 479 /** 480 * Define whether or not the harvest name is case sensitive. 481 * 482 * @param isHarvestNameCaseSensitive If true, harvestname is case sensitive, otherwise not. 483 */ 484 public void setCaseSensitiveHarvestName(boolean isHarvestNameCaseSensitive) { 485 this.caseSensitiveHarvestName = isHarvestNameCaseSensitive; 486 } 487 488 /** 489 * @return true, if the harvest name is case sensitive, otherwise false 490 */ 491 public boolean getCaseSensitiveHarvestName() { 492 return caseSensitiveHarvestName; 493 } 494 495 /** 496 * Set the selected states in the query. 497 * 498 * @param chosenStates the set of selected states. 499 */ 500 public void setJobStatus(Set<JobStatus> chosenStates) { 501 this.jobStatuses = chosenStates; 502 } 503}