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.Clob; 027import java.sql.Connection; 028import java.sql.PreparedStatement; 029import java.sql.ResultSet; 030import java.sql.SQLException; 031import java.sql.Statement; 032import java.sql.Timestamp; 033import java.sql.Types; 034import java.util.ArrayList; 035import java.util.Arrays; 036import java.util.Collections; 037import java.util.Date; 038import java.util.HashMap; 039import java.util.Iterator; 040import java.util.LinkedList; 041import java.util.List; 042import java.util.Map; 043import java.util.stream.Collectors; 044 045import org.slf4j.Logger; 046import org.slf4j.LoggerFactory; 047 048import dk.netarkivet.common.exceptions.ArgumentNotValid; 049import dk.netarkivet.common.exceptions.IOFailure; 050import dk.netarkivet.common.exceptions.PermissionDenied; 051import dk.netarkivet.common.exceptions.UnknownID; 052import dk.netarkivet.common.utils.DBUtils; 053import dk.netarkivet.common.utils.DomainUtils; 054import dk.netarkivet.common.utils.FilterIterator; 055import dk.netarkivet.common.utils.StringUtils; 056import dk.netarkivet.harvester.datamodel.eav.EAV; 057import dk.netarkivet.harvester.datamodel.eav.EAV.AttributeAndType; 058import dk.netarkivet.harvester.datamodel.extendedfield.ExtendedFieldValue; 059import dk.netarkivet.harvester.datamodel.extendedfield.ExtendedFieldValueDAO; 060import dk.netarkivet.harvester.datamodel.extendedfield.ExtendedFieldValueDBDAO; 061 062/** 063 * A database-based implementation of the DomainDAO. 064 * <p> 065 * The statements to create the tables are located in: 066 * <ul> 067 * <li><em>Derby:</em> scripts/sql/createfullhddb.sql</li> 068 * <li><em>MySQL:</em> scripts/sql/createfullhddb.mysql</li> 069 * <li><em>PostgreSQL:</em> scripts/postgresql/netarchivesuite_init.sql</li> 070 * </ul> 071 */ 072public class DomainDBDAO extends DomainDAO { 073 074 /** The log. */ 075 private static final Logger log = LoggerFactory.getLogger(DomainDBDAO.class); 076 077 /** 078 * Creates a database-based implementation of the DomainDAO. Will check that all schemas have correct versions, and 079 * update the ones that haven't. 080 * 081 * @throws IOFailure on trouble updating tables to new versions, or on tables with wrong versions that we don't know 082 * how to change to expected version. 083 */ 084 protected DomainDBDAO() { 085 Connection connection = HarvestDBConnection.get(); 086 try { 087 HarvesterDatabaseTables.checkVersion(connection, HarvesterDatabaseTables.CONFIGURATIONS); 088 HarvesterDatabaseTables.checkVersion(connection, HarvesterDatabaseTables.DOMAINS); 089 HarvesterDatabaseTables.checkVersion(connection, HarvesterDatabaseTables.CONFIGPASSWORDS); 090 HarvesterDatabaseTables.checkVersion(connection, HarvesterDatabaseTables.CONFIGSEEDLISTS); 091 HarvesterDatabaseTables.checkVersion(connection, HarvesterDatabaseTables.SEEDLISTS); 092 HarvesterDatabaseTables.checkVersion(connection, HarvesterDatabaseTables.PASSWORDS); 093 HarvesterDatabaseTables.checkVersion(connection, HarvesterDatabaseTables.OWNERINFO); 094 HarvesterDatabaseTables.checkVersion(connection, HarvesterDatabaseTables.HISTORYINFO); 095 HarvesterDatabaseTables.checkVersion(connection, HarvesterDatabaseTables.EXTENDEDFIELDTYPE); 096 HarvesterDatabaseTables.checkVersion(connection, HarvesterDatabaseTables.EXTENDEDFIELD); 097 HarvesterDatabaseTables.checkVersion(connection, HarvesterDatabaseTables.EXTENDEDFIELDVALUE); 098 } finally { 099 HarvestDBConnection.release(connection); 100 } 101 } 102 103 @Override 104 protected void create(Connection connection, Domain d) { 105 ArgumentNotValid.checkNotNull(d, "d"); 106 ArgumentNotValid.checkNotNullOrEmpty(d.getName(), "d.getName()"); 107 ArgumentNotValid.checkTrue(DomainUtils.isValidDomainName(d.getName()),"Not creating domain wth invalid name " + d.getName()); 108 109 if (exists(connection, d.getName())) { 110 String msg = "Cannot create already existing domain " + d; 111 log.debug(msg); 112 throw new PermissionDenied(msg); 113 } 114 115 PreparedStatement s = null; 116 log.debug("trying to create domain with name: " + d.getName()); 117 try { 118 connection.setAutoCommit(false); 119 s = connection.prepareStatement("INSERT INTO domains " 120 + "(name, comments, defaultconfig, crawlertraps, edition, alias, lastaliasupdate ) " 121 + "VALUES ( ?, ?, -1, ?, ?, ?, ? )", Statement.RETURN_GENERATED_KEYS); 122 // Id is autogenerated 123 // defaultconfig cannot exist yet, so we put in -1 124 // until we have configs 125 DBUtils.setName(s, 1, d, Constants.MAX_NAME_SIZE); 126 DBUtils.setComments(s, 2, d, Constants.MAX_COMMENT_SIZE); 127 s.setString(3, StringUtils.conjoin("\n", d.getCrawlerTraps())); 128 long initialEdition = 1; 129 s.setLong(4, initialEdition); 130 AliasInfo aliasInfo = d.getAliasInfo(); 131 DBUtils.setLongMaybeNull( 132 s, 133 5, 134 aliasInfo == null ? null : DBUtils.selectLongValue(connection, 135 "SELECT domain_id FROM domains WHERE name = ?", aliasInfo.getAliasOf())); 136 DBUtils.setDateMaybeNull(s, 6, aliasInfo == null ? null : aliasInfo.getLastChange()); 137 s.executeUpdate(); 138 139 d.setID(DBUtils.getGeneratedID(s)); 140 s.close(); 141 142 Iterator<Password> passwords = d.getAllPasswords(); 143 while (passwords.hasNext()) { 144 Password p = passwords.next(); 145 insertPassword(connection, d, p); 146 } 147 148 Iterator<SeedList> seedlists = d.getAllSeedLists(); 149 if (!seedlists.hasNext()) { 150 String msg = "No seedlists for domain " + d; 151 log.debug(msg); 152 throw new ArgumentNotValid(msg); 153 } 154 while (seedlists.hasNext()) { 155 SeedList sl = seedlists.next(); 156 insertSeedlist(connection, d, sl); 157 } 158 159 Iterator<DomainConfiguration> dcs = d.getAllConfigurations(); 160 if (!dcs.hasNext()) { 161 String msg = "No configurations for domain " + d; 162 log.debug(msg); 163 throw new ArgumentNotValid(msg); 164 } 165 while (dcs.hasNext()) { 166 DomainConfiguration dc = dcs.next(); 167 insertConfiguration(connection, d, dc); 168 169 // Create xref tables for seedlists referenced by this config 170 createConfigSeedlistsEntries(connection, d, dc); 171 172 // Create xref tables for passwords referenced by this config 173 createConfigPasswordsEntries(connection, d, dc); 174 } 175 176 // Now that configs are defined, set the default config. 177 s = connection.prepareStatement("UPDATE domains SET defaultconfig = (SELECT config_id FROM configurations " 178 + "WHERE configurations.name = ? AND configurations.domain_id = ?) WHERE domain_id = ?"); 179 DBUtils.setName(s, 1, d.getDefaultConfiguration(), Constants.MAX_NAME_SIZE); 180 s.setLong(2, d.getID()); 181 s.setLong(3, d.getID()); 182 s.executeUpdate(); 183 s.close(); 184 for (Iterator<HarvestInfo> hi = d.getHistory().getHarvestInfo(); hi.hasNext();) { 185 insertHarvestInfo(connection, d, hi.next()); 186 } 187 188 for (DomainOwnerInfo doi : d.getAllDomainOwnerInfo()) { 189 insertOwnerInfo(connection, d, doi); 190 } 191 192 saveExtendedFieldValues(connection, d); 193 194 connection.commit(); 195 d.setEdition(initialEdition); 196 } catch (SQLException e) { 197 String message = "SQL error creating domain " + d + " in database" + "\n"; 198 log.warn(message, e); 199 throw new IOFailure(message, e); 200 } finally { 201 DBUtils.rollbackIfNeeded(connection, "creating", d); 202 } 203 } 204 205 @Override 206 public synchronized void update(Domain d) { 207 ArgumentNotValid.checkNotNull(d, "domain"); 208 209 if (!exists(d.getName())) { 210 throw new UnknownID("No domain named " + d.getName() + " exists"); 211 } 212 Connection connection = HarvestDBConnection.get(); 213 PreparedStatement s = null; 214 try { 215 connection.setAutoCommit(false); 216 // Domain object may not have ID yet, so get it from the DB 217 long domainID = DBUtils.selectLongValue(connection, "SELECT domain_id FROM domains WHERE name = ?", 218 d.getName()); 219 if (d.hasID() && d.getID() != domainID) { 220 String message = "Domain " + d + " has wrong id: Has " + d.getID() + ", but persistent store claims " 221 + domainID; 222 log.warn(message); 223 throw new ArgumentNotValid(message); 224 } 225 d.setID(domainID); 226 227 // The alias field is now updated using a separate select request 228 // rather than embedding the select inside the update statement. 229 // This change was needed to accommodate MySQL, and may lower 230 // performance. 231 s = connection.prepareStatement("UPDATE domains SET " 232 + "comments = ?, crawlertraps = ?, edition = ?, alias = ?, lastAliasUpdate = ? " 233 + "WHERE domain_id = ? AND edition = ?"); 234 DBUtils.setComments(s, 1, d, Constants.MAX_COMMENT_SIZE); 235 s.setString(2, StringUtils.conjoin("\n", d.getCrawlerTraps())); 236 final long newEdition = d.getEdition() + 1; 237 s.setLong(3, newEdition); 238 AliasInfo aliasInfo = d.getAliasInfo(); 239 DBUtils.setLongMaybeNull( 240 s, 241 4, 242 aliasInfo == null ? null : DBUtils.selectLongValue(connection, 243 "SELECT domain_id FROM domains WHERE name = ?", aliasInfo.getAliasOf())); 244 DBUtils.setDateMaybeNull(s, 5, aliasInfo == null ? null : aliasInfo.getLastChange()); 245 s.setLong(6, d.getID()); 246 s.setLong(7, d.getEdition()); 247 int rows = s.executeUpdate(); 248 if (rows == 0) { 249 String message = "Edition " + d.getEdition() + " has expired, cannot update " + d; 250 log.debug(message); 251 throw new PermissionDenied(message); 252 } 253 s.close(); 254 255 updatePasswords(connection, d); 256 257 updateSeedlists(connection, d); 258 259 updateConfigurations(connection, d); 260 261 updateOwnerInfo(connection, d); 262 263 updateHarvestInfo(connection, d); 264 265 saveExtendedFieldValues(connection, d); 266 267 // Now that configs are updated, we can set default_config 268 s = connection.prepareStatement("UPDATE domains SET defaultconfig = (SELECT config_id " 269 + "FROM configurations WHERE domain_id = ? AND name = ?) WHERE domain_id = ?"); 270 s.setLong(1, d.getID()); 271 s.setString(2, d.getDefaultConfiguration().getName()); 272 s.setLong(3, d.getID()); 273 s.executeUpdate(); 274 connection.commit(); 275 d.setEdition(newEdition); 276 } catch (SQLException e) { 277 String message = "SQL error updating domain " + d + " in database" + "\n"; 278 log.warn(message, e); 279 throw new IOFailure(message, e); 280 } finally { 281 DBUtils.closeStatementIfOpen(s); 282 DBUtils.rollbackIfNeeded(connection, "updating", d); 283 HarvestDBConnection.release(connection); 284 } 285 } 286 287 /** 288 * Update the list of passwords for the given domain, keeping IDs where applicable. 289 * 290 * @param c A connection to the database 291 * @param d A domain to update. 292 * @throws SQLException If any database problems occur during the update process. 293 */ 294 private void updatePasswords(Connection c, Domain d) throws SQLException { 295 Map<String, Long> oldNames = DBUtils.selectStringLongMap(c, 296 "SELECT name, password_id FROM passwords WHERE domain_id = ?", d.getID()); 297 PreparedStatement s = c.prepareStatement("UPDATE passwords SET " + "comments = ?, " + "url = ?, " 298 + "realm = ?, username = ?, " + "password = ? " + "WHERE name = ? AND domain_id = ?"); 299 for (Iterator<Password> pwds = d.getAllPasswords(); pwds.hasNext();) { 300 Password pwd = pwds.next(); 301 if (oldNames.containsKey(pwd.getName())) { 302 DBUtils.setComments(s, 1, pwd, Constants.MAX_COMMENT_SIZE); 303 DBUtils.setStringMaxLength(s, 2, pwd.getPasswordDomain(), Constants.MAX_URL_SIZE, pwd, "password url"); 304 DBUtils.setStringMaxLength(s, 3, pwd.getRealm(), Constants.MAX_REALM_NAME_SIZE, pwd, "password realm"); 305 DBUtils.setStringMaxLength(s, 4, pwd.getUsername(), Constants.MAX_USER_NAME_SIZE, pwd, 306 "password username"); 307 DBUtils.setStringMaxLength(s, 5, pwd.getPassword(), Constants.MAX_PASSWORD_SIZE, pwd, "password"); 308 s.setString(6, pwd.getName()); 309 s.setLong(7, d.getID()); 310 s.executeUpdate(); 311 s.clearParameters(); 312 pwd.setID(oldNames.get(pwd.getName())); 313 oldNames.remove(pwd.getName()); 314 } else { 315 insertPassword(c, d, pwd); 316 } 317 } 318 s.close(); 319 s = c.prepareStatement("DELETE FROM passwords WHERE password_id = ?"); 320 for (Long gone : oldNames.values()) { 321 // Check that we're not deleting something that's in use 322 // Since deletion is very rare, this is allowed to take 323 // some time. 324 String usages = DBUtils.getUsages(c, "SELECT configurations.name FROM configurations, config_passwords" 325 + " WHERE configurations.config_id = config_passwords.config_id " 326 + "AND config_passwords.password_id = ?", gone, gone); 327 if (usages != null) { 328 String name = DBUtils.selectStringValue(c, "SELECT name FROM passwords WHERE password_id = ?", gone); 329 String message = "Cannot delete password " + name + " as it is used in " + usages; 330 log.debug(message); 331 throw new PermissionDenied(message); 332 } 333 s.setLong(1, gone); 334 s.executeUpdate(); 335 s.clearParameters(); 336 } 337 } 338 339 /** 340 * Update the list of seedlists for the given domain, keeping IDs where applicable. 341 * 342 * @param c A connection to the database 343 * @param d A domain to update. 344 * @throws SQLException If any database problems occur during the update process. 345 */ 346 private void updateSeedlists(Connection c, Domain d) throws SQLException { 347 Map<String, Long> oldNames = DBUtils.selectStringLongMap(c, "SELECT name, seedlist_id FROM seedlists " 348 + "WHERE domain_id = ?", d.getID()); 349 PreparedStatement s = c.prepareStatement("UPDATE seedlists SET comments = ?, " + "seeds = ? " 350 + "WHERE name = ? AND domain_id = ?"); 351 for (Iterator<SeedList> sls = d.getAllSeedLists(); sls.hasNext();) { 352 SeedList sl = sls.next(); 353 if (oldNames.containsKey(sl.getName())) { 354 DBUtils.setComments(s, 1, sl, Constants.MAX_COMMENT_SIZE); 355 DBUtils.setClobMaxLength(s, 2, sl.getSeedsAsString(), Constants.MAX_SEED_LIST_SIZE, sl, "seedlist"); 356 s.setString(3, sl.getName()); 357 s.setLong(4, d.getID()); 358 s.executeUpdate(); 359 s.clearParameters(); 360 sl.setID(oldNames.get(sl.getName())); 361 oldNames.remove(sl.getName()); 362 } else { 363 insertSeedlist(c, d, sl); 364 } 365 } 366 s.close(); 367 s = c.prepareStatement("DELETE FROM seedlists WHERE seedlist_id = ?"); 368 for (Long gone : oldNames.values()) { 369 // Check that we're not deleting something that's in use 370 // Since deletion is very rare, this is allowed to take 371 // some time. 372 String usages = DBUtils.getUsages(c, "SELECT configurations.name FROM configurations, config_seedlists" 373 + " WHERE configurations.config_id = config_seedlists.config_id " 374 + "AND config_seedlists.seedlist_id = ?", gone, gone); 375 if (usages != null) { 376 String name = DBUtils.selectStringValue(c, "SELECT name FROM seedlists WHERE seedlist_id = ?", gone); 377 String message = "Cannot delete seedlist " + name + " as it is used in " + usages; 378 log.debug(message); 379 throw new PermissionDenied(message); 380 } 381 s.setLong(1, gone); 382 s.executeUpdate(); 383 s.clearParameters(); 384 } 385 } 386 387 /** 388 * Update the list of configurations for the given domain, keeping IDs where applicable. This also builds the xref 389 * tables for passwords and seedlists used in configurations, and so should be run after those are updated. 390 * 391 * @param connection A connection to the database 392 * @param d A domain to update. 393 * @throws SQLException If any database problems occur during the update process. 394 */ 395 private void updateConfigurations(Connection connection, Domain d) throws SQLException { 396 Map<String, Long> oldNames = DBUtils.selectStringLongMap(connection, 397 "SELECT name, config_id FROM configurations WHERE domain_id = ?", d.getID()); 398 PreparedStatement s = connection.prepareStatement("UPDATE configurations SET comments = ?, " 399 + "template_id = ( SELECT template_id FROM ordertemplates " + "WHERE name = ? ), " + "maxobjects = ?, " 400 + "maxrate = ?, " + "maxbytes = ? " + "WHERE name = ? AND domain_id = ?"); 401 for (Iterator<DomainConfiguration> dcs = d.getAllConfigurations(); dcs.hasNext();) { 402 DomainConfiguration dc = dcs.next(); 403 404 if (oldNames.containsKey(dc.getName())) { 405 // Update 406 DBUtils.setComments(s, 1, dc, Constants.MAX_COMMENT_SIZE); 407 s.setString(2, dc.getOrderXmlName()); 408 s.setLong(3, dc.getMaxObjects()); 409 s.setInt(4, dc.getMaxRequestRate()); 410 s.setLong(5, dc.getMaxBytes()); 411 s.setString(6, dc.getName()); 412 s.setLong(7, d.getID()); 413 s.executeUpdate(); 414 s.clearParameters(); 415 dc.setID(oldNames.get(dc.getName())); 416 oldNames.remove(dc.getName()); 417 } else { 418 insertConfiguration(connection, d, dc); 419 } 420 421 updateConfigPasswordsEntries(connection, d, dc); 422 updateConfigSeedlistsEntries(connection, d, dc); 423 } 424 s.close(); 425 s = connection.prepareStatement("DELETE FROM configurations WHERE config_id = ?"); 426 for (Long gone : oldNames.values()) { 427 // Before deleting, check if this is unused. Since deletion is 428 // rare, this is allowed to take some time to give good output 429 String usages = DBUtils.getUsages(connection, "SELECT harvestdefinitions.name" 430 + " FROM harvestdefinitions, harvest_configs WHERE harvestdefinitions.harvest_id = " 431 + "harvest_configs.harvest_id AND harvest_configs.config_id = ?", gone, gone); 432 if (usages != null) { 433 String name = DBUtils.selectStringValue(connection, "SELECT name FROM configurations " 434 + "WHERE config_id = ?", gone); 435 String message = "Cannot delete configuration " + name + " as it is used in " + usages; 436 log.debug(message); 437 throw new PermissionDenied(message); 438 } 439 s.setLong(1, gone); 440 s.executeUpdate(); 441 s.clearParameters(); 442 } 443 } 444 445 /** 446 * Update the list of owner info for the given domain, keeping IDs where applicable. 447 * 448 * @param c A connection to the database 449 * @param d A domain to update. 450 * @throws SQLException If any database problems occur during the update process. 451 */ 452 private void updateOwnerInfo(Connection c, Domain d) throws SQLException { 453 List<Long> oldIDs = DBUtils.selectLongList(c, "SELECT ownerinfo_id FROM ownerinfo " + "WHERE domain_id = ?", 454 d.getID()); 455 PreparedStatement s = c.prepareStatement("UPDATE ownerinfo SET " + "created = ?, " + "info = ? " 456 + "WHERE ownerinfo_id = ?"); 457 for (DomainOwnerInfo doi : d.getAllDomainOwnerInfo()) { 458 if (doi.hasID() && oldIDs.remove(doi.getID())) { 459 s.setTimestamp(1, new Timestamp(doi.getDate().getTime())); 460 DBUtils.setStringMaxLength(s, 2, doi.getInfo(), Constants.MAX_OWNERINFO_SIZE, doi, "owner info"); 461 s.setLong(3, doi.getID()); 462 s.executeUpdate(); 463 s.clearParameters(); 464 } else { 465 insertOwnerInfo(c, d, doi); 466 } 467 } 468 if (oldIDs.size() != 0) { 469 String message = "Not allowed to delete ownerinfo " + oldIDs + " on " + d; 470 log.debug(message); 471 throw new IOFailure(message); 472 } 473 } 474 475 /** 476 * Update the list of harvest info for the given domain, keeping IDs where applicable. 477 * 478 * @param c A connection to the database 479 * @param d A domain to update. 480 * @throws SQLException If any database problems occur during the update process. 481 */ 482 private void updateHarvestInfo(Connection c, Domain d) throws SQLException { 483 List<Long> oldIDs = DBUtils.selectLongList(c, "SELECT historyinfo.historyinfo_id " 484 + "FROM historyinfo, configurations WHERE historyinfo.config_id = configurations.config_id" 485 + " AND configurations.domain_id = ?", d.getID()); 486 PreparedStatement s = c.prepareStatement("UPDATE historyinfo SET " + "stopreason = ?, " + "objectcount = ?, " 487 + "bytecount = ?, " + "config_id = " + " (SELECT config_id FROM configurations, domains" 488 + " WHERE domains.domain_id = ?" + " AND configurations.name = ?" 489 + " AND configurations.domain_id = domains.domain_id), " + "harvest_id = ?, " + "job_id = ? " 490 + "WHERE historyinfo_id = ?"); 491 Iterator<HarvestInfo> his = d.getHistory().getHarvestInfo(); 492 while (his.hasNext()) { 493 HarvestInfo hi = his.next(); 494 if (hi.hasID() && oldIDs.remove(hi.getID())) { 495 s.setInt(1, hi.getStopReason().ordinal()); 496 s.setLong(2, hi.getCountObjectRetrieved()); 497 s.setLong(3, hi.getSizeDataRetrieved()); 498 s.setLong(4, d.getID()); 499 s.setString(5, d.getConfiguration(hi.getDomainConfigurationName()).getName()); 500 s.setLong(6, hi.getHarvestID()); 501 if (hi.getJobID() != null) { 502 s.setLong(7, hi.getJobID()); 503 } else { 504 s.setNull(7, Types.BIGINT); 505 } 506 s.setLong(8, hi.getID()); 507 s.executeUpdate(); 508 s.clearParameters(); 509 } else { 510 insertHarvestInfo(c, d, hi); 511 } 512 } 513 if (oldIDs.size() != 0) { 514 String message = "Not allowed to delete historyinfo " + oldIDs + " on " + d; 515 log.debug(message); 516 throw new IOFailure(message); 517 } 518 } 519 520 /** 521 * Insert new harvest info for a domain. 522 * 523 * @param c A connection to the database 524 * @param d A domain to insert on. The domains ID must be correct. 525 * @param harvestInfo Harvest info to insert. 526 */ 527 private void insertHarvestInfo(Connection c, Domain d, HarvestInfo harvestInfo) { 528 PreparedStatement s = null; 529 try { 530 // Note that the config_id is grabbed from the configurations table. 531 s = c.prepareStatement("INSERT INTO historyinfo " + "( stopreason, objectcount, bytecount, config_id, " 532 + "job_id, harvest_id, harvest_time ) " + "VALUES ( ?, ?, ?, ?, ?, ?, ? )", 533 Statement.RETURN_GENERATED_KEYS); 534 s.setInt(1, harvestInfo.getStopReason().ordinal()); 535 s.setLong(2, harvestInfo.getCountObjectRetrieved()); 536 s.setLong(3, harvestInfo.getSizeDataRetrieved()); 537 // TODO More stable way to get IDs, use a select 538 s.setLong(4, d.getConfiguration(harvestInfo.getDomainConfigurationName()).getID()); 539 if (harvestInfo.getJobID() != null) { 540 s.setLong(5, harvestInfo.getJobID()); 541 } else { 542 s.setNull(5, Types.BIGINT); 543 } 544 s.setLong(6, harvestInfo.getHarvestID()); 545 s.setTimestamp(7, new Timestamp(harvestInfo.getDate().getTime())); 546 s.executeUpdate(); 547 harvestInfo.setID(DBUtils.getGeneratedID(s)); 548 } catch (SQLException e) { 549 throw new IOFailure("SQL error while inserting harvest info " + harvestInfo + " for " + d + "\n", e); 550 } 551 } 552 553 /** 554 * Insert new owner info for a domain. 555 * 556 * @param c A connection to the database 557 * @param d A domain to insert on. The domains ID must be correct. 558 * @param doi Owner info to insert. 559 * @throws SQLException If any database problems occur during the insertion process. 560 */ 561 private void insertOwnerInfo(Connection c, Domain d, DomainOwnerInfo doi) throws SQLException { 562 PreparedStatement s = c.prepareStatement("INSERT INTO ownerinfo ( domain_id, created, info ) " 563 + "VALUES ( ?, ?, ?)", Statement.RETURN_GENERATED_KEYS); 564 s.setLong(1, d.getID()); 565 s.setTimestamp(2, new Timestamp(doi.getDate().getTime())); 566 s.setString(3, doi.getInfo()); 567 s.executeUpdate(); 568 doi.setID(DBUtils.getGeneratedID(s)); 569 } 570 571 /** 572 * Insert new seedlist for a domain. 573 * 574 * @param c A connection to the database 575 * @param d A domain to insert on. The domains ID must be correct. 576 * @param sl Seedlist to insert. 577 * @throws SQLException If some database error occurs during the insertion process. 578 */ 579 private void insertSeedlist(Connection c, Domain d, SeedList sl) throws SQLException { 580 PreparedStatement s = c.prepareStatement("INSERT INTO seedlists ( name, comments, domain_id, seeds ) " 581 + "VALUES ( ?, ?, ?, ? )", Statement.RETURN_GENERATED_KEYS); 582 // ID is autogenerated 583 DBUtils.setName(s, 1, sl, Constants.MAX_NAME_SIZE); 584 DBUtils.setComments(s, 2, sl, Constants.MAX_COMMENT_SIZE); 585 s.setLong(3, d.getID()); 586 DBUtils.setClobMaxLength(s, 4, sl.getSeedsAsString(), Constants.MAX_SEED_LIST_SIZE, sl, "seedlist"); 587 s.executeUpdate(); 588 sl.setID(DBUtils.getGeneratedID(s)); 589 } 590 591 /** 592 * Inserts a new password entry into the database. 593 * 594 * @param c A connection to the database 595 * @param d A domain to insert on. The domains ID must be correct. 596 * @param p A password entry to insert. 597 * @throws SQLException If some database error occurs during the insertion process. 598 */ 599 private void insertPassword(Connection c, Domain d, Password p) throws SQLException { 600 PreparedStatement s = c.prepareStatement("INSERT INTO passwords " 601 + "( name, comments, domain_id, url, realm, username, " + "password ) " 602 + "VALUES ( ?, ?, ?, ?, ?, ?, ? )", Statement.RETURN_GENERATED_KEYS); 603 // ID is autogenerated 604 DBUtils.setName(s, 1, p, Constants.MAX_NAME_SIZE); 605 DBUtils.setComments(s, 2, p, Constants.MAX_COMMENT_SIZE); 606 s.setLong(3, d.getID()); 607 DBUtils.setStringMaxLength(s, 4, p.getPasswordDomain(), Constants.MAX_URL_SIZE, p, "password url"); 608 DBUtils.setStringMaxLength(s, 5, p.getRealm(), Constants.MAX_REALM_NAME_SIZE, p, "password realm"); 609 DBUtils.setStringMaxLength(s, 6, p.getUsername(), Constants.MAX_USER_NAME_SIZE, p, "password username"); 610 DBUtils.setStringMaxLength(s, 7, p.getPassword(), Constants.MAX_PASSWORD_SIZE, p, "password"); 611 s.executeUpdate(); 612 p.setID(DBUtils.getGeneratedID(s)); 613 } 614 615 /** 616 * Insert the basic configuration info into the DB. This does not establish the connections with seedlists and 617 * passwords, use {create,update}Config{Passwords,Seedlists}Entries for that. 618 * 619 * @param connection A connection to the database 620 * @param d a domain 621 * @param dc a domainconfiguration 622 * @throws SQLException If some database error occurs during the insertion process. 623 */ 624 private void insertConfiguration(Connection connection, Domain d, DomainConfiguration dc) throws SQLException { 625 long templateId = DBUtils.selectLongValue(connection, "SELECT template_id FROM ordertemplates WHERE name = ?", 626 dc.getOrderXmlName()); 627 PreparedStatement s = connection.prepareStatement("INSERT INTO configurations " 628 + "( name, comments, domain_id, template_id, maxobjects, " + "maxrate, maxbytes ) " 629 + "VALUES ( ?, ?, ?, ?, ?, ?, ?)", Statement.RETURN_GENERATED_KEYS); 630 // Id is autogenerated 631 DBUtils.setName(s, 1, dc, Constants.MAX_NAME_SIZE); 632 DBUtils.setComments(s, 2, dc, Constants.MAX_COMMENT_SIZE); 633 s.setLong(3, d.getID()); 634 s.setLong(4, templateId); 635 s.setLong(5, dc.getMaxObjects()); 636 s.setInt(6, dc.getMaxRequestRate()); 637 s.setLong(7, dc.getMaxBytes()); 638 int rows = s.executeUpdate(); 639 if (rows != 1) { 640 String message = "Error inserting configuration " + dc; 641 log.warn(message); 642 throw new IOFailure(message); 643 } 644 dc.setID(DBUtils.getGeneratedID(s)); 645 } 646 647 /** 648 * Delete all entries in the given crossref table that belong to the configuration. 649 * 650 * @param c A connection to the database 651 * @param configId The domain configuration to remove entries for. 652 * @param table One of "config_passwords" or "config_seedlists" 653 * @throws SQLException If any database problems occur during the delete process. 654 */ 655 private void deleteConfigFromTable(Connection c, long configId, String table) throws SQLException { 656 PreparedStatement s = c.prepareStatement("DELETE FROM " + table + " WHERE " + table + ".config_id = ?"); 657 s.setLong(1, configId); 658 s.executeUpdate(); 659 } 660 661 /** 662 * Delete all entries from the config_passwords table that refer to the given configuration and insert the current 663 * ones. 664 * 665 * @param c A connection to the database 666 * @param d A domain to operate on 667 * @param dc Configuration to update. 668 * @throws SQLException If any database problems occur during the update process. 669 */ 670 private void updateConfigPasswordsEntries(Connection c, Domain d, DomainConfiguration dc) throws SQLException { 671 deleteConfigFromTable(c, dc.getID(), "config_passwords"); 672 createConfigPasswordsEntries(c, d, dc); 673 } 674 675 /** 676 * Create the xref table for passwords used by configurations. 677 * 678 * @param c A connection to the database 679 * @param d A domain to operate on. 680 * @param dc A configuration to create xref table for. 681 * @throws SQLException If any database problems occur during the insertion of password entries for the given domain 682 * configuration 683 */ 684 private void createConfigPasswordsEntries(Connection c, Domain d, DomainConfiguration dc) throws SQLException { 685 PreparedStatement s = c.prepareStatement("INSERT INTO config_passwords " + "( config_id, password_id ) " 686 + "SELECT config_id, password_id " + " FROM configurations, passwords" 687 + " WHERE configurations.domain_id = ?" + " AND configurations.name = ?" 688 + " AND passwords.name = ?" + " AND passwords.domain_id = configurations.domain_id"); 689 for (Iterator<Password> passwords = dc.getPasswords(); passwords.hasNext();) { 690 Password p = passwords.next(); 691 s.setLong(1, d.getID()); 692 s.setString(2, dc.getName()); 693 s.setString(3, p.getName()); 694 s.executeUpdate(); 695 s.clearParameters(); 696 } 697 } 698 699 /** 700 * Delete all entries from the config_seedlists table that refer to the given configuration and insert the current 701 * ones. 702 * 703 * @param c An open connection to the harvestDatabase. 704 * @param d A domain to operate on 705 * @param dc Configuration to update. 706 * @throws SQLException If any database problems occur during the update process. 707 */ 708 private void updateConfigSeedlistsEntries(Connection c, Domain d, DomainConfiguration dc) throws SQLException { 709 deleteConfigFromTable(c, dc.getID(), "config_seedlists"); 710 createConfigSeedlistsEntries(c, d, dc); 711 } 712 713 /** 714 * Create the xref table for seedlists used by configurations. 715 * 716 * @param c A connection to the database 717 * @param d A domain to operate on. 718 * @param dc A configuration to create xref table for. 719 * @throws SQLException If any database problems occur during the insertion of seedlist entries for the given domain 720 * configuration 721 */ 722 private void createConfigSeedlistsEntries(Connection c, Domain d, DomainConfiguration dc) throws SQLException { 723 PreparedStatement s = c.prepareStatement("INSERT INTO config_seedlists " + " ( config_id, seedlist_id ) " 724 + "SELECT configurations.config_id, seedlists.seedlist_id" + " FROM configurations, seedlists" 725 + " WHERE configurations.name = ?" + " AND seedlists.name = ?" 726 + " AND configurations.domain_id = ?" + " AND seedlists.domain_id = ?"); 727 for (Iterator<SeedList> seedlists = dc.getSeedLists(); seedlists.hasNext();) { 728 SeedList sl = seedlists.next(); 729 s.setString(1, dc.getName()); 730 s.setString(2, sl.getName()); 731 s.setLong(3, d.getID()); 732 s.setLong(4, d.getID()); 733 s.executeUpdate(); 734 s.clearParameters(); 735 } 736 } 737 738 @Override 739 protected synchronized Domain read(Connection c, String domainName) { 740 ArgumentNotValid.checkNotNullOrEmpty(domainName, "domainName"); 741 ArgumentNotValid.checkTrue(DomainUtils.isValidDomainName(domainName), "Invalid domain name " + domainName); 742 if (!exists(c, domainName)) { 743 throw new UnknownID("No domain by the name '" + domainName + "'"); 744 } 745 return readKnown(c, domainName); 746 } 747 748 @Override 749 protected synchronized Domain readKnown(Connection c, String domainName) { 750 ArgumentNotValid.checkNotNullOrEmpty(domainName, "domainName"); 751 ArgumentNotValid.checkTrue(DomainUtils.isValidDomainName(domainName), "Invalid domain name " + domainName); 752 Domain result; 753 PreparedStatement s = null; 754 try { 755 s = c.prepareStatement("SELECT domains.domain_id, " + "domains.comments, " + "domains.crawlertraps, " 756 + "domains.edition, " + "configurations.name, " + " (SELECT name FROM domains as aliasdomains" 757 + " WHERE aliasdomains.domain_id = domains.alias), " + "domains.lastaliasupdate " 758 + "FROM domains, configurations " + "WHERE domains.name = ?" 759 + " AND domains.defaultconfig = configurations.config_id"); 760 s.setString(1, domainName); 761 ResultSet res = s.executeQuery(); 762 if (!res.next()) { 763 final String message = "Error reading existing domain '" + domainName 764 + "' due to database inconsistency. " 765 + "Note that this should never happen. Please ask your database admin to check " 766 + "your 'domains' and 'configurations' tables for any inconsistencies."; 767 log.warn(message); 768 throw new IOFailure(message); 769 } 770 int domainId = res.getInt(1); 771 String comments = res.getString(2); 772 String crawlertraps = res.getString(3); 773 long edition = res.getLong(4); 774 String defaultconfig = res.getString(5); 775 String alias = res.getString(6); 776 Date lastAliasUpdate = DBUtils.getDateMaybeNull(res, 7); 777 s.close(); 778 Domain d = new Domain(domainName); 779 d.setComments(comments); 780 // don't throw exception if illegal regexps are found. 781 boolean strictMode = false; 782 String[] traps = crawlertraps.split("\n"); 783 List<String> insertList = new ArrayList<String>(); 784 for (String trap: traps) { 785 if (!trap.isEmpty()) { // Ignore empty traps (NAS-2480) 786 insertList.add(trap); 787 } 788 } 789 log.debug("Found {} crawlertraps for domain '{}' in database", insertList.size(), domainName); 790 d.setCrawlerTraps(insertList, strictMode); 791 d.setID(domainId); 792 d.setEdition(edition); 793 if (alias != null) { 794 d.setAliasInfo(new AliasInfo(domainName, alias, lastAliasUpdate)); 795 } 796 797 readSeedlists(c, d); 798 readPasswords(c, d); 799 readConfigurations(c, d); 800 // Now that configs are in, we can set the default 801 d.setDefaultConfiguration(defaultconfig); 802 readOwnerInfo(c, d); 803 readHistoryInfo(c, d); 804 805 result = d; 806 } catch (SQLException e) { 807 throw new IOFailure("SQL Error while reading domain " + domainName + "\n", e); 808 } 809 810 return result; 811 } 812 813 /** 814 * Read the configurations for the domain. This should not be called until after passwords and seedlists are read. 815 * 816 * @param c A connection to the database 817 * @param d The domain being read. Its ID must be set. 818 * @throws SQLException If database errors occur. 819 */ 820 private void readConfigurations(Connection c, Domain d) throws SQLException { 821 // Read the configurations now that passwords and seedlists exist 822 PreparedStatement s = c.prepareStatement("SELECT " + "config_id, " + "configurations.name, " + "comments, " 823 + "ordertemplates.name, " + "maxobjects, " + "maxrate, " + "maxbytes" 824 + " FROM configurations, ordertemplates " + "WHERE domain_id = ?" 825 + " AND configurations.template_id = " + "ordertemplates.template_id"); 826 s.setLong(1, d.getID()); 827 ResultSet res = s.executeQuery(); 828 while (res.next()) { 829 long domainconfigId = res.getLong(1); 830 String domainconfigName = res.getString(2); 831 String domainConfigComments = res.getString(3); 832 String order = res.getString(4); 833 long maxobjects = res.getLong(5); 834 int maxrate = res.getInt(6); 835 long maxbytes = res.getLong(7); 836 PreparedStatement s1 = c.prepareStatement("SELECT seedlists.name " + "FROM seedlists, config_seedlists " 837 + "WHERE config_seedlists.config_id = ? " + "AND config_seedlists.seedlist_id = " 838 + "seedlists.seedlist_id"); 839 s1.setLong(1, domainconfigId); 840 ResultSet seedlistResultset = s1.executeQuery(); 841 List<SeedList> seedlists = new ArrayList<SeedList>(); 842 while (seedlistResultset.next()) { 843 seedlists.add(d.getSeedList(seedlistResultset.getString(1))); 844 } 845 s1.close(); 846 if (seedlists.isEmpty()) { 847 String message = "Configuration " + domainconfigName + " of " + d + " has no seedlists"; 848 log.warn(message); 849 throw new IOFailure(message); 850 } 851 852 s1 = c.prepareStatement("SELECT passwords.name FROM passwords, config_passwords " 853 + "WHERE config_passwords.config_id = ? AND config_passwords.password_id = passwords.password_id"); 854 s1.setLong(1, domainconfigId); 855 ResultSet passwordResultset = s1.executeQuery(); 856 List<Password> passwords = new ArrayList<Password>(); 857 while (passwordResultset.next()) { 858 passwords.add(d.getPassword(passwordResultset.getString(1))); 859 } 860 DomainConfiguration dc = new DomainConfiguration(domainconfigName, d, seedlists, passwords); 861 dc.setOrderXmlName(order); 862 dc.setMaxObjects(maxobjects); 863 dc.setMaxRequestRate(maxrate); 864 dc.setComments(domainConfigComments); 865 dc.setMaxBytes(maxbytes); 866 dc.setID(domainconfigId); 867 d.addConfiguration(dc); 868 s1.close(); 869 870 // EAV 871 List<AttributeAndType> attributesAndTypes = EAV.getInstance().getAttributesAndTypes(EAV.DOMAIN_TREE_ID, (int)domainconfigId); 872 dc.setAttributesAndTypes(attributesAndTypes); 873 } 874 if (!d.getAllConfigurations().hasNext()) { 875 String message = "Loaded domain " + d + " with no configurations"; 876 log.warn(message); 877 throw new IOFailure(message); 878 } 879 } 880 881 @Override 882 public List<Long> findUsedConfigurations(Long domainID) { 883 Connection connection = HarvestDBConnection.get(); 884 try { 885 List<Long> usedConfigurations = new LinkedList<Long>(); 886 887 PreparedStatement readUsedConfigurations = connection 888 .prepareStatement(" SELECT configurations.config_id, configurations.name" + " FROM configurations " 889 + " JOIN harvest_configs USING (config_id) " 890 + " JOIN harvestdefinitions USING (harvest_id) " + " WHERE configurations.domain_id = ? " 891 + "AND harvestdefinitions.isactive = ?"); 892 readUsedConfigurations.setLong(1, domainID); 893 readUsedConfigurations.setBoolean(2, true); 894 ResultSet res = readUsedConfigurations.executeQuery(); 895 while (res.next()) { 896 usedConfigurations.add(res.getLong(1)); 897 } 898 readUsedConfigurations.close(); 899 900 return usedConfigurations; 901 } catch (SQLException e) { 902 throw new IOFailure("SQL Error while reading configuration + seeds lists", e); 903 } finally { 904 HarvestDBConnection.release(connection); 905 } 906 } 907 908 /** 909 * Read owner info entries for the domain. 910 * 911 * @param c A connection to the database 912 * @param d The domain being read. Its ID must be set. 913 * @throws SQLException If database errors occur. 914 */ 915 private void readOwnerInfo(Connection c, Domain d) throws SQLException { 916 // Read owner info 917 PreparedStatement s = c.prepareStatement("SELECT ownerinfo_id, created, info" 918 + " FROM ownerinfo WHERE domain_id = ?"); 919 s.setLong(1, d.getID()); 920 ResultSet res = s.executeQuery(); 921 while (res.next()) { 922 final DomainOwnerInfo ownerinfo = new DomainOwnerInfo(new Date(res.getTimestamp(2).getTime()), 923 res.getString(3)); 924 ownerinfo.setID(res.getLong(1)); 925 d.addOwnerInfo(ownerinfo); 926 } 927 } 928 929 /** 930 * Read history info entries for the domain. 931 * 932 * @param c A connection to the database 933 * @param d The domain being read. Its ID must be set. 934 * @throws SQLException If database errors occur. 935 */ 936 private void readHistoryInfo(Connection c, Domain d) throws SQLException { 937 // Read history info 938 PreparedStatement s = c.prepareStatement("SELECT historyinfo_id, stopreason, " + "objectcount, bytecount, " 939 + "name, job_id, harvest_id, harvest_time " + "FROM historyinfo, configurations " 940 + "WHERE configurations.domain_id = ?" + " AND historyinfo.config_id = configurations.config_id"); 941 s.setLong(1, d.getID()); 942 ResultSet res = s.executeQuery(); 943 while (res.next()) { 944 long hiID = res.getLong(1); 945 int stopreasonNum = res.getInt(2); 946 StopReason stopreason = StopReason.getStopReason(stopreasonNum); 947 long objectCount = res.getLong(3); 948 long byteCount = res.getLong(4); 949 String configName = res.getString(5); 950 Long jobId = res.getLong(6); 951 if (res.wasNull()) { 952 jobId = null; 953 } 954 long harvestId = res.getLong(7); 955 Date harvestTime = new Date(res.getTimestamp(8).getTime()); 956 HarvestInfo hi; 957 // XML DAOs didn't keep the job id in harvestinfo, so some 958 // entries will be null. 959 hi = new HarvestInfo(harvestId, jobId, d.getName(), configName, harvestTime, byteCount, objectCount, 960 stopreason); 961 hi.setID(hiID); 962 d.getHistory().addHarvestInfo(hi); 963 } 964 } 965 966 /** 967 * Read passwords for the domain. 968 * 969 * @param c A connection to the database 970 * @param d The domain being read. Its ID must be set. 971 * @throws SQLException If database errors occur. 972 */ 973 private void readPasswords(Connection c, Domain d) throws SQLException { 974 PreparedStatement s = c.prepareStatement("SELECT password_id, name, comments, url, " 975 + "realm, username, password " + "FROM passwords WHERE domain_id = ?"); 976 s.setLong(1, d.getID()); 977 ResultSet res = s.executeQuery(); 978 while (res.next()) { 979 final Password pwd = new Password(res.getString(2), res.getString(3), res.getString(4), res.getString(5), 980 res.getString(6), res.getString(7)); 981 pwd.setID(res.getLong(1)); 982 d.addPassword(pwd); 983 } 984 } 985 986 /** 987 * Read seedlists for the domain. 988 * 989 * @param c A connection to the database 990 * @param d The domain being read. Its ID must be set. 991 * @throws SQLException If database errors occur. 992 */ 993 private void readSeedlists(Connection c, Domain d) throws SQLException { 994 PreparedStatement s = c.prepareStatement("SELECT seedlist_id, name, comments, seeds" 995 + " FROM seedlists WHERE domain_id = ?"); 996 s.setLong(1, d.getID()); 997 ResultSet res = s.executeQuery(); 998 while (res.next()) { 999 final SeedList seedlist = getSeedListFromResultset(res); 1000 d.addSeedList(seedlist); 1001 } 1002 s.close(); 1003 if (!d.getAllSeedLists().hasNext()) { 1004 final String msg = "Domain " + d + " loaded with no seedlists"; 1005 log.warn(msg); 1006 throw new IOFailure(msg); 1007 } 1008 } 1009 1010 /** 1011 * Make SeedList based on entry from seedlists (id, name, comments, seeds). 1012 * 1013 * @param res a Resultset 1014 * @return a SeedList based on ResultSet entry. 1015 * @throws SQLException if unable to get data from database 1016 */ 1017 private SeedList getSeedListFromResultset(ResultSet res) throws SQLException { 1018 final long seedlistId = res.getLong(1); 1019 final String seedlistName = res.getString(2); 1020 String seedlistComments = res.getString(3); 1021 1022 String seedlistContents = ""; 1023 if (DBSpecifics.getInstance().supportsClob()) { 1024 Clob clob = res.getClob(4); 1025 seedlistContents = clob.getSubString(1, (int) clob.length()); 1026 } else { 1027 seedlistContents = res.getString(4); 1028 } 1029 final SeedList seedlist = new SeedList(seedlistName, seedlistContents); 1030 seedlist.setComments(seedlistComments); 1031 seedlist.setID(seedlistId); 1032 return seedlist; 1033 } 1034 1035 @Override 1036 public synchronized boolean exists(String domainName) { 1037 ArgumentNotValid.checkNotNullOrEmpty(domainName, "domainName"); 1038 if (!DomainUtils.isValidDomainName(domainName)) { 1039 return false; 1040 } 1041 Connection c = HarvestDBConnection.get(); 1042 try { 1043 return exists(c, domainName); 1044 } finally { 1045 HarvestDBConnection.release(c); 1046 } 1047 } 1048 1049 /** 1050 * Return true if a domain with the given name exists. 1051 * 1052 * @param c an open connection to the harvestDatabase 1053 * @param domainName a name of a domain 1054 * @return true if a domain with the given name exists, otherwise false. 1055 */ 1056 private synchronized boolean exists(Connection c, String domainName) { 1057 if (!DomainUtils.isValidDomainName(domainName)) { 1058 return false; 1059 } 1060 return 1 == DBUtils.selectIntValue(c, "SELECT COUNT(*) FROM domains WHERE name = ?", domainName); 1061 } 1062 1063 @Override 1064 public synchronized int getCountDomains() { 1065 Connection c = HarvestDBConnection.get(); 1066 try { 1067 return DBUtils.selectIntValue(c, "SELECT COUNT(*) FROM domains"); 1068 } finally { 1069 HarvestDBConnection.release(c); 1070 } 1071 } 1072 1073 @Override 1074 public synchronized Iterator<Domain> getAllDomains() { 1075 Connection c = HarvestDBConnection.get(); 1076 try { 1077 List<String> domainNames = DBUtils.selectStringList(c, "SELECT name FROM domains ORDER BY name"); 1078 List<Domain> orderedDomains = new LinkedList<Domain>(); 1079 for (String name : domainNames) { 1080 if (DomainUtils.isValidDomainName(name)) { 1081 orderedDomains.add(read(c, name)); 1082 } 1083 } 1084 return orderedDomains.iterator(); 1085 } finally { 1086 HarvestDBConnection.release(c); 1087 } 1088 } 1089 1090 @Override 1091 public Iterator<Domain> getAllDomainsInSnapshotHarvestOrder() { 1092 Connection c = HarvestDBConnection.get(); 1093 try { 1094 log.info("Starting a select of all domains used for Snapshot harvesting"); 1095 // Note: maxbytes are ordered with largest first for symmetry 1096 // with HarvestDefinition.CompareConfigDesc 1097 List<String> domainNames = DBUtils.selectStringList(c, "SELECT domains.name" 1098 + " FROM domains, configurations, ordertemplates" 1099 + " WHERE domains.defaultconfig=configurations.config_id" + " AND configurations.template_id" 1100 + "=ordertemplates.template_id" + " ORDER BY" + " ordertemplates.name," 1101 + " configurations.maxbytes DESC," + " domains.name"); 1102 log.info("Retrieved all {} domains used for Snapshot harvesting without searching for attributes for their default configs", domainNames.size()); 1103 List<String> domainNamesWithAttributes = DBUtils.selectStringList(c, // Don't order this - it will be ordered later 1104 "SELECT DISTINCT domains.name" 1105 + " FROM domains, configurations, eav_attribute" 1106 + " WHERE domains.defaultconfig=configurations.config_id" 1107 + " AND configurations.config_id=eav_attribute.entity_id"); 1108 log.info("Retrieved all {} domains used for Snapshot harvesting that has attributes for their default configs", domainNamesWithAttributes.size()); 1109 domainNames = domainNames.stream().filter(DomainUtils::isValidDomainName).collect(Collectors.toList()); 1110 // Remove the content of domainNamesWithAttributes from domainNames 1111 domainNames.removeAll(domainNamesWithAttributes); 1112 log.info("Removed all {} domains with attributes from the total list, reducing total-list to {}", domainNamesWithAttributes.size(), domainNames.size()); 1113 // Add the remainder of domainNames to domainNamesWithAttributes, so the domain configs with attributes will be handled first. 1114 domainNamesWithAttributes.addAll(domainNames); 1115 log.info("Remainder of total list merged with list of domains w/ attributes"); 1116 1117 return new FilterIterator<String, Domain>(domainNamesWithAttributes.iterator()) { 1118 public Domain filter(String s) { 1119 return readKnown(s); 1120 } 1121 }; 1122 } finally { 1123 HarvestDBConnection.release(c); 1124 } 1125 } 1126 1127 @Override 1128 public List<String> getDomains(String glob) { 1129 ArgumentNotValid.checkNotNullOrEmpty(glob, "glob"); 1130 // SQL uses % and _ instead of * and ? 1131 String sqlGlob = DBUtils.makeSQLGlob(glob); 1132 Connection c = HarvestDBConnection.get(); 1133 try { 1134 List<String> names = DBUtils.selectStringList(c, "SELECT name FROM domains WHERE name LIKE ? ORDER BY name", sqlGlob); 1135 return names.stream().filter(DomainUtils::isValidDomainName).collect(Collectors.toList()); 1136 } finally { 1137 HarvestDBConnection.release(c); 1138 } 1139 } 1140 1141 @Override 1142 public boolean mayDelete(DomainConfiguration config) { 1143 ArgumentNotValid.checkNotNull(config, "config"); 1144 String defaultConfigName = this.getDefaultDomainConfigurationName(config.getDomainName()); 1145 Connection c = HarvestDBConnection.get(); 1146 try { 1147 // Never delete default config and don't delete configs being used. 1148 return !config.getName().equals(defaultConfigName) 1149 && !DBUtils.selectAny(c, "SELECT config_id" + " FROM harvest_configs WHERE config_id = ?", 1150 config.getID()); 1151 } finally { 1152 HarvestDBConnection.release(c); 1153 } 1154 } 1155 1156 @Override 1157 public String getDefaultDomainConfigurationName(String domainName) { 1158 ArgumentNotValid.checkTrue(DomainUtils.isValidDomainName(domainName), "Cannot read invalid domain name " + domainName); 1159 Connection c = HarvestDBConnection.get(); 1160 try { 1161 return DBUtils.selectStringValue(c, "SELECT configurations.name " + "FROM domains, configurations " 1162 + "WHERE domains.defaultconfig = configurations.config_id" + " AND domains.name = ?", domainName); 1163 } finally { 1164 HarvestDBConnection.release(c); 1165 } 1166 } 1167 1168 @Override 1169 public synchronized SparseDomain readSparse(String domainName) { 1170 ArgumentNotValid.checkNotNullOrEmpty(domainName, "domainName"); 1171 ArgumentNotValid.checkTrue(DomainUtils.isValidDomainName(domainName), "Cannot read invalid domain name " + domainName); 1172 Connection c = HarvestDBConnection.get(); 1173 try { 1174 List<String> domainConfigurationNames = DBUtils.selectStringList(c, "SELECT configurations.name " 1175 + " FROM configurations, domains " + "WHERE domains.domain_id = configurations.domain_id " 1176 + " AND domains.name = ?", domainName); 1177 if (domainConfigurationNames.size() == 0) { 1178 throw new UnknownID("No domain exists with name '" + domainName + "'"); 1179 } 1180 return new SparseDomain(domainName, domainConfigurationNames); 1181 } finally { 1182 HarvestDBConnection.release(c); 1183 } 1184 } 1185 1186 @Override 1187 public List<AliasInfo> getAliases(String domain) { 1188 ArgumentNotValid.checkNotNullOrEmpty(domain, "String domain"); 1189 ArgumentNotValid.checkTrue(DomainUtils.isValidDomainName(domain), "Cannot read invalid domain name " + domain); 1190 List<AliasInfo> resultSet = new ArrayList<AliasInfo>(); 1191 Connection c = HarvestDBConnection.get(); 1192 PreparedStatement s = null; 1193 // return all <domain, alias, lastaliasupdate> tuples 1194 // where alias = domain 1195 if (!exists(c, domain)) { 1196 log.debug("domain named '{}' does not exist. Returning empty result set", domain); 1197 return resultSet; 1198 } 1199 try { 1200 s = c.prepareStatement("SELECT domains.name, " + "domains.lastaliasupdate " 1201 + " FROM domains, domains as fatherDomains " + " WHERE domains.alias = fatherDomains.domain_id AND" 1202 + " fatherDomains.name = ?" + " ORDER BY domains.name"); 1203 s.setString(1, domain); 1204 ResultSet res = s.executeQuery(); 1205 while (res.next()) { 1206 AliasInfo ai = new AliasInfo(res.getString(1), domain, DBUtils.getDateMaybeNull(res, 2)); 1207 resultSet.add(ai); 1208 } 1209 1210 return resultSet; 1211 } catch (SQLException e) { 1212 throw new IOFailure("Failure getting alias-information" + "\n", e); 1213 } finally { 1214 DBUtils.closeStatementIfOpen(s); 1215 HarvestDBConnection.release(c); 1216 } 1217 } 1218 1219 @Override 1220 public List<AliasInfo> getAllAliases() { 1221 List<AliasInfo> resultSet = new ArrayList<AliasInfo>(); 1222 Connection c = HarvestDBConnection.get(); 1223 PreparedStatement s = null; 1224 // return all <domain, alias, lastaliasupdate> tuples 1225 // where alias is not-null 1226 try { 1227 s = c.prepareStatement("SELECT domains.name, " + "(SELECT name FROM domains as aliasdomains" 1228 + " WHERE aliasdomains.domain_id " + "= domains.alias), " + " domains.lastaliasupdate " 1229 + " FROM domains " + " WHERE domains.alias IS NOT NULL" + " ORDER BY " + " lastaliasupdate ASC"); 1230 ResultSet res = s.executeQuery(); 1231 while (res.next()) { 1232 String domainName = res.getString(1); 1233 String aliasOf = res.getString(2); 1234 Date lastchanged = DBUtils.getDateMaybeNull(res, 3); 1235 AliasInfo ai = new AliasInfo(domainName, aliasOf, lastchanged); 1236 if (DomainUtils.isValidDomainName(domainName) && DomainUtils.isValidDomainName(aliasOf)) { 1237 resultSet.add(ai); 1238 } 1239 } 1240 return resultSet; 1241 } catch (SQLException e) { 1242 throw new IOFailure("Failure getting alias-information" + "\n", e); 1243 } finally { 1244 DBUtils.closeStatementIfOpen(s); 1245 HarvestDBConnection.release(c); 1246 } 1247 } 1248 1249 /** 1250 * Return all TLDs represented by the domains in the domains table. it was asked that a level X TLD belong appear in 1251 * TLD list where the level is <=X for example bidule.bnf.fr belong to .bnf.fr and to .fr it appear in the level 1 1252 * list of TLD and in the level 2 list 1253 * 1254 * @param level maximum level of TLD 1255 * @return a list of TLDs 1256 * @see DomainDAO#getTLDs(int) 1257 */ 1258 @Override 1259 public List<TLDInfo> getTLDs(int level) { 1260 Map<String, TLDInfo> resultMap = new HashMap<String, TLDInfo>(); 1261 Connection c = HarvestDBConnection.get(); 1262 PreparedStatement s = null; 1263 try { 1264 s = c.prepareStatement("SELECT name FROM domains"); 1265 ResultSet res = s.executeQuery(); 1266 while (res.next()) { 1267 String domain = res.getString(1); 1268 if (DomainUtils.isValidDomainName(domain)) { 1269 // getting the TLD level of the domain 1270 int domainTLDLevel = TLDInfo.getTLDLevel(domain); 1271 1272 // restraining to max level 1273 if (domainTLDLevel > level) { 1274 domainTLDLevel = level; 1275 } 1276 1277 // looping from level 1 to level max of the domain 1278 for (int currentLevel = 1; currentLevel <= domainTLDLevel; currentLevel++) { 1279 // getting the tld of the domain by level 1280 String tld = TLDInfo.getMultiLevelTLD(domain, currentLevel); 1281 TLDInfo i = resultMap.get(tld); 1282 if (i == null) { 1283 i = new TLDInfo(tld); 1284 resultMap.put(tld, i); 1285 } 1286 i.addSubdomain(domain); 1287 } 1288 } 1289 } 1290 1291 List<TLDInfo> resultSet = new ArrayList<TLDInfo>(resultMap.values()); 1292 Collections.sort(resultSet); 1293 return resultSet; 1294 1295 } catch (SQLException e) { 1296 throw new IOFailure("Failure getting TLD-information" + "\n", e); 1297 } finally { 1298 DBUtils.closeStatementIfOpen(s); 1299 HarvestDBConnection.release(c); 1300 } 1301 } 1302 1303 @Override 1304 public HarvestInfo getDomainJobInfo(Job j, String domainName, String configName) { 1305 ArgumentNotValid.checkNotNull(j, "j"); 1306 ArgumentNotValid.checkNotNullOrEmpty(domainName, "domainName"); 1307 ArgumentNotValid.checkTrue(DomainUtils.isValidDomainName(domainName), "Cannot read invalid domain name " + domainName); 1308 ArgumentNotValid.checkNotNullOrEmpty(configName, "configName"); 1309 HarvestInfo resultInfo = null; 1310 1311 Connection connection = HarvestDBConnection.get(); 1312 PreparedStatement s = null; 1313 try { 1314 // Get domain_id for domainName 1315 long domainId = DBUtils.selectLongValue(connection, "SELECT domain_id FROM domains WHERE name=?", 1316 domainName); 1317 1318 s = connection.prepareStatement("SELECT stopreason, " + "objectcount, bytecount, " 1319 + "harvest_time FROM historyinfo WHERE " + "job_id = ? AND " + "config_id = ? AND " 1320 + "harvest_id = ?"); 1321 s.setLong(1, j.getJobID()); 1322 s.setLong(2, DBUtils.selectLongValue(connection, "SELECT config_id FROM configurations " 1323 + "WHERE name = ? AND domain_id=?", configName, domainId)); 1324 s.setLong(3, j.getOrigHarvestDefinitionID()); 1325 ResultSet res = s.executeQuery(); 1326 // If no result, the job may not have been run yet 1327 // return null HarvestInfo 1328 if (res.next()) { 1329 StopReason reason = StopReason.getStopReason(res.getInt(1)); 1330 long objectCount = res.getLong(2); 1331 long byteCount = res.getLong(3); 1332 Date harvestTime = res.getDate(4); 1333 resultInfo = new HarvestInfo(j.getOrigHarvestDefinitionID(), j.getJobID(), domainName, configName, 1334 harvestTime, byteCount, objectCount, reason); 1335 } 1336 1337 return resultInfo; 1338 1339 } catch (SQLException e) { 1340 throw new IOFailure("Failure getting DomainJobInfo" + "\n", e); 1341 } finally { 1342 DBUtils.closeStatementIfOpen(s); 1343 HarvestDBConnection.release(connection); 1344 } 1345 } 1346 1347 @Override 1348 public List<DomainHarvestInfo> listDomainHarvestInfo(String domainName, String orderBy, boolean asc) { 1349 ArgumentNotValid.checkNotNullOrEmpty(domainName, "domainName"); 1350 ArgumentNotValid.checkTrue(DomainUtils.isValidDomainName(domainName), "Cannot read invalid domain name " + domainName); 1351 Connection c = HarvestDBConnection.get(); 1352 PreparedStatement s = null; 1353 final ArrayList<DomainHarvestInfo> domainHarvestInfos = new ArrayList<DomainHarvestInfo>(); 1354 final String ascOrDesc = asc ? "ASC" : "DESC"; 1355 log.debug("Using ascOrDesc=" + ascOrDesc + " after receiving " + asc); 1356 try { 1357 // For historical reasons, not all historyinfo objects have the 1358 // information required to find the job that made them. Therefore, 1359 // we must left outer join them onto the jobs list to get the 1360 // start date and end date for those where they can be found. 1361 s = c.prepareStatement("SELECT jobs.job_id, hdname, hdid," + " harvest_num," + " configname, startdate," 1362 + " enddate, objectcount, bytecount, stopreason" + " FROM ( " 1363 + " SELECT harvestdefinitions.name AS hdname," + " harvestdefinitions.harvest_id AS hdid," 1364 + " configurations.name AS configname," 1365 + " objectcount, bytecount, job_id, stopreason" 1366 + " FROM domains, configurations, historyinfo, " + " harvestdefinitions" 1367 + " WHERE domains.name = ? " + " AND domains.domain_id = configurations.domain_id" 1368 + " AND historyinfo.config_id = " + "configurations.config_id" 1369 + " AND historyinfo.harvest_id = " + "harvestdefinitions.harvest_id" + " ) AS hist" 1370 + " LEFT OUTER JOIN jobs" + " ON hist.job_id = jobs.job_id ORDER BY " + orderBy + " " + ascOrDesc); 1371 s.setString(1, domainName); 1372 ResultSet res = s.executeQuery(); 1373 while (res.next()) { 1374 final int jobID = res.getInt(1); 1375 final String harvestName = res.getString(2); 1376 final int harvestID = res.getInt(3); 1377 final int harvestNum = res.getInt(4); 1378 final String configName = res.getString(5); 1379 final Date startDate = DBUtils.getDateMaybeNull(res, 6); 1380 final Date endDate = DBUtils.getDateMaybeNull(res, 7); 1381 final long objectCount = res.getLong(8); 1382 final long byteCount = res.getLong(9); 1383 final StopReason reason = StopReason.getStopReason(res.getInt(10)); 1384 domainHarvestInfos.add(new DomainHarvestInfo(domainName, jobID, harvestName, harvestID, harvestNum, 1385 configName, startDate, endDate, byteCount, objectCount, reason)); 1386 } 1387 return domainHarvestInfos; 1388 } catch (SQLException e) { 1389 String message = "SQL error getting domain harvest info for " + domainName + "\n"; 1390 log.warn(message, e); 1391 throw new IOFailure(message, e); 1392 } finally { 1393 DBUtils.closeStatementIfOpen(s); 1394 HarvestDBConnection.release(c); 1395 } 1396 } 1397 1398 /** 1399 * Saves all extended Field values for a Domain in the Database. 1400 * 1401 * @param c Connection to Database 1402 * @param d Domain where loaded extended Field Values will be set 1403 * @throws SQLException If database errors occur. 1404 */ 1405 private void saveExtendedFieldValues(Connection c, Domain d) throws SQLException { 1406 List<ExtendedFieldValue> list = d.getExtendedFieldValues(); 1407 for (int i = 0; i < list.size(); i++) { 1408 ExtendedFieldValue efv = list.get(i); 1409 efv.setInstanceID(d.getID()); 1410 1411 ExtendedFieldValueDBDAO dao = (ExtendedFieldValueDBDAO) ExtendedFieldValueDAO.getInstance(); 1412 if (efv.getExtendedFieldValueID() != null) { 1413 dao.update(c, efv, false); 1414 } else { 1415 dao.create(c, efv, false); 1416 } 1417 } 1418 } 1419 1420 @Override 1421 public DomainConfiguration getDomainConfiguration(String domainName, String configName) { 1422 ArgumentNotValid.checkTrue(DomainUtils.isValidDomainName(domainName), "Cannot read invalid domain name " + domainName); 1423 DomainHistory history = getDomainHistory(domainName); 1424 List<String> crawlertraps = getCrawlertraps(domainName); 1425 1426 Connection c = HarvestDBConnection.get(); 1427 List<DomainConfiguration> foundConfigs = new ArrayList<DomainConfiguration>(); 1428 PreparedStatement s = null; 1429 try { 1430 // Read the configurations now that passwords and seedlists exist 1431 // TODO Seriously? Use a join. 1432 s = c.prepareStatement("SELECT config_id, " + "configurations.name, " + "comments, " 1433 + "ordertemplates.name, " + "maxobjects, " + "maxrate, " + "maxbytes" 1434 + " FROM configurations, ordertemplates " + "WHERE domain_id = (SELECT domain_id FROM domains " 1435 + " WHERE name=?)" + " AND configurations.name = ?" + " AND configurations.template_id = " 1436 + "ordertemplates.template_id"); 1437 s.setString(1, domainName); 1438 s.setString(2, configName); 1439 ResultSet res = s.executeQuery(); 1440 while (res.next()) { 1441 long domainconfigId = res.getLong(1); 1442 String domainconfigName = res.getString(2); 1443 String domainConfigComments = res.getString(3); 1444 final String order = res.getString(4); 1445 long maxobjects = res.getLong(5); 1446 int maxrate = res.getInt(6); 1447 long maxbytes = res.getLong(7); 1448 PreparedStatement s1 = c.prepareStatement("SELECT seedlists.seedlist_id, seedlists.name, " 1449 + " seedlists.comments, seedlists.seeds " + "FROM seedlists, config_seedlists " 1450 + "WHERE config_seedlists.config_id = ? " + "AND config_seedlists.seedlist_id = " 1451 + "seedlists.seedlist_id"); 1452 s1.setLong(1, domainconfigId); 1453 ResultSet seedlistResultset = s1.executeQuery(); 1454 List<SeedList> seedlists = new ArrayList<SeedList>(); 1455 while (seedlistResultset.next()) { 1456 SeedList seedlist = getSeedListFromResultset(seedlistResultset); 1457 seedlists.add(seedlist); 1458 } 1459 s1.close(); 1460 if (seedlists.isEmpty()) { 1461 String message = "Configuration " + domainconfigName + " of domain '" + domainName 1462 + " has no seedlists"; 1463 log.warn(message); 1464 throw new IOFailure(message); 1465 } 1466 1467 PreparedStatement s2 = c.prepareStatement("SELECT passwords.password_id, " 1468 + "passwords.name, passwords.comments, " + "passwords.url, passwords.realm, " 1469 + "passwords.username, passwords.password " + "FROM passwords, config_passwords " 1470 + "WHERE config_passwords.config_id = ? " + "AND config_passwords.password_id = " 1471 + "passwords.password_id"); 1472 s2.setLong(1, domainconfigId); 1473 ResultSet passwordResultset = s2.executeQuery(); 1474 List<Password> passwords = new ArrayList<Password>(); 1475 while (passwordResultset.next()) { 1476 final Password pwd = new Password(passwordResultset.getString(2), passwordResultset.getString(3), 1477 passwordResultset.getString(4), passwordResultset.getString(5), 1478 passwordResultset.getString(6), passwordResultset.getString(7)); 1479 pwd.setID(passwordResultset.getLong(1)); 1480 passwords.add(pwd); 1481 } 1482 1483 DomainConfiguration dc = new DomainConfiguration(domainconfigName, domainName, history, crawlertraps, 1484 seedlists, passwords); 1485 dc.setOrderXmlName(order); 1486 dc.setMaxObjects(maxobjects); 1487 dc.setMaxRequestRate(maxrate); 1488 dc.setComments(domainConfigComments); 1489 dc.setMaxBytes(maxbytes); 1490 dc.setID(domainconfigId); 1491 foundConfigs.add(dc); 1492 s2.close(); 1493 1494 // EAV 1495 List<AttributeAndType> attributesAndTypes = EAV.getInstance().getAttributesAndTypes(EAV.DOMAIN_TREE_ID, (int)domainconfigId); 1496 dc.setAttributesAndTypes(attributesAndTypes); 1497 } // While 1498 } catch (SQLException e) { 1499 throw new IOFailure("Error while fetching DomainConfigration: ", e); 1500 } finally { 1501 DBUtils.closeStatementIfOpen(s); 1502 HarvestDBConnection.release(c); 1503 } 1504 return foundConfigs.get(0); 1505 } 1506 1507 /** 1508 * Retrieve the crawlertraps for a specific domain. TODO should this method be public? 1509 * 1510 * @param domainName the name of a domain. 1511 * @return the crawlertraps for given domain. 1512 */ 1513 private List<String> getCrawlertraps(String domainName) { 1514 ArgumentNotValid.checkTrue(DomainUtils.isValidDomainName(domainName), "Cannot read invalid domain name " + domainName); 1515 Connection c = HarvestDBConnection.get(); 1516 String traps = null; 1517 PreparedStatement s = null; 1518 try { 1519 s = c.prepareStatement("SELECT crawlertraps FROM domains WHERE name = ?"); 1520 s.setString(1, domainName); 1521 ResultSet crawlertrapsResultset = s.executeQuery(); 1522 if (crawlertrapsResultset.next()) { 1523 traps = crawlertrapsResultset.getString(1); 1524 } else { 1525 throw new IOFailure("Unable to find crawlertraps for domain '" + domainName + "'. " 1526 + "The domain doesn't seem to exist."); 1527 } 1528 } catch (SQLException e) { 1529 throw new IOFailure("Error while fetching crawlertraps for domain '" + domainName + "': ", e); 1530 } finally { 1531 DBUtils.closeStatementIfOpen(s); 1532 HarvestDBConnection.release(c); 1533 } 1534 return Arrays.asList(traps.split("\n")); 1535 } 1536 1537 @Override 1538 public Iterator<HarvestInfo> getHarvestInfoBasedOnPreviousHarvestDefinition( 1539 final HarvestDefinition previousHarvestDefinition) { 1540 ArgumentNotValid.checkNotNull(previousHarvestDefinition, "previousHarvestDefinition"); 1541 // For each domainConfig, get harvest infos if there is any for the 1542 // previous harvest definition 1543 return new FilterIterator<DomainConfiguration, HarvestInfo>(previousHarvestDefinition.getDomainConfigurations()) { 1544 /** 1545 * @see FilterIterator#filter(Object) 1546 */ 1547 protected HarvestInfo filter(DomainConfiguration o) { 1548 DomainConfiguration config = o; 1549 DomainHistory domainHistory = getDomainHistory(config.getDomainName()); 1550 HarvestInfo hi = domainHistory.getSpecifiedHarvestInfo(previousHarvestDefinition.getOid(), 1551 config.getName()); 1552 return hi; 1553 } 1554 }; // Here ends the above return-statement 1555 } 1556 1557 @Override 1558 public DomainHistory getDomainHistory(String domainName) { 1559 ArgumentNotValid.checkNotNullOrEmpty(domainName, "String domainName"); 1560 ArgumentNotValid.checkTrue(DomainUtils.isValidDomainName(domainName), "Cannot read invalid domain name " + domainName); 1561 Connection c = HarvestDBConnection.get(); 1562 DomainHistory history = new DomainHistory(); 1563 // Read history info 1564 PreparedStatement s = null; 1565 try { 1566 s = c.prepareStatement("SELECT historyinfo_id, stopreason, " + "objectcount, bytecount, " 1567 + "name, job_id, harvest_id, harvest_time " + "FROM historyinfo, configurations " 1568 + "WHERE configurations.domain_id = " + "(SELECT domain_id FROM domains WHERE name=?)" 1569 + " AND historyinfo.config_id " + " = configurations.config_id"); 1570 s.setString(1, domainName); 1571 ResultSet res = s.executeQuery(); 1572 while (res.next()) { 1573 long hiID = res.getLong(1); 1574 int stopreasonNum = res.getInt(2); 1575 StopReason stopreason = StopReason.getStopReason(stopreasonNum); 1576 long objectCount = res.getLong(3); 1577 long byteCount = res.getLong(4); 1578 String configName = res.getString(5); 1579 Long jobId = res.getLong(6); 1580 if (res.wasNull()) { 1581 jobId = null; 1582 } 1583 long harvestId = res.getLong(7); 1584 Date harvestTime = new Date(res.getTimestamp(8).getTime()); 1585 HarvestInfo hi; 1586 1587 hi = new HarvestInfo(harvestId, jobId, domainName, configName, harvestTime, byteCount, objectCount, 1588 stopreason); 1589 hi.setID(hiID); 1590 history.addHarvestInfo(hi); 1591 } 1592 } catch (SQLException e) { 1593 throw new IOFailure("Error while fetching DomainHistory for domain '" + domainName + "': ", e); 1594 } finally { 1595 DBUtils.closeStatementIfOpen(s); 1596 HarvestDBConnection.release(c); 1597 } 1598 1599 return history; 1600 } 1601 1602 @Override 1603 public List<String> getDomains(String glob, String searchField) { 1604 ArgumentNotValid.checkNotNullOrEmpty(glob, "glob"); 1605 ArgumentNotValid.checkNotNullOrEmpty(searchField, "searchField"); 1606 // SQL uses % and _ instead of * and ? 1607 String sqlGlob = DBUtils.makeSQLGlob(glob); 1608 1609 Connection c = HarvestDBConnection.get(); 1610 try { 1611 return DBUtils.selectStringList(c, "SELECT name FROM domains WHERE " + searchField.toLowerCase() 1612 + " LIKE ?", sqlGlob).stream().filter(DomainUtils::isValidDomainName).collect(Collectors.toList()); 1613 } finally { 1614 HarvestDBConnection.release(c); 1615 } 1616 } 1617 1618 @Override 1619 public void renameAndUpdateConfig(Domain domain, DomainConfiguration domainConf, 1620 String configOldName) { 1621 Connection connection = HarvestDBConnection.get(); 1622 Long configId = DBUtils.selectLongValue(connection, 1623 "SELECT config_id FROM configurations WHERE domain_id = ? and name = ?", domain.getID(), configOldName); 1624 1625 try { 1626 PreparedStatement s = connection.prepareStatement("UPDATE configurations SET name = ?, comments = ?, " 1627 + "template_id = ( SELECT template_id FROM ordertemplates " + "WHERE name = ? ), " + "maxobjects = ?, " 1628 + "maxrate = ?, " + "maxbytes = ? " + "WHERE config_id = ? AND domain_id = ?"); 1629 s.setString(1, domainConf.getName()); 1630 DBUtils.setComments(s, 2, domainConf, Constants.MAX_COMMENT_SIZE); 1631 s.setString(3, domainConf.getOrderXmlName()); 1632 s.setLong(4, domainConf.getMaxObjects()); 1633 s.setInt(5, domainConf.getMaxRequestRate()); 1634 s.setLong(6, domainConf.getMaxBytes()); 1635 s.setLong(7, configId); 1636 s.setLong(8, domain.getID()); 1637 s.executeUpdate(); 1638 s.clearParameters(); 1639 updateConfigPasswordsEntries(connection, domain, domainConf); 1640 updateConfigSeedlistsEntries(connection, domain, domainConf); 1641 s.close(); 1642 } catch (SQLException e) { 1643 throw new IOFailure("Error while renaming configuration '" + configOldName + "' to: " + domainConf.getName(), e); 1644 } finally { 1645 HarvestDBConnection.release(connection); 1646 } 1647 } 1648 1649 @Override 1650 public List<String> getAllDomainNames() { 1651 Connection c = HarvestDBConnection.get(); 1652 try { 1653 return DBUtils.selectStringList(c, "SELECT name FROM domains").stream().filter(DomainUtils::isValidDomainName).collect( 1654 Collectors.toList()); 1655 } finally { 1656 HarvestDBConnection.release(c); 1657 } 1658 } 1659}