/********************************************************************************* * 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.util.Iterator; import java.util.List; import java.util.Locale; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.agnitas.dao.TargetDao; import org.agnitas.dao.RecipientDao; import org.agnitas.dao.exception.target.TargetGroupLockedException; import org.agnitas.dao.exception.target.TargetGroupPersistenceException; import org.agnitas.target.*; import org.agnitas.target.impl.TargetNodeDate; import org.agnitas.target.impl.TargetNodeNumeric; import org.agnitas.target.impl.TargetNodeString; import org.agnitas.util.AgnUtils; import org.agnitas.util.SafeString; import org.apache.struts.Globals; 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; /** * Implementation of <strong>Action</strong> that handles Targets * * @author Martin Helff, Nicole Serek */ public class TargetAction extends StrutsActionBase { public static final int ACTION_CREATE_ML = ACTION_LAST + 1; public static final int ACTION_CLONE = ACTION_LAST + 2; public static final int ACTION_DELETE_RECIPIENTS_CONFIRM = ACTION_LAST + 3; public static final int ACTION_DELETE_RECIPIENTS = ACTION_LAST + 4; public static final int ACTION_BACK_TO_MAILINGWIZARD = ACTION_LAST + 5; protected TargetDao targetDao; private RecipientDao recipientDao; private TargetRepresentationFactory targetRepresentationFactory; private TargetNodeFactory targetNodeFactory; protected TargetFactory targetFactory; // --------------------------------------------------------- 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. * * ACTION_LIST: loads list of target groups into request. Initializes columns width list for the table if * necessary. Forwards to "after_delete" which leads to targets list page. * <br><br> * ACTION_VIEW: If the target group ID of form is 0 - clears all the rules data in form.<br><br> * Otherwise loads target from database, sets name and description to form and fills form with data from * targetRepresentation property of target group. TargetRepresentation is an object containing list of * target-nodes; each target-node contains information about one rule of target group such as: profile-fields * used, primary operator, chain operator, brackets for current rule etc. For filling the form action * iterates through the target-nodes of targetRepresentation and puts each node's data to form properties at * separate index (for each target rule we have its own index in form properties)<br><br> * Forwards to target group view page. * <br><br> * ACTION_SAVE: checks if there wasn't adding new rule or deleting existing rule performed. * If the check is passed - performs saving of target group:<br> * creates target representation from form (iterates through rules and creates TargetNode for each rule * contained in form and puts all target nodes to target representation);<br> * generates targetSQL from target representation (this targetSQL is used for creating SQL queries for * filtering recipients matching the target group);<br> * if it is a new target - creates new target object with ID 0;<br> * saves name and description to target group object;<br> * finally saves target group to database;<br> * If there was any problem while saving target-group (target was locked, target wasn't saved in db etc.) - * forwards to target view page with appropriate error message. If the saving was ok - forwards to "success" * (which is currently target view page) * <br><br> * ACTION_NEW: if there wasn't adding of new rule performed - saves target to database (the detailed description * of that process can be found above in description of ACTION_SAVE). Forwards to target view page. * <br><br> * ACTION_CONFIRM_DELETE: loads data into form (the detailed description of loading target to form can be found * above in description of ACTION_VIEW), forwards to jsp with question to confirm deletion. * <br><br> * ACTION_DELETE: marks target group as deleted in database, loads list of target groups into request, * forwards to "after_delete" (currently target groups list page) * <br><br> * ACTION_CREATE_ML: forwards to jsp with question to confirm of creation new mailing list * <br><br> * ACTION_CLONE: loads data of chosen target group data into form (see description of loading above). Creates a new * target group in database with that data. Forwards to target view page. * <br><br> * ACTION_DELETE_RECIPIENTS_CONFIRM: loads target group data into form (see description in ACTION_VIEW). Calculates * number of recipients matching target group and sets that number to form. Forwards to jsp with question to * confirm deletion of recipients of chosen target group. * <br><br> * ACTION_DELETE_RECIPIENTS: loads target group data into form (see description in ACTION_VIEW). Deletes the * recipients matching target group from database. Loads list of target groups into request. Forwards to * "after_delete" (currently target groups list page) * <br><br> * * Also, with each call of execute, the method updateTargetFormProperties is called (independent of current value * of "action" property). That method performs the following functions:<br> * If addTargetNode property of form is set - the data of new target node is taken from form properties for new * rule and is put to form properties containing data of all rules of target group. The new rule is put at the end * of rules list.<br> * Method updates the list of possible operations for each rule according to rule type and updates the type of * data for each rule;<br> * Method removes the target rule with selected index which is set by property targetNodeToRemove (chosen by user * on the view page)<br><br> * * If the destination is "success" - loads list of targets to request. * * @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 */ @Override public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException { TargetForm aForm = null; ActionMessages errors = new ActionMessages(); ActionMessages messages = new ActionMessages(); ActionForward destination = null; if (!AgnUtils.isUserLoggedIn(req)) { return mapping.findForward("logon"); } if (form != null) { aForm = (TargetForm) form; } else { aForm = new TargetForm(); } boolean removeSelected = this.updateTargetFormProperties(aForm, req); AgnUtils.logger().info("Action: " + aForm.getAction()); if (!allowed("targets.show", req)) { errors.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage("error.permissionDenied")); saveErrors(req, errors); return null; } try { switch (aForm.getAction()) { case ACTION_LIST: destination = mapping.findForward( listTargetGroups( aForm, req)); break; case ACTION_VIEW: if (aForm.getTargetID() != 0) { aForm.setAction(TargetAction.ACTION_SAVE); loadTarget(aForm, req); } else { aForm.clearRules(); aForm.setAction(TargetAction.ACTION_NEW); } destination = mapping.findForward("view"); break; case ACTION_SAVE: if (!aForm.getAddTargetNode() && !removeSelected) { try { if( saveTarget(aForm, req) != 0) { messages.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage("default.changes_saved")); destination = mapping.findForward("success"); } else { errors.add( ActionMessages.GLOBAL_MESSAGE, new ActionMessage( "error.target.saving")); destination = mapping.findForward("view"); } } catch( TargetGroupLockedException e) { errors.add( ActionMessages.GLOBAL_MESSAGE, new ActionMessage( "target.locked")); } catch( TargetGroupPersistenceException e) { errors.add( ActionMessages.GLOBAL_MESSAGE, new ActionMessage( "error.target.saving.general")); } } else { destination = mapping.findForward("success"); } break; case ACTION_NEW: if (!aForm.getAddTargetNode()) { if(saveTarget(aForm, req) != 0) { aForm.setAction(TargetAction.ACTION_SAVE); messages.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage("default.changes_saved")); } else { aForm.setAction(TargetAction.ACTION_SAVE); errors.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage("error.target.saving")); } } destination = mapping.findForward("view"); break; case ACTION_CONFIRM_DELETE: loadTarget(aForm, req); destination = mapping.findForward("delete"); aForm.setAction(TargetAction.ACTION_DELETE); break; case ACTION_DELETE: try { this.deleteTarget(aForm, req); messages.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage("default.changes_saved")); } catch( TargetGroupLockedException e) { errors.add( ActionMessages.GLOBAL_MESSAGE, new ActionMessage("target.locked")); } catch( TargetGroupPersistenceException e) { errors.add( ActionMessages.GLOBAL_MESSAGE, new ActionMessage("error.target.delete")); } aForm.setAction(TargetAction.ACTION_LIST); destination = mapping.findForward(listTargetGroups( aForm, req)); break; case ACTION_CREATE_ML: destination = mapping.findForward("create_ml"); break; case ACTION_CLONE: if (aForm.getTargetID() != 0) { loadTarget(aForm, req); cloneTarget(aForm, req); aForm.setAction(TargetAction.ACTION_SAVE); } destination = mapping.findForward("view"); break; case ACTION_DELETE_RECIPIENTS_CONFIRM: loadTarget(aForm, req); this.getRecipientNumber(aForm, req); destination = mapping.findForward("delete_recipients"); break; case ACTION_DELETE_RECIPIENTS: loadTarget(aForm, req); this.deleteRecipients(aForm, req); aForm.setAction(TargetAction.ACTION_LIST); destination = mapping.findForward(listTargetGroups( aForm, req)); break; default: destination = mapping.findForward(listTargetGroups( aForm, req)); break; } } catch (Exception e) { AgnUtils.logger().error( "execute: " + e + "\n" + AgnUtils.getStackTrace(e)); errors.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage( "error.exception")); } if( "success".equals(destination.getName())) { req.setAttribute("targetlist", loadTargetList(req, aForm) ); setNumberOfRows(req, aForm); } // Report any errors we have discovered back to the original form if (!errors.isEmpty()) { saveErrors(req, errors); return (new ActionForward(mapping.getInput())); } // Report any message (non-errors) we have discovered if (!messages.isEmpty()) { saveMessages(req, messages); } return destination; } protected String listTargetGroups( TargetForm form, HttpServletRequest request) { if ( form.getColumnwidthsList() == null) { form.setColumnwidthsList(getInitializedColumnWidthList(3)); } request.setAttribute("targetlist", loadTargetList(request, form) ); setNumberOfRows(request, form); return "after_delete"; } /** * Loads target. */ protected void loadTarget(TargetForm aForm, HttpServletRequest req) throws Exception { Target aTarget = targetDao.getTarget(aForm.getTargetID(), getCompanyID(req)); if (aTarget.getId() == 0) { AgnUtils.logger().warn( "loadTarget: could not load target " + aForm.getTargetID()); aTarget = targetFactory.newTarget(); aTarget.setId(aForm.getTargetID()); } aForm.setShortname(aTarget.getTargetName()); aForm.setDescription(aTarget.getTargetDescription()); fillFormFromTargetRepresentation(aForm, aTarget.getTargetStructure()); AgnUtils.userlogger().info(AgnUtils.getAdmin(req).getUsername() + ": do load target group " + aForm.getShortname()); AgnUtils.logger().info("loadTarget: target " + aForm.getTargetID() + " loaded"); } /** * Clone target. */ protected void cloneTarget(TargetForm aForm, HttpServletRequest req) throws Exception { aForm.setTargetID(0); aForm.setShortname(SafeString.getLocaleString("mailing.CopyOf", (Locale) req .getSession().getAttribute(Globals.LOCALE_KEY)) + " " + aForm.getShortname()); saveTarget(aForm, req); } /** * Saves target. */ protected int saveTarget(TargetForm aForm, HttpServletRequest req) throws Exception { Target aTarget = targetDao.getTarget(aForm.getTargetID(), getCompanyID(req)); if (aTarget == null) { // be sure to use id 0 if there is no existing object aForm.setTargetID(0); aTarget = targetFactory.newTarget(); aTarget.setCompanyID(this.getCompanyID(req)); } TargetRepresentation targetRepresentation = createTargetRepresentationFromForm(aForm); aTarget.setTargetName(aForm.getShortname()); aTarget.setTargetDescription(aForm.getDescription()); aTarget.setTargetSQL(targetRepresentation.generateSQL()); aTarget.setTargetStructure(targetRepresentation); int result = targetDao.saveTarget(aTarget); if (aForm.getTargetID() == 0) { AgnUtils.userlogger().info(AgnUtils.getAdmin(req).getUsername() + ": create target group " + aForm.getShortname()); } else { AgnUtils.userlogger().info(AgnUtils.getAdmin(req).getUsername() + ": edit target group " + aForm.getShortname()); } AgnUtils.logger().info("saveTarget: save target " + aTarget.getId()); if( aForm.getTargetID() == 0) aForm.setTargetID(aTarget.getId()); return result; } /** * Removes target. */ protected void deleteTarget(TargetForm aForm, HttpServletRequest req) throws TargetGroupPersistenceException { targetDao.deleteTarget(aForm.getTargetID(), getCompanyID(req)); AgnUtils.userlogger().info(AgnUtils.getAdmin(req).getUsername() + ": delete target group " + aForm.getShortname()); } /** * Gets number of recipients affected in a target group. */ protected void getRecipientNumber(TargetForm aForm, HttpServletRequest req) { Target target = targetFactory.newTarget(); target = targetDao.getTarget(aForm.getTargetID(), aForm.getCompanyID(req)); int numOfRecipients = recipientDao.sumOfRecipients(aForm.getCompanyID(req), target.getTargetSQL()); aForm.setNumOfRecipients(numOfRecipients); } /** * Removes recipients affected in a target group. */ protected void deleteRecipients(TargetForm aForm, HttpServletRequest req) { Target target = targetFactory.newTarget(); target = targetDao.getTarget(aForm.getTargetID(), aForm.getCompanyID(req)); recipientDao.deleteRecipients(aForm.getCompanyID(req), target.getTargetSQL()); } /** * load the list of targets * @param request * @return */ protected List loadTargetList(HttpServletRequest request, TargetForm form) { return targetDao.getTargets(AgnUtils.getCompanyID(request)); } /** * add new rule if necessary and update exist target rules and properties of form * <br><br> * * @param form * @param request * @return success or failed result of removing rules from form */ private boolean updateTargetFormProperties(TargetForm form, HttpServletRequest request) { int lastIndex = form.getNumTargetNodes(); int removeIndex = -1; // If "add" was clicked, add new rule if (form.getAddTargetNode()) { form.setColumnAndType(lastIndex, form.getColumnAndTypeNew()); form.setChainOperator(lastIndex, form.getChainOperatorNew()); form.setParenthesisOpened(lastIndex, form.getParenthesisOpenedNew()); form.setPrimaryOperator(lastIndex, form.getPrimaryOperatorNew()); form.setPrimaryValue(lastIndex, form.getPrimaryValueNew()); form.setParenthesisClosed(lastIndex, form.getParenthesisClosedNew()); form.setDateFormat(lastIndex, form.getDateFormatNew()); form.setSecondaryOperator(lastIndex, form.getSecondaryOperatorNew()); form.setSecondaryValue(lastIndex, form.getSecondaryValueNew()); form.setAddTargetNode( false); lastIndex++; } int nodeToRemove = form.getTargetNodeToRemove(); // Iterate over all target rules for(int index = 0; index < lastIndex; index++) { if(index != nodeToRemove) { String colAndType = form.getColumnAndType(index); String column = colAndType.substring(0, colAndType.indexOf('#')); String type = colAndType.substring(colAndType.indexOf('#') + 1); form.setColumnName(index, column); if (type.equalsIgnoreCase("VARCHAR") || type.equalsIgnoreCase("VARCHAR2") || type.equalsIgnoreCase("CHAR")) { form.setValidTargetOperators(index, TargetNodeString.getValidOperators()); form.setColumnType(index, TargetForm.COLUMN_TYPE_STRING); } else if (type.equalsIgnoreCase("INTEGER") || type.equalsIgnoreCase("DOUBLE") || type.equalsIgnoreCase("NUMBER")) { form.setValidTargetOperators(index, TargetNodeNumeric.getValidOperators()); form.setColumnType(index, TargetForm.COLUMN_TYPE_NUMERIC); } else if (type.equalsIgnoreCase("DATE")) { form.setValidTargetOperators(index, TargetNodeDate.getValidOperators()); form.setColumnType(index, TargetForm.COLUMN_TYPE_DATE); } } else { if (removeIndex != -1) throw new RuntimeException( "duplicate remove??? (removeIndex = " + removeIndex + ", index = " + index + ")"); removeIndex = index; } } if (removeIndex != -1) { form.removeRule(removeIndex); return true; } else { return false; } } /** * fill the data to form from TargetRepresentation object * <br><br> * * @param form * @param target */ protected void fillFormFromTargetRepresentation(TargetForm form, TargetRepresentation target) { // First, remove all previously defined rules from target form form.clearRules(); // Now, convert target nodes to form data Iterator<TargetNode> it = target.getAllNodes().iterator(); int index = 0; while (it.hasNext()) { TargetNode node = it.next(); form.setChainOperator(index, node.getChainOperator()); String primaryField = node.getPrimaryField() == null ? null : node.getPrimaryField().toUpperCase(); form.setColumnAndType(index, primaryField + "#" + node.getPrimaryFieldType()); form.setPrimaryOperator(index, node.getPrimaryOperator()); form.setPrimaryValue(index, node.getPrimaryValue()); form.setColumnName(index, node.getPrimaryField()); form.setParenthesisOpened(index, node.isOpenBracketBefore() ? 1 : 0); form.setParenthesisClosed(index, node.isCloseBracketAfter() ? 1 : 0); if (node instanceof TargetNodeString) { form.setColumnType(index, TargetForm.COLUMN_TYPE_STRING); form.setValidTargetOperators(index, TargetNodeString.getValidOperators()); } else if (node instanceof TargetNodeNumeric) { TargetNodeNumeric numericNode = (TargetNodeNumeric) node; form.setColumnType(index, TargetForm.COLUMN_TYPE_NUMERIC); form.setSecondaryOperator(index, numericNode.getSecondaryOperator()); form.setSecondaryValue(index, Integer.toString(numericNode.getSecondaryValue())); form.setValidTargetOperators(index, TargetNodeNumeric.getValidOperators()); } else if (node instanceof TargetNodeDate) { TargetNodeDate dateNode = (TargetNodeDate) node; form.setDateFormat(index, dateNode.getDateFormat()); form.setColumnType(index, TargetForm.COLUMN_TYPE_DATE); form.setValidTargetOperators(index, TargetNodeDate.getValidOperators()); } else { // uh oh. It seems, somebody forgot to add a new target node type here :( AgnUtils.logger().warn("cannot handle target node class " + node.getClass().getCanonicalName()); throw new RuntimeException("cannot handle target node class " + node.getClass().getCanonicalName()); } index++; } } protected TargetRepresentation createTargetRepresentationFromForm(TargetForm form) { TargetRepresentation target = targetRepresentationFactory.newTargetRepresentation(); int lastIndex = form.getNumTargetNodes(); for(int index = 0; index < lastIndex; index++) { String colAndType = form.getColumnAndType(index); String column = colAndType.substring(0, colAndType.indexOf('#')); String type = colAndType.substring(colAndType.indexOf('#') + 1); TargetNode node = null; if (type.equalsIgnoreCase("VARCHAR") || type.equalsIgnoreCase("VARCHAR2") || type.equalsIgnoreCase("CHAR")) { node = createStringNode(form, column, type, index); } else if (type.equalsIgnoreCase("INTEGER") || type.equalsIgnoreCase("DOUBLE") || type.equalsIgnoreCase("NUMBER")) { node = createNumericNode(form, column, type, index); } else if (type.equalsIgnoreCase("DATE")) { node = createDateNode(form, column, type, index); } target.addNode(node); } return target; } private TargetNodeString createStringNode(TargetForm form, String column, String type, int index) { return targetNodeFactory.newStringNode( form.getChainOperator(index), form.getParenthesisOpened(index), column, type, form.getPrimaryOperator(index), form.getPrimaryValue(index), form.getParenthesisClosed(index)); } private TargetNodeNumeric createNumericNode(TargetForm form, String column, String type, int index) { int primaryOperator = form.getPrimaryOperator(index); int secondaryOperator = form.getSecondaryOperator(index); int secondaryValue = 0; if(primaryOperator == TargetNode.OPERATOR_MOD.getOperatorCode()) { try { secondaryOperator = form.getSecondaryOperator(index); } catch (Exception e) { secondaryOperator = TargetNode.OPERATOR_EQ.getOperatorCode(); } try { secondaryValue = Integer.parseInt(form.getSecondaryValue(index)); } catch (Exception e) { secondaryValue = 0; } } return targetNodeFactory.newNumericNode( form.getChainOperator(index), form.getParenthesisOpened(index), column, type, primaryOperator, form.getPrimaryValue(index), secondaryOperator, secondaryValue, form.getParenthesisClosed(index)); } private TargetNodeDate createDateNode(TargetForm form, String column, String type, int index) { return targetNodeFactory.newDateNode( form.getChainOperator(index), form.getParenthesisOpened(index), column, type, form.getPrimaryOperator(index), form.getDateFormat(index), form.getPrimaryValue(index), form.getParenthesisClosed(index)); } public void setTargetDao(TargetDao targetDao) { this.targetDao = targetDao; } public void setRecipientDao(RecipientDao recipientDao) { this.recipientDao = recipientDao; } public void setTargetRepresentationFactory(TargetRepresentationFactory targetRepresentationFactory) { this.targetRepresentationFactory = targetRepresentationFactory; } public void setTargetNodeFactory(TargetNodeFactory targetNodeFactory) { this.targetNodeFactory = targetNodeFactory; } public void setTargetFactory(TargetFactory targetFactory) { this.targetFactory = targetFactory; } }