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