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.datamodel;
024
025import java.io.Serializable;
026import java.util.Date;
027
028import dk.netarkivet.common.exceptions.ArgumentNotValid;
029import dk.netarkivet.common.utils.Named;
030
031/**
032 * This class implements a schedule that can be either repeating or timed, depending on the subclass.
033 * <p>
034 * A schedule is a combination of a frequency, defining how often this schedule and at which points in time will trigger
035 * an event , plus information about when to start and when to stop triggering events.
036 * <p>
037 * Methods are provided to check when the first event should happen, and to calculate the next events from the previous
038 * event time.
039 */
040@SuppressWarnings({"serial"})
041public abstract class Schedule implements Serializable, Named {
042
043    /** Human readable name for the schedule. */
044    protected String name;
045    /** Any comments added by the user. */
046    protected String comments;
047    /** first run of job: date, time (hour:min:sec). May be null, meaning at any time */
048    protected Date startDate;
049    /** Frequency of runs, possibly with a time it should happen at. */
050    protected Frequency frequency;
051    /** Edition is used by the DAO to keep track of changes. */
052    long edition = -1;
053    /** ID autogenerated by DB, ignored otherwise. */
054    private Long id;
055
056    /**
057     * Create a new schedule starting at a specific time and going on for an undefined time.
058     *
059     * @param startDate Time at which the schedule starts happening (though not necessarily the time of the first
060     * event). May be null, meaning at any time.
061     * @param frequency How frequently the events should happen
062     * @param name The unique name of this schedule.
063     * @param comments Comments entered by the user
064     * @throws ArgumentNotValid if frequency, name or comments is null, or name is ""
065     */
066    protected Schedule(Date startDate, Frequency frequency, String name, String comments) {
067        ArgumentNotValid.checkNotNullOrEmpty(name, "name");
068        // startDate can be null in case of "start now"
069        ArgumentNotValid.checkNotNull(frequency, "frequency");
070        ArgumentNotValid.checkNotNull(comments, "comments");
071
072        this.name = name;
073        this.startDate = startDate;
074        this.frequency = frequency;
075        this.comments = comments;
076    }
077
078    /**
079     * Get a new Schedule instance for a schedule that runs over a certain period.
080     *
081     * @param startDate The first date an event is allowed to happen. May be null, meaning at any time.
082     * @param endDate The last date an event is allowed to happen. May be null meaning continue forever.
083     * @param freq How frequently the events should happen.
084     * @param name The name of this schedule.
085     * @param comments Comments entered by the user
086     * @return A new Schedule.
087     * @throws ArgumentNotValid if frequency, name or comments is null, or name is ""
088     */
089    public static Schedule getInstance(Date startDate, Date endDate, Frequency freq, String name, String comments) {
090        return new TimedSchedule(startDate, endDate, freq, name, comments);
091    }
092
093    /**
094     * Get a new Schedule instance for a schedule that runs a certain number of times.
095     *
096     * @param startDate The first date an event is allowed to happen. May be null, meaning at any time.
097     * @param repeats How many events should happen on this schedule.
098     * @param freq How frequently the events should happen.
099     * @param name The name of this schedule.
100     * @param comments Comments entered by the user
101     * @return A new Schedule.
102     * @throws ArgumentNotValid if frequency, name or comments is null, or name is "" or repeats is 0 or negative
103     */
104    public static Schedule getInstance(Date startDate, int repeats, Frequency freq, String name, String comments) {
105        return new RepeatingSchedule(startDate, repeats, freq, name, comments);
106    }
107
108    /**
109     * Return the date at which the first event will happen.
110     *
111     * @param now The first time it can happen. Will be normalized to only second precision, milliseconds are set to 0.
112     * @return The date of the first event to happen after startDate.
113     * @throws ArgumentNotValid if now is null
114     */
115    public Date getFirstEvent(Date now) {
116        ArgumentNotValid.checkNotNull(now, "now");
117
118        now = new Date(now.getTime() / 1000 * 1000);
119        if (startDate != null && now.before(startDate)) {
120            return frequency.getFirstEvent(startDate);
121        }
122        return frequency.getFirstEvent(now);
123    }
124
125    /**
126     * Return the date at which the next event will happen.
127     *
128     * @param lastEvent The time at which the previous event happened.
129     * @param numPreviousEvents How many events have previously happened.
130     * @return The date of the next event to happen or null for no more events.
131     * @throws ArgumentNotValid if numPreviousEvents is negative
132     */
133    public abstract Date getNextEvent(Date lastEvent, int numPreviousEvents);
134
135    /**
136     * Get the name of this schedule.
137     *
138     * @return The name
139     */
140    public String getName() {
141        return name;
142    }
143
144    /**
145     * Get the first possible date this schedule is allowed to run.
146     *
147     * @return The first possible date or null for any time
148     */
149    public Date getStartDate() {
150        return startDate;
151    }
152
153    /**
154     * Get the frequency defining how often and when this schedule should be run.
155     *
156     * @return The frequency
157     */
158    public Frequency getFrequency() {
159        return frequency;
160    }
161
162    /**
163     * Autogenerated equals.
164     *
165     * @param o The object to compare with
166     * @return Whether objects are equal
167     */
168    public boolean equals(Object o) {
169        if (this == o) {
170            return true;
171        }
172        if (!(o instanceof Schedule)) {
173            return false;
174        }
175
176        final Schedule schedule = (Schedule) o;
177
178        if (!comments.equals(schedule.comments)) {
179            return false;
180        }
181        if (!frequency.equals(schedule.frequency)) {
182            return false;
183        }
184        if (!name.equals(schedule.name)) {
185            return false;
186        }
187        if (startDate != null ? !startDate.equals(schedule.startDate) : schedule.startDate != null) {
188            return false;
189        }
190
191        return true;
192    }
193
194    /**
195     * Autogenerated hashcode method.
196     *
197     * @return the hashcode
198     */
199    public int hashCode() {
200        int result;
201        result = name.hashCode();
202        result = 29 * result + (startDate != null ? startDate.hashCode() : 0);
203        result = 29 * result + frequency.hashCode();
204        result = 29 * result + comments.hashCode();
205        return result;
206    }
207
208    /**
209     * Returns any user-entered comments about this schedule.
210     *
211     * @return A string of free-form comments.
212     */
213    public String getComments() {
214        return comments;
215    }
216
217    /**
218     * Set the comments for the schedule.
219     *
220     * @param comments The new comments
221     */
222    public void setComments(String comments) {
223        ArgumentNotValid.checkNotNull(comments, "comments");
224        this.comments = comments;
225    }
226
227    /**
228     * Get the edition number.
229     *
230     * @return The edition number
231     */
232    public long getEdition() {
233        return edition;
234    }
235
236    /**
237     * Set the edition number.
238     *
239     * @param theEdition The new edition
240     */
241    public void setEdition(long theEdition) {
242        edition = theEdition;
243    }
244
245    /**
246     * Get the ID of this schedule. Only for use by DBDAO
247     *
248     * @return the ID of this schedule
249     */
250    long getID() {
251        return id;
252    }
253
254    /**
255     * Set the ID of this schedule. Only for use by DBDAO
256     *
257     * @param id the new ID of this schedule
258     */
259    void setID(long id) {
260        this.id = id;
261    }
262
263    /**
264     * Check if this schedule has an ID set yet (doesn't happen until the DBDAO persists it).
265     *
266     * @return true if this schedule has an ID set yet
267     */
268    boolean hasID() {
269        return id != null;
270    }
271
272}