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 */
023
024package dk.netarkivet.harvester.webinterface;
025
026import java.text.ParseException;
027import java.text.SimpleDateFormat;
028import java.util.Date;
029
030import javax.servlet.ServletRequest;
031import javax.servlet.jsp.PageContext;
032
033import dk.netarkivet.common.exceptions.ArgumentNotValid;
034import dk.netarkivet.common.exceptions.ForwardedToErrorPage;
035import dk.netarkivet.common.utils.I18n;
036import dk.netarkivet.common.webinterface.HTMLUtils;
037import dk.netarkivet.harvester.datamodel.DailyFrequency;
038import dk.netarkivet.harvester.datamodel.Frequency;
039import dk.netarkivet.harvester.datamodel.HourlyFrequency;
040import dk.netarkivet.harvester.datamodel.MinuteFrequency;
041import dk.netarkivet.harvester.datamodel.MonthlyFrequency;
042import dk.netarkivet.harvester.datamodel.Schedule;
043import dk.netarkivet.harvester.datamodel.ScheduleDAO;
044import dk.netarkivet.harvester.datamodel.WeeklyFrequency;
045
046/**
047 * Contains utility methods for creating and editing schedule definitions for harvests.
048 */
049public final class ScheduleDefinition {
050
051    /*
052     * Parameters for the Definitions-edit-schedule.jsp page. They should be self explanatory.
053     */
054    /** BeginAt parameter. */
055    static final String BEGIN_AT_PARAMETER = "beginAt";
056    /** FirsrHarvestTime parameter. */
057    static final String FIRST_HARVEST_TIME_PARAMETER = "firstHarvestTime";
058    /** Continue parameter. */
059    static final String CONTINUE_PARAMETER = "continue";
060    /** EndHarvestTime parameter. */
061    static final String END_HARVEST_TIME_PARAMETER = "endHarvestTime";
062    /** Timespan parameter. */
063    static final String TIMESPAN_PARAMETER = "timespan";
064    /** HarvestTime parameter. */
065    static final String HARVEST_TIME_PARAMETER = "harvestTime";
066    /** Frequency in minutes parameter. */
067    static final String FREQUENCY_MINUTES_PARAMETER = "frequency_minutes";
068    /** Frequency in hours parameter. */
069    static final String FREQUENCY_HOURS_PARAMETER = "frequency_hours";
070    /** Frequency defined by day parameter. */
071    static final String FREQUENCY_DAY_PARAMETER = "frequency_day";
072    /** Frequency defined by date parameter. */
073    static final String FREQUENCY_DATE_PARAMETER = "frequency_date";
074    /** Comments parameter. */
075    static final String COMMENTS_PARAMETERS = "comments";
076    /** Edition parameter. */
077    static final String EDITION_PARAMETER = "edition";
078    /** Number of harvests parameter. */
079    static final String NUMBER_OF_HARVESTS_PARAMETER = "numberOfHarvests";
080    /** name parameter. */
081    static final String NAME_PARAMETER = "name";
082    /** update parameter. */
083    static final String UPDATE_PARAMETER = "update";
084    /** Frequency parameter. */
085    static final String FREQUENCY_PARAMETER = "frequency";
086
087    /**
088     * Private constructor. No instances.
089     */
090    private ScheduleDefinition() {
091
092    }
093
094    /**
095     * Processes the request parameters for the page Definitions-edit-schedule.jsp The parameters are first checked for
096     * validity. If they are not acceptable, an exception is thrown, otherwise the parameters are passed on to the
097     * methods editScheduleDefinition() which edits or creates the relevant schedule.
098     * <p>
099     * update: if set, execute this method name: the name of the schedule If name is unset and update is unset, the GUI
100     * can be used to create a new schedule. It is an error condition to have name unset and update set.
101     * <p>
102     * edition: the edition of the schedule being edited. If not specified, the name must not refer to an existing
103     * schedule. frequency: castable to an integer > 0. Actually the period between harvests in units of ... timespan:
104     * allowable values dage, timer, uger, m\u00e5neder
105     * <p>
106     * harvestTime: allowable values, whenever, aTime
107     * <p>
108     * If whenever is set then ignore remaining values in this group frequency_hours: the hour time for harvesting,
109     * integer 0<=x<=23, must be set if aTime is set and timespan is not "hours" frequency_minutes: the minute time for
110     * harvesting, integer 0<=x<=59, must be set if aTime is set and timespan is not "hours" frequency_day: the day of
111     * the week on which the harvest is to take place. Allowable values 1-7. Must be set if timespan is set to "weeks".
112     * frequency_date: the date of the month on which harvests are to occur. Integer 1<=x<=31. Must be set if timespan
113     * is "months"
114     * <p>
115     * beginAt: allowable values "asSoonAsPossible", "beginning". Not null firstHarvestTime: a date/time field in format
116     * DD/MM YYYY hh:mm. Must be set if beginAt="beginning"
117     * <p>
118     * continue: allowable values "forever", "toTime", "numberOfHarvests" endHarvestTime: a date/time field in format
119     * DD/MM YYYY hh:mm. Must be set if continue="beginning" numberOfHarvests: int > 0. Must be set if
120     * continue="numberOfHarvests"
121     *
122     * @param context Context of web request
123     * @param i18n I18N information
124     */
125    public static void processRequest(PageContext context, I18n i18n) {
126        ArgumentNotValid.checkNotNull(context, "PageContext context");
127        ArgumentNotValid.checkNotNull(i18n, "I18n i18n");
128
129        ServletRequest request = context.getRequest();
130        String update = request.getParameter(UPDATE_PARAMETER);
131        if (update == null) {
132            return;
133        }
134
135        HTMLUtils.forwardOnEmptyParameter(context, NAME_PARAMETER);
136
137        String name = request.getParameter(NAME_PARAMETER).trim();
138
139        long edition = HTMLUtils.parseOptionalLong(context, EDITION_PARAMETER, -1L);
140        if (ScheduleDAO.getInstance().exists(name)) {
141            if (ScheduleDAO.getInstance().read(name).getEdition() != edition) {
142                HTMLUtils.forwardWithRawErrorMessage(
143                        context,
144                        i18n,
145                        "errormsg;schedule.has.changed.0.retry.1",
146                        "<br/><a href=\"Definitions-edit-schedule.jsp?name="
147                                + HTMLUtils.escapeHtmlValues(HTMLUtils.encode(name)) + "\">", "</a>");
148                throw new ForwardedToErrorPage("Schedule '" + name + "' has changed");
149            }
150        } else {
151            // Creating new schedule, always edition 0
152            edition = 0;
153        }
154
155        Frequency freq = getFrequency(context, i18n);
156        Date startDate = getStartDate(context, i18n);
157        Date endDate = getEndDate(context, i18n);
158        String continueS = request.getParameter(CONTINUE_PARAMETER);
159        int repeats = HTMLUtils.parseOptionalLong(context, NUMBER_OF_HARVESTS_PARAMETER, 0L).intValue();
160
161        String comments = request.getParameter(COMMENTS_PARAMETERS);
162        updateSchedule(freq, startDate, continueS, endDate, repeats, name, edition, comments);
163    }
164
165    /**
166     * Update or create the schedule in persistent storage.
167     *
168     * @param freq The frequency of the schedule
169     * @param startDate The start date of the schedule
170     * @param continueS The continue-mode of the schedule
171     * @param endDate The end date for the schedule (if any)
172     * @param repeats How many time should the schedule be repeated
173     * @param name The name of the schedule
174     * @param edition The edition of the schedule
175     * @param comments Any comments associated with the schedule
176     */
177    private static void updateSchedule(Frequency freq, Date startDate, String continueS, Date endDate, int repeats,
178            String name, long edition, String comments) {
179        ScheduleDAO sdao = ScheduleDAO.getInstance();
180        Schedule schedule;
181
182        if (comments == null) {
183            comments = "";
184        }
185        if (continueS.equals(NUMBER_OF_HARVESTS_PARAMETER)) {
186            schedule = Schedule.getInstance(startDate, repeats, freq, name, comments);
187        } else {
188            schedule = Schedule.getInstance(startDate, endDate, freq, name, comments);
189        }
190
191        if (sdao.exists(name)) {
192            schedule.setEdition(edition);
193            sdao.update(schedule);
194        } else {
195            sdao.create(schedule);
196        }
197    }
198
199    /**
200     * If the beginAt parameter is set then this returns the first time at which the harvest is to be run. Otherwise it
201     * returns null.
202     *
203     * @param context Web context of the request
204     * @param i18n I18N information
205     * @return the first time to run the harvest
206     */
207    private static Date getStartDate(PageContext context, I18n i18n) {
208        Date startDate = null;
209        HTMLUtils.forwardOnIllegalParameter(context, BEGIN_AT_PARAMETER, "asSoonAsPossible", "beginning");
210        String beginAt = context.getRequest().getParameter(BEGIN_AT_PARAMETER);
211        if (beginAt.equals("beginning")) {
212            String firstHarvestTime = context.getRequest().getParameter(FIRST_HARVEST_TIME_PARAMETER);
213            try {
214                startDate = (new SimpleDateFormat(I18n.getString(dk.netarkivet.harvester.Constants.TRANSLATIONS_BUNDLE,
215                        context.getResponse().getLocale(), "harvestdefinition.schedule.edit.timeformat")))
216                        .parse(firstHarvestTime);
217            } catch (ParseException e) {
218                HTMLUtils.forwardWithErrorMessage(context, i18n, "errormsg;illegal.time.value.0", firstHarvestTime);
219                throw new ForwardedToErrorPage("Illegal start time format. '" + firstHarvestTime + "'", e);
220            }
221        }
222        return startDate;
223    }
224
225    /**
226     * If the toTime parameter is set then this returns the last time at which the harvest is to be run. Otherwise it
227     * returns null.
228     *
229     * @param context Web context of the request
230     * @param i18n I18N information
231     * @return the last time to run the harvest
232     */
233    private static Date getEndDate(PageContext context, I18n i18n) {
234        Date endDate = null;
235        HTMLUtils.forwardOnIllegalParameter(context, CONTINUE_PARAMETER, "forever", "toTime", "numberOfHarvests");
236        String continueS = context.getRequest().getParameter(CONTINUE_PARAMETER);
237        if (continueS.equals("toTime")) {
238            String endHarvestTime = context.getRequest().getParameter(END_HARVEST_TIME_PARAMETER);
239            try {
240                endDate = (new SimpleDateFormat(I18n.getString(dk.netarkivet.harvester.Constants.TRANSLATIONS_BUNDLE,
241                        context.getResponse().getLocale(), "harvestdefinition.schedule.edit.timeformat")))
242                        .parse(endHarvestTime);
243            } catch (ParseException e) {
244                HTMLUtils.forwardWithErrorMessage(context, i18n, "errormsg;illegal.time.value.0", endHarvestTime, e);
245                throw new ForwardedToErrorPage("Illegal end time format. '" + endHarvestTime + "'", e);
246            }
247        }
248        return endDate;
249    }
250
251    /**
252     * Returns a frequency object specifying whether the harvest is to be minute, hourly, daily, weekly, or monthly and
253     * how often it is to be run.
254     *
255     * @param context Web context of the request
256     * @param i18n I18N information
257     * @return the Frequency for the harvest
258     */
259    private static Frequency getFrequency(PageContext context, I18n i18n) {
260        int frequency = HTMLUtils.parseAndCheckInteger(context, FREQUENCY_PARAMETER, 1, Integer.MAX_VALUE);
261        HTMLUtils.forwardOnEmptyParameter(context, TIMESPAN_PARAMETER, HARVEST_TIME_PARAMETER);
262        HTMLUtils.forwardOnIllegalParameter(context, TIMESPAN_PARAMETER, "days", "hours", "weeks", "months", "minutes");
263        HTMLUtils.forwardOnIllegalParameter(context, HARVEST_TIME_PARAMETER, "whenever", "aTime");
264        ServletRequest request = context.getRequest();
265        String timespan = request.getParameter(TIMESPAN_PARAMETER);
266        String harvestTime = request.getParameter(HARVEST_TIME_PARAMETER);
267        if (harvestTime.equals("whenever")) {
268            if (timespan.equals("hours")) {
269                return new HourlyFrequency(frequency);
270            } else if (timespan.equals("days")) {
271                return new DailyFrequency(frequency);
272            } else if (timespan.equals("weeks")) {
273                return new WeeklyFrequency(frequency);
274            } else if (timespan.equals("minutes")) {
275                return new MinuteFrequency(frequency);
276            } else {
277                return new MonthlyFrequency(frequency);
278            }
279        }
280        int frequencyMinutes;
281        int frequencyHours = 0;
282        int frequencyDay = 0;
283        int frequencyDate = 0;
284        frequencyMinutes = HTMLUtils.parseAndCheckInteger(context, FREQUENCY_MINUTES_PARAMETER, 0, 59);
285        if (!timespan.equals("hours")) {
286            frequencyHours = HTMLUtils.parseAndCheckInteger(context, FREQUENCY_HOURS_PARAMETER, 0, 23);
287        }
288        if (timespan.equals("weeks")) {
289            frequencyDay = HTMLUtils.parseAndCheckInteger(context, FREQUENCY_DAY_PARAMETER, 1, 7);
290        }
291        if (timespan.equals("months")) {
292            frequencyDate = HTMLUtils.parseAndCheckInteger(context, FREQUENCY_DATE_PARAMETER, 1, 31);
293        }
294        if (timespan.equals("hours")) {
295            return new HourlyFrequency(frequency, frequencyMinutes);
296        } else if (timespan.equals("days")) {
297            return new DailyFrequency(frequency, frequencyHours, frequencyMinutes);
298        } else if (timespan.equals("weeks")) {
299            return new WeeklyFrequency(frequency, frequencyDay, frequencyHours, frequencyMinutes);
300        } else if (timespan.equals("minutes")) {
301            return new MinuteFrequency(frequency);
302        } else {
303            return new MonthlyFrequency(frequency, frequencyDate, frequencyHours, frequencyMinutes);
304        }
305    }
306}