org.agnitas.backend.Data.java Source code

Java tutorial

Introduction

Here is the source code for org.agnitas.backend.Data.java

Source

/*********************************************************************************
 * 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.io.File;
import java.io.FileInputStream;
import java.sql.Date;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Types;
import java.text.SimpleDateFormat;
import java.util.Map;
import java.util.List;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Locale;
import java.util.TimeZone;
import java.util.Vector;
import java.util.ResourceBundle;

import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;

import org.agnitas.util.Const;
import org.agnitas.beans.BindingEntry;
import org.agnitas.target.TargetRepresentation;
import org.agnitas.preview.Page;
import org.agnitas.util.Config;
import org.agnitas.util.Log;
import org.agnitas.util.Title;

/** Class holding most of central configuration and global database
 * information
 */
public class Data {
    final static long serialVersionUID = 0x055da1a;
    /** the file to read the configuration from */
    final static String INI_FILE = "Mailgun.ini";
    /** default value for domain entry */
    final static String DEF_DOMAIN = "openemm.org";
    /** default value for boundary entry */
    final static String DEF_BOUNDARY = "AGNITAS";
    /** default value for EOL coding */
    final static String DEF_EOL = "\r\n";
    /** default value for X-Mailer: header */
    final static String DEF_MAILER = "OpenEMM 2013/Agnitas AG";

    /** Constant for onepixellog: no automatic insertion */
    final public static int OPL_NONE = 0;
    /** Constant for onepixellog: insertion on top */
    final public static int OPL_TOP = 1;
    /** Constant for onepixellog: insertion at bottom */
    final public static int OPL_BOTTOM = 2;

    /** Configuration from properties file */
    protected Config cfg = null;
    /** Loglevel */
    private int logLevel = Log.ERROR;
    /** directory to write admin-/testmails to */
    public String mailDir = null;
    /** default encoding for all blocks */
    public String defaultEncoding = "quoted-printable";
    /** default character set for all blocks */
    public String defaultCharset = "UTF-8";
    /** database driver name */
    protected String dbDriver = null;
    /** database login */
    protected String dbLogin = null;
    /** database password */
    protected String dbPassword = null;
    /** database connect expression */
    protected String dbConnect = null;
    /** database pool size */
    protected int dbPoolsize = 12;
    /** database growable pool */
    protected boolean dbPoolgrow = true;
    /** used block size */
    private int blockSize = 1000;
    /** directory to store meta files for further processing */
    private String metaDir = null;
    /** name of program to execute meta files */
    private String xmlBack = "xmlback";
    /** validate each generated block */
    private boolean xmlValidate = false;
    /** Send samples of worldmailing to dedicated address(es) */
    private String sampleEmails = null;
    /** write a DB record after creating that number of receiver */
    private int mailLogNumber = 0;
    /** path to accounting logfile */
    private String accLogfile = "log/account.log";
    /** path to bounce logfile */
    private String bncLogfile = "log/extbounce.log";

    /** the user_status for this query */
    public long defaultUserStatus = BindingEntry.USER_STATUS_ACTIVE;
    /** in case of campaing mailing, send mail only to this customer */
    public long campaignCustomerID = 0;
    /** in case of a transaction, use this transaction ID */
    public long campaignTransactionID = 0;
    /** for campaign mailings, use this user status in the binding table */
    public long campaignUserStatus = BindingEntry.USER_STATUS_ACTIVE;
    /** for sending to one fix address */
    public String fixedEmail = null;
    /** for preview mailings use this for matching the customer ID */
    public long previewCustomerID = 0;
    /** for preview mailings store output */
    public Page previewOutput = null;
    /** for preview of external text input */
    public String previewInput = null;
    /** for preview mailings if preview should be anon */
    public boolean previewAnon = false;
    /** for preview mailings if only partial preview requested */
    public String previewSelector = null;
    /** for preview mailings if preview is cachable */
    public boolean previewCachable = true;
    /** for preview mailings to convert to entities */
    public boolean previewConvertEntities = false;
    /** for preview mailings to use legacy UIDs */
    public boolean previewLegacyUIDs = false;
    /** for preview mailings to create all possible parts */
    public boolean previewCreateAll = false;
    /** alternative campaign mailing selection */
    public TargetRepresentation campaignSubselect = null;
    /** custom generated tags */
    public Vector<String> customTags = null;
    /** custom generated tags with values */
    public Hashtable<String, String> customMap = null;
    /** overwtite existing database fields */
    public Hashtable<String, String> overwriteMap = null;
    /** overwtite existing database fields for more receivers */
    public Hashtable<Long, Hashtable<String, String>> overwriteMapMulti = null;
    /** virtual database fields */
    public Hashtable<String, String> virtualMap = null;
    /** virtual database fields for more receivers */
    public Hashtable<Long, Hashtable<String, String>> virtualMapMulti = null;
    /** optional infos for that company */
    public Hashtable<String, String> companyInfo = null;
    /** optional infos for this mailing */
    public Hashtable<String, String> mailingInfo = null;
    /** keeps track of already read EMMTags from database */
    private Hashtable<String, String[]> tagCache = null;
    /** instance to write logs to */
    private Log log = null;
    /** the ID to write as marker in the logfile */
    private String lid = null;
    /** the connection to the database */
    public DBase dbase = null;
    /** status_id from maildrop_status_tbl */
    public long maildrop_status_id = -1;
    /** assigned company to this mailing */
    public long company_id = -1;
    /** mailinglist assigned to this mailing */
    public long mailinglist_id = -1;
    /** for subselect, the target expression of the mailing */
    public String targetExpression = null;
    /** mailign_id of this mailing */
    public long mailing_id = -1;
    /** status_field from maildrop_status_tbl */
    public String status_field = null;
    /** when to send the mailing, date */
    public Date senddate = null;
    /** when to send the mailing, time */
    public Time sendtime = null;
    /** when to send the mailing, date+time */
    public Timestamp sendtimestamp = null;
    /** current send date, calculated from sendtimstamp and stepping */
    public java.util.Date currentSendDate = null;
    /** the currentSendDate in epoch */
    public long sendSeconds = 0;
    /** steps in seconds between two entities */
    public int step = 0;
    /** number of blocks per entity */
    public int blocksPerStep = 1;
    /** start steping at this block */
    public int startBlockForStep = 1;
    /** the subselection for the receiver of this mailing */
    public String subselect = null;
    /** the name of this mailing */
    public String mailing_name = null;
    /** the subject for this mailing */
    public String subject = null;
    /** the sender address for this mailing */
    public EMail fromEmail = null;
    /** the optional reply-to address for this mailing */
    public EMail replyTo = null;
    /** the envelope address */
    public EMail envelopeFrom = null;
    /** the encoding for this mailing */
    public String encoding = null;
    /** the charachter set for this mailing */
    public String charset = null;
    /** domain used to build message-ids */
    public String domain = DEF_DOMAIN;
    /** boundary part to build multipart messages */
    public String boundary = DEF_BOUNDARY;
    /** EOL coding for spoolfiles */
    public String eol = DEF_EOL;
    /** content of the X-Mailer: header */
    public String mailer = DEF_MAILER;
    /** the base for the profile URL */
    public String profileURL = null;
    public String profileTag = "/p.html?";
    /** the base for the unsubscribe URL */
    public String unsubscribeURL = null;
    public String unsubscribeTag = "/uq.html?";
    /** the base for the auto URL */
    public String autoURL = null;
    public String autoTag = "/r.html?";
    /** the base for the onepixellog URL */
    public String onePixelURL = null;
    public String onePixelTag = "/g.html?";
    /** the largest mailtype to generate */
    public int masterMailtype = Const.Mailtype.MASK;
    /** default line length in text part */
    public int lineLength = 72;
    /** where to automatically place the onepixellog */
    public int onepixlog = OPL_NONE;
    /** Password for signatures */
    public String password = null;
    /** the base domain to build the base URLs */
    public String rdirDomain = null;
    /** the mailloop domain */
    public String mailloopDomain = null;
    /** Collection of media information */
    public Media media = null;
    /** Bitfield of available media types in mailing */
    public long availableMedias = 0;
    /** number of all subscriber of a mailing */
    public long totalSubscribers = -1;
    public long totalReceivers = -1;
    /** number of all subscriber of a mailing */
    private BC bigClause = null;
    /** keep track of collected targets */
    private Hashtable<Long, Target> targets = null;
    /** all URLs from rdir_url_tbl */
    public Vector<URL> URLlist = null;
    /** number of entries in URLlist */
    public int urlcount = 0;
    /** all title tags */
    private Hashtable<Long, Title> titles = null;
    /** layout of the customer table */
    public Vector<Column> layout = null;
    /** number of entries in layout */
    public int lcount = 0;
    /** number of entries in layout used */
    public int lusecount = 0;
    /** name of the company (for logfile display only) */
    public String company_name = null;
    /** name of mailtracking table */
    public String mailtracking_table = null;
    /** for housekeeping of created files */
    private Vector<String> toRemove = null;

