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.datamodel; 025 026import java.sql.Connection; 027import java.sql.PreparedStatement; 028import java.sql.ResultSet; 029import java.sql.SQLException; 030import java.sql.Statement; 031import java.sql.Types; 032import java.util.Date; 033import java.util.Iterator; 034import java.util.List; 035 036import org.slf4j.Logger; 037import org.slf4j.LoggerFactory; 038 039import dk.netarkivet.common.exceptions.ArgumentNotValid; 040import dk.netarkivet.common.exceptions.IOFailure; 041import dk.netarkivet.common.exceptions.PermissionDenied; 042import dk.netarkivet.common.exceptions.UnknownID; 043import dk.netarkivet.common.utils.DBUtils; 044import dk.netarkivet.common.utils.ExceptionUtils; 045import dk.netarkivet.common.utils.FilterIterator; 046 047/** 048 * A database-based implementation of the ScheduleDAO. 049 * <p> 050 * The statements to create the tables are now in scripts/sql/createfullhddb.sql and scripts/sql/createfullhddb.mysql. 051 */ 052public class ScheduleDBDAO extends ScheduleDAO { 053 054 /** The logger. */ 055 private static final Logger log = LoggerFactory.getLogger(ScheduleDBDAO.class); 056 057 /** 058 * Constructor for this class, that only checks that the schedules table has the expected version. 059 */ 060 protected ScheduleDBDAO() { 061 Connection connection = HarvestDBConnection.get(); 062 try { 063 HarvesterDatabaseTables.checkVersion(connection, HarvesterDatabaseTables.SCHEDULES); 064 } finally { 065 HarvestDBConnection.release(connection); 066 } 067 } 068 069 /** 070 * Create a new schedule. 071 * 072 * @param schedule The schedule to create 073 * @throws ArgumentNotValid if schedule is null 074 * @throws PermissionDenied if a schedule already exists 075 */ 076 public synchronized void create(Schedule schedule) { 077 ArgumentNotValid.checkNotNull(schedule, "schedule"); 078 079 Connection c = HarvestDBConnection.get(); 080 PreparedStatement s = null; 081 try { 082 if (exists(c, schedule.getName())) { 083 String msg = "Cannot create already existing schedule " + schedule; 084 log.debug(msg); 085 throw new PermissionDenied(msg); 086 } 087 088 s = c.prepareStatement("INSERT INTO schedules " + "( name, comments, startdate, enddate, maxrepeats, " 089 + "timeunit, numtimeunits, anytime, onminute, onhour," + " ondayofweek, ondayofmonth, edition )" 090 + "VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )", Statement.RETURN_GENERATED_KEYS); 091 setScheduleParameters(s, schedule); 092 final long edition = 1; 093 s.setLong(13, edition); 094 s.executeUpdate(); 095 schedule.setID(DBUtils.getGeneratedID(s)); 096 schedule.setEdition(edition); 097 } catch (SQLException e) { 098 throw new IOFailure("SQL error while creating schedule " + schedule + "\n" 099 + ExceptionUtils.getSQLExceptionCause(e), e); 100 } finally { 101 HarvestDBConnection.release(c); 102 } 103 } 104 105 /** 106 * Sets the first twelve parameters of a Schedule in the order. name, comments, startdate, enddate, maxrepeats, 107 * timeunit, numtimeunits, anytime, onminute, onhour, ondayofweek, ondayofmonth 108 * 109 * @param s a prepared SQL statement 110 * @param schedule a given schedule. 111 * @throws SQLException If the operation fails. 112 */ 113 private void setScheduleParameters(PreparedStatement s, Schedule schedule) throws SQLException { 114 DBUtils.setName(s, 1, schedule, Constants.MAX_NAME_SIZE); 115 DBUtils.setComments(s, 2, schedule, Constants.MAX_COMMENT_SIZE); 116 final Date startDate = schedule.getStartDate(); 117 final int fieldNum = 3; 118 DBUtils.setDateMaybeNull(s, fieldNum, startDate); 119 if (schedule instanceof TimedSchedule) { 120 TimedSchedule ts = (TimedSchedule) schedule; 121 DBUtils.setDateMaybeNull(s, 4, ts.getEndDate()); 122 s.setNull(5, Types.BIGINT); 123 } else { 124 s.setNull(4, Types.DATE); 125 RepeatingSchedule rs = (RepeatingSchedule) schedule; 126 s.setLong(5, rs.getRepeats()); 127 } 128 Frequency freq = schedule.getFrequency(); 129 s.setInt(6, freq.ordinal()); 130 s.setInt(7, freq.getNumUnits()); 131 s.setBoolean(8, freq.isAnytime()); 132 DBUtils.setIntegerMaybeNull(s, 9, freq.getOnMinute()); 133 DBUtils.setIntegerMaybeNull(s, 10, freq.getOnHour()); 134 DBUtils.setIntegerMaybeNull(s, 11, freq.getOnDayOfWeek()); 135 DBUtils.setIntegerMaybeNull(s, 12, freq.getOnDayOfMonth()); 136 } 137 138 /** 139 * Returns whether a named schedule exists. 140 * 141 * @param scheduleName The name of a schedule 142 * @return True if the schedule exists. 143 * @throws ArgumentNotValid if the schedulename is null or empty 144 */ 145 public synchronized boolean exists(String scheduleName) { 146 ArgumentNotValid.checkNotNullOrEmpty(scheduleName, "String scheduleName"); 147 148 Connection c = HarvestDBConnection.get(); 149 try { 150 return exists(c, scheduleName); 151 } finally { 152 HarvestDBConnection.release(c); 153 } 154 } 155 156 /** 157 * Returns whether a named schedule exists. 158 * 159 * @param c An open connection to the harvestDatabase. 160 * @param scheduleName The name of a schedule 161 * @return True if the schedule exists. 162 */ 163 private synchronized boolean exists(Connection c, String scheduleName) { 164 final int count = DBUtils.selectIntValue(c, "SELECT COUNT(*) FROM schedules WHERE name = ?", scheduleName); 165 return (1 == count); 166 } 167 168 /** 169 * Read an existing schedule. 170 * 171 * @param scheduleName the name of the schedule 172 * @return The schedule read 173 * @throws ArgumentNotValid if schedulename is null or empty 174 * @throws UnknownID if the schedule doesn't exist 175 */ 176 public synchronized Schedule read(String scheduleName) { 177 ArgumentNotValid.checkNotNullOrEmpty(scheduleName, "String scheduleName"); 178 Connection c = HarvestDBConnection.get(); 179 PreparedStatement s = null; 180 try { 181 s = c.prepareStatement("SELECT schedule_id, comments, startdate, " + "enddate, maxrepeats, timeunit, " 182 + "numtimeunits, anytime, onminute, " + "onhour, ondayofweek, ondayofmonth, edition " 183 + "FROM schedules WHERE name = ?"); 184 s.setString(1, scheduleName); 185 ResultSet rs = s.executeQuery(); 186 if (!rs.next()) { 187 throw new UnknownID("No schedule named '" + scheduleName + "' found"); 188 } 189 long id = rs.getLong(1); 190 boolean isTimedSchedule; 191 String comments = rs.getString(2); 192 Date startdate = DBUtils.getDateMaybeNull(rs, 3); 193 Date enddate = DBUtils.getDateMaybeNull(rs, 4); 194 int maxrepeats = rs.getInt(5); 195 isTimedSchedule = rs.wasNull(); 196 int timeunit = rs.getInt(6); 197 int numtimeunits = rs.getInt(7); 198 boolean anytime = rs.getBoolean(8); 199 Integer minute = DBUtils.getIntegerMaybeNull(rs, 9); 200 Integer hour = DBUtils.getIntegerMaybeNull(rs, 10); 201 Integer dayofweek = DBUtils.getIntegerMaybeNull(rs, 11); 202 Integer dayofmonth = DBUtils.getIntegerMaybeNull(rs, 12); 203 if (log.isDebugEnabled()) { 204 log.debug("Creating frequency for (timeunit,anytime,numtimeunits,hour, minute, dayofweek, dayofmonth)" 205 + " = ({},{},{},{},{},{},{},)", timeunit, anytime, numtimeunits, minute, hour, dayofweek, 206 dayofmonth); 207 } 208 Frequency freq = Frequency.getNewInstance(timeunit, anytime, numtimeunits, minute, hour, dayofweek, 209 dayofmonth); 210 long edition = rs.getLong(13); 211 final Schedule schedule; 212 if (isTimedSchedule) { 213 schedule = Schedule.getInstance(startdate, enddate, freq, scheduleName, comments); 214 } else { 215 schedule = Schedule.getInstance(startdate, maxrepeats, freq, scheduleName, comments); 216 } 217 schedule.setID(id); 218 schedule.setEdition(edition); 219 return schedule; 220 } catch (SQLException e) { 221 throw new IOFailure("SQL error reading schedule " + scheduleName + "\n" 222 + ExceptionUtils.getSQLExceptionCause(e), e); 223 } finally { 224 DBUtils.closeStatementIfOpen(s); 225 HarvestDBConnection.release(c); 226 } 227 } 228 229 /** 230 * Update a schedule in the DAO. 231 * 232 * @param schedule The schedule to update 233 * @throws ArgumentNotValid If the schedule is null 234 * @throws UnknownID If the schedule doesn't exist in the DAO 235 * @throws PermissionDenied If the edition of the schedule to update is older than the DAO's 236 */ 237 public synchronized void update(Schedule schedule) { 238 ArgumentNotValid.checkNotNull(schedule, "schedule"); 239 240 Connection c = HarvestDBConnection.get(); 241 PreparedStatement s = null; 242 try { 243 if (!exists(c, schedule.getName())) { 244 throw new PermissionDenied("No schedule with name " + schedule.getName() + " exists"); 245 } 246 247 s = c.prepareStatement("UPDATE schedules " + "SET name = ?," + " comments = ?," + " startdate = ?," 248 + " enddate = ?," + " maxrepeats = ?," + " timeunit = ?," + " numtimeunits = ?," 249 + " anytime = ?," + " onminute = ?," + " onhour = ?, " + " ondayofweek = ?," 250 + " ondayofmonth = ?," + " edition = ?" + " WHERE name = ? AND edition = ?"); 251 setScheduleParameters(s, schedule); 252 long newEdition = schedule.getEdition() + 1; 253 s.setLong(13, newEdition); 254 s.setString(14, schedule.getName()); 255 s.setLong(15, schedule.getEdition()); 256 int rows = s.executeUpdate(); 257 if (rows == 0) { 258 String message = "Edition " + schedule.getEdition() + " has expired, cannot update " + schedule; 259 log.debug(message); 260 throw new PermissionDenied(message); 261 } 262 schedule.setEdition(newEdition); 263 } catch (SQLException e) { 264 throw new IOFailure("SQL error while creating schedule " + schedule + "\n" 265 + ExceptionUtils.getSQLExceptionCause(e), e); 266 } finally { 267 HarvestDBConnection.release(c); 268 } 269 } 270 271 /** 272 * Get iterator to all available schedules. 273 * 274 * @return iterator to all available schedules 275 */ 276 public synchronized Iterator<Schedule> getAllSchedules() { 277 Connection c = HarvestDBConnection.get(); 278 try { 279 List<String> names = DBUtils.selectStringList(c, "SELECT name FROM schedules ORDER BY name"); 280 return new FilterIterator<String, Schedule>(names.iterator()) { 281 /** 282 * Returns the object corresponding to the given object, or null if that object is to be skipped. 283 * 284 * @param s An object in the source iterator domain 285 * @return An object in this iterators domain, or null 286 */ 287 public Schedule filter(String s) { 288 return read(s); 289 } 290 }; 291 } finally { 292 HarvestDBConnection.release(c); 293 } 294 } 295 296 @Override 297 public synchronized int getCountSchedules() { 298 Connection c = HarvestDBConnection.get(); 299 try { 300 return DBUtils.selectIntValue(c, "SELECT COUNT(*) FROM schedules"); 301 } finally { 302 HarvestDBConnection.release(c); 303 } 304 } 305 306}