diff --git a/housekeeping/src/main/groovy/whelk/housekeeping/InquirySender.groovy b/housekeeping/src/main/groovy/whelk/housekeeping/InquirySender.groovy new file mode 100644 index 0000000000..b1803d8318 --- /dev/null +++ b/housekeeping/src/main/groovy/whelk/housekeeping/InquirySender.groovy @@ -0,0 +1,88 @@ +package whelk.housekeeping + +import groovy.transform.CompileStatic +import groovy.util.logging.Log4j2 +import whelk.Whelk + +import java.sql.Connection +import java.sql.PreparedStatement +import java.sql.ResultSet +import java.sql.Timestamp +import java.time.Instant +import java.time.ZoneOffset +import java.time.ZonedDateTime +import java.time.format.DateTimeFormatter +import java.time.temporal.ChronoUnit + +@CompileStatic +@Log4j2 +class InquirySender extends HouseKeeper { + private final String STATE_KEY = "CXZ inquiry email sender" + private String status = "OK" + private final Whelk whelk + + public InquirySender(Whelk whelk) { + this.whelk = whelk + } + + @Override + String getName() { + return "Inquiry sender" + } + + @Override + String getStatusDescription() { + return status + } + + public String getCronSchedule() { + return "* * * * *" + } + + @Override + void trigger() { + + Timestamp from = Timestamp.from(Instant.now().minus(1, ChronoUnit.DAYS)) + Map sendState = whelk.getStorage().getState(STATE_KEY) + if (sendState && sendState.notifiedChangesUpTo) + from = Timestamp.from( ZonedDateTime.parse( (String) sendState.notifiedChangesUpTo, DateTimeFormatter.ISO_OFFSET_DATE_TIME).toInstant() ) + + Connection connection + PreparedStatement statement + ResultSet resultSet + + Instant notifiedChangesUpTo = from.toInstant() + + connection = whelk.getStorage().getOuterConnection() + connection.setAutoCommit(false) + try { + String sql = "SELECT modified, data FROM lddb WHERE data#>>'{@graph,1,@type}' IN ('InquiryAction', 'ChangeNotice') AND modified > ?;" + connection.setAutoCommit(false) + statement = connection.prepareStatement(sql) + statement.setTimestamp(1, from) + statement.setFetchSize(512) + resultSet = statement.executeQuery() + + while (resultSet.next()) { + String data = resultSet.getString("data") + + // TODO: Send email! + + Instant lastChangeObservationForInstance = resultSet.getTimestamp("modified").toInstant() + if (lastChangeObservationForInstance.isAfter(notifiedChangesUpTo)) + notifiedChangesUpTo = lastChangeObservationForInstance + } + } catch (Throwable e) { + status = "Failed with:\n" + e + "\nat:\n" + e.getStackTrace().toString() + throw e + } finally { + connection.close() + if (notifiedChangesUpTo.isAfter(from.toInstant())) { + Map newState = new HashMap() + newState.notifiedChangesUpTo = notifiedChangesUpTo.atOffset(ZoneOffset.UTC).toString() + whelk.getStorage().putState(STATE_KEY, newState) + } + } + + } +} diff --git a/housekeeping/src/main/groovy/whelk/housekeeping/NotificationSender.groovy b/housekeeping/src/main/groovy/whelk/housekeeping/NotificationSender.groovy index 3819ee9331..d6c7d151a1 100644 --- a/housekeeping/src/main/groovy/whelk/housekeeping/NotificationSender.groovy +++ b/housekeeping/src/main/groovy/whelk/housekeeping/NotificationSender.groovy @@ -31,25 +31,9 @@ class NotificationSender extends HouseKeeper { private final String STATE_KEY = "CXZ notification email sender" private String status = "OK" private final Whelk whelk - private final Mailer mailer - private final String senderAddress public NotificationSender(Whelk whelk) { this.whelk = whelk - Properties props = PropertyLoader.loadProperties("secret") - if (props.containsKey("smtpServer") && - props.containsKey("smtpPort") && - props.containsKey("smtpSender") && - props.containsKey("smtpUser") && - props.containsKey("smtpPassword")) - mailer = MailerBuilder - .withSMTPServer( - (String) props.get("smtpServer"), - Integer.parseInt((String)props.get("smtpPort")), - (String) props.get("smtpUser"), - (String) props.get("smtpPassword") - ).buildMailer() - senderAddress = props.get("smtpSender") } @Override @@ -68,26 +52,7 @@ class NotificationSender extends HouseKeeper { @Override void trigger() { - // Build a multi-map of library -> list of settings objects for that library's users - Map> heldByToUserSettings = new HashMap<>(); - { - List allUserSettingStrings = whelk.getStorage().getAllUserData() - for (Map settings : allUserSettingStrings) { - if (!settings["notificationEmail"]) - continue - settings?.requestedNotifications?.each { request -> - if (!request instanceof Map) - return - if (!request["heldBy"]) - return - - String heldBy = request["heldBy"] - if (!heldByToUserSettings.containsKey(heldBy)) - heldByToUserSettings.put(heldBy, []) - heldByToUserSettings[heldBy].add(settings) - } - } - } + Map> heldByToUserSettings = NotificationUtils.getAllSubscribingUsers(whelk) // Determine the time interval of ChangeObservations to consider Timestamp from = Timestamp.from(Instant.now().minus(1, ChronoUnit.DAYS)) // Default to last 24h if first time. @@ -177,9 +142,7 @@ class NotificationSender extends HouseKeeper { if (!matchedObservations.isEmpty() && user.notificationEmail && user.notificationEmail instanceof String) { String body = generateEmailBody(instanceId, matchedObservations) - sendEmail(senderAddress, (String) user.notificationEmail, "CXZ", body) - - System.err.println("Now send email to " + user.notificationEmail + "\n\t" + body) + NotificationUtils.sendEmail((String) user.notificationEmail, "CXZ", body) } } } @@ -199,22 +162,6 @@ class NotificationSender extends HouseKeeper { return null } - private void sendEmail(String sender, String recipient, String subject, String body) { - if (mailer) { - Email email = EmailBuilder.startingBlank() - .to(recipient) - .withSubject(subject) - .from(sender) - .withPlainText(body) - .buildEmail() - - log.info("Sending notification (cxz) email to " + recipient) - mailer.sendMail(email) - } else { - log.info("Should now have sent notification (cxz) email to " + recipient + " but SMTP is not configured.") - } - } - private String generateEmailBody(String changedInstanceId, List triggeredObservations) { Document current = whelk.getStorage().load(changedInstanceId) diff --git a/housekeeping/src/main/groovy/whelk/housekeeping/NotificationUtils.groovy b/housekeeping/src/main/groovy/whelk/housekeeping/NotificationUtils.groovy new file mode 100644 index 0000000000..45571c687e --- /dev/null +++ b/housekeeping/src/main/groovy/whelk/housekeeping/NotificationUtils.groovy @@ -0,0 +1,77 @@ +package whelk.housekeeping + +import groovy.transform.CompileStatic +import groovy.util.logging.Log4j2 +import org.simplejavamail.api.email.Email +import org.simplejavamail.api.mailer.Mailer +import org.simplejavamail.email.EmailBuilder +import org.simplejavamail.mailer.MailerBuilder +import whelk.Whelk +import whelk.util.PropertyLoader + +@CompileStatic +@Log4j2 +class NotificationUtils { + + /** + * Get all user settings and arrange them by requested library-uri, so that you + * might for example start with a uri https://libris.kb.se/library/Utb1 and from it + * get a list of user(-settings)s that subscribes to updates for things held by + * that library + */ + static Map> getAllSubscribingUsers(Whelk whelk) { + Map> libraryToUserSettings = new HashMap<>() + List allUserSettingStrings = whelk.getStorage().getAllUserData() + for (Map settings : allUserSettingStrings) { + if (!settings["notificationEmail"]) + continue + settings?.requestedNotifications?.each { request -> + if (!request instanceof Map) + return + if (!request["heldBy"]) + return + + String heldBy = request["heldBy"] + if (!libraryToUserSettings.containsKey(heldBy)) + libraryToUserSettings.put(heldBy, []) + libraryToUserSettings[heldBy].add(settings) + } + } + return libraryToUserSettings + } + + static Mailer mailer = null + static String senderAddress + static synchronized void sendEmail(String recipient, String subject, String body) { + if (mailer == null) { + Properties props = PropertyLoader.loadProperties("secret") + if (props.containsKey("smtpServer") && + props.containsKey("smtpPort") && + props.containsKey("smtpSender") && + props.containsKey("smtpUser") && + props.containsKey("smtpPassword")) + mailer = MailerBuilder + .withSMTPServer( + (String) props.get("smtpServer"), + Integer.parseInt((String)props.get("smtpPort")), + (String) props.get("smtpUser"), + (String) props.get("smtpPassword") + ).buildMailer() + senderAddress = props.get("smtpSender") + } + + if (mailer) { + Email email = EmailBuilder.startingBlank() + .to(recipient) + .withSubject(subject) + .from(senderAddress) + .withPlainText(body) + .buildEmail() + + log.info("Sending notification (cxz) email to " + recipient) + mailer.sendMail(email) + } else { + log.info("Should now have sent notification (cxz) email to " + recipient + " but SMTP is not configured.") + } + } +} diff --git a/housekeeping/src/main/groovy/whelk/housekeeping/WebInterface.groovy b/housekeeping/src/main/groovy/whelk/housekeeping/WebInterface.groovy index 1dacf1c7db..9914a72724 100755 --- a/housekeeping/src/main/groovy/whelk/housekeeping/WebInterface.groovy +++ b/housekeeping/src/main/groovy/whelk/housekeeping/WebInterface.groovy @@ -43,6 +43,7 @@ public class WebInterface extends HttpServlet { List houseKeepers = [ new NotificationGenerator(whelk), new NotificationSender(whelk), + new InquirySender(whelk), new NotificationCleaner(whelk), ]