    /** check if database is available
     */
    private void checkDatabase() throws Exception {
        if (dbase == null)
            throw new Exception("Database not available");
    }

    public Object mkDBase(Object me) throws Exception {
        return new DBase((Data) me);
    }

    public Object mkBigClause() {
        return new BC();
    }

    /**
     * setup database connection and retreive a list of all available
     * tables
     * @param conn an optional existing database connection
     */
    private void setupDatabase() throws Exception {
        try {
            dbase = (DBase) mkDBase(this);
            dbase.setup();
        } catch (Exception e) {
            throw new Exception("Database setup failed: " + e);
        }
    }

    /** close a database and free all assigned data
     */
    private void closeDatabase() throws Exception {
        if (dbase != null) {
            try {
                dbase.done();
                dbase = null;
            } catch (Exception e) {
                throw new Exception("Database close failed: " + e);
            }
        }
    }

    /**
     * find an entry from the media record for this mailing
     * @param m instance of media record
     * @param id the ID to look for
     * @param dflt a default value if no entry is found
     * @return the found entry or the default
     */
    public String findMediadata(Media m, String id, String dflt) {
        String rc;

        Vector<String> v = m.findParameterValues(id);
        rc = null;
        if ((v != null) && (v.size() > 0))
            rc = v.elementAt(0);
        return rc == null ? dflt : rc;
    }

    /**
     * find a numeric entry from the media record for this mailing
     * @param m instance of media record
     * @param id the ID to look for
     * @param dflt a default value if no entry is found
     * @return the found entry or the default
     */
    public long ifindMediadata(Media m, String id, long dflt) {
        String tmp = findMediadata(m, id, null);
        long rc;

        if (tmp != null)
            try {
                rc = Integer.parseInt(tmp);
            } catch (Exception e) {
                rc = dflt;
            }
        else
            rc = dflt;
        return rc;
    }

    public int ifindMediadata(Media m, String id, int dflt) {
        return (int) ifindMediadata(m, id, (long) dflt);
    }

    /**
     * find a boolean entry from the media record for this mailing
     * @param m instance of media record
     * @param id the ID to look for
     * @param dflt a default value if no entry is found
     * @return the found entry or the default
     */
    public boolean bfindMediadata(Media m, String id, boolean dflt) {
        String tmp = findMediadata(m, id, null);
        boolean rc = dflt;

        if (tmp != null)
            if (tmp.length() == 0)
                rc = true;
            else {
                String tok = tmp.substring(0, 1).toLowerCase();

                if (tok.equals("t") || tok.equals("y") || tok.equals("+") || tok.equals("1"))
                    rc = true;
            }
        else
            rc = false;
        return rc;
    }

    /**
     * Retreive basic mailing data
     */
    public void retreiveMailingInformation() throws Exception {
        Map<String, Object> rc;

        rc = dbase.querys("SELECT mailinglist_id, shortname, target_expression "
                + "FROM mailing_tbl WHERE mailing_id = :mailingID", "mailingID", mailing_id);
        mailinglist_id = dbase.asInt(rc.get("mailinglist_id"));
        mailing_name = dbase.asString(rc.get("shortname"));
        targetExpression = dbase.asString(rc.get("target_expression"));
        if (isPreviewMailing()) {
            targetExpression = null;
        }
    }

    public Object mkMedia(int mediatype, int priority, int status, String parameter) {
        return new Media(mediatype, priority, status, parameter);
    }

    /**
     * Retreive the media data
     */
    public void retreiveMediaInformation() throws Exception {
        List<Map<String, Object>> rc;

        rc = dbase.query("SELECT mediatype, param FROM mailing_mt_tbl " + "WHERE mailing_id = :mailingID",
                "mailingID", mailing_id);
        if (rc.size() > 0) {
            Map<String, Object> row = rc.get(0);

            media = (Media) mkMedia(dbase.asInt(row.get("mediatype")), 0, Media.STAT_ACTIVE,
                    dbase.asString(row.get("param")));
            availableMedias = (1 << Media.TYPE_EMAIL);
            if (media.findParameterValues("charset") == null)
                media.setParameter("charset", defaultCharset);
            if (media.findParameterValues("encoding") == null)
                media.setParameter("encoding", defaultEncoding);
            fromEmail = new EMail(findMediadata(media, "from", null));
            replyTo = new EMail(findMediadata(media, "reply", null));
            subject = findMediadata(media, "subject", subject);
            charset = findMediadata(media, "charset", charset);
            masterMailtype = ifindMediadata(media, "mailformat", masterMailtype) & Const.Mailtype.MASK;
            if (masterMailtype == 2) {
                masterMailtype = Const.Mailtype.HTML | Const.Mailtype.HTML_OFFLINE;
            }
            encoding = findMediadata(media, "encoding", encoding);
            lineLength = ifindMediadata(media, "linefeed", lineLength);

            String opl = findMediadata(media, "onepixlog", "none");
            if (opl.equals("top"))
                onepixlog = OPL_TOP;
            else if (opl.equals("bottom"))
                onepixlog = OPL_BOTTOM;
            else
                onepixlog = OPL_NONE;
        }
        envelopeFrom = fromEmail;
    }

    /**
     * query company specific details
     */
    public void retreiveCompanyInfo() throws Exception {
        Map<String, Object> rc;

        mailtracking_table = "mailtrack_tbl";
        rc = dbase.querys(
                "SELECT c.shortname, c.xor_key, if(not ml.rdir_domain is null and ml.rdir_domain != '', ml.rdir_domain, c.rdir_domain) as rdir_domain, c.mailloop_domain, if(not ml.messageid_domain is null and ml.messageid_domain != '', ml.messageid_domain, c.mailloop_domain) as messageid_domain FROM company_tbl c inner join mailinglist_tbl ml on c.company_id = ml.company_id  WHERE ml.mailinglist_id = :mailinglistID and ml.company_id = :companyID",
                "companyID", company_id, "mailinglistID", mailinglist_id);
        company_name = dbase.asString(rc.get("shortname"));
        password = dbase.asString(rc.get("xor_key"));
        rdirDomain = dbase.asString(rc.get("rdir_domain"));
        mailloopDomain = dbase.asString(rc.get("mailloop_domain"));
        String tmpDomain = dbase.asString(rc.get("messageid_domain"));
        if (tmpDomain != null && tmpDomain.length() > 0)
            domain = tmpDomain;
    }

    public Object mkTarget(long tid, String sql) {
        return new Target(tid, sql);
    }

    public Target getTarget(long tid) throws Exception {
        if (targets == null) {
            targets = new Hashtable<Long, Target>();
        }

        Long targetID = new Long(tid);
        Target rc = targets.get(targetID);
        String reason = "invalid";

        if (rc == null) {
            String sql = null;
            int deleted = 0;

            try {
                Map<String, Object> row = dbase.querys(
                        "SELECT target_sql, deleted FROM dyn_target_tbl WHERE target_id = :targetID", "targetID",
                        tid);

                deleted = dbase.asInt(row.get("deleted"));
                if (deleted != 0) {
                    logging(Log.ERROR, "targets", "TargetID " + tid + " is marked as deleted");
                    sql = null;
                    reason = "deleted";
                } else {
                    sql = dbase.asString(row.get("target_sql"), 3);
                    if (sql == null) {
                        reason = "empty";
                    }
                }
            } catch (Exception e) {
                logging(Log.ERROR, "targets",
                        "No target with ID " + tid + " found in dyn_target_tbl: " + e.toString());
                sql = null;
                reason = "non existing";
            }
            rc = (Target) mkTarget(tid, sql);
            targets.put(targetID, rc);
        }
        if (!rc.valid()) {
            throw new Exception("TargetID " + tid + ": " + reason + " target found");
        }
        return rc;
    }

    public String moreStatusColumns() {
        return null;
    }

    public void moreStatusColumnsParse(Map<String, Object> row) throws Exception {
    }

    public void moreStatusColumnsPostParse() throws Exception {
    }

