/*********************************************************************************
* 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.web;
import java.io.IOException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.GregorianCalendar;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TimeZone;
import java.util.Vector;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.sql.DataSource;
import org.agnitas.beans.DeliveryStatFactory;
import org.agnitas.beans.DynamicTag;
import org.agnitas.beans.DynamicTagContent;
import org.agnitas.beans.MaildropEntry;
import org.agnitas.beans.Mailing;
import org.agnitas.beans.MailingComponent;
import org.agnitas.beans.Mailinglist;
import org.agnitas.beans.TrackableLink;
import org.agnitas.beans.factory.MailingFactory;
import org.agnitas.cms.utils.CmsUtils;
import org.agnitas.dao.MailingComponentDao;
import org.agnitas.dao.MailingDao;
import org.agnitas.dao.MailinglistDao;
import org.agnitas.dao.RecipientDao;
import org.agnitas.dao.TargetDao;
import org.agnitas.emm.core.target.service.TargetService;
import org.agnitas.emm.core.commons.util.DateUtil;
import org.agnitas.mailing.beans.MaildropEntryFactory;
import org.agnitas.preview.AgnTagException;
import org.agnitas.preview.Page;
import org.agnitas.preview.Preview;
import org.agnitas.preview.PreviewFactory;
import org.agnitas.preview.PreviewHelper;
import org.agnitas.preview.TAGCheck;
import org.agnitas.preview.TAGCheckFactory;
import org.agnitas.service.LinkcheckService;
import org.agnitas.stat.DeliveryStat;
import org.agnitas.target.Target;
import org.agnitas.util.AgnUtils;
import org.apache.log4j.Logger;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.ActionMessage;
import org.apache.struts.action.ActionMessages;
import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
/**
* Implementation of <strong>Action</strong> that validates a user logon.
*
* @author Martin Helff
*/
public class MailingSendAction extends StrutsActionBase {
private static final transient Logger logger = Logger.getLogger(MailingSendAction.class);
public static final int ACTION_VIEW_SEND = ACTION_LAST + 1;
public static final int ACTION_SEND_ADMIN = ACTION_LAST + 2;
public static final int ACTION_SEND_TEST = ACTION_LAST + 3;
public static final int ACTION_SEND_WORLD = ACTION_LAST + 4;
public static final int ACTION_VIEW_SEND2 = ACTION_LAST + 5;
public static final int ACTION_VIEW_DELSTATBOX = ACTION_LAST + 6;
public static final int ACTION_ACTIVATE_CAMPAIGN = ACTION_LAST + 7;
public static final int ACTION_ACTIVATE_RULEBASED = ACTION_LAST + 8;
public static final int ACTION_PREVIEW_SELECT = ACTION_LAST + 9;
public static final int ACTION_PREVIEW = ACTION_LAST + 10;
public static final int ACTION_PREVIEW_HEADER = ACTION_LAST + 13;
public static final int ACTION_DEACTIVATE_MAILING = ACTION_LAST + 14;
public static final int ACTION_CHANGE_SENDDATE = ACTION_LAST + 15;
public static final int ACTION_CANCEL_MAILING_REQUEST = ACTION_LAST + 16;
public static final int ACTION_CANCEL_MAILING = ACTION_LAST + 17;
public static final int ACTION_CONFIRM_SEND_WORLD = ACTION_LAST + 18;
public static final int ACTION_CHECK_LINKS = ACTION_LAST + 19;
public static final int ACTION_SEND_LAST = ACTION_LAST + 19;
public static final int PREVIEW_MODE_HEADER = 1;
public static final int PREVIEW_MODE_TEXT = 2;
public static final int PREVIEW_MODE_HTML = 3;
public static final int PREVIEW_MODE_OFFLINE = 4;
// tag errors
public static final String TEMPLATE = "__TEMPLATE__";
public static final String SUBJECT = "__SUBJECT__";
public static final String FROM = "__FROM__";
protected TargetDao targetDao;
protected MailingDao mailingDao;
protected RecipientDao recipientDao;
private MailingComponentDao mailingComponentDao;
private LinkcheckService linkcheckService;
private MailingFactory mailingFactory;
private DeliveryStatFactory deliveryStatFactory;
private MaildropEntryFactory maildropEntryFactory;
private MailinglistDao mailinglistDao;
private TAGCheckFactory tagCheckFactory;
private DataSource dataSource;
protected PreviewFactory previewFactory;
private TargetService targetService;
protected int maxAdminMails;
// --------------------------------------------------------- Public Methods
/**
* Process the specified HTTP request, and create the corresponding HTTP
* response (or forward to another web component that will create it).
* Return an <code>ActionForward</code> instance describing where and how
* control should be forwarded, or <code>null</code> if the response has
* already been completed.
* <br>
* ACTION_VIEW_SEND: loads mailing into the form;<br>
* loads list of target groups into request;<br>
* loads delivery statistic data from db into the form;<br>
* loads names of mailing target groups into a form;<br>
* calculates statistics-frame size according to number of target groups selected for mailing;<br>
* sets the flag for displaying of send test- or admin-mails buttons;<br>
* sets destination="send".
* <br><br>
* ACTION_VIEW_DELSTATBOX: loads delivery statistic data from db into the form;<br>
* loads names of mailing target groups into a form;<br>
* calculates statistics-frame size according to number of target groups selected for mailing;<br>
* sets the flag for displaying of send test- or admin-mails buttons;<br>
* sets destination="view_delstatbox".
* <br><br>
* ACTION_CANCEL_MAILING_REQUEST: loads mailing into the form;<br>
* loads list of target groups into request;<br>
* forwards to jsp with question to cancel mailing generation.
* <br><br>
* ACTION_CANCEL_MAILING: loads mailing into the form;<br>
* loads list of target groups into request;<br>
* if the request parameter "kill" is set - cancels mailing generation; if the mailing generation was
* canceled successfully - loads delivery statistic to form, loads mailing targets to request, calculates
* statistics-frame size according to number of target groups selected for mailing, sets the flag for
* displaying of send test- or admin-mails buttons;<br>
* forwards to mailing send page ("send");
* <br><br>
* ACTION_VIEW_SEND2: loads mailing into the form;<br>
* loads list of target groups into request;<br>
* loads send statistic data from db into the form (number of sent html-mails, text-mails, offline-mails,
* total number of sent mails);<br>
* loads target group names into the form<br>
* sets destination="send2".
* <br><br>
* ACTION_SEND_ADMIN: loads mailing into the form;<br>
* loads list of target groups into request;<br>
* creates maildrop-entry with type <code>MaildropEntry.STATUS_ADMIN</code> ('A');<br>
* performs mailing validation before sending: checks that there's at least 1 recipient, checks that subject and
* sender address are not empty, checks that html and text versions are not empty;<br>
* if it is CMS-mailing - regenerates mailing content from CMS data;<br>
* triggers mailing sending with a mailgun and a newly created maildrop-entry; mailiing will be sent only to
* admin-recipients.<br>
* loads delivery statistic data from db into the form;<br>
* loads names of mailing target groups into a form;<br>
* Sets destination="send".
* <br><br>
* ACTION_SEND_TEST: do exactly the same things as ACTION_SEND_ADMIN with the only difference that the type
* of created maidrop-entry is <code>MaildropEntry.STATUS_TEST</code> ('T'). Mailing will be sent to admin- and
* test-recipients
* <br><br>
* ACTION_DEACTIVATE_MAILING: removes maildrop status entries of mailing with status
* <code>MaildropEntry.STATUS_ACTIONBASED</code> and <code>MaildropEntry.STATUS_DATEBASED</code> ('E' and 'R');
* In fact that means that the sending of actionbased/datebased mailing will be stopped<br>
* loads mailing into the form;<br>
* loads list of target groups into request;<br>
* sets destination="send".
* <br><br>
* ACTION_CONFIRM_SEND_WORLD: loads mailing into the form;<br>
* loads list of target groups into request;<br>
* forwards to jsp with question to send the mailing.
* <br><br>
* ACTION_ACTIVATE_RULEBASED:
* ACTION_ACTIVATE_CAMPAIGN:
* ACTION_SEND_WORLD: creates maildrop-entry with appropriate type depending on action:
* <code>MaildropEntry.STATUS_WORLD</code>, <code>MaildropEntry.STATUS_ACTIONBASED</code> or
* <code>MaildropEntry.STATUS_DATEBASED</code> ('W', 'E' or 'R');<br>
* performs mailing validation before sending: checks that there's at least 1 recipient, checks that subject and
* sender address are not empty, checks that html and text versions are not empty;<br>
* If the send should be performed now - regenerates mailing content from CMS data (if CMS mailing), triggers
* mailing sending with a mailgun and a newly created maildrop-entry; mailiing will be sent to all active
* recipients (admin, test and world).<br>
* loads mailing into the form;<br>
* loads list of target groups into request;<br>
* loads delivery statistic data from db into the form;<br>
* loads names of mailing target groups into a form;<br>
* Sets destination="send".
* <br><br>
* ACTION_PREVIEW_SELECT: loads mailing into the form;<br>
* loads list of target groups into request;<br>
* loads list of mailing admin and test recipients into the request;<br>
* checks if the mailing has at least one admin or test recipient and stores the check result in the form;
* if the check result is "true" and there wasn't preview-recipient selected by user - stores the
* smallest value of recipient id in the form as a preview recipient. (Preview recipient is a recipient
* the mailing preview will be generated for)<br>
* Sets destination="preview_select".
* <br><br>
* ACTION_PREVIEW_HEADER: performs tag-check for mailing subject and from-address, if the check is ok - generates
* backend preview. If the preview header is not null - takes subject and from-address from backend preview and
* sets that to form, in other case sets subject and from-address taken from emailParam of mailing into form.
* Sets mail format and mailinglist ID to form.<br>
* If preview header generation fails, loads dynamic tag error report into request and forwards to error
* report page;<br>
* in case of successful preview header generation, loads mailing attachments and personalized attachments into
* request and sets destination="preview_header".
* <br><br>
* ACTION_PREVIEW: Checks if preview customer ID is set and sets the result of check to form property<br>
* If the preview customer ID is set in form - generates mailing preview according to format selected by user
* and sets it to form; sets mail format and mailinglist ID to form. Forwards to preview page. If preview
* generation failed - stores error report to request and forwards to error report page<br>
* If the preview customer id is not set in form forwards to error report page
* <br><br>
* ACTION_CHECK_LINKS: loads mailing into the form;<br>
* loads list of target groups into request;<br>
* loads delivery statistic data from db into the form;<br>
* checks if the mailing links are available (the url-request ), if there's at least one link invalid - creates
* error message with the list of invalid links; if all links are ok - adds success message<br>
* Forwards to "send".
* <br><br>
* @param form ActionForm object
* @param req request
* @param res response
* @param mapping the ActionMapping used to select this instance
* @exception IOException if an input/output error occurs
* @exception ServletException if a servlet exception occurs
* @return destination specified in struts-config.xml to forward to next jsp
*/
@Override
public ActionForward execute(ActionMapping mapping,
ActionForm form,
HttpServletRequest req,
HttpServletResponse res)
throws IOException, ServletException {
// Validate the request parameters specified by the user
MailingSendForm aForm=null;
ActionMessages errors = new ActionMessages();
ActionMessages messages = new ActionMessages();
ActionForward destination=null;
if(!AgnUtils.isUserLoggedIn(req)) {
return mapping.findForward("logon");
}
aForm=(MailingSendForm)form;
if (logger.isInfoEnabled()) logger.info("Action: " + aForm.getAction());
try {
switch(aForm.getAction()) {
case ACTION_VIEW_SEND:
if(allowed("mailing.send.show", req)) {
loadMailing(aForm, req);
// TODO Remove this quick-hack and replace it with some more sophisticated code
loadDeliveryStats(aForm, req);
destination=mapping.findForward("send");
} else {
errors.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage("error.permissionDenied"));
}
break;
case ACTION_VIEW_DELSTATBOX:
if(allowed("mailing.send.show", req)) {
loadDeliveryStats(aForm, req);
destination=mapping.findForward("view_delstatbox");
} else {
errors.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage("error.permissionDenied"));
}
break;
case ACTION_CANCEL_MAILING_REQUEST:
loadMailing(aForm, req);
aForm.setAction(MailingSendAction.ACTION_CANCEL_MAILING);
destination=mapping.findForward("cancel_generation_question");
break;
case ACTION_CANCEL_MAILING:
loadMailing(aForm, req);
if(req.getParameter("kill")!=null) {
if(cancelMailingDelivery(aForm, req)) {
loadDeliveryStats(aForm, req);
}
destination=mapping.findForward("send");
}
break;
case MailingSendAction.ACTION_VIEW_SEND2:
if(allowed("mailing.send.show", req)) {
loadMailing(aForm, req);
loadSendStats(aForm, req);
aForm.setAction(MailingSendAction.ACTION_CONFIRM_SEND_WORLD);
Set<Integer> targetGroups = (Set<Integer>) aForm.getTargetGroups();
if(targetGroups == null){
targetGroups = new HashSet<Integer>();
}
List<String> targetGroupNames = getTargetDao().getTargetNamesByIds(AgnUtils.getAdmin(req).getCompanyID(), targetGroups);
req.setAttribute("targetGroupNames", targetGroupNames);
destination=mapping.findForward("send2");
} else {
errors.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage("error.permissionDenied"));
}
break;
case MailingSendAction.ACTION_SEND_ADMIN:
if(allowed("mailing.send.admin", req)) {
loadMailing(aForm, req);
sendMailing(aForm, req);
loadDeliveryStats(aForm, req);
} else {
errors.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage("error.permissionDenied"));
}
aForm.setAction(MailingSendAction.ACTION_VIEW_SEND);
destination=mapping.findForward("send");
break;
case MailingSendAction.ACTION_SEND_TEST:
if(allowed("mailing.send.test", req)) {
loadMailing(aForm, req);
sendMailing(aForm, req);
loadDeliveryStats(aForm, req);
} else {
errors.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage("error.permissionDenied"));
}
aForm.setAction(MailingSendAction.ACTION_VIEW_SEND);
destination=mapping.findForward("send");
break;
case MailingSendAction.ACTION_DEACTIVATE_MAILING:
if(allowed("mailing.send.world", req)) {
deactivateMailing(aForm, req);
} else {
errors.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage("error.permissionDenied"));
}
loadMailing(aForm, req);
aForm.setAction(MailingSendAction.ACTION_VIEW_SEND);
destination=mapping.findForward("send");
break;
case ACTION_CONFIRM_SEND_WORLD:
loadMailing(aForm, req);
aForm.setAction(MailingSendAction.ACTION_SEND_WORLD);
destination=mapping.findForward("send_confirm");
break;
case MailingSendAction.ACTION_ACTIVATE_RULEBASED:
case MailingSendAction.ACTION_ACTIVATE_CAMPAIGN:
case MailingSendAction.ACTION_SEND_WORLD:
if(allowed("mailing.send.world", req)) {
try {
sendMailing(aForm, req);
} finally {
loadMailing(aForm, req);
}
} else {
errors.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage("error.permissionDenied"));
loadMailing(aForm, req);
}
// loadMailing(aForm, req);
loadDeliveryStats(aForm, req);
aForm.setAction(MailingSendAction.ACTION_VIEW_SEND);
destination=mapping.findForward("send");
break;
case MailingSendAction.ACTION_PREVIEW_SELECT:
loadMailing(aForm, req);
Map<Integer, String> recipientList = putPreviewRecipientsInRequest(req, aForm.getMailingID(), AgnUtils.getAdmin(req).getCompanyID(), getRecipientDao());
if(hasPreviewRecipient(aForm, req)) {
aForm.setHasPreviewRecipient(true);
if( aForm.getPreviewCustomerID() == 0 && recipientList.size() > 0) {
int minId = Collections.min(recipientList.keySet());
aForm.setPreviewCustomerID(minId);
}
} else {
aForm.setHasPreviewRecipient(false);
}
destination=mapping.findForward("preview_select");
break;
case MailingSendAction.ACTION_PREVIEW_HEADER:
try {
getHeaderPreview(aForm, req);
List<MailingComponent> components = mailingComponentDao.getPreviewHeaderComponents(aForm.getMailingID(), AgnUtils.getAdmin(req).getCompanyID());
req.setAttribute("components", components);
destination=mapping.findForward("preview_header");
} catch(AgnTagException e) {
req.setAttribute("errorReport", e.getReport());
destination=mapping.findForward("preview_errors");
}
break;
case MailingSendAction.ACTION_PREVIEW:
if (aForm.getPreviewCustomerID() > 0) {
aForm.setHasPreviewRecipient(true);
try {
destination = getPreview(mapping, aForm, req);
} catch (AgnTagException agnTagException) {
req.setAttribute("errorReport", agnTagException.getReport());
errors.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage("error.template.dyntags"));
destination = mapping.findForward("preview_errors");
}
} else {
aForm.setHasPreviewRecipient(false);
destination = mapping.findForward("preview_errors");
errors.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage("error.preview.no_recipient"));
}
break;
case MailingSendAction.ACTION_CHECK_LINKS:
loadMailing(aForm, req);
loadDeliveryStats(aForm, req);
List<String> listInvalidUrl = checkForInvalidLinks(aForm, req);
if (listInvalidUrl.size() > 0) {
for (String invalidUrl : listInvalidUrl) {
errors.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage("error.invalid.link", invalidUrl + " <br>"));
}
} else {
messages.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage("link.check.success"));
}
aForm.setAction(MailingSendAction.ACTION_VIEW_SEND);
destination = mapping.findForward("send");
break;
}
} catch (Exception e) {
logger.error("execute: "+e+"\n"+AgnUtils.getStackTrace(e));
if(e.getMessage().equals("error.mailing.send.admin.maxMails")) {
errors.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage(e.getMessage(), maxAdminMails));
} else {
errors.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage(e.getMessage()));
}
destination=mapping.findForward("send");
}
// Report any errors we have discovered back to the original form
if (!errors.isEmpty()) {
this.saveErrors(req, errors);
if(aForm.getAction()==MailingSendAction.ACTION_SEND_ADMIN || aForm.getAction()==MailingSendAction.ACTION_SEND_TEST || aForm.getAction()==MailingSendAction.ACTION_SEND_WORLD) {
return (new ActionForward(mapping.getInput()));
}
}
if (!messages.isEmpty()){
this.saveMessages(req,messages);
}
return destination;
}
/**
* Returns a list of invalid links
* @param aForm MailingSendForm object
* @param req HTTP request
* @return list of invalid links
*/
protected List<String> checkForInvalidLinks(MailingSendForm aForm, HttpServletRequest req) {
ArrayList<String> invalidlinks = new ArrayList<String>();
Collection<TrackableLink> links;
// retrieve the list of links
Mailing aMailing = mailingDao.getMailing(aForm.getMailingID(),
AgnUtils.getAdmin(req).getCompanyID());
try {
WebApplicationContext webApplicationContext = WebApplicationContextUtils.getRequiredWebApplicationContext(req.getSession().getServletContext());
aMailing.buildDependencies(false, webApplicationContext);
links = aMailing.getTrackableLinks().values();
} catch (Exception e) {
logger.error("checkForInvalidLinks: "+e+"\n"+AgnUtils.getStackTrace(e));
return invalidlinks;
}
invalidlinks.addAll(linkcheckService.checkAvailability(links));
return invalidlinks;
}
/**
* Check if there is at list one admin or test recipient binding for the mailing list of the mailing.
* @param aForm MailingSendForm object
* @param req HTTP request
* @return true==success
* false==the mailing list has no admin or test recipient bindings
*/
protected boolean hasPreviewRecipient(MailingSendForm aForm, HttpServletRequest req) {
return mailingDao.hasPreviewRecipients(aForm.getMailingID(), AgnUtils.getAdmin(req).getCompanyID());
}
protected boolean hasPreviewRecipientPossiblyOtherClient(MailingSendForm aForm, HttpServletRequest req) {
int companyId = AgnUtils.getAdmin(req).getCompanyID();
try {
final Object bulkGenerate = req.getSession().getAttribute("bulkGenerate");
if (bulkGenerate != null){
String companyIdString = req.getParameter("previewCompanyId");
companyId = Integer.valueOf(companyIdString).intValue();
}
} catch (Exception e) {
// do nothing, will take company id from action
}
return mailingDao.hasPreviewRecipients(aForm.getMailingID(), companyId);
}
/**
* Loads mailing data from db into form; also gets list of target groups from db and stores it in the request.
* @param aForm MailingSendForm object
* @param req HTTP request
*/
protected void loadMailing(MailingSendForm aForm, HttpServletRequest req) {
Mailing aMailing=mailingDao.getMailing(aForm.getMailingID(), AgnUtils.getAdmin(req).getCompanyID());
if(aMailing==null) {
aMailing = mailingFactory.newMailing();
aMailing.init(AgnUtils.getAdmin(req).getCompanyID(), getApplicationContext(req));
aMailing.setId(0);
aForm.setMailingID(0);
}
aForm.setShortname(aMailing.getShortname());
aForm.setDescription(aMailing.getDescription());
aForm.setIsTemplate(aMailing.isIsTemplate());
aForm.setMailingtype(aMailing.getMailingType());
aForm.setWorldMailingSend(aMailing.isWorldMailingSend());
aForm.setTargetGroups(aMailing.getTargetGroups());
aForm.setEmailFormat(aMailing.getEmailParam().getMailFormat());
aForm.setMailinglistID(aMailing.getMailinglistID());
aForm.setMailing(aMailing);
aForm.setHasDeletedTargetGroups( this.targetService.hasMailingDeletedTargetGroups(aMailing));
String entityName = aMailing.isIsTemplate() ? "template" : "mailing";
AgnUtils.userlogger().info(AgnUtils.getAdmin(req).getUsername() + ": do load " + entityName + " " + aMailing.getShortname());
req.setAttribute("targetGroups", targetDao.getTargets(AgnUtils.getAdmin(req).getCompanyID()));
}
/**
* Loads delivery statistic data from db into the form;
* adjusts delivery stats frame height and stores the height value in the form;
* sets flag for displaying admin and test send buttons.
* @param aForm MailingSendForm object
* @param req HTTP request
* @throws Exception
*/
protected void loadDeliveryStats(MailingSendForm aForm, HttpServletRequest req) throws Exception {
DeliveryStat deliveryStat = deliveryStatFactory.createDeliveryStat();
deliveryStat.setCompanyID(AgnUtils.getAdmin(req).getCompanyID());
deliveryStat.setMailingID(aForm.getMailingID());
deliveryStat.getDeliveryStatsFromDB(aForm.getMailingtype());
aForm.setDeliveryStat(deliveryStat);
Mailing aMailing=mailingDao.getMailing(aForm.getMailingID(), AgnUtils.getAdmin(req).getCompanyID());
List<String> targetNames = targetDao.getTargetNamesByIds(AgnUtils.getAdmin(req).getCompanyID(), this.targetService.getTargetIdsFromExpression(aMailing));
aForm.setTargetGroupsNames(targetNames);
int frameHeight = 201;
if (targetNames.size() > 1) {
frameHeight += (targetNames.size() - 1) * 13;
}
aForm.setFrameHeight(frameHeight);
// set flag for displaying admin- and test-send-buttons
aForm.setTransmissionRunning(mailingDao.isTransmissionRunning(aForm.getMailingID()));
}
/**
* Tries to cancel mailing delivery and returns true if delivery is canceled, or false - if the delivery could not be
* canceled or some error occurred on execution of delivery canceling.
* @param aForm MailingSendForm object
* @param req HTTP request
* @return true=success
* false=delivery could not be canceled
*/
protected boolean cancelMailingDelivery(MailingSendForm aForm, HttpServletRequest req) {
DeliveryStat deliveryStat = deliveryStatFactory.createDeliveryStat();
deliveryStat.setCompanyID(AgnUtils.getAdmin(req).getCompanyID());
deliveryStat.setMailingID(aForm.getMailingID());
if(deliveryStat.cancelDelivery()) {
aForm.setWorldMailingSend(false);
aForm.setMailingtype(Mailing.TYPE_NORMAL);
return true;
}
return false;
}
/**
* Creates maildrop entry object for storing mailing send data (status of send mailing, mailing send date and time,
* mailing content); checks syntax of mailing content by generating dummy preview; loads maildrop entry into mailing,
* saves mailing in database; checks send date and time, and sends mailing if it should be sent immediately.
* @param aForm MailingSendForm object
* @param req HTTP request
* @throws Exception
*/
protected void sendMailing(MailingSendForm aForm, HttpServletRequest req) throws Exception {
int stepping, blocksize;
boolean admin=false;
boolean test=false;
boolean world=false;
java.util.Date sendDate=new java.util.Date();
java.util.Date genDate=new java.util.Date();
int startGen=1;
MaildropEntry maildropEntry = maildropEntryFactory.createMaildropEntry();
switch(aForm.getAction()) {
case MailingSendAction.ACTION_SEND_ADMIN:
maildropEntry.setStatus(MaildropEntry.STATUS_ADMIN);
admin=true;
break;
case MailingSendAction.ACTION_SEND_TEST:
maildropEntry.setStatus(MaildropEntry.STATUS_TEST);
admin=true;
test=true;
break;
case MailingSendAction.ACTION_SEND_WORLD:
maildropEntry.setStatus(MaildropEntry.STATUS_WORLD);
admin=true;
test=true;
world=true;
break;
case MailingSendAction.ACTION_ACTIVATE_RULEBASED:
maildropEntry.setStatus(MaildropEntry.STATUS_DATEBASED);
world=true;
break;
case MailingSendAction.ACTION_ACTIVATE_CAMPAIGN:
maildropEntry.setStatus(MaildropEntry.STATUS_ACTIONBASED);
world=true;
}
if(aForm.getSendDate()!=null) {
GregorianCalendar aCal=new GregorianCalendar(TimeZone.getTimeZone(AgnUtils.getAdmin(req).getAdminTimezone()));
aCal.set(Integer.parseInt(aForm.getSendDate().substring(0, 4)), Integer.parseInt(aForm.getSendDate().substring(4, 6))-1, Integer.parseInt(aForm.getSendDate().substring(6, 8)), aForm.getSendHour(), aForm.getSendMinute());
sendDate=aCal.getTime();
}
Mailing aMailing=mailingDao.getMailing(aForm.getMailingID(), AgnUtils.getAdmin(req).getCompanyID());
if(aMailing==null) {
return;
}
stepping = 0;
blocksize = 0;
try {
stepping = aForm.getStepping();
blocksize = aForm.getBlocksize();
} catch (Exception e) {
stepping = 0;
blocksize = 0;
}
Mailinglist aList=mailinglistDao.getMailinglist(aMailing.getMailinglistID(), AgnUtils.getAdmin(req).getCompanyID());
String preview=null;
if(mailinglistDao.getNumberOfActiveSubscribers(admin, test, world, aMailing.getTargetID(), aList.getCompanyID(), aList.getId())==0) {
throw new Exception("error.mailing.no_subscribers");
}
// check syntax of mailing by generating dummy preview
preview=aMailing.getPreview(aMailing.getTextTemplate().getEmmBlock(), Mailing.INPUT_TYPE_TEXT, aForm.getPreviewCustomerID(), this.getApplicationContext(req));
if(preview.trim().length()==0) {
throw new Exception("error.mailing.no_text_version");
}
preview=aMailing.getPreview(aMailing.getHtmlTemplate().getEmmBlock(), Mailing.INPUT_TYPE_HTML, aForm.getPreviewCustomerID(), this.getApplicationContext(req));
if(aForm.getEmailFormat()>0 && preview.trim().length()==0) {
throw new Exception("error.mailing.no_html_version");
}
preview=aMailing.getPreview(aMailing.getEmailParam().getSubject(), Mailing.INPUT_TYPE_HTML, aForm.getPreviewCustomerID(), this.getApplicationContext(req));
if(preview.trim().length()==0) {
throw new Exception("error.mailing.subject.too_short");
}
preview=aMailing.getPreview(aMailing.getEmailParam().getFromAdr(), Mailing.INPUT_TYPE_HTML, aForm.getPreviewCustomerID(), this.getApplicationContext(req));
if(preview.trim().length()==0) {
throw new Exception("error.mailing.sender_adress");
}
maildropEntry.setSendDate(sendDate);
if(!DateUtil.isSendDateForImmediateDelivery(sendDate)) {
// sent gendate if senddate is in future
GregorianCalendar tmpGen=new GregorianCalendar();
GregorianCalendar now=new GregorianCalendar();
tmpGen.setTime(sendDate);
tmpGen.add(Calendar.HOUR_OF_DAY, -3);
if(tmpGen.before(now)) {
tmpGen=now;
}
genDate=tmpGen.getTime();
}
if(!DateUtil.isDateForImmediateGeneration(genDate) && ((aMailing.getMailingType() == Mailing.TYPE_NORMAL) || (aMailing.getMailingType() == Mailing.TYPE_FOLLOWUP))) {
startGen=0;
}
if(world && aMailing.isWorldMailingSend()) {
return;
}
maildropEntry.setGenStatus(startGen);
maildropEntry.setGenDate(genDate);
maildropEntry.setGenChangeDate(new java.util.Date());
maildropEntry.setMailingID(aMailing.getId());
maildropEntry.setCompanyID(aMailing.getCompanyID());
maildropEntry.setStepping(stepping);
maildropEntry.setBlocksize(blocksize);
aMailing.getMaildropStatus().add(maildropEntry);
mailingDao.saveMailing(aMailing);
if (startGen==1 && maildropEntry.getStatus() != MaildropEntry.STATUS_ACTIONBASED && maildropEntry.getStatus() != MaildropEntry.STATUS_DATEBASED) {
CmsUtils.generateClassicTemplate(aForm.getMailingID(), req, getApplicationContext(req));
aMailing.triggerMailing(maildropEntry.getId(), new Hashtable<String, Object>(), this.getApplicationContext(req));
}
if (logger.isInfoEnabled()) logger.info("send mailing id: "+aMailing.getId()+" type: "+maildropEntry.getStatus());
AgnUtils.userlogger().info(AgnUtils.getAdmin(req).getUsername() + ": send mailing " + aMailing.getShortname() + " type: " + maildropEntry.getStatus());
}
/**
* Gets mailing from database, removes maildrop entry of the mailing and save the mailing in database.
* @param aForm MailingSendForm object
* @param req HTTP request
* @throws Exception
*/
protected void deactivateMailing(MailingSendForm aForm, HttpServletRequest req) throws Exception {
Mailing aMailing=mailingDao.getMailing(aForm.getMailingID(), AgnUtils.getAdmin(req).getCompanyID());
if(aMailing==null) {
return;
}
aMailing.cleanupMaildrop(getApplicationContext(req));
mailingDao.saveMailing(aMailing);
}
/**
* Generates mailing preview by given mailing format. Calls generate preview method; for mailings of HTML or text
* format, if the generated preview string is empty, analyzes preview content blocks and throws AgnTagException contains
* report with data about invalid content.
* Forwards to preview page according to mailing format.
* @param mapping the ActionMapping used to select this instance
* @param aForm MailingSendForm object
* @param req HTTP request
* @return action forward for displaying page with preview
* @throws Exception
*/
protected ActionForward getPreview(ActionMapping mapping, MailingSendForm aForm, HttpServletRequest req) throws Exception {
Mailing aMailing = mailingDao.getMailing(aForm.getMailingID(), AgnUtils.getAdmin(req).getCompanyID());
if (aMailing == null)
return mapping.findForward("preview." + aForm.getPreviewFormat());
String[] tmplNames = { "Text", "Html", "FAX", "PRINT", "MMS", "SMS" };
if (aForm.getPreviewFormat() == Mailing.INPUT_TYPE_HTML || aForm.getPreviewFormat() == Mailing.INPUT_TYPE_TEXT) {
Preview preview = previewFactory.createPreview();
Page output = preview.makePreview (aMailing.getId(), aForm.getPreviewCustomerID(), false);
preview.done();
if (aForm.getPreviewFormat() == Mailing.INPUT_TYPE_HTML) {
String previewAsString = output.getHTML();
if (previewAsString == null) {
String htmlTemplate = aMailing.getHtmlTemplate().getEmmBlock();
Map<String, DynamicTag> dynTagsMap = aMailing.getDynTags();
int mailingID = aForm.getMailingID();
analyzePreview(htmlTemplate, dynTagsMap, mailingID);
} else {
aForm.setPreview(previewAsString);
}
} else if (aForm.getPreviewFormat() == Mailing.INPUT_TYPE_TEXT) {
String previewString = output.getText();
if (previewString == null) {
String htmlTemplate = aMailing.getTextTemplate().getEmmBlock();
Map<String, DynamicTag> dynTagsMap = aMailing.getDynTags();
int mailingID = aForm.getMailingID();
analyzePreview(htmlTemplate, dynTagsMap, mailingID);
} else {
if (previewString.indexOf("<pre>") > -1) {
previewString = previewString.substring(previewString.indexOf("<pre>") + 5, previewString.length());
}
if (previewString.lastIndexOf("</pre>") > -1) {
previewString = previewString.substring(0, previewString.lastIndexOf("</pre>"));
}
aForm.setPreview(previewString);
}
}
aForm.setEmailFormat(aMailing.getEmailParam().getMailFormat());
aForm.setMailinglistID(aMailing.getMailinglistID());
return mapping.findForward("preview." + aForm.getPreviewFormat());
} else {
aForm.setPreview(aMailing.getPreview(aMailing.getTemplate(tmplNames[aForm.getPreviewFormat()]).getEmmBlock(), aForm.getPreviewFormat(), aForm.getPreviewCustomerID(),
true, this.getApplicationContext(req)));
aForm.setEmailFormat(aMailing.getEmailParam().getMailFormat());
aForm.setMailinglistID(aMailing.getMailinglistID());
return mapping.findForward("preview." + aForm.getPreviewFormat());
}
}
/**
* Gets mailing from database, validates mailing subject and from address, if the subject and the from address is ok,
* generates preview, parses preview header to get values for sender and subject and loads parsed values into the form.
* If the validation fails, the method throws AgnTagException contains the report about invalid characters.
* @param aForm MailingSendForm object
* @param req HTTP request
* @throws Exception
*/
protected void getHeaderPreview(MailingSendForm aForm, HttpServletRequest req) throws Exception {
Mailing aMailing=mailingDao.getMailing(aForm.getMailingID(), AgnUtils.getAdmin(req).getCompanyID());
if(aMailing!=null) {
TAGCheck tagCheck = tagCheckFactory.createTAGCheck(aForm.getMailingID());
String fromParameter = aMailing.getEmailParam().getFromAdr();
String subjectParameter = aMailing.getEmailParam().getSubject();
List<String[]> errorReport = new ArrayList<String[]>();
Vector<String> failures = new Vector<String>();
StringBuffer fromReportBuffer = new StringBuffer();
boolean fromIsOK = tagCheck.checkContent(fromParameter, fromReportBuffer,failures);
if(!fromIsOK) {
appendErrorsToList(FROM, errorReport, fromReportBuffer);
}
StringBuffer subjectReportBuffer = new StringBuffer();
boolean subjectIsOK = tagCheck.checkContent(subjectParameter,subjectReportBuffer, failures);
if(!subjectIsOK) {
appendErrorsToList(SUBJECT, errorReport, subjectReportBuffer);
}
tagCheck.done();
if( fromIsOK && subjectIsOK) {
Hashtable<String, Object> output = generateBackEndPreview(aMailing.getId(), aForm.getPreviewCustomerID());
String header = (String) output.get(Preview.ID_HEAD);
if( header != null) {
aForm.setSenderPreview(PreviewHelper.getFrom(header));
aForm.setSubjectPreview(PreviewHelper.getSubject(header));
} else {
aForm.setSenderPreview(fromParameter);
aForm.setSubjectPreview(subjectParameter);
}
// don't know why we need this here, but no time to figure it out
aForm.setEmailFormat(aMailing.getEmailParam().getMailFormat());
aForm.setMailinglistID(aMailing.getMailinglistID());
} else {
throw new AgnTagException("error.template.dyntags", errorReport);
}
}
}
/**
* Creates report about errors in dynamic tags.
* @param blockName name of content block with invalid content
* @param errorReports list of messages about parsing errors (is changing inside the method)
* @param templateReport content with errors
*/
protected void appendErrorsToList(String blockName, List<String[]> errorReports,
StringBuffer templateReport) {
Map<String,String> tagsWithErrors = PreviewHelper.getTagsWithErrors(templateReport);
for(Entry<String,String> entry:tagsWithErrors.entrySet()) {
String[] errorRow = new String[3];
errorRow[0] = blockName; // block
errorRow[1] = entry.getKey(); // tag
errorRow[2] = entry.getValue(); // value
errorReports.add(errorRow);
}
List<String> errorsWithoutATag = PreviewHelper.getErrorsWithoutATag(templateReport);
for(String error:errorsWithoutATag){
String[] errorRow = new String[3];
errorRow[0] = blockName;
errorRow[1] = "";
errorRow[2] = error;
errorReports.add(errorRow);
}
}
/**
* Loads sent mailing statistics from database.
* @param aForm MailingSendForm object
* @param req HTTP request
* @throws Exception
*/
protected void loadSendStats(MailingSendForm aForm, HttpServletRequest req) throws Exception {
int numText=0;
int numHtml=0;
int numOffline=0;
int numTotal=0;
StringBuffer sqlSelection=new StringBuffer(" ");
Target aTarget=null;
boolean isFirst=true;
int numTargets=0;
String tmpOp = "AND ";
Mailing aMailing=mailingDao.getMailing(aForm.getMailingID(), AgnUtils.getAdmin(req).getCompanyID());
if(aMailing.getTargetMode()==Mailing.TARGET_MODE_OR) {
tmpOp = "OR ";
}
if(aForm.getTargetGroups()!=null && aForm.getTargetGroups().size() > 0) {
Iterator<Integer> aIt=aForm.getTargetGroups().iterator();
while(aIt.hasNext()) {
aTarget = targetDao.getTarget((aIt.next()).intValue(), AgnUtils.getAdmin(req).getCompanyID());
if(aTarget!=null) {
if(isFirst) {
isFirst=false;
} else {
sqlSelection.append(tmpOp);
}
sqlSelection.append("(" + aTarget.getTargetSQL() + ") ");
numTargets++;
}
}
if(numTargets>1) {
sqlSelection.insert(0, " AND (");
} else {
sqlSelection.insert(0, " AND ");
}
if(!isFirst && numTargets>1) {
sqlSelection.append(") ");
}
}
String sqlStatement="SELECT count(*), bind.mediatype, cust.mailtype FROM customer_" + AgnUtils.getAdmin(req).getCompanyID() + "_tbl cust, customer_" +
AgnUtils.getAdmin(req).getCompanyID() + "_binding_tbl bind WHERE bind.mailinglist_id=" + aMailing.getMailinglistID() +
" AND cust.customer_id=bind.customer_id" + sqlSelection.toString() + " AND bind.user_status=1 GROUP BY bind.mediatype, cust.mailtype";
Connection con=DataSourceUtils.getConnection(dataSource);
try {
Statement stmt=con.createStatement();
ResultSet rset=stmt.executeQuery(sqlStatement);
while(rset.next()){
switch(rset.getInt(2)) {
case 0:
switch(rset.getInt(3)) {
case 0: // nur Text
numText+=rset.getInt(1);
break;
case 1: // Online-HTML
if(aMailing.getEmailParam().getMailFormat()==0) { // nur Text-Mailing
numText+=rset.getInt(1);
} else {
numHtml+=rset.getInt(1);
}
break;
case 2: // Offline-HTML
if(aMailing.getEmailParam().getMailFormat()==0) { // nur Text-Mailing
numText+=rset.getInt(1);
}
if(aMailing.getEmailParam().getMailFormat()==1) { // nur Text/Online-HTML-Mailing
numHtml+=rset.getInt(1);
}
if(aMailing.getEmailParam().getMailFormat()==2) { // alle Formate
numOffline+=rset.getInt(1);
}
break;
}
break;
default:
aForm.setSendStat(rset.getInt(2), rset.getInt(1));
}
}
rset.close();
stmt.close();
} catch ( Exception e) {
DataSourceUtils.releaseConnection(con, dataSource);
logger.error("loadSendStats: " + e);
logger.error("SQL: " + sqlStatement);
throw new Exception("SQL-Error: " + e);
}
DataSourceUtils.releaseConnection(con, dataSource);
numTotal+=numText;
numTotal+=numHtml;
numTotal+=numOffline;
aForm.setSendStatText(numText);
aForm.setSendStatHtml(numHtml);
aForm.setSendStatOffline(numOffline);
aForm.setSendStat(0, numTotal);
}
/**
* Generates mailing preview.
* @param mailingID id of mailing
* @param customerID id of customer
* @return Hashtable object contains mailing preview
*/
public Hashtable<String, Object> generateBackEndPreview(int mailingID,int customerID) {
Preview preview = previewFactory.createPreview();
Hashtable<String,Object> output = preview.createPreview (mailingID,customerID, false);
preview.done();
return output;
}
/**
* Checks content of dynamic tags; prepares error report and raises AgnTagException.
* @param template mailing template with dynamic tags
* @param dynTagsMap map of dynamic tags
* @param mailingID id of mailing
* @throws Exception
*/
protected void analyzePreview(String template, Map<String, DynamicTag> dynTagsMap,
int mailingID) throws Exception {
Set<String> dynTagsKeys = dynTagsMap.keySet();
List<String[]> errorReports = new ArrayList<String[]>();
Vector<String> outFailures = new Vector<String>();
TAGCheck tagCheck =tagCheckFactory.createTAGCheck(mailingID);
StringBuffer templateReport = new StringBuffer();
if( !tagCheck.checkContent(template,templateReport,outFailures)) {
appendErrorsToList(TEMPLATE,errorReports, templateReport);
}
for(String dynTagKey:dynTagsKeys) {
DynamicTag tag = dynTagsMap.get(dynTagKey);
Map<String, DynamicTagContent> tagContentMap = tag.getDynContent();
Set<String> tagContentKeys = tagContentMap.keySet();
for(String dynContentKey:tagContentKeys) {
DynamicTagContent content = tagContentMap.get(dynContentKey);
{
StringBuffer contentOutReport = new StringBuffer();
if(!tagCheck.checkContent(content.getDynContent(), contentOutReport, outFailures)) {
appendErrorsToList(tag.getDynName(), errorReports, contentOutReport);
}
}
}
}
throw new AgnTagException("error.template.dyntags", errorReports);
}
public TargetDao getTargetDao() {
return targetDao;
}
public void setTargetDao(TargetDao targetDao) {
this.targetDao = targetDao;
}
public MailingDao getMailingDao() {
return mailingDao;
}
public void setMailingDao(MailingDao mailingDao) {
this.mailingDao = mailingDao;
}
public MailingComponentDao getMailingComponentDao() {
return mailingComponentDao;
}
public void setMailingComponentDao(MailingComponentDao mailingComponentDao) {
this.mailingComponentDao = mailingComponentDao;
}
public LinkcheckService getLinkcheckService() {
return linkcheckService;
}
public void setLinkcheckService(LinkcheckService linkcheckService) {
this.linkcheckService = linkcheckService;
}
public void setMailingFactory(MailingFactory mailingFactory) {
this.mailingFactory = mailingFactory;
}
public DeliveryStatFactory getDeliveryStatFactory() {
return deliveryStatFactory;
}
public void setDeliveryStatFactory(DeliveryStatFactory deliveryStatFactory) {
this.deliveryStatFactory = deliveryStatFactory;
}
public MaildropEntryFactory getMaildropEntryFactory() {
return maildropEntryFactory;
}
public void setMaildropEntryFactory(MaildropEntryFactory maildropEntryFactory) {
this.maildropEntryFactory = maildropEntryFactory;
}
public MailinglistDao getMailinglistDao() {
return mailinglistDao;
}
public void setMailinglistDao(MailinglistDao mailinglistDao) {
this.mailinglistDao = mailinglistDao;
}
public TAGCheckFactory getTagCheckFactory() {
return tagCheckFactory;
}
public void setTagCheckFactory(TAGCheckFactory tagCheckFactory) {
this.tagCheckFactory = tagCheckFactory;
}
public DataSource getDataSource() {
return dataSource;
}
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
public PreviewFactory getPreviewFactory() {
return previewFactory;
}
public void setPreviewFactory(PreviewFactory previewFactory) {
this.previewFactory = previewFactory;
}
public RecipientDao getRecipientDao() {
return recipientDao;
}
public void setRecipientDao(RecipientDao recipientDao) {
this.recipientDao = recipientDao;
}
public org.springframework.web.context.WebApplicationContext getApplicationContext(HttpServletRequest request) {
WebApplicationContext webApplicationContext = getWebApplicationContext();
if (webApplicationContext == null) {
webApplicationContext = WebApplicationContextUtils.getRequiredWebApplicationContext(request.getSession().getServletContext());
}
return webApplicationContext;
}
public void setTargetService( TargetService targetService) {
this.targetService = targetService;
}
}