Java tutorial
/********************************************************************************* * 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) 2009 AGNITAS AG. All Rights * Reserved. * * Contributor(s): AGNITAS AG. ********************************************************************************/ package org.agnitas.web; import java.beans.IntrospectionException; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.util.*; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.agnitas.beans.Admin; import org.agnitas.beans.ColumnMapping; import org.agnitas.beans.CustomerImportStatus; import org.agnitas.beans.DatasourceDescription; import org.agnitas.beans.ImportProfile; import org.agnitas.beans.Mailinglist; import org.agnitas.beans.ProfileRecipientFields; import org.agnitas.dao.ImportProfileDao; import org.agnitas.dao.ImportRecipientsDao; import org.agnitas.dao.MailinglistDao; import org.agnitas.service.ImportErrorRecipientQueryWorker; import org.agnitas.service.ImportRecipientsAssignMailinglistsWorker; import org.agnitas.service.ImportRecipientsProcessWorker; import org.agnitas.service.NewImportWizardService; import org.agnitas.service.csv.Toolkit; import org.agnitas.service.impl.CSVColumnState; import org.agnitas.service.impl.ImportWizardContentParseException; import org.agnitas.service.impl.NewImportWizardServiceImpl; import org.agnitas.util.AgnUtils; import org.agnitas.util.EmailAttachment; import org.agnitas.util.ImportCsvGenerator; import org.agnitas.util.ImportReportEntry; import org.agnitas.util.ImportUtils; import org.agnitas.util.importvalues.Charset; import org.agnitas.util.importvalues.DateFormat; import org.agnitas.util.importvalues.ImportMode; import org.agnitas.util.importvalues.Separator; import org.agnitas.util.importvalues.TextRecognitionChar; import org.agnitas.web.forms.NewImportWizardForm; import org.apache.commons.io.FileUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.validator.GenericValidator; import org.apache.commons.validator.ValidatorResults; 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.apache.struts.upload.FormFile; import org.displaytag.pagination.PaginatedList; import org.springframework.context.ApplicationContext; import org.springframework.jdbc.datasource.SingleConnectionDataSource; /** * @author Viktor Gema */ public class NewImportWizardAction extends ImportBaseFileAction { private static final transient Logger logger = Logger.getLogger(NewImportWizardAction.class); public static final String FUTURE_TASK1 = "IMPORT_RECIPIENT_LIST"; public static final String FUTURE_TASK2 = "IMPORT_RECIPIENT_PROCESS"; public static final String FUTURE_TASK3 = "IMPORT_RECIPIENT_WORKER"; public static final int ACTION_START = 1; public static final int ACTION_PREVIEW = 2; public static final int ACTION_PROCEED = 3; public static final int ACTION_ERROR_EDIT = 4; public static final int ACTION_MLISTS = 5; public static final int ACTION_RESULT_PAGE = 6; public static final int ACTION_DOWNLOAD_CSV_FILE = 7; public static final int ACTION_MLISTS_SAVE = 8; /** * 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> * Error and success messages are set during the action to give the user * a feedback in the forwarded web component.<br> * <br> * ACTION_START: Loads the list of all available profiles for import and ID of default import profile for current user.<br> * Resets import status and resultPagePrepared property of form. Forwards to "start". * <br><br> * ACTION_PREVIEW: performs general validation before starting recipients import.<br> * Initializes import helper with initial values; <br> * Created unique import ID and sets it to import profile; <br> * If csv-file is missing or there are no import profiles in system - forwards to "start" with corresponding * error messages.<br> * If import profile doesn't match the csv-file or has no key column in its column mappings forwards to * "profile_edit" with corresponding error messages.<br> * If validation is passed loads first 20 recipients from csv-file for preview, loads mailinglists into form, * chooses mailinglist message according to import mode, forwards to "preview".<br> * If the session parameter "IMPORT_KEY_COLUMNS" is set - import ignores key column from import profile and uses * list of key columns from "IMPORT_KEY_COLUMNS" (example "email, firstname, lastname"). The values from this * session parameter is set to import profile. * <br><br> * ACTION_PROCEED:<br> * In general performs recipient import using settings from selected import profile.<br><br> * If the FutureHolder doesn't contain yet the recipient import worker:<br> * Checks if there are mailinglists selected. If the current import mode needs mailinglists and no list is * selected - forwards to "preview" with appropriate error message. Stores the list of mailinglists to assign in * form. Creates import recipient worker and puts it to FutureHolder. Stores import parameters (importMode, * separator, delimiter etc.) to status property of form.<br><br> * If FutureHolder is finished and there are errors for edit error page - forwards to error editing page and * also checks for import limits.<br><br> * If FutureHolder is finished and there are no errors for edit error page: puts worker for assigning * mailinglists into FutureHolder (and forwards to "progress"), if that FutureHolder is finished - removes * temporary table of import recipients from DB, removes stored csv, forwards to "result_page"; if it is still * running - increases refresh rate and forwards to "progress". Also checks for import limits.<br><br> * If FutureHolder is still running - increases refresh time and forwards to "progress"<br><br> * If there were errors during the import - removed the recipient import worker from the FutureHolder and * forwards to "preview" page with error messages. * <br><br> * ACTION_ERROR_EDIT: * If the FutureHolder doesn't have task running and there's attribute "recipientsInCurrentTable" in request - * validates recipient fixed on error edit page and performs the import for fixed recipients.<br> * If there are still errors for error-edit-page - forwards to error-edit page. If there are no errors left - * prepares result page: calls future holder for assigning mailing lists and forwards to "progress" or * "result_page" depending on FutureHolder finished or not. * <br><br> * ACTION_MLISTS: puts worker for assigning mailinglists into FutureHolder (and forwards to "progress"), if that * FutureHolder is finished - removes temporary table of import recipients from DB, removes stored csv, * forwards to "result_page"; if it is still running - increases refresh rate and forwards to "progress". * <br><br> * ACTION_DOWNLOAD_CSV_FILE: checks what type of file user wants to download. Gets the file depending on that type. * Writes that file to response (for user to download). Forwards to null destination. * <br><br> * Any other ACTION_* would cause a forward to null * <br><br> * If current destination is "error_edit":<br> * If the FutureHolder is not running yet - puts there a worker for getting invalid recipients from temporary table * containing beans of import-recipients from csv-file<br> * If the FutureHolder is not finished yet - increases refresh time and forwards to "loading"<br> * If FutureHolder is finished - puts found invalid recipients to request and to session attribute * "recipientsInCurrentTable", sets the total number of invalid recipients to form, removes current task from * FutureHolder * <br><br> * @param form data for the action filled by the jsp * @param req request from jsp <br> * If the request parameter "start_proceed" is set - changes action to ACTION_PREVIEW.<br> * If the request parameter "preview_back" is set - changes action to ACTION_START.<br> * If the request parameter "preview_proceed" is set - changes action to ACTION_PROCEED.<br> * If the request parameter "edit_page_save" is set - changes action to ACTION_ERROR_EDIT.<br> * @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 */ public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException { super.execute(mapping, form, req, res); AbstractMap<String, Object> futureHolder = (AbstractMap<String, Object>) getBean("futureHolder"); String futureKeyList = FUTURE_TASK1 + "@" + req.getSession(false).getId(); String futureKeyProcess = FUTURE_TASK2 + "@" + req.getSession(false).getId(); String futureKeyWorker = FUTURE_TASK3 + "@" + req.getSession(false).getId(); // Validate the request parameters specified by the user NewImportWizardForm aForm = null; ActionMessages errors = new ActionMessages(); ActionForward destination = null; ApplicationContext aContext = this.getWebApplicationContext(); if (!AgnUtils.isUserLoggedIn(req)) { return mapping.findForward("logon"); } if (form != null) { aForm = (NewImportWizardForm) form; } else { aForm = new NewImportWizardForm(); } if (logger.isInfoEnabled()) logger.info("NewImportWizard action: " + aForm.getAction()); if (AgnUtils.parameterNotEmpty(req, "start_proceed")) { aForm.setAction(NewImportWizardAction.ACTION_PREVIEW); } if (AgnUtils.parameterNotEmpty(req, "preview_back")) { aForm.setAction(NewImportWizardAction.ACTION_START); } if (AgnUtils.parameterNotEmpty(req, "preview_proceed")) { aForm.setAction(NewImportWizardAction.ACTION_PROCEED); } if (req.getParameter("edit_page_save") != null) { aForm.setAction(NewImportWizardAction.ACTION_ERROR_EDIT); } if (!allowed("wizard.import", req)) { errors.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage("error.permissionDenied")); saveErrors(req, errors); return null; } try { NewImportWizardService importWizardHelper = aForm.getImportWizardHelper(); switch (aForm.getAction()) { case NewImportWizardAction.ACTION_START: final Map<Integer, String> importProfiles = new HashMap<Integer, String>(); aForm.setDefaultProfileId(AgnUtils.getAdmin(req).getDefaultImportProfileID()); final List<ImportProfile> importProfileList = getProfileList(req); for (ImportProfile importProfile : importProfileList) { importProfiles.put(importProfile.getId(), importProfile.getName()); } aForm.setImportProfiles(importProfiles); aForm.setStatus((CustomerImportStatus) getWebApplicationContext().getBean("CustomerImportStatus")); destination = mapping.findForward("start"); aForm.setResultPagePrepared(false); break; case NewImportWizardAction.ACTION_PREVIEW: if (!aForm.getHasFile() && (aForm.getCsvFile() == null || StringUtils.isEmpty(aForm.getCsvFile().getFileName()))) { errors.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage("error.import.no_file")); destination = mapping.findForward("start"); } else if (aForm.getImportProfiles().size() == 0) { errors.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage("error.import.no_profile")); destination = mapping.findForward("start"); } else { boolean profileFits = initImportWizardHelper(req, aForm, errors); boolean keyColumnValid = isProfileKeyColumnValid(aForm, errors); logImportStart(aForm); if ((!profileFits || !keyColumnValid) && !errors.isEmpty()) { HttpSession session = req.getSession(); session.setAttribute(ImportProfileAction.IMPORT_PROFILE_ERRORS_KEY, errors); session.setAttribute(ImportProfileAction.IMPORT_PROFILE_ID_KEY, aForm.getDefaultProfileId()); destination = mapping.findForward("profile_edit"); } else if (!errors.isEmpty()) { destination = mapping.findForward("start"); } else { aForm.setPreviewParsedContent(importWizardHelper.getPreviewParsedContent(errors)); if (!errors.isEmpty()) { destination = mapping.findForward("start"); break; } aForm.setAllMailingLists(getAllMailingLists(req)); aForm.setMailinglistAddMessage(createMailinglistAddMessage(aForm)); destination = mapping.findForward("preview"); } } break; case NewImportWizardAction.ACTION_PROCEED: aForm.setAction(NewImportWizardAction.ACTION_PROCEED); destination = mapping.findForward("progress"); if (!futureHolder.containsKey(futureKeyProcess) && aForm.getErrorsDuringImport() == null) { List<Integer> assignedLists = getAssignedMailingLists(req, aForm); if (!ignoreEmptyAssignedList()) { int importMode = aForm.getImportWizardHelper().getImportProfile().getImportMode(); if ((importMode == ImportMode.ADD.getIntValue() || importMode == ImportMode.ADD_AND_UPDATE.getIntValue()) && assignedLists.size() == 0) { errors.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage("error.import.no_mailinglist")); destination = mapping.findForward("preview"); break; } } aForm.setListsToAssign(assignedLists); final ImportRecipientsProcessWorker worker = new ImportRecipientsProcessWorker( importWizardHelper); ImportProfile importProfile = aForm.getImportWizardHelper().getImportProfile(); futureHolder.put(futureKeyProcess, importRecipientsProcess(aForm, req, aContext, worker, importProfile)); futureHolder.put(futureKeyWorker, worker); String charset = Charset.getPublicValue(importProfile.getCharset()); String separator = Separator.getValue(importProfile.getSeparator()); int mode = importProfile.getImportMode(); int doublette = importProfile.getCheckForDuplicates(); int nullValues = importProfile.getNullValuesAction(); String recognitionChar = TextRecognitionChar.getValue(importProfile.getTextRecognitionChar()); aForm.getStatus().setCharset(charset); aForm.getStatus().setSeparator(separator); aForm.getStatus().setMode(mode); aForm.getStatus().setDoubleCheck(doublette); aForm.getStatus().setIgnoreNull(nullValues); aForm.getStatus().setDelimiter(recognitionChar); } if (aForm.getErrorsDuringImport() != null) { errors.add(aForm.getErrorsDuringImport()); destination = mapping.findForward("preview"); futureHolder.remove(futureKeyProcess); futureHolder.remove(futureKeyWorker); aForm.setRefreshMillis(RecipientForm.DEFAULT_REFRESH_MILLIS); aForm.setErrorsDuringImport(null); break; } if (futureHolder.containsKey(futureKeyWorker) && futureHolder.get(futureKeyWorker) != null) { final ActionMessage message = ((ImportRecipientsProcessWorker) futureHolder .get(futureKeyWorker)).getMessage(); if (message != null) { errors.add(ActionMessages.GLOBAL_MESSAGE, message); futureHolder.remove(futureKeyProcess); futureHolder.remove(futureKeyWorker); aForm.setRefreshMillis(RecipientForm.DEFAULT_REFRESH_MILLIS); destination = mapping.findForward("preview"); break; } } if (futureHolder.containsKey(futureKeyProcess) && ((Future) futureHolder.get(futureKeyProcess)).isDone()) { futureHolder.remove(futureKeyProcess); futureHolder.remove(futureKeyWorker); if (importWizardHelper.isPresentErrorForErrorEditPage()) { destination = mapping.findForward("error_edit"); } else { destination = prepareResultPage(mapping, req, aForm, futureHolder, futureKeyProcess); } destination = checkImportLimits(mapping, errors, destination, importWizardHelper, aForm, req, futureHolder, futureKeyProcess); if ("start".equals(destination.getName())) { futureHolder.remove(futureKeyProcess); futureHolder.remove(futureKeyWorker); } aForm.setRefreshMillis(RecipientForm.DEFAULT_REFRESH_MILLIS); } else { if (aForm.getRefreshMillis() < 100000) { // raise the refresh time aForm.setRefreshMillis(aForm.getRefreshMillis() + 50); } //aForm.set } break; case NewImportWizardAction.ACTION_ERROR_EDIT: if (!futureHolder.containsKey(futureKeyList) && (PaginatedList) req.getSession().getAttribute("recipientsInCurrentTable") != null) { final HashMap<String, ProfileRecipientFields> mapRecipientsFromTable = new HashMap<String, ProfileRecipientFields>(); final PaginatedList recipientsFromTable = (PaginatedList) req.getSession() .getAttribute("recipientsInCurrentTable"); for (Object object : recipientsFromTable.getList()) { final Map dynaBean = (Map) object; final ProfileRecipientFields recipient = (ProfileRecipientFields) dynaBean .get(NewImportWizardService.ERROR_EDIT_RECIPIENT_EDIT_RESERVED); mapRecipientsFromTable.put(recipient.getTemporaryId(), recipient); } final Map<String, String> changedRecipients = getChangedRecipients(req); for (String key : changedRecipients.keySet()) { final String temporaryId = key.substring(0, key.indexOf("/RESERVED/")); final String propertyName = key.substring(key.indexOf("/RESERVED/") + 10, key.length()); final ProfileRecipientFields recipient = mapRecipientsFromTable.get(temporaryId); Toolkit.setValueFromBean(recipient, propertyName, changedRecipients.get(key)); } importWizardHelper.setBeansAfterEditOnErrorEditPage( new ArrayList<ProfileRecipientFields>(mapRecipientsFromTable.values())); importWizardHelper.doValidate(true); } if (importWizardHelper.isPresentErrorForErrorEditPage()) { destination = mapping.findForward("error_edit"); } else { destination = prepareResultPage(mapping, req, aForm, futureHolder, futureKeyProcess); } destination = checkImportLimits(mapping, errors, destination, importWizardHelper, aForm, req, futureHolder, futureKeyProcess); break; case NewImportWizardAction.ACTION_MLISTS: destination = prepareResultPage(mapping, req, aForm, futureHolder, futureKeyProcess); break; case NewImportWizardAction.ACTION_DOWNLOAD_CSV_FILE: File outfile = null; if (aForm.getDownloadFileType() == NewImportWizardService.RECIPIENT_TYPE_VALID) { outfile = aForm.getValidRecipientsFile(); } else if (aForm.getDownloadFileType() == NewImportWizardService.RECIPIENT_TYPE_INVALID) { outfile = aForm.getInvalidRecipientsFile(); } else if (aForm.getDownloadFileType() == NewImportWizardService.RECIPIENT_TYPE_FIXED_BY_HAND) { outfile = aForm.getFixedRecipientsFile(); } else if (aForm .getDownloadFileType() == NewImportWizardService.RECIPIENT_TYPE_DUPLICATE_RECIPIENT) { outfile = aForm.getDuplicateRecipientsFile(); } else if (aForm.getDownloadFileType() == NewImportWizardService.RESULT_TYPE) { outfile = aForm.getResultFile(); } transferFile(res, errors, outfile); destination = null; break; } } catch (Exception e) { logger.error("execute: " + e + "\n" + AgnUtils.getStackTrace(e)); errors.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage("error.exception")); } if (destination != null && "error_edit".equals(destination.getName())) { try { setNumberOfRows(req, aForm); destination = mapping.findForward("loading"); if (!futureHolder.containsKey(futureKeyList)) { futureHolder.put(futureKeyList, getRecipientListFuture(req, aContext, aForm)); } if (futureHolder.containsKey(futureKeyList) && ((Future) futureHolder.get(futureKeyList)).isDone()) { req.setAttribute("recipientList", ((Future) futureHolder.get(futureKeyList)).get()); req.getSession().setAttribute("recipientsInCurrentTable", ((Future) futureHolder.get(futureKeyList)).get()); destination = mapping.findForward("error_edit"); aForm.setAll( ((PaginatedList) ((Future) futureHolder.get(futureKeyList)).get()).getFullListSize()); futureHolder.remove(futureKeyList); aForm.setRefreshMillis(RecipientForm.DEFAULT_REFRESH_MILLIS); } else { if (aForm.getRefreshMillis() < 1000) { // raise the refresh time aForm.setRefreshMillis(aForm.getRefreshMillis() + 50); } aForm.setError(false); } } catch (Exception e) { logger.error("recipientList: " + e + "\n" + AgnUtils.getStackTrace(e)); errors.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage("error.exception")); aForm.setError(true); // do not refresh when an error has been occurred } } // Report any errors we have discovered back to the original form if (!errors.isEmpty() && destination != null) { saveErrors(req, errors); if (destination.getName().equals("progress")) { aForm.setErrorsDuringImport(errors); } // return new ActionForward(mapping.getForward()); } return destination; } private void logImportStart(NewImportWizardForm aForm) { Random random = new Random(); int importId = Math.abs(random.nextInt()); aForm.getImportWizardHelper().getImportProfile().setImportId(importId); if (logger.isInfoEnabled()) logger.info("Import ID: " + importId + "\nImport Profile ID: " + aForm.getImportWizardHelper().getImportProfile().getId() + "\nImport Profile Name: " + aForm.getImportWizardHelper().getImportProfile().getName() + "\nFile Name: " + aForm.getCurrentFileName()); } protected boolean ignoreEmptyAssignedList() { return false; } private ActionForward prepareResultPage(ActionMapping mapping, HttpServletRequest req, NewImportWizardForm aForm, AbstractMap<String, Object> futureHolder, String futureKeyProcess) { if (!aForm.isResultPagePrepared()) { if (!futureHolder.containsKey(futureKeyProcess) && aForm.getErrorsDuringImport() == null) { futureHolder.put(futureKeyProcess, assignMailinglists(aForm, req)); aForm.setAction(NewImportWizardAction.ACTION_MLISTS); return mapping.findForward("progress"); } if (futureHolder.containsKey(futureKeyProcess) && ((Future) futureHolder.get(futureKeyProcess)).isDone()) { removeTemporaryTable(req, aForm); removeStoredCsvFile(req); aForm.setResultPagePrepared(true); futureHolder.remove(futureKeyProcess); aForm.setRefreshMillis(RecipientForm.DEFAULT_REFRESH_MILLIS); NewImportWizardService importWizardHelper = aForm.getImportWizardHelper(); if (importWizardHelper != null) { destroyTemporaryConnection(importWizardHelper.getImportRecipientsDao()); } FormFile file = aForm.getCsvFile(); Admin admin = AgnUtils.getAdmin(req); String userName = admin != null ? admin.getUsername() : ""; String fileName = file != null ? file.getFileName() : ""; AgnUtils.userlogger().info(userName + ": do import from file " + fileName); return mapping.findForward("result_page"); } else { if (aForm.getRefreshMillis() < 100000) { // raise the refresh time aForm.setRefreshMillis(aForm.getRefreshMillis() + 50); } } aForm.setAction(NewImportWizardAction.ACTION_MLISTS); return mapping.findForward("progress"); } NewImportWizardService importWizardHelper = aForm.getImportWizardHelper(); if (importWizardHelper != null) { destroyTemporaryConnection(importWizardHelper.getImportRecipientsDao()); } return mapping.findForward("result_page"); } private void destroyTemporaryConnection(ImportRecipientsDao importRecipientsDao) { if (importRecipientsDao != null) { SingleConnectionDataSource temporaryConnection = importRecipientsDao.getTemporaryConnection(); if (temporaryConnection != null) { temporaryConnection.destroy(); importRecipientsDao.setTemporaryConnection(null); } } } private Object assignMailinglists(NewImportWizardForm aForm, HttpServletRequest req) { ExecutorService service = (ExecutorService) getWebApplicationContext().getBean("workerExecutorService"); ImportProfileDao profileDao = (ImportProfileDao) getWebApplicationContext().getBean("ImportProfileDao"); Locale locale = (Locale) req.getSession().getAttribute(org.apache.struts.Globals.LOCALE_KEY); TimeZone zone = TimeZone.getTimeZone(AgnUtils.getAdmin(req).getAdminTimezone()); final Future future = service.submit(new ImportRecipientsAssignMailinglistsWorker(aForm, profileDao, getMessageSourceAccessor(), locale, zone)); return future; } /** * Method removes temporary recipient table used by import * @param req request * @param aForm form */ private void removeTemporaryTable(HttpServletRequest req, NewImportWizardForm aForm) { final String prefix = "cust_" + AgnUtils.getAdmin(req).getAdminID() + "_tmp_"; final String tableName = prefix + aForm.getDatasourceId() + "_tbl"; aForm.getImportWizardHelper().getImportRecipientsDao().removeTemporaryTable(tableName, req.getSession().getId()); } /** * Method checks if current import reaches the recipient limits (recipients * per import; max recipient number in DB) * @param mapping action mapping * @param errors errors * @param destination the current destination * @param importWizardHelper the import service * @param aForm the form * @param req request * @param futureHolder *@param futureKeyProcess @return the action destination */ private ActionForward checkImportLimits(ActionMapping mapping, ActionMessages errors, ActionForward destination, NewImportWizardService importWizardHelper, NewImportWizardForm aForm, HttpServletRequest req, AbstractMap<String, Object> futureHolder, String futureKeyProcess) { if (importWizardHelper.isImportLimitReached()) { errors.add("global", new ActionMessage("error.import.too_many_records", AgnUtils.getDefaultValue("import.maxRows"))); destination = mapping.findForward("start"); removeTemporaryTable(req, aForm); } else if (importWizardHelper.isRecipientLimitReached()) { errors.add("global", new ActionMessage("error.import.maxCount")); destination = prepareResultPage(mapping, req, aForm, futureHolder, futureKeyProcess); } else if (importWizardHelper.isNearLimit()) { errors.add("global", new ActionMessage("warning.import.maxCount")); } return destination; } /** * Method checks if profile has key column in its column mappings * * @param aForm a form * @param errors errors to add error to if key column is not imported * @return true if key column is contained in one of column mappings */ private boolean isProfileKeyColumnValid(NewImportWizardForm aForm, ActionMessages errors) { ImportProfile profile = aForm.getImportWizardHelper().getImportProfile(); List<ColumnMapping> columns = profile.getColumnMapping(); List<String> keyColumns = profile.getKeyColumns(); List<String> dbColumns = new ArrayList<String>(); for (ColumnMapping column : columns) { dbColumns.add(column.getDatabaseColumn()); } if (keyColumns.isEmpty()) { if (dbColumns.contains(profile.getKeyColumn())) { return true; } } else { if (dbColumns.containsAll(keyColumns)) { return true; } } errors.add("profile", new ActionMessage("error.import.keycolumn_not_imported")); return false; } /** * Method takes changed recipient fields that were changed on error-edit-page * Gets changed data from request * * @param request request * @return result map (temporary id -> new value) */ private Map<String, String> getChangedRecipients(HttpServletRequest request) { Map<String, String> result = new HashMap<String, String>(); Enumeration parameterNames = request.getParameterNames(); while (parameterNames.hasMoreElements()) { String pName = (String) parameterNames.nextElement(); String paramBeginStr = "changed_recipient_"; if (pName.startsWith(paramBeginStr)) { String id = pName.substring(paramBeginStr.length()); String value = request.getParameter(pName); result.put(id, value); } } return result; } /** * Method creates date format for error-edit-page calendar using import * profile date format * * @param aForm a form * @return date format for jscalendar */ private String createCalendarDateFormat(NewImportWizardForm aForm) { int dateFormat = aForm.getImportWizardHelper().getImportProfile().getDateFormat(); String csvFormat = DateFormat.getValue(dateFormat); csvFormat = csvFormat.replace("yyyy", "%Y"); csvFormat = csvFormat.replace("MM", "%m"); csvFormat = csvFormat.replace("dd", "%d"); csvFormat = csvFormat.replace("HH", "%H"); csvFormat = csvFormat.replace("mm", "%M"); csvFormat = csvFormat.replace("ss", "%S"); return csvFormat; } /** * Method creates message about assigning recipients to mailing lists * according to import mode for displaying in result page and in report * email * * @param aForm form * @return mailing list add message */ private String createMailinglistAddMessage(NewImportWizardForm aForm) { int importMode = aForm.getImportWizardHelper().getImportProfile().getImportMode(); String mlistAddedMessage = ""; if (importMode == ImportMode.ADD.getIntValue() || importMode == ImportMode.ADD_AND_UPDATE.getIntValue() || importMode == ImportMode.UPDATE.getIntValue()) { mlistAddedMessage = "import.result.subscribersAdded"; } else if (importMode == ImportMode.MARK_OPT_OUT.getIntValue() || importMode == ImportMode.TO_BLACKLIST.getIntValue()) { mlistAddedMessage = "import.result.subscribersUnsubscribed"; } else if (importMode == ImportMode.MARK_BOUNCED.getIntValue()) { mlistAddedMessage = "import.result.subscribersBounced"; } return mlistAddedMessage; } /** * Gets all mailing lists for current company id * * @param req request to take company id * @return all mailing lists for current company id */ private List<Mailinglist> getAllMailingLists(HttpServletRequest req) { MailinglistDao dao = (MailinglistDao) getWebApplicationContext().getBean("MailinglistDao"); List mailinglists = dao.getMailinglists(AgnUtils.getCompanyID(req)); return mailinglists; } /** * Gets list of mailing lists ids that were assigned on * assign-mailinglist-page; takes data from request * * @param req request * @param aForm form * @return ids of assigned mailing lists */ private List<Integer> getAssignedMailingLists(HttpServletRequest req, NewImportWizardForm aForm) { String aParam = null; List<Integer> mailingLists = new ArrayList<Integer>(); Enumeration e = req.getParameterNames(); while (e.hasMoreElements()) { aParam = (String) e.nextElement(); if (aParam.startsWith("agn_mlid_")) { mailingLists.add(Integer.valueOf(aParam.substring(9))); } } return mailingLists; } /** * Inits import wizard helper, checks if import profile matches the csv-file * * @param req request * @param aForm form * @param errors list of errors * @return true if no profile-match errors occured, false otherwise */ private boolean initImportWizardHelper(HttpServletRequest req, NewImportWizardForm aForm, ActionMessages errors) { NewImportWizardService importWizardHelper = aForm.getImportWizardHelper(); try { importWizardHelper.setProfileId(aForm.getDefaultProfileId()); importWizardHelper.setInputFile(getCurrentFile(req)); importWizardHelper.setAdminId(AgnUtils.getAdmin(req).getAdminID()); importWizardHelper.setCompanyId(AgnUtils.getCompanyID(req)); if (AgnUtils.getAdmin(req).permissionAllowed("recipient.gender.extended")) { importWizardHelper.setMaxGenderValue(NewImportWizardService.MAX_GENDER_VALUE_EXTENDED); } else { importWizardHelper.setMaxGenderValue(NewImportWizardService.MAX_GENDER_VALUE_BASIC); } importWizardHelper.validateImportProfileMatchGivenCVSFile(); String importKeyColumns = (String) req.getSession().getAttribute("IMPORT_KEY_COLUMNS"); if (!StringUtils.isEmpty(importKeyColumns)) { List<String> keyColumns = importWizardHelper.getImportProfile().getKeyColumns(); keyColumns.clear(); String[] columns = importKeyColumns.split(", "); keyColumns.addAll(Arrays.asList(columns)); // remove session var req.getSession().removeAttribute("IMPORT_KEY_COLUMNS"); } return true; } catch (ImportWizardContentParseException e) { aForm.setAction(NewImportWizardAction.ACTION_START); if (e.getParameterValue() != null) { errors.add("profile", new ActionMessage(e.getErrorMessageKey(), e.getParameterValue())); } else { errors.add("profile", new ActionMessage(e.getErrorMessageKey())); } return false; } catch (IOException e) { errors.add("csvFile", new ActionMessage("error.import.no_file")); return true; } } /** * Method transfers given file to action response (for user to download) * * @param response action response * @param errors errors * @param outfile file to transfer * @throws IOException exceptions that can occur while working with IO */ private void transferFile(HttpServletResponse response, ActionMessages errors, File outfile) throws IOException { if (outfile != null) { byte bytes[] = new byte[16384]; int len; FileInputStream instream = new FileInputStream(outfile); try { if (outfile.getName().endsWith(".zip")) { response.setContentType("application/zip"); } else if (outfile.getName().endsWith(".txt")) { response.setContentType("application/txt"); } response.setHeader("Content-Disposition", "attachment; filename=\"" + outfile.getName() + "\";"); response.setContentLength((int) outfile.length()); @SuppressWarnings("resource") // Do not close this stream, it's managed by the servlet container ServletOutputStream ostream = response.getOutputStream(); while ((len = instream.read(bytes)) != -1) { ostream.write(bytes, 0, len); } } finally { instream.close(); } } else { errors.add("global", new ActionMessage("error.export.file_not_ready")); } } /** * @param request request * @return list of import profiles for overview page with current company id */ private List<ImportProfile> getProfileList(HttpServletRequest request) throws InstantiationException, IllegalAccessException { ImportProfileDao profileDao = (ImportProfileDao) getWebApplicationContext().getBean("ImportProfileDao"); return profileDao.getImportProfilesByCompanyId(AgnUtils.getCompanyID(request)); } /** * Get a list of recipients according to your validation * * @param request * @param aContext * @param aForm * @return * @throws NumberFormatException * @throws IllegalAccessException * @throws InstantiationException * @throws java.util.concurrent.ExecutionException * * @throws InterruptedException */ public Future getRecipientListFuture(HttpServletRequest request, ApplicationContext aContext, NewImportWizardForm aForm) throws NumberFormatException, IllegalAccessException, InstantiationException, InterruptedException, ExecutionException, IntrospectionException, InvocationTargetException { //ImportRecipientsDao recipientDao = (ImportRecipientsDao) aContext.getBean("ImportRecipientsDao"); String sort = getSort(request, aForm); String direction = request.getParameter("dir"); int rownums = aForm.getNumberofRows(); if (direction == null) { direction = aForm.getOrder(); } else { aForm.setOrder(direction); } String pageStr = request.getParameter("page"); if (pageStr == null || "".equals(pageStr.trim())) { if (aForm.getPage() == null || "".equals(aForm.getPage().trim())) { aForm.setPage("1"); } pageStr = aForm.getPage(); } else { aForm.setPage(pageStr); } if (aForm.isNumberOfRowsChanged()) { aForm.setPage("1"); aForm.setNumberOfRowsChanged(false); pageStr = "1"; } final CSVColumnState[] columns = aForm.getImportWizardHelper().getColumns(); ExecutorService service = (ExecutorService) aContext.getBean("workerExecutorService"); Future future = service.submit(new ImportErrorRecipientQueryWorker( aForm.getImportWizardHelper().getImportRecipientsDao(), AgnUtils.getAdmin(request).getAdminID(), sort, direction, Integer.parseInt(pageStr), rownums, aForm.getAll(), columns, aForm.getImportWizardHelper().getStatus().getDatasourceID())); //return aForm.getImportWizardHelper().getImportRecipientsDao().getInvalidRecipientList(columns, sort, direction, Integer.parseInt(pageStr), rownums, 0, AgnUtils.getAdmin(request).getAdminID(), aForm.getImportWizardHelper().getStatus().getDatasourceID()); return future; } public Future importRecipientsProcess(NewImportWizardForm aForm, HttpServletRequest req, ApplicationContext aContext, ImportRecipientsProcessWorker worker, ImportProfile importProfile) { String calendarDateFormat = createCalendarDateFormat(aForm); aForm.setCalendarDateFormat(calendarDateFormat); //set datasource id final DatasourceDescription dsDescription = ImportUtils .getNewDatasourceDescription(AgnUtils.getCompanyID(req), aForm.getCurrentFileName(), aContext); aForm.getStatus().setDatasourceID(dsDescription.getId()); aForm.setDatasourceId(dsDescription.getId()); ImportUtils.createTemporaryTable(AgnUtils.getAdmin(req).getAdminID(), dsDescription.getId(), importProfile, req.getSession().getId(), aForm.getImportWizardHelper().getImportRecipientsDao()); ExecutorService service = (ExecutorService) aContext.getBean("workerExecutorService"); final Future future = service.submit(worker); return future; } /** * Initialize the list which keeps the current width of the columns, with a default value of '-1' * A JavaScript in the corresponding jsp will set the style.width of the column. * * @param size number of columns * @return */ protected List<String> getInitializedColumnWidthList(int size) { List<String> columnWidthList = new ArrayList<String>(); for (int i = 0; i < size; i++) { columnWidthList.add("-1"); } return columnWidthList; } protected String getSort(HttpServletRequest request, NewImportWizardForm aForm) { String sort = request.getParameter("sort"); if (sort == null) { sort = aForm.getSort(); } else { aForm.setSort(sort); } return sort; } /** * Method generates result recipient files (valid, invalid, fixed) * and stores them to form. If there are no recipients of some type * (i.e. invalid) the corresponding form file will be set to null * * @param request request * @param aForm a form */ private void generateResultFiles(HttpServletRequest request, NewImportWizardForm aForm) { // generate valid recipients file File validRecipients = createRecipientsCsv(request, aForm, new Integer[] { NewImportWizardService.RECIPIENT_TYPE_VALID, NewImportWizardService.RECIPIENT_TYPE_DUPLICATE_RECIPIENT }, "valid_recipients"); aForm.setValidRecipientsFile(validRecipients); // generate invalid recipietns file (invalid by wrong field values + other invalid: blacklisted etc.) File invalidRecipients = createRecipientsCsv(request, aForm, new Integer[] { NewImportWizardService.RECIPIENT_TYPE_INVALID, NewImportWizardService.RECIPIENT_TYPE_FIELD_INVALID }, "invalid_recipients"); aForm.setInvalidRecipientsFile(invalidRecipients); // generate fixed recipients file (fixed on error edit page) File fixedRecipients = createRecipientsCsv(request, aForm, new Integer[] { NewImportWizardService.RECIPIENT_TYPE_FIXED_BY_HAND }, "fixed_recipients"); aForm.setFixedRecipientsFile(fixedRecipients); // generate duplicate recipients file File duplicateRecipients = createRecipientsCsv(request, aForm, new Integer[] { NewImportWizardService.RECIPIENT_TYPE_DUPLICATE_IN_NEW_DATA_RECIPIENT, NewImportWizardService.RECIPIENT_TYPE_DUPLICATE_RECIPIENT }, "duplicate_recipients"); aForm.setDuplicateRecipientsFile(duplicateRecipients); } /** * Method generates recipients csv-file for the given types of recipients. * If there are no recipients of such type(s) in temporary table the * returned file will be null. * * @param request request * @param aForm a form * @param types types of recipients to include in csv-file * @param fileName file name start part (random number will be appended) * @return generated csv-file */ private File createRecipientsCsv(HttpServletRequest request, NewImportWizardForm aForm, Integer[] types, String fileName) { ImportRecipientsDao recipientDao = aForm.getImportWizardHelper().getImportRecipientsDao(); int adminId = AgnUtils.getAdmin(request).getAdminID(); int datasourceId = aForm.getStatus().getDatasourceID(); int recipientCount = recipientDao.getRecipientsCountByType(types, adminId, datasourceId); if (recipientCount == 0) { return null; } ImportProfileDao profileDao = (ImportProfileDao) getWebApplicationContext().getBean("ImportProfileDao"); ImportProfile profile = profileDao.getImportProfileById(aForm.getDefaultProfileId()); ImportCsvGenerator generator = new ImportCsvGenerator(); CSVColumnState[] columns = aForm.getImportWizardHelper().getColumns(); generator.createCsv(profile, columns, fileName); int page = 0; int rowNum = NewImportWizardService.BLOCK_SIZE; HashMap<ProfileRecipientFields, ValidatorResults> recipients = null; while (recipients == null || recipients.size() == rowNum) { recipients = recipientDao.getRecipientsByTypePaginated(types, page, rowNum, adminId, datasourceId); generator.writeDataToFile(recipients.keySet(), columns, profile); page++; } File file = generator.finishFileGeneration(); return file; } /** * Generates report data: import statistics, recipient files; sends report email * * @param request request * @param aForm a form */ private void generateReportData(HttpServletRequest request, NewImportWizardForm aForm) { CustomerImportStatus status = aForm.getImportWizardHelper().getStatus(); ImportProfile profile = aForm.getImportWizardHelper().getImportProfile(); generateResultStatistics(request, aForm, status); Collection<ImportReportEntry> reportEntries = generateReportData(status, profile); final Map<String, String> statusReportMap = localizeReportItems(request, reportEntries); aForm.getImportWizardHelper().log(aForm.getDatasourceId(), status.getInserted() + status.getUpdated(), ImportUtils.describeMap(statusReportMap)); aForm.setReportEntries(reportEntries); generateResultFiles(request, aForm); sendReportEmail(request, aForm); } private void generateResultStatistics(HttpServletRequest request, NewImportWizardForm aForm, CustomerImportStatus status) { ImportRecipientsDao dao = aForm.getImportWizardHelper().getImportRecipientsDao(); int adminId = AgnUtils.getAdmin(request).getAdminID(); int datasourceId = aForm.getDatasourceId(); Integer[] types = { NewImportWizardService.RECIPIENT_TYPE_FIELD_INVALID }; int page = 0; int rowNum = NewImportWizardService.BLOCK_SIZE; HashMap<ProfileRecipientFields, ValidatorResults> recipients = null; while (recipients == null || recipients.size() == rowNum) { recipients = dao.getRecipientsByTypePaginated(types, page, rowNum, adminId, datasourceId); for (ValidatorResults validatorResults : recipients.values()) { for (CSVColumnState column : aForm.getImportWizardHelper().getColumns()) { if (column.getImportedColumn()) { if (!ImportUtils.checkIsCurrentFieldValid(validatorResults, column.getColName())) { if (column.getColName().equals("email")) { status.addError(NewImportWizardServiceImpl.EMAIL_ERROR); } else if (column.getColName().equals("mailtype")) { status.addError(NewImportWizardServiceImpl.MAILTYPE_ERROR); } else if (column.getColName().equals("gender")) { status.addError(NewImportWizardServiceImpl.GENDER_ERROR); } else if (column.getType() == CSVColumnState.TYPE_DATE) { status.addError(NewImportWizardServiceImpl.DATE_ERROR); } else if (column.getType() == CSVColumnState.TYPE_NUMERIC) { status.addError(NewImportWizardServiceImpl.NUMERIC_ERROR); } } } } } page++; } } private Map<String, String> localizeReportItems(HttpServletRequest request, Collection<ImportReportEntry> reportEntries) { final Map<String, String> statusReportMap = new HashMap<String, String>(); for (ImportReportEntry entry : reportEntries) { final String messageKey = entry.getKey(); final String localizedMessage = getMessage(messageKey, request); statusReportMap.put(localizedMessage, entry.getValue()); } return statusReportMap; } /** * Sends import report if profile has emailForReport * * @param request request * @param aForm a form */ private void sendReportEmail(HttpServletRequest request, NewImportWizardForm aForm) { ImportProfileDao profileDao = (ImportProfileDao) getWebApplicationContext().getBean("ImportProfileDao"); ImportProfile profile = profileDao.getImportProfileById(aForm.getDefaultProfileId()); String address = profile.getMailForReport(); if (!GenericValidator.isBlankOrNull(address) && GenericValidator.isEmail(address)) { Locale locale = (Locale) request.getSession().getAttribute(org.apache.struts.Globals.LOCALE_KEY); ResourceBundle bundle = ResourceBundle.getBundle("messages", locale); Collection<EmailAttachment> attachments = new ArrayList<EmailAttachment>(); File invalidRecipientsFile = aForm.getInvalidRecipientsFile(); File fixedRecipientsFile = aForm.getFixedRecipientsFile(); EmailAttachment invalidRecipients = createZipAttachment(invalidRecipientsFile, "invalid_recipients.zip", bundle.getString("import.recipients.invalid")); if (invalidRecipients != null) { attachments.add(invalidRecipients); } EmailAttachment fixedRecipients = createZipAttachment(fixedRecipientsFile, "fixed_recipients.zip", bundle.getString("import.recipients.fixed")); if (fixedRecipients != null) { attachments.add(fixedRecipients); } EmailAttachment[] attArray = attachments.toArray(new EmailAttachment[] {}); String subject = bundle.getString("import.recipients.report"); String message = generateReportEmailBody(bundle, aForm); message = subject + ":\n" + message; ImportUtils.sendEmailWithAttachments(AgnUtils.getDefaultValue("import.report.from.address"), AgnUtils.getDefaultValue("import.report.from.name"), address, subject, message, attArray); } } /** * Generates body of report email * * @param bundle message resource bundle * @param aForm a form * @return body of email containing import statistics */ private String generateReportEmailBody(ResourceBundle bundle, NewImportWizardForm aForm) { String body = ""; for (ImportReportEntry entry : aForm.getReportEntries()) { body = body + bundle.getString(entry.getKey()) + ": " + entry.getValue() + "\n"; } for (Mailinglist mlist : aForm.getAssignedMailingLists()) { String mlistStr = mlist.getShortname() + ": " + aForm.getMailinglistAssignStats().get(mlist.getId()) + " " + bundle.getString(aForm.getMailinglistAddMessage()) + "\n"; body = body + mlistStr; } return body; } /** * Generates report statistics: errros, update information, datasource id * * @param status recipient import status * @param profile import profile * @return collection of statistics entries */ private Collection<ImportReportEntry> generateReportData(CustomerImportStatus status, ImportProfile profile) { Collection<ImportReportEntry> reportValues = new ArrayList<ImportReportEntry>(); reportValues.add(new ImportReportEntry("import.csv_errors_email", String.valueOf(status.getError(NewImportWizardService.EMAIL_ERROR)))); reportValues.add(new ImportReportEntry("import.csv_errors_blacklist", String.valueOf(status.getError(NewImportWizardService.BLACKLIST_ERROR)))); reportValues.add(new ImportReportEntry("import.csv_errors_double", String.valueOf(status.getError(NewImportWizardService.EMAILDOUBLE_ERROR)))); reportValues.add(new ImportReportEntry("import.csv_errors_numeric", String.valueOf(status.getError(NewImportWizardService.NUMERIC_ERROR)))); reportValues.add(new ImportReportEntry("import.csv_errors_mailtype", String.valueOf(status.getError(NewImportWizardService.MAILTYPE_ERROR)))); reportValues.add(new ImportReportEntry("import.csv_errors_gender", String.valueOf(status.getError(NewImportWizardService.GENDER_ERROR)))); reportValues.add(new ImportReportEntry("import.csv_errors_date", String.valueOf(status.getError(NewImportWizardService.DATE_ERROR)))); reportValues.add(new ImportReportEntry("import.csv_errors_linestructure", String.valueOf(status.getError(NewImportWizardService.STRUCTURE_ERROR)))); reportValues.add( new ImportReportEntry("import.RecipientsAllreadyinDB", String.valueOf(status.getAlreadyInDb()))); reportValues.add(new ImportReportEntry("import.result.imported", String.valueOf(status.getInserted()))); reportValues.add(new ImportReportEntry("import.result.updated", String.valueOf(status.getUpdated()))); if (profile.getImportMode() == ImportMode.ADD.getIntValue() || profile.getImportMode() == ImportMode.ADD_AND_UPDATE.getIntValue()) { reportValues.add( new ImportReportEntry("import.result.datasource_id", String.valueOf(status.getDatasourceID()))); } return reportValues; } /** * Creates attachment from zip-file * * @param recipientsFile file * @param name name of attachment * @param description attachment description * @return created attachment */ private EmailAttachment createZipAttachment(File recipientsFile, String name, String description) { EmailAttachment attachment; if (recipientsFile != null) { try { byte[] data = FileUtils.readFileToByteArray(recipientsFile); attachment = new EmailAttachment(name, data, "application/zip", description); return attachment; } catch (IOException e) { logger.error("Error creating attachment: " + AgnUtils.getStackTrace(e)); } } return null; } /** * Loads available mailing lists, creates mailing list add message * * @param mapping action mapping * @param req request * @param aForm form * @return destination to mailing list assignment page */ private ActionForward prepareMailingListPage(ActionMapping mapping, HttpServletRequest req, NewImportWizardForm aForm) { aForm.setAllMailingLists(getAllMailingLists(req)); aForm.setMailinglistAddMessage(createMailinglistAddMessage(aForm)); ActionForward destination = mapping.findForward("mailing_lists"); aForm.setAction(ACTION_MLISTS_SAVE); return destination; } }