    public void setupMailingInformations(String prefix, String status) throws Exception {
        if (prefix.equals("preview")) {
            String[] opts = status.split(",");

            if (opts.length > 0) {
                try {
                    mailing_id = Long.parseLong(opts[0]);
                } catch (NumberFormatException e) {
                    logging(Log.WARNING, "setup",
                            "Unparseable input string for mailing_id: \"" + opts[0] + "\": " + e.toString());
                    mailing_id = 0;
                }
            } else {
                mailing_id = 0;
            }
            if (mailing_id > 0) {
                try {
                    company_id = dbase.queryLong("SELECT company_id FROM mailing_tbl WHERE mailing_id = :mailingID",
                            "mailingID", mailing_id);
                } catch (Exception e) {
                    throw new Exception(
                            "Failed to query company_id for mailing_id " + mailing_id + ": " + e.toString());
                }
            } else {
                mailing_id = 0;
                company_id = 1;
                mailinglist_id = 1;
                try {
                    if (opts.length > 1) {
                        company_id = Long.parseLong(opts[1]);
                        if (opts.length > 2) {
                            mailinglist_id = Long.parseLong(opts[2]);
                        }
                    }
                } catch (NumberFormatException e) {
                    logging(Log.WARNING, "setup",
                            "Unparseable input string for preview \"" + status + "\": " + e.toString());
                }
            }
            status_field = "P";
            sendtimestamp = null;
        } else
            throw new Exception("Unknown status prefix \"" + prefix + "\" encountered");
    }

    class Layout implements org.springframework.jdbc.core.ResultSetExtractor {
        private Vector<Column> layout;
        private String ref;

        public Layout(Vector<Column> nLayout, String nRef) {
            layout = nLayout;
            ref = nRef;
        }

        public Vector<Column> getLayout() {
            return layout;
        }

        public Object extractData(ResultSet rset) throws SQLException, org.springframework.dao.DataAccessException {
            ResultSetMetaData meta = rset.getMetaData();
            int ccnt = meta.getColumnCount();

            for (int n = 0; n < ccnt; ++n) {
                String cname = meta.getColumnName(n + 1);
                int ctype = meta.getColumnType(n + 1);

                if (ctype == -1) {
                    String tname = meta.getColumnTypeName(n + 1);

                    if (tname != null) {
                        tname = tname.toLowerCase();
                        if (tname.equals("varchar")) {
                            ctype = Types.VARCHAR;
                        }
                    }
                }
                if (Column.typeStr(ctype) != null) {
                    Column c = new Column(cname, ctype);

                    c.setRef(ref);
                    if (layout == null) {
                        layout = new Vector<Column>();
                    }
                    layout.addElement(c);
                }
            }
            return this;
        }
    }

    protected void getTableLayout(String table, String ref) throws Exception {
        String query = "SELECT * FROM " + table + " WHERE 1 = 0";
        Layout temp = new Layout(layout, ref);

        dbase.op().query(query, temp);

        layout = temp.getLayout();

        if (layout != null) {
            lcount = layout.size();
            lusecount = lcount;
        }
    }

    private void setGenerationStatus(int fromStatus, int toStatus) throws Exception {
        if (maildrop_status_id > 0) {
            String query;

            query = "UPDATE maildrop_status_tbl SET genchange = " + dbase.sysdate + ", genstatus = :tostatus "
                    + "WHERE status_id = :statusID";
            if (fromStatus > 0) {
                query += " AND genstatus = :fromStatus";
            }
            try {
                int cnt = dbase.update(query, "tostatus", toStatus, "fromStatus", fromStatus, "statusID",
                        maildrop_status_id);

                if (cnt != 1) {
                    throw new Exception("change should affect one row, but affects " + cnt + " rows");
                }
            } catch (Exception e) {
                String m = "Unable to update generation state " + (fromStatus > 0 ? "from " + fromStatus + " " : "")
                        + "to " + toStatus;

                throw new Exception(m + ": " + e.toString());
            }
        }
    }

    public String extraURLTableClause() {
        return "";
    }

    public String extraURLTableColumns() {
        return "";
    }

    public void extraURLTableGetColumns(URL url, Map<String, Object> row) {
    }

    public void getURLDetails() {
    }

    /**
     * query all basic information about this mailing
     * @param status_id the reference to the mailing
     */
    private void queryMailingInformations(String status_id) throws Exception {
        checkDatabase();
        try {
            String[] sdetail = status_id.split(":", 2);

            if (sdetail.length == 2) {
                setupMailingInformations(sdetail[0], sdetail[1]);
            } else {
                Map<String, Object> row;
                int bs, st;
                int genstat;
                String moreCols;
                String query;

                maildrop_status_id = Long.parseLong(status_id);

                moreCols = moreStatusColumns();
                query = "SELECT company_id, mailing_id, status_field, senddate, step, blocksize, genstatus";
                if (moreCols != null) {
                    query += ", " + moreCols;
                }
                query += " FROM maildrop_status_tbl WHERE status_id = :statusID";
                row = dbase.querys(query, "statusID", maildrop_status_id);
                company_id = dbase.asLong(row.get("company_id"));
                mailing_id = dbase.asLong(row.get("mailing_id"));
                status_field = dbase.asString(row.get("status_field"));
                sendtimestamp = (Timestamp) row.get("senddate");
                st = dbase.asInt(row.get("step"));
                bs = dbase.asInt(row.get("blocksize"));
                genstat = dbase.asInt(row.get("genstatus"));
                moreStatusColumnsParse(row);
                moreStatusColumnsPostParse();
                if (status_field.equals("C"))
                    status_field = "E";
                if (bs > 0)
                    setBlockSize(bs);
                setStepping(st);
                if (genstat != 1)
                    throw new Exception("Generation state is not 1, but " + genstat);
                if (isAdminMailing() || isTestMailing() || isWorldMailing() || isRuleMailing()
                        || isOnDemandMailing()) {
                    setGenerationStatus(1, 2);
                }
            }
            if (mailing_id > 0) {
                retreiveMailingInformation();

                if (targetExpression != null) {
                    StringBuffer buf = new StringBuffer();
                    int tlen = targetExpression.length();

                    for (int n = 0; n < tlen; ++n) {
                        char ch = targetExpression.charAt(n);

                        if ((ch == '(') || (ch == ')')) {
                            buf.append(ch);
                        } else if ((ch == '&') || (ch == '|')) {
                            if (ch == '&')
                                buf.append(" AND");
                            else
                                buf.append(" OR");
                            while (((n + 1) < tlen) && (targetExpression.charAt(n + 1) == ch))
                                ++n;
                        } else if (ch == '!') {
                            buf.append(" NOT");
                        } else if ("0123456789".indexOf(ch) != -1) {
                            int newn = n;
                            long tid = 0;
                            int pos;
                            Target temp;

                            while ((n < tlen) && ((pos = "0123456789".indexOf(ch)) != -1)) {
                                newn = n;
                                tid *= 10;
                                tid += pos;
                                ++n;
                                if (n < tlen)
                                    ch = targetExpression.charAt(n);
                                else
                                    ch = '\0';
                            }
                            n = newn;
                            temp = getTarget(tid);
                            if ((temp != null) && temp.valid())
                                buf.append(" (" + temp.sql + ")");
                        }
                    }
                    if (buf.length() >= 3)
                        subselect = buf.toString();
                }
                retreiveMediaInformation();
            }
            if (subselect == null) {
                if (isRuleMailing()) {
                    try {
                        dbase.update("DELETE FROM maildrop_status_tbl WHERE status_id = :statusID", "statusID",
                                maildrop_status_id);
                    } catch (SQLException e) {
                        logging(Log.ERROR, "init", "Failed to disable rule based mailing: " + e.toString());
                    }
                    throw new Exception("Missing target: Rule based mailing generation aborted and disabled");
                } else if (isOnDemandMailing()) {
                    try {
                        setGenerationStatus(0, 4);
                    } catch (Exception e) {
                        logging(Log.ERROR, "init", "Failed to set genreation status: " + e.toString());
                    }
                    throw new Exception(
                            "Missing target: On Demand mailing generation aborted and left in undefined condition");
                }
            }
            if ((encoding == null) || (encoding.length() == 0))
                encoding = defaultEncoding;
            if ((charset == null) || (charset.length() == 0))
                charset = defaultCharset;
            //
            // get all possible URLs that should be replaced
            List<Map<String, Object>> rc;

            URLlist = new Vector<URL>();
            if (mailing_id > 0) {
                rc = dbase.query("SELECT url_id, full_url, " + dbase.measureType + extraURLTableColumns()
                        + " FROM rdir_url_tbl " + "WHERE company_id = :companyID AND mailing_id = :mailingID"
                        + extraURLTableClause(), "companyID", company_id, "mailingID", mailing_id);
                for (int n = 0; n < rc.size(); ++n) {
                    Map<String, Object> row = rc.get(n);
                    long id = dbase.asLong(row.get("url_id"));
                    String dest = dbase.asString(row.get("full_url"));
                    long usage = dbase.asLong(row.get(dbase.measureRepr));

                    if (usage != 0) {
                        URL url = new URL(id, dest, usage);

                        extraURLTableGetColumns(url, row);
                        URLlist.addElement(url);
                    }
                }
            }
            urlcount = URLlist.size();
            getURLDetails();

            //
            // and now try to determinate the layout of the
            // customer table
            getTableLayout("customer_" + company_id + "_tbl", null);

            Hashtable<String, Column> cmap = new Hashtable<String, Column>();

            for (int n = 0; n < lcount; ++n) {
                Column c = layout.get(n);

                cmap.put(c.name.toLowerCase(), c);
            }

            rc = dbase.query("SELECT col_name, shortname FROM customer_field_tbl WHERE company_id = :companyID",
                    "companyID", company_id);
            for (int n = 0; n < rc.size(); ++n) {
                Map<String, Object> row = rc.get(n);
                String column = dbase.asString(row.get("col_name"));

                if (column != null) {
                    Column c = cmap.get(column);

                    if (c != null) {
                        c.setAlias(dbase.asString(row.get("shortname")));
                    }
                }
            }

            retreiveCompanyInfo();
            if (rdirDomain != null) {
                if (profileURL == null)
                    profileURL = rdirDomain + profileTag;
                if (unsubscribeURL == null)
                    unsubscribeURL = rdirDomain + unsubscribeTag;
                if (autoURL == null)
                    autoURL = rdirDomain + autoTag;
                if (onePixelURL == null)
                    onePixelURL = rdirDomain + onePixelTag;
            }
        } catch (Exception e) {
            logging(Log.ERROR, "init", "Error in quering initial data: " + e);
            throw new Exception("Database error/initial query: " + e, e);
        }
    }

