/********************************************************************************* * The contents of this file are subject to the Common Public Attribution * License Version 1.0 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://www.openemm.org/cpal1.html. The License is based on the Mozilla * Public License Version 1.1 but Sections 14 and 15 have been added to cover * use of software over a computer network and provide for limited attribution * for the Original Developer. In addition, Exhibit A has been modified to be * consistent with Exhibit B. * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for * the specific language governing rights and limitations under the License. * * The Original Code is OpenEMM. * The Original Developer is the Initial Developer. * The Initial Developer of the Original Code is AGNITAS AG. All portions of * the code written by AGNITAS AG are Copyright (c) 2007 AGNITAS AG. All Rights * Reserved. * * Contributor(s): AGNITAS AG. ********************************************************************************/ package org.agnitas.backend; import java.util.Calendar; import java.util.GregorianCalendar; import java.util.Hashtable; import java.util.List; import java.util.Map; import java.util.Vector; import org.agnitas.util.Log; /** Handle date controled mailings */ public class RulerImpl implements Ruler { /** Simple struct to keep track of status_id/mailing_id * mapping for delayed generation */ private class Entry { long statusID; long mailingID; boolean active; /** Constructor * @param nStatusID the status ID * @param nMailingID the mailing ID */ public Entry (long nStatusID, long nMailingID) { statusID = nStatusID; mailingID = nMailingID; active = false; } /** Getter for status id * @return the status ID */ public long getStatusID () { return statusID; } /** Getter for status id as string * @return the status ID in string form */ public String getStatusIDasString () { return Long.toString (statusID); } /** Getter for mailing id * @return the mailing ID */ public long getMailingID () { return mailingID; } /** Setter for active * @param nActive new active flag */ public void setActive (boolean nActive) { active = nActive; } /** Getter for active * @return current active state */ public boolean getActive () { return active; } } /** Reference to configuration */ private Data data; /** The hour for which to send mailings */ private int hour; /** Allocates new data structure * @return new instance */ public Object mkData () throws Exception { return new Data ("ruler"); } public Object mkMailgunImpl () { return new MailgunImpl (); } /** Constructor */ public RulerImpl () { super (); data = null; hour = -1; } /** Cleanup */ public void done () { if (data != null) { try { data.done (); } catch (Exception e) { data.logging (Log.ERROR, "rule", "Failed to cleanup: " + e.toString ()); } finally { data = null; } } } /** Check for database connection and try to reopen it */ private void setup () throws Exception { if (data == null) { data = (Data) mkData (); } } /** Setter for hour * @param nhour the new hour to send mailings for */ public void setHour (int nhour) { hour = nhour; } /** Wrapper for kickOff to be used in Quartz scheduler */ public void kickOffSimple () { try { kickOff (); } catch (Exception e) { if (data != null) { data.logging (Log.ERROR, "rule", "Failed in kickOffSimple: " + e.getMessage ()); } } } /** Returns the query to get current date * @return query as string */ public String getQueryNow () { return "SELECT date_format(" + data.dbase.sysdate + ", '%Y-%m-%d') FROM dual"; } public String getQueryLastsent () { return "SELECT mailing_id, date_format(lastsent, '%Y-%m-%d') lsent FROM rulebased_sent_tbl"; } public String getFormatHour () { return "date_format(senddate, '%H')"; } public String getQueryLastsent (Long mid) { return "SELECT date_format(lastsent, '%Y-%m-%d') lsent FROM rulebased_sent_tbl WHERE mailing_id = " + mid; } /** Loop over all entries for today and start the * mailings, which are ready to run */ public synchronized String kickOff () throws Exception { String msg; msg = "Ruler:"; try { String now; Hashtable <Long, String> sent; Vector <Long> ids, mids; int queryHour; String query; Map <String, Object> row; List <Map <String, Object>> rq; setup (); query = getQueryNow (); now = data.dbase.queryString (query); if (now == null) throw new Exception ("Unable to get current date from database"); sent = new Hashtable <Long, String> (); query = getQueryLastsent (); rq = data.dbase.query (query); for (int n = 0; n < rq.size (); ++n) { row = rq.get (n); Long mailing_id = data.dbase.asLong (row.get ("mailing_id")); String lastsent = data.dbase.asString (row.get ("lsent")); sent.put (mailing_id, lastsent); } ids = new Vector <Long> (); mids = new Vector <Long> (); query = "SELECT status_id, mailing_id FROM maildrop_status_tbl WHERE status_field = 'R' AND genstatus = 1 AND "; if ((hour >= 0) && (hour <= 24)) { queryHour = hour; } else { queryHour = (new GregorianCalendar ()).get (Calendar.HOUR_OF_DAY); } query += getFormatHour () + " = '" + StringOps.format_number (queryHour, 2) + "'"; rq = data.dbase.query (query); for (int n = 0; n < rq.size (); ++n) { row = rq.get (n); Long id = data.dbase.asLong (row.get ("status_id")); Long mid = data.dbase.asLong (row.get ("mailing_id")); if ((! sent.containsKey (mid)) || (! sent.get (mid).equals (now))) { ids.addElement (id); mids.addElement (mid); } else data.logging (Log.WARNING, "rule", "Mailing ID " + mid + " already sent today"); } data.logging (Log.INFO, "rule", "Read " + ids.size () + " maildrop entr" + Log.exty (ids.size ())); for (int n = 0; n < ids.size (); ++n) { Long id = ids.elementAt (n); Long mid = mids.elementAt (n); boolean valid = false; long del; query = "SELECT deleted FROM mailing_tbl WHERE mailing_id = :mailingID"; rq = data.dbase.query (query, "mailingID", mid); if (rq.size () > 0) { row = rq.get (0); del = data.dbase.asLong (row.get ("deleted")); if (del == 0) valid = true; else data.logging (Log.WARNING, "rule", "Skipping deleted mailing " + mid); } else { data.logging (Log.WARNING, "rule", "Entry without mailing found " + mid); } if (valid) { query = getQueryLastsent (mid); rq = data.dbase.query (query); if (rq.size () > 0) { row = rq.get (0); String lastsent = data.dbase.asString (row.get ("lsent")); if (lastsent.equals (now)) { data.logging (Log.WARNING, "rule", "Rule based mailing " + mid + " already sent!"); valid = false; } } } if (valid) { valid = false; if (sent.containsKey (mid)) query = "UPDATE rulebased_sent_tbl SET lastsent = " + data.dbase.sysdate + " WHERE mailing_id = :mailingID"; else query = "INSERT INTO rulebased_sent_tbl (mailing_id, lastsent) VALUES (:mailingID, " + data.dbase.sysdate + ")"; try { data.dbase.update (query, "mailingID", mid); valid = true; } catch (Exception e) { data.logging (Log.ERROR, "rule", "Unable to update lastsent using: " + query + " (" + e.toString () + ")"); } if (valid) { if (n > 0) Thread.sleep (2 * 1000); data.logging (Log.DEBUG, "rule", "Execute maildrop_status_id " + id); try { MailgunImpl mg = (MailgunImpl) mkMailgunImpl (); String mmsg; mg.initializeMailgun (id.toString ()); mmsg = mg.fire (null); msg += "\n" + id + ": " + (mmsg == null ? "*unset*" : mmsg); data.logging (Log.DEBUG, "rule", "Mailgun returns " + (mmsg == null ? "nothing" : mmsg)); } catch (Exception e) { msg += "\n" + id + ": [Exception] " + e.toString (); data.logging (Log.DEBUG, "rule", "Mailgun fails with " + e.toString ()); } } } } msg += "done."; } catch (Exception e) { msg += e.toString (); throw e; } finally { done (); } return msg; } /** Start delayed mail generation */ public synchronized void kickOffDelayed () { String query = null; try { Vector <Entry> mids; List <Map <String, Object>> rq; Map <String, Object> row; setup (); query = "SELECT status_id, mailing_id FROM maildrop_status_tbl " + "WHERE genstatus = 0 AND status_field = 'W' AND gendate < now() ORDER BY gendate"; mids = new Vector <Entry> (); rq = data.dbase.query (query); for (int n = 0; n < rq.size (); ++n) { row = rq.get (n); Long statusID = data.dbase.asLong (row.get ("status_id")); Long mailingID = data.dbase.asLong (row.get ("mailing_id")); mids.add (new Entry (statusID, mailingID)); } int entries = mids.size (); if (entries > 0) { data.logging (Log.INFO, "rule", "Found " + entries + " delayed mailing" + Log.exts (entries)); for (int n = 0; n < entries; ++n) { Entry e = mids.elementAt (n); query = "SELECT deleted FROM mailing_tbl WHERE mailing_id = :mailingID"; rq = data.dbase.query (query, "mailingID", e.getMailingID ()); if (rq.size () > 0) { row = rq.get (0); int deleted = data.dbase.asInt (row.get ("deleted")); if (deleted == 0) { e.setActive (true); } else { data.logging (Log.WARNING, "rule", "Deleted mailing " + e.getMailingID () + " found"); } } else { data.logging (Log.WARNING, "rule", "Mailing " + e.getMailingID () + " has no entry in mailing_tbl"); } if (e.getActive ()) { try { MailgunImpl mg = (MailgunImpl) mkMailgunImpl (); String msg; query = "UPDATE maildrop_status_tbl SET genstatus = 1, genchange = now() WHERE status_id = :statusID AND genstatus = 0"; data.dbase.update (query, "statusID", e.getStatusID ()); mg.initializeMailgun (e.getStatusIDasString ()); msg = mg.fire (null); data.logging (Log.DEBUG, "rule", "Mailgun returns " + (msg == null ? "*nothing*" : msg)); } catch (Exception ec) { data.logging (Log.DEBUG, "rule", "Mailgun fails with " + ec.toString ()); } } else { query = "UPDATE maildrop_status_tbl SET genstatus = 4, genchange = now() WHERE status_id = :statusID AND genstatus < 3"; data.dbase.update (query, "statusID", e.getStatusID ()); } } } } catch (Exception e) { data.logging (Log.ERROR, "rule", "Failed in delayedKickOff on query \"" + query + "\": " + e.toString ()); } finally { done (); } } }