    public Locale getLocale(String language, String country) {
        if (language == null) {
            return null;
        }
        if (country == null) {
            return new Locale(language);
        }
        return new Locale(language, country);
    }

    public Title getTitle(Long tid) {
        if (titles == null) {
            titles = new Hashtable<Long, Title>();

            try {
                List<Map<String, Object>> rc;

                rc = dbase.query("SELECT title_id, title, gender FROM title_gender_tbl "
                        + "WHERE title_id IN (SELECT title_id FROM title_tbl WHERE company_id = :companyID OR company_id = 0 OR company_id IS null)",
                        "companyID", company_id);
                for (int n = 0; n < rc.size(); ++n) {
                    Map<String, Object> row = rc.get(n);
                    Long id = dbase.asLong(row.get("title_id"));
                    String title = dbase.asString(row.get("title"));
                    int gender = dbase.asInt(row.get("gender"));
                    Title cur = null;

                    if ((cur = titles.get(id)) == null) {
                        cur = new Title(id);
                        titles.put(id, cur);
                    }
                    cur.setTitle(gender, title);
                }
            } catch (Exception e1) {
                logging(Log.ERROR, "title", "Failed to query titles: " + e1.toString());
            }
        }
        return titles.get(tid);
    }

    public String[] getTag(String tagName) {
        String[] rc = null;

        if (tagCache == null) {
            tagCache = new Hashtable<String, String[]>();
        } else {
            rc = tagCache.get(tagName);
        }
        if (rc == null) {
            String query;
            HashMap<String, Object> input;
            SimpleJdbcTemplate jdbc;

            rc = new String[2];
            query = "SELECT selectvalue, type FROM tag_tbl WHERE tagname = :tagname AND (company_id = :companyID OR company_id = 0) ORDER BY company_id DESC";
            input = new HashMap<String, Object>(2);
            input.put("tagname", tagName);
            input.put("companyID", company_id);
            jdbc = null;
            try {
                List<Map<String, Object>> rq;

                jdbc = dbase.request(query);
                rq = jdbc.queryForList(query, input);
                if (rq.size() > 0) {
                    Map<String, Object> row = rq.get(0);

                    rc[0] = dbase.asString(row.get("selectvalue"));
                    rc[1] = dbase.asString(row.get("type"));
                }
                tagCache.put(tagName, rc);
            } catch (Exception e) {
                logging(Log.WARNING, "gettag",
                        "Failed to retreive tag \"" + tagName + "\" using \"" + query + "\": " + e.toString());
            } finally {
                if (jdbc != null) {
                    dbase.release(jdbc);
                }
            }
        }
        return rc;
    }

    /**
     * Fill already sent recipient in seen hashset for
     * recovery prupose
     * @param seen the hashset to fill with seen customerIDs
     */
    public void prefillRecipients(HashSet<Long> seen) throws Exception {
        if (isWorldMailing() || isRuleMailing() || isOnDemandMailing()) {
            File recovery = new File(metaDir, "recover-" + maildrop_status_id + ".list");

            if (recovery.exists()) {
                logging(Log.INFO, "recover", "Found recovery file " + recovery.getAbsolutePath());
                markToRemove(recovery.getAbsolutePath());

                FileInputStream in = new FileInputStream(recovery);
                byte[] content = new byte[(int) recovery.length()];

                in.read(content);
                in.close();
                String[] data = (new String(content, "US-ASCII")).split("\n");

                for (int n = 0; n < data.length; ++n) {
                    if (data[n].length() > 0) {
                        seen.add(Long.decode(data[n]));
                    }
                }
            }
        }
    }

    /**
     * Set the blocksize for generation doing some sanity checks
     * @param newBlockSize the new block size to use
     */
    public void setBlockSize(int newBlockSize) {
        blocksPerStep = 1;
        blockSize = newBlockSize;
    }

    /**
     * Set the stepping in minutes
     * @param stepping value
     */
    public void setStepping(int newStep) {
        step = newStep;
    }

    /** increase starting block
     */
    public void increaseStartblockForStepping() {
        ++startBlockForStep;
    }

    /**
     * Validate all set variables and make a sanity check
     * on the database to avoid double triggering of a
     * mailing
     */
    public void checkMailingData() throws Exception {
        int cnt;
        String msg;

        cnt = 0;
        msg = "";
        if (isWorldMailing())
            try {
                List<Map<String, Object>> rq;
                Map<String, Object> row;
                long nid;

                checkDatabase();
                rq = dbase.query(
                        "SELECT status_id FROM maildrop_status_tbl WHERE status_field = :statusField AND mailing_id = :mailingID ORDER BY status_id",
                        "statusField", "W", "mailingID", mailing_id);
                if (rq.size() == 0) {
                    throw new Exception("no entry at all for mailingID " + mailing_id + " found");
                }
                row = rq.get(0);
                nid = dbase.asLong(row.get("status_id"));
                if (nid != maildrop_status_id) {
                    ++cnt;
                    msg += "\tlowest maildrop_status_id is not mine (" + maildrop_status_id + ") but " + nid + "\n";
                    dbase.update("DELETE FROM maildrop_status_tbl WHERE status_id = :statusID", "statusID",
                            maildrop_status_id);
                }
            } catch (Exception e) {
                ++cnt;
                msg += "\tunable to requery my status_id: " + e.toString() + "\n";
            }
        if ((!isPreviewMailing()) && (maildrop_status_id < 0)) {
            ++cnt;
            msg += "\tmaildrop_status_id is less than 1 (" + maildrop_status_id + ")\n";
        }
        if (company_id <= 0) {
            ++cnt;
            msg += "\tcompany_id is less than 1 (" + company_id + ")\n";
        }
        if (mailinglist_id <= 0) {
            ++cnt;
            msg += "\tmailinglist_id is less than 1 (" + mailinglist_id + ")\n";
        }
        if (mailing_id < 0) {
            ++cnt;
            msg += "\tmailing_id is less than 0 (" + mailing_id + ")\n";
        }
        if ((!isAdminMailing()) && (!isTestMailing()) && (!isCampaignMailing()) && (!isRuleMailing())
                && (!isOnDemandMailing()) && (!isWorldMailing()) && (!isPreviewMailing())) {
            ++cnt;
            msg += "\tstatus_field must be one of A, V, T, E, R, D, W or P (" + status_field + ")\n";
        }

        long now = System.currentTimeMillis() / 1000;
        if (sendtimestamp != null)
            sendSeconds = sendtimestamp.getTime() / 1000;
        else if ((senddate != null) && (sendtime != null))
            sendSeconds = (senddate.getTime() + sendtime.getTime()) / 1000;
        else
            sendSeconds = now;
        if (sendSeconds < now)
            currentSendDate = new java.util.Date(now * 1000);
        else
            currentSendDate = new java.util.Date(sendSeconds * 1000);
        if (step < 0) {
            ++cnt;
            msg += "\tstep is less than 0 (" + step + ")\n";
        }
        if ((encoding == null) || (encoding.length() == 0)) {
            ++cnt;
            msg += "\tmissing or empty encoding\n";
        }
        if ((charset == null) || (charset.length() == 0)) {
            ++cnt;
            msg += "\tmissing or empty charset\n";
        }
        if ((profileURL == null) || (profileURL.length() == 0)) {
            ++cnt;
            msg += "\tmissing or empty profile_url\n";
        }
        if ((unsubscribeURL == null) || (unsubscribeURL.length() == 0)) {
            ++cnt;
            msg += "\tmissing or empty unsubscribe_url\n";
        }
        if ((autoURL == null) || (autoURL.length() == 0)) {
            ++cnt;
            msg += "\tmissing or empty auto_url\n";
        }
        if ((onePixelURL == null) || (onePixelURL.length() == 0)) {
            //          ++cnt;
            onePixelURL = "file://localhost/";
            msg += "\tmissing or empty onepixel_url\n";
        }
        if (lineLength < 0) {
            ++cnt;
            msg += "\tlinelength is less than zero\n";
        }
        if (cnt > 0) {
            logging(Log.ERROR, "init", "Error configuration report:\n" + msg);
            throw new Exception(msg);
        }
        if (msg.length() > 0)
            logging(Log.INFO, "init", "Configuration report:\n" + msg);
    }

    /** Setup logging interface
     * @param program to create the logging path
     * @param setprinter if we should also log to stdout
     */
    private void setupLogging(String program, boolean setprinter) {
        log = new Log(program, logLevel);
        if (setprinter)
            log.setPrinter(System.out);
    }

    /**
     * Write all settings to logfile
     */
    public void logSettings() {
        logging(Log.DEBUG, "init", "Initial data valid");
        logging(Log.DEBUG, "init", "All set variables:");
        logging(Log.DEBUG, "init", "\tlogLevel = " + log.levelDescription() + " (" + log.level() + ")");
        logging(Log.DEBUG, "init", "\tmailDir = " + mailDir);
        logging(Log.DEBUG, "init", "\tdefaultEncoding = " + defaultEncoding);
        logging(Log.DEBUG, "init", "\tdefaultCharset = " + defaultCharset);
        logging(Log.DEBUG, "init", "\tdbDriver = " + dbDriver);
        logging(Log.DEBUG, "init", "\tdbLogin = " + dbLogin);
        logging(Log.DEBUG, "init", "\tdbPassword = ******");
        logging(Log.DEBUG, "init", "\tdbConnect = " + dbConnect);
        logging(Log.DEBUG, "init", "\tdbPoolsize = " + dbPoolsize);
        logging(Log.DEBUG, "init", "\tdbPoolgrow = " + dbPoolgrow);
        logging(Log.DEBUG, "init", "\tblockSize = " + blockSize);
        logging(Log.DEBUG, "init", "\tmetaDir = " + metaDir);
        logging(Log.DEBUG, "init", "\txmlBack = " + xmlBack);
        logging(Log.DEBUG, "init", "\txmlValidate = " + xmlValidate);
        logging(Log.DEBUG, "init", "\tsampleEmails = " + sampleEmails);
        logging(Log.DEBUG, "init", "\tmailLogNumber = " + mailLogNumber);
        logging(Log.DEBUG, "init", "\taccLogfile = " + accLogfile);
        logging(Log.DEBUG, "init", "\tbncLogfile = " + bncLogfile);
        logging(Log.DEBUG, "init", "\tdefaultUserStatus = " + defaultUserStatus);
        logging(Log.DEBUG, "init", "\tdbase = " + dbase);
        logging(Log.DEBUG, "init", "\tmaildrop_status_id = " + maildrop_status_id);
        logging(Log.DEBUG, "init", "\tcompany_id = " + company_id);
        if (company_name != null)
            logging(Log.DEBUG, "init", "\tcompany_name = " + company_name);
        if (mailtracking_table != null)
            logging(Log.DEBUG, "init", "\tmailtracking_table = " + mailtracking_table);
        logging(Log.DEBUG, "init", "\tmailinglist_id = " + mailinglist_id);
        logging(Log.DEBUG, "init", "\tmailing_id = " + mailing_id);
        logging(Log.DEBUG, "init", "\tstatus_field = " + status_field);
        logging(Log.DEBUG, "init", "\tsenddate = " + senddate);
        logging(Log.DEBUG, "init", "\tsendtime = " + sendtime);
        logging(Log.DEBUG, "init", "\tsendtimestamp = " + sendtimestamp);
        logging(Log.DEBUG, "init", "\tsendSeconds = " + sendSeconds);
        logging(Log.DEBUG, "init", "\tstep = " + step);
        logging(Log.DEBUG, "init", "\tblocksPerStep = " + blocksPerStep);
        logging(Log.DEBUG, "init", "\tsubselect = " + (subselect == null ? "*not set*" : subselect));
        logging(Log.DEBUG, "init", "\tmailing_name = " + (mailing_name == null ? "*not set*" : mailing_name));
        logging(Log.DEBUG, "init", "\tsubject = " + (subject == null ? "*not set*" : subject));
        logging(Log.DEBUG, "init", "\tfromEmail = " + (fromEmail == null ? "*not set*" : fromEmail.toString()));
        logging(Log.DEBUG, "init", "\treplyTo = " + (replyTo == null ? "*not set*" : replyTo.toString()));
        logging(Log.DEBUG, "init",
                "\tenvelopeFrom = " + (envelopeFrom == null ? "*not set*" : envelopeFrom.toString()));
        logging(Log.DEBUG, "init", "\tencoding = " + encoding);
        logging(Log.DEBUG, "init", "\tcharset = " + charset);
        logging(Log.DEBUG, "init", "\tdomain = " + domain);
        logging(Log.DEBUG, "init", "\tboundary = " + boundary);
        if (eol.equals("\r\n"))
            logging(Log.DEBUG, "init", "\teol = CRLF");
        else if (eol.equals("\n"))
            logging(Log.DEBUG, "init", "\teol = LF");
        else
            logging(Log.DEBUG, "init", "\teol = unknown (" + eol.length() + ")");
        logging(Log.DEBUG, "init", "\tmailer = " + mailer);
        logging(Log.DEBUG, "init", "\tprofileURL = " + profileURL);
        logging(Log.DEBUG, "init", "\tunsubscribeURL = " + unsubscribeURL);
        logging(Log.DEBUG, "init", "\tautoURL = " + autoURL);
        logging(Log.DEBUG, "init", "\tonePixelURL = " + onePixelURL);
        logging(Log.DEBUG, "init", "\tmasterMailtype = " + masterMailtype);
        logging(Log.DEBUG, "init", "\tlineLength = " + lineLength);
        logging(Log.DEBUG, "init", "\tonepixlog = " + onepixlog);
        logging(Log.DEBUG, "init", "\tpassword = " + password);
        logging(Log.DEBUG, "init", "\trdirDomain = " + rdirDomain);
        logging(Log.DEBUG, "init", "\tmailloopDomain = " + mailloopDomain);
    }

    /*
     * the main configuration
     */
    public void configure() throws Exception {
        String val;

        if ((val = cfg.cget("LOGLEVEL")) != null) {
            try {
                logLevel = Log.matchLevel(val);
                if (log != null) {
                    log.level(logLevel);
                }
            } catch (NumberFormatException e) {
                throw new Exception("Loglevel must be a known string or a numerical value, not " + val);
            }
        }
        mailDir = cfg.cget("MAILDIR", mailDir);
        defaultEncoding = cfg.cget("DEFAULT_ENCODING", defaultEncoding);
        defaultCharset = cfg.cget("DEFAULT_CHARSET", defaultCharset);
        dbDriver = cfg.cget("DB_DRIVER", dbDriver);
        dbLogin = cfg.cget("DB_LOGIN", dbLogin);
        dbPassword = cfg.cget("DB_PASSWORD", dbPassword);
        dbConnect = cfg.cget("SQL_CONNECT", dbConnect);
        dbPoolsize = cfg.cget("DB_POOLSIZE", dbPoolsize);
        dbPoolgrow = cfg.cget("DB_POOLGROW", dbPoolgrow);
        blockSize = cfg.cget("BLOCKSIZE", blockSize);
        metaDir = cfg.cget("METADIR", metaDir);
        xmlBack = cfg.cget("XMLBACK", xmlBack);
        xmlValidate = cfg.cget("XMLVALIDATE", xmlValidate);
        if (((sampleEmails = cfg.cget("SAMPLE_EMAILS", sampleEmails)) != null)
                && ((sampleEmails.length() == 0) || sampleEmails.equals("-"))) {
            sampleEmails = null;
        }
        domain = cfg.cget("DOMAIN", domain);
        boundary = cfg.cget("BOUNDARY", boundary);
        if ((val = cfg.cget("EOL")) != null) {
            if (val.equalsIgnoreCase("CRLF")) {
                eol = "\r\n";
            } else if (val.equalsIgnoreCase("LF")) {
                eol = "\n";
            } else {
                throw new Exception("EOL must be either CRLF or LF, not " + val);
            }
        }
        mailer = cfg.cget("MAILER", mailer);
        mailLogNumber = cfg.cget("MAIL_LOG_NUMBER", mailLogNumber);
        accLogfile = cfg.cget("ACCOUNT_LOGFILE", accLogfile);
        bncLogfile = cfg.cget("BOUNCE_LOGFILE", bncLogfile);
    }

    /*
     * Setup configuration
     * @param checkRsc first check for resource bundle
     */
    private void configuration(boolean checkRsc) throws Exception {
        boolean done;

        cfg = new Config(log);
        done = false;
        if (checkRsc) {
            try {
                ResourceBundle rsc;

                rsc = ResourceBundle.getBundle("emm");
                if (rsc != null) {
                    done = cfg.loadConfig(rsc, "mailgun.ini");
                }
            } catch (Exception e) {
                ;
            }
        }
        if (!done) {
            cfg.loadConfig(INI_FILE);
        }
        configure();
    }

    /**
     * Constructor for the class
     * @param program the name of the program (for logging setup)
     * @param status_id the status_id to read the mailing information from
     * @param option output option
     * @param conn optional opened database connection
     */
    public Data(String program, String status_id, String option) throws Exception {
        setupLogging(program, (option == null || !option.equals("silent")));
        configuration(true);

        logging(Log.DEBUG, "init", "Data read from " + cfg.getSource() + " for " + status_id);
        setupDatabase();
        logging(Log.DEBUG, "init", "Initial database connection established");
        try {
            queryMailingInformations(status_id);
        } catch (Exception e) {
            throw new Exception("Database failure: " + e, e);
        }
        logging(Log.DEBUG, "init", "Initial data read from database");
        checkMailingData();
        lid = "(" + company_id + "/" + mailinglist_id + "/" + mailing_id + "/" + maildrop_status_id + ")";
        if (islog(Log.DEBUG)) {
            logSettings();
        }
    }

    /**
     * Constructor for non mailing based instances
     * @param program the program name for logging
     */
    public Data(String program) throws Exception {
        setupLogging(program, true);
        configuration(true);
        logging(Log.DEBUG, "init", "Starting up");
        setupDatabase();
    }

    /**
     * Suspend call between setup and main execution
     * @param conn optional database connection
     */
    public void suspend() throws Exception {
        if (isCampaignMailing() || isPreviewMailing())
            closeDatabase();
    }

    /**
     * Resume before main execution
     * @param conn optional database connection
     */
    public void resume() throws Exception {
        if (isCampaignMailing() || isPreviewMailing())
            if (dbase == null) {
                setupDatabase();
            }
    }

    /**
     * Cleanup all open resources and write mailing status before
     */
    public void done() throws Exception {
        int cnt;
        String msg;

        cnt = 0;
        msg = "";
        if (bigClause != null) {
            bigClause.done();
            bigClause = null;
        }
        if (dbase != null) {
            logging(Log.DEBUG, "deinit", "Shuting down database connection");
            try {
                closeDatabase();
            } catch (Exception e) {
                ++cnt;
                msg += "\t" + e + "\n";
            }
        }
        if (toRemove != null) {
            int fcnt = toRemove.size();

            if (fcnt > 0) {
                logging(Log.DEBUG, "deinit", "Remove " + fcnt + " file" + Log.exts(fcnt) + " if existing");
                while (fcnt-- > 0) {
                    String fname = toRemove.remove(0);
                    File file = new File(fname);

                    if (file.exists())
                        if (!file.delete())
                            msg += "\trm " + fname + "\n";
                    file = null;
                }
            }
            toRemove = null;
        }
        if (cnt > 0)
            throw new Exception("Unable to cleanup:\n" + msg);
        logging(Log.DEBUG, "deinit", "Cleanup done: " + msg);
    }

    /**
     * Sanity check for mismatch company_id and perhaps deleted
     * mailing
     */
    public void sanityCheck() throws Exception {
        if (!isPreviewMailing()) {
            try {
                long cid, del;
                Map<String, Object> row;

                row = dbase.querys("SELECT company_id, deleted FROM mailing_tbl WHERE mailing_id = :mailingID",
                        "mailingID", mailing_id);
                cid = dbase.asLong(row.get("company_id"));
                del = dbase.asLong(row.get("deleted"));
                if (cid != company_id) {
                    throw new Exception("Original companyID " + company_id + " for mailing " + mailing_id
                            + " does not match current company_id " + cid);
                }
                if (del != 0) {
                    try {
                        setGenerationStatus(0, 4);
                    } catch (Exception e) {
                        logging(Log.ERROR, "sanity", "Failed to set generation status: " + e.toString());
                    }
                    throw new Exception("Mailing " + mailing_id + " marked as deleted");
                }
            } catch (Exception e) {
                logging(Log.ERROR, "sanity", "Error in quering mailing_tbl: " + e);
                throw new Exception("Unable to find entry in mailing_tbl for " + mailing_id + ": " + e);
            }
        }
    }

    /**
     * Executed at start of mail generation
     */
    public void startExecution() throws Exception {
        bigClause = (BC) mkBigClause();
        bigClause.setData(this);
        if (!bigClause.prepareClause()) {
            throw new Exception("Failed to setup main clause");
        }
        totalSubscribers = bigClause.subscriber();
        totalReceivers = bigClause.receiver();
        logging(Log.DEBUG, "start", "\ttotalSubscribers = " + totalSubscribers);
        logging(Log.DEBUG, "start", "\ttotalReceivers = " + totalReceivers);
    }

    /**
     * Executed at end of mail generation
     */
    public void endExecution() {
        if (bigClause != null) {
            bigClause.done();
            bigClause = null;
        }
    }

    /**
     * Change generation state for the current mailing
     */
    public void updateGenerationState(int newstatus) {
        if (isAdminMailing() || isTestMailing() || isWorldMailing() || isRuleMailing() || isOnDemandMailing()) {
            try {
                if (newstatus == 0) {
                    if (isRuleMailing() || isOnDemandMailing())
                        newstatus = 1;
                    else
                        newstatus = 3;
                }
                setGenerationStatus(2, newstatus);
            } catch (Exception e) {
                logging(Log.ERROR, "genstate", "Unable to update generation state: " + e.toString());
            }
        }
    }

    public void updateGenerationState() {
        updateGenerationState(0);
    }

    /**
     * Called when main generation starts
     */
    public Vector<String> generationClauses() {
        return bigClause.createClauses();
    }

    /**
     * Save receivers to mailtracking table
     */
    public void toMailtrack() {
        if (mailtracking_table != null) {
            String query = bigClause.mailtrackStatement(mailtracking_table);

            if (query != null)
                try {
                    dbase.update(query);
                } catch (Exception e) {
                    logging(Log.ERROR, "execute",
                            "Unable to add mailtrack information using \"" + query + "\": " + e.toString());
                }
        }
    }

    /**
     * Convert a given object to an integer
     * @param o the input object
     * @param what for logging purpose
     * @return the converted value
     */
    private int obj2int(Object o, String what) throws Exception {
        int rc;

        if (o.getClass() == new Integer(0).getClass())
            rc = ((Integer) o).intValue();
        else if (o.getClass() == new Long(0L).getClass())
            rc = ((Long) o).intValue();
        else if (o.getClass() == new String().getClass())
            rc = Integer.parseInt((String) o);
        else
            throw new Exception("Unknown data type for " + what);
        return rc;
    }

    /**
     * Convert a given object to a long
     * @param o the input object
     * @param what for logging purpose
     * @return the converted value
     */
    private long obj2long(Object o, String what) throws Exception {
        long rc;

        if (o.getClass() == new Integer(0).getClass())
            rc = ((Integer) o).longValue();
        else if (o.getClass() == new Long(0L).getClass())
            rc = ((Long) o).longValue();
        else if (o.getClass() == new String().getClass())
            rc = Long.parseLong((String) o);
        else
            throw new Exception("Unknown data type for " + what);
        return rc;
    }

    /**
     * Convert a given object to a boolean
     * @param o the input object
     * @param what for logging purpose
     * @return the converted value
     */
    private boolean obj2bool(Object o, String what) throws Exception {
        boolean rc;

        if (o.getClass() == new Boolean(false).getClass())
            rc = ((Boolean) o).booleanValue();
        else
            throw new Exception("Unknown data type for " + what);
        return rc;
    }

    /**
     * Convert a given object to a date
     * @param o the input object
     * @param what for logging purpose
     * @return the converted value
     */
    private java.util.Date obj2date(Object o, String what) throws Exception {
        java.util.Date rc;

        if (o.getClass() == new java.util.Date().getClass())
            rc = (java.util.Date) o;
        else
            throw new Exception("Unknown data type for " + what);
        return rc;
    }

    /**
     * Parse options passed during runtime
     * @param opts the options to use
     * @param state if 1, the before initialization pass, 2 on execution pass
     */
    @SuppressWarnings("unchecked")
    public void options(Hashtable<String, Object> opts, int state) throws Exception {
        Object tmp;

        if (opts == null) {
            return;
        }
        if (state == 1) {
            tmp = opts.get("custom-tags");
            if (tmp != null) {
                if (customTags == null)
                    customTags = new Vector<String>();
                for (Enumeration<String> e = ((Hashtable<String, Object>) tmp).keys(); e.hasMoreElements();) {
                    String s = e.nextElement();

                    if (s != null)
                        customTags.add(s);
                }
            }
            previewInput = (String) opts.get("preview-input");
            tmp = opts.get("preview-create-all");
            if (tmp != null)
                previewCreateAll = obj2bool(tmp, "preview-create-all");
        } else if (state == 2) {
            tmp = opts.get("customer-id");
            if (tmp != null)
                campaignCustomerID = obj2long(tmp, "customer-id");
            tmp = opts.get("transaction-id");
            if (tmp != null)
                campaignTransactionID = obj2long(tmp, "transaction-id");
            tmp = opts.get("user-status");
            if (tmp != null)
                campaignUserStatus = obj2int(tmp, "user-status");
            fixedEmail = (String) opts.get("fixed-email");
            tmp = opts.get("preview-for");
            if (tmp != null)
                previewCustomerID = obj2long(tmp, "preview-for");
            previewOutput = (Page) opts.get("preview-output");
            tmp = opts.get("preview-anon");
            if (tmp != null)
                previewAnon = obj2bool(tmp, "preview-anon");
            previewSelector = (String) opts.get("preview-selector");
            tmp = opts.get("preview-cachable");
            if (tmp != null)
                previewCachable = obj2bool(tmp, "preview-cachable");
            tmp = opts.get("preview-convert-entities");
            if (tmp != null)
                previewConvertEntities = obj2bool(tmp, "preview-convert-entities");
            tmp = opts.get("preview-legacy-uids");
            if (tmp != null)
                previewLegacyUIDs = obj2bool(tmp, "preview-legacy-uids");
            tmp = opts.get("send-date");
            if (tmp != null) {
                currentSendDate = obj2date(tmp, "send-date");
                sendSeconds = currentSendDate.getTime() / 1000;

                long now = System.currentTimeMillis() / 1000;
                if (sendSeconds < now)
                    sendSeconds = now;
            }

            tmp = opts.get("step");
            if (tmp != null)
                setStepping(obj2int(tmp, "step"));
            tmp = opts.get("block-size");
            if (tmp != null)
                setBlockSize(obj2int(tmp, "block-size"));

            campaignSubselect = (TargetRepresentation) opts.get("select");

            customMap = (Hashtable<String, String>) opts.get("custom-tags");
            overwriteMap = (Hashtable<String, String>) opts.get("overwrite");
            virtualMap = (Hashtable<String, String>) opts.get("virtual");
            overwriteMapMulti = (Hashtable<Long, Hashtable<String, String>>) opts.get("overwrite-multi");
            virtualMapMulti = (Hashtable<Long, Hashtable<String, String>>) opts.get("virtual-multi");
        }
    }

    /**
     * Should we use this record, according to our virtual data?
     * @return true if we should
     */
    public boolean useRecord(Long cid) {
        return true;
    }

    /**
     * Optional initialization for virtual data
     * @param column the column to initialize
     */
    public void initializeVirtualData(String column) {
    }

    /**
     * Do we have data available to overwrite columns?
     * @return true in this case
     */
    public boolean overwriteData() {
        return (overwriteMap != null) || (overwriteMapMulti != null);
    }

    /**
     * Find entry in map for overwrite/virtual records
     * @param cid the customer id
     * @param multi optional available multi hash table
     * @param simple optional simple hash table
     * @param colname the name of the column
     * @return the found string or null
     */
    private String findInMap(Long cid, Hashtable<Long, Hashtable<String, String>> multi,
            Hashtable<String, String> simple, String colname) {
        Hashtable<String, String> map;

        if ((multi != null) && multi.containsKey(cid))
            map = multi.get(cid);
        else
            map = simple;
        if ((map != null) && map.containsKey(colname))
            return map.get(colname);
        return null;
    }

    /**
     * Find an overwrite column
     * @param cid the customer id
     * @param colname the name of the column
     * @return the found string or null
     */
    public String overwriteData(Long cid, String colname) {
        return findInMap(cid, overwriteMapMulti, overwriteMap, colname);
    }

    /**
     * Find a virtual column
     * @param cid the customer id
     * @param colname the name of the column
     * @return the found string or null
     */
    public String virtualData(Long cid, String colname) {
        return findInMap(cid, virtualMapMulti, virtualMap, colname);
    }

    /**
     * Get envelope address
     * @return the punycoded envelope address
     */
    public String getEnvelopeFrom() {
        return (envelopeFrom != null && envelopeFrom.pure_puny != null) ? envelopeFrom.pure_puny
                : (fromEmail != null ? fromEmail.pure_puny : null);
    }

    /**
     * If we have another subselection during runtime
     * return it from here
     * @return the extra subselect or null
     */
    public String getCampaignSubselect() {
        String rc = null;
        String sql;

        if (campaignSubselect != null) {
            sql = campaignSubselect.generateSQL();
            if ((sql != null) && (sql.length() > 0))
                rc = sql;
        }
        return rc;
    }

    /** If we have further restrictions due to reference mailing
     * @return extra subsulect or null
     */
    public String getReferenceSubselect() {
        return null;
    }

    /** If we have further restrictions due to selected media
     * @return extra subsulect or null
     */
    public String getMediaSubselect() {
        return null;
    }

    /** Returns a default image link for a generic picture
     * @param name the image name
     * @return the created link
     */
    public String defaultImageLink(String name) {
        return rdirDomain + "/image?ci=" + Long.toString(company_id) + "&mi=" + Long.toString(mailing_id) + "&name="
                + name;
    }

    /**
     * Mark a filename to be removed during cleanup phase
     * @param fname the filename
     */
    public void markToRemove(String fname) {
        if (toRemove == null)
            toRemove = new Vector<String>();
        if (!toRemove.contains(fname))
            toRemove.addElement(fname);
    }

    /**
     * Mark a file to be removed during cleanup
     * @param file a File instance for the file to be removed
     */
    public void markToRemove(File file) {
        markToRemove(file.getAbsolutePath());
    }

    /**
     * Unmark a filename to be removed, if we already removed
     * it by hand
     * @param fname the filename
     */
    public void unmarkToRemove(String fname) {
        if ((toRemove != null) && toRemove.contains(fname))
            toRemove.remove(fname);
    }

    /**
     * Unmark a file to be removed
     * @param file a File instance
     */
    public void unmarkToRemove(File file) {
        unmarkToRemove(file.getAbsolutePath());
    }

    /**
     * Check if we have to write logging for a given loglevel
     * @param loglvl the loglevel to check against
     * @return true if we should log
     */
    public boolean islog(int loglvl) {
        return log.islog(loglvl);
    }

    /**
     * Write entry to logfile
     * @param loglvl the level to report
     * @param mid the ID of the message
     * @param msg the message itself
     */
    public void logging(int loglvl, String mid, String msg) {
        if (lid != null)
            if (mid != null)
                mid = mid + "/" + lid;
            else
                mid = lid;
        if (log != null)
            log.out(loglvl, mid, msg);
    }

    /**
     * Create a path to write test-/admin mails to
     * @return the path to use
     */
    public String mailDir() {
        return mailDir;
    }

    private void iniValidate(String value, String name) throws Exception {
        if (value == null) {
            throw new Exception("mailgun.ini." + name + " is unset");
        }
    }

    public String dbDriver() throws Exception {
        iniValidate(dbDriver, "db_driver");
        return dbDriver;
    }

    /** returns the database login
     * @return login string
     */
    public String dbLogin() throws Exception {
        iniValidate(dbLogin, "db_login");
        return dbLogin;
    }

    /** returns the database password
     * @return password string
     */
    public String dbPassword() throws Exception {
        iniValidate(dbPassword, "db_password");
        return dbPassword;
    }

    /** returns the connection string for the database
     * @return connection string
     */
    public String dbConnect() throws Exception {
        iniValidate(dbConnect, "db_connect");
        return dbConnect;
    }

    public int dbPoolsize() {
        return dbPoolsize;
    }

    public boolean dbPoolgrow() {
        return dbPoolgrow;
    }

    /** returns the block size to be used
     * @return block size
     */
    public int blockSize() {
        return blockSize;
    }

    /** returns the directory to write meta files to
     * @return path to meta
     */
    public String metaDir() throws Exception {
        iniValidate(metaDir, "metadir");
        return metaDir;
    }

    /** returns the path to xmlback program
     * @return path to xmlback
     */
    public String xmlBack() {
        return xmlBack;
    }

    /** returns the path to the acounting logfile
     * @return path to logfile
     */
    public String accLogfile() {
        return accLogfile;
    }

    /** returns the path to the bounce logfile
     * @return path to logfile
     */
    public String bncLogfile() {
        return bncLogfile;
    }

    /** returns wether we should validate generated XML files
     * @return true if validation should take place
     */
    public boolean xmlValidate() {
        return xmlValidate;
    }

    /** returns the optional used sample receivers
     * @return receiver list
     */
    public String sampleEmails() {
        return sampleEmails;
    }

    /** returns the number of generate mails to write log entries for
     * @return number of mails
     */
    public int mailLogNumber() {
        return mailLogNumber;
    }

    /** returns the X-Mailer: header content
     * @return mailer name
     */
    public String makeMailer() {
        if ((mailer != null) && (company_name != null))
            return StringOps.replace(mailer, "[agnMANDANT]", company_name);
        return mailer;
    }

    /** if this is a admin mail
     * @return true, if admin mail
     */
    public boolean isAdminMailing() {
        return status_field.equals("A");
    }

    /** if this is a test mail
     * @return true, if test mail
     */
    public boolean isTestMailing() {
        return status_field.equals("T");
    }

    /** if this is a campaign mail
     * @return true, if campaign mail
     */
    public boolean isCampaignMailing() {
        return status_field.equals("E");
    }

    /** if this is a date based mailing
     * @return true, if its date based
     */
    public boolean isRuleMailing() {
        return status_field.equals("R");
    }

    /** if this an on demand mailing
     * @return true, if this is on demand
     */
    public boolean isOnDemandMailing() {
        return status_field.equals("D");
    }

    /** if this is a world mail
     * @return true, if world mail
     */
    public boolean isWorldMailing() {
        return status_field.equals("W");
    }

    /** if this is a preview
     * @return true, if preview
     */
    public boolean isPreviewMailing() {
        return status_field.equals("P");
    }

    /**
     * Set standard field to be retreived from database
     * @param predef the hashset to store field name to
     */
    public void setStandardFields(HashSet<String> predef, Hashtable<String, EMMTag> tags) {
        predef.add("email");
        for (Enumeration<EMMTag> e = tags.elements(); e.hasMoreElements();) {
            EMMTag tag = e.nextElement();

            try {
                tag.requestFields(this, predef);
            } catch (Exception ex) {
                logging(Log.ERROR, "tag",
                        "Failed to get required fields for tag " + tag.mTagFullname + ": " + ex.toString());
            }
        }
    }

    /**
     * Set standard columns, if they are not already found in database
     * @param use already used column names
     */
    public void setUsedFieldsInLayout(HashSet<String> use, Hashtable<String, EMMTag> tags) {
        int sanity = 0;
        HashSet<String> predef;

        if (use != null) {
            predef = new HashSet<String>(use);
        } else {
            predef = new HashSet<String>();
        }
        if (targets != null) {
            for (Enumeration<Target> e = targets.elements(); e.hasMoreElements();) {
                Target t = e.nextElement();

                t.requestFields(this, predef);
            }
        }
        setStandardFields(predef, tags);
        for (int n = 0; n < lcount; ++n) {
            Column c = layout.elementAt(n);

            if (predef.contains(c.qname)) {
                if (!c.inuse) {
                    c.inuse = true;
                    ++lusecount;
                }
                ++sanity;
            } else {
                if (c.inuse) {
                    c.inuse = false;
                    --lusecount;
                }
            }
        }
        if (sanity != lusecount)
            logging(Log.ERROR, "layout", "Sanity check failed in setUsedFieldsInLayout");
    }

    /** find a column by its alias
     * @param alias
     * @return the column on success, null otherwise
     */
    public Column columnByAlias(String alias) {
        for (int n = 0; n < lcount; ++n) {
            Column c = layout.elementAt(n);

            if ((c.alias != null) && c.alias.equalsIgnoreCase(alias))
                return c;
        }
        return null;
    }

    /** find a column by its name
     * @param name
     * @return the column on success, null otherwise
     */
    public Column columnByName(String name, String ref) {
        for (int n = 0; n < lcount; ++n) {
            Column c = layout.elementAt(n);

            if (c.name.equalsIgnoreCase(name) && (((c.ref == null) && (ref == null))
                    || ((c.ref != null) && (ref != null) && (c.ref.equalsIgnoreCase(ref)))))
                return c;
        }
        return null;
    }

    public Column columnByName(String name) {
        return columnByName(name, null);
    }

    public Column columnByIndex(int idx) {
        return (idx >= 0) && (idx < lcount) ? layout.elementAt(idx) : null;
    }

    /** return the name of the column at a given position
     * @param col the position in the column layout
     * @return the column name
     */
    public String columnName(int col) {
        return layout.elementAt(col).name;
    }

    /** return the type of the column at a given position
     * @param col the position in the column layout
     * @return the column type
     */
    public int columnType(int col) {
        return layout.elementAt(col).type;
    }

    /** return the type as string of the column at a given position
     * @param col the position in the column layout
     * @return the column type as string
     */
    public String columnTypeStr(int col) {
        return layout.elementAt(col).typeStr();
    }

    /** Set a column from a result set
     * @param col the position in the column layout
     * @param rset the result set
     * @param index position in the result set
     */
    public void columnSet(int col, ResultSet rset, int index) {
        layout.elementAt(col).set(rset, index);
    }

    /** Get a value from a column
     * @param col the position in the column layout
     * @return the contents of that column
     */
    public String columnGetStr(int col) {
        return layout.elementAt(col).get();
    }

    /** Check wether a columns value is NULL
     * @param col the position in the column layout
     * @return true of column value is NULL
     */
    public boolean columnIsNull(int col) {
        return layout.elementAt(col).isNull();
    }

    /** Check wether a column is in use
     * @param col the position in the column layout
     * @return true if column is in use
     */
    public boolean columnUse(int col) {
        return layout.elementAt(col).inUse();
    }

    /** create a RFC compatible Date: line
     * @param ts the input time
     * @return the RFC representation
     */
    public String RFCDate(java.util.Date ts) {
        SimpleDateFormat fmt = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss z", new Locale("en"));
        fmt.setTimeZone(TimeZone.getTimeZone("GMT"));
        if (ts == null)
            ts = new java.util.Date();
        return fmt.format(ts);
    }

    /** Optional string to add to filename generation
     * @return optional string
     */
    public String getFilenameDetail() {
        return "";
    }

    public String getFilenameCompanyID() {
        return Long.toString(company_id);
    }

    public String getFilenameMailingID() {
        return Long.toString(mailing_id);
    }

    /** Should we generate URLs already here?
     * @return true, if we should generate them
     */
    public boolean generateCodedURLs() {
        return true;
    }
}