/*********************************************************************************
* 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.beans.impl;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.regex.Pattern;
import javax.servlet.http.HttpServletRequest;
import org.agnitas.beans.BindingEntry;
import org.agnitas.beans.ProfileField;
import org.agnitas.beans.Recipient;
import org.agnitas.beans.factory.BindingEntryFactory;
import org.agnitas.beans.factory.RecipientFactory;
import org.agnitas.dao.RecipientDao;
import org.agnitas.service.ColumnInfoService;
import org.agnitas.util.CaseInsensitiveMap;
import org.apache.commons.lang.StringUtils;
/**
* Handles all kind of operations to be done with subscriber-data. Requires that
* a valid companyID is set after creating a new instance.
*
* @author mhe
*/
public class RecipientImpl implements Recipient {
protected ColumnInfoService columnInfoService;
protected RecipientDao recipientDao;
protected BindingEntryFactory bindingEntryFactory;
protected RecipientFactory recipientFactory;
protected int companyID;
protected int customerID;
protected Map<Integer, Map<Integer, BindingEntry>> listBindings;
protected Map<String, String> custDBStructure;
protected CaseInsensitiveMap<ProfileField> custDBProfileStructure;
protected CaseInsensitiveMap<Object> custParameters = new CaseInsensitiveMap<Object>();
protected boolean changeFlag = false;
// ----------------------------------------------------------------------------------------------------------------
// Dependency Injection
public void setRecipientDao(RecipientDao recipientDao) {
this.recipientDao = recipientDao;
}
public void setColumnInfoService(ColumnInfoService columnInfoService) {
this.columnInfoService = columnInfoService;
}
public void setBindingEntryFactory(BindingEntryFactory bindingEntryFactory) {
this.bindingEntryFactory = bindingEntryFactory;
}
public void setRecipientFactory(RecipientFactory recipientFactory) {
this.recipientFactory = recipientFactory;
}
// ----------------------------------------------------------------------------------------------------------------
// Business Logic
@Override
public boolean blacklistCheck() {
return recipientDao.blacklistCheck(((String) getCustParameters().get("email")).toLowerCase().trim(),getCompanyID());
}
@Override
public boolean updateInDB() {
return recipientDao.updateInDB(this);
}
@Override
public int findByColumn(String col, String value) {
return recipientDao.findByColumn(companyID, col, value);
}
@Override
public int findByKeyColumn(String col, String value) {
return recipientDao.findByKeyColumn(this, col, value);
}
@Override
public void deleteCustomerDataFromDb() {
recipientDao.deleteCustomerDataFromDb(companyID, customerID);
}
@Override
public int findByUserPassword(String userCol, String userValue, String passCol, String passValue) {
return recipientDao.findByUserPassword(companyID, userCol, userValue, passCol, passValue);
}
@Override
public Map<String, Object> getCustomerDataFromDb() {
custParameters = recipientDao.getCustomerDataFromDb(companyID, customerID);
return custParameters;
}
@Override
public Map<Integer, Map<Integer, BindingEntry>> loadAllListBindings() {
listBindings = recipientDao.loadAllListBindings(companyID, customerID);
return listBindings;
}
@Override
public int insertNewCust() {
return recipientDao.insertNewCust(this);
}
@Override
public int getCustomerID() {
return customerID;
}
@Override
public void setCustomerID(int customerID) {
this.customerID = customerID;
}
@Override
public int getCompanyID() {
return companyID;
}
@Override
public void setCompanyID(int companyID) {
this.companyID = companyID;
}
@Override
public Map<Integer, Map<Integer, BindingEntry>> getListBindings() {
return listBindings;
}
@Override
public void setListBindings(Map<Integer, Map<Integer, BindingEntry>> listBindings) {
this.listBindings = listBindings;
}
@Override
public Map<String, String> getCustDBStructure() {
return custDBStructure;
}
@Override
public void setCustDBStructure(Map<String, String> custDBStructure) {
this.custDBStructure = custDBStructure;
}
@Override
public String getCustParameters(String key) {
return (String) custParameters.get(key);
}
@Override
public boolean isChangeFlag() {
return changeFlag;
}
@Override
public void setChangeFlag(boolean changeFlag) {
this.changeFlag = changeFlag;
}
@Override
public Map<String, Object> getCustParameters() {
return custParameters;
}
@Override
public void resetCustParameters() {
custParameters.clear();
}
@Override
public Map<Integer, Map<Integer, BindingEntry>> getAllMailingLists() {
return recipientDao.getAllMailingLists(customerID, companyID);
}
@Override
public CaseInsensitiveMap<ProfileField> getCustDBProfileStructure() {
return custDBProfileStructure;
}
@Override
public String getEmail() {
return (String) custParameters.get("email");
}
@Override
public String getFirstname() {
return (String) custParameters.get("firstname");
}
@Override
public int getGender() {
return ((Number) custParameters.get("gender")).intValue();
}
@Override
public String getLastname() {
return (String) custParameters.get("lastname");
}
/**
* Load structure of Customer-Table for the given Company-ID in member
* variable "companyID". Load profile data into map. Has to be done before
* working with customer-data in class instance
*
* @return true on success
*/
@Override
public boolean loadCustDBStructure() {
custDBStructure = new CaseInsensitiveMap<String>();
custDBProfileStructure = new CaseInsensitiveMap<ProfileField>();
try {
for (ProfileField fieldDescription : columnInfoService.getColumnInfos(companyID)) {
custDBStructure.put(fieldDescription.getColumn(), fieldDescription.getDataType());
custDBProfileStructure.put(fieldDescription.getColumn(), fieldDescription);
}
return true;
} catch (Exception e) {
return false;
}
}
/**
* Indexed setter for property custParameters.
*
* @param aKey
* identifies field in customer-record, must be the same like in
* Database
* @param custParameter
* New value of the property at <CODE>aKey</CODE>.
*/
@Override
public void setCustParameters(String aKey, String custParameter) {
String key = aKey;
String aValue = null;
if (key.toUpperCase().endsWith("_DAY_DATE")) {
key = key.substring(0, key.length() - "_DAY_DATE".length());
}
if (key.toUpperCase().endsWith("_MONTH_DATE")) {
key = key.substring(0, key.length() - "_MONTH_DATE".length());
}
if (key.toUpperCase().endsWith("_YEAR_DATE")) {
key = key.substring(0, key.length() - "_YEAR_DATE".length());
}
if (key.toUpperCase().endsWith("_HOUR_DATE")) {
key = key.substring(0, key.length() - "_HOUR_DATE".length());
}
if (key.toUpperCase().endsWith("_MINUTE_DATE")) {
key = key.substring(0, key.length() - "_MINUTE_DATE".length());
}
if (key.toUpperCase().endsWith("_SECOND_DATE")) {
key = key.substring(0, key.length() - "_SECOND_DATE".length());
}
if (custDBStructure.containsKey(key)) {
aValue = null;
if (custParameters.get(aKey) != null) {
aValue = (String) custParameters.get(aKey);
}
if (!StringUtils.equals(custParameter, aValue)) {
changeFlag = true;
custParameters.put(aKey, custParameter);
}
}
}
/**
* Setter for property custParameters.
*
* @param custParameters
* New value of property custParameters.
*/
@Override
public void setCustParameters(Map<String, Object> custParameters) {
if (custParameters instanceof CaseInsensitiveMap) {
this.custParameters = (CaseInsensitiveMap<Object>) custParameters;
} else {
this.custParameters = new CaseInsensitiveMap<Object>(custParameters);
}
changeFlag = true;
}
/**
* Check security of a request parameter. Checks the given string for
* certain patterns that could be used for exploits.
*/
private boolean isSecure(String value) {
return !value.contains("<");
}
/**
* Copy a date from reqest to database values.
*
* @param req
* a Map of request parameters (name/value pairs).
* @param name
* the name of the field to copy.
* @param suffix
* a suffix for the parameters in the map.
* @return true when the copying was successfull.
*/
private boolean copyDate(Map<String, Object> req, String name, String suffix) {
String[] field = { "_DAY_DATE", "_MONTH_DATE", "_YEAR_DATE", "_HOUR_DATE", "_MINUTE_DATE", "_SECOND_DATE" };
String s = null;
name = name.toUpperCase();
for (int c = 0; c < field.length; c++) {
if (req.get(name + field[c] + suffix) != null) {
String fieldname = name + field[c] + suffix;
Object o = req.get(fieldname);
s = o.toString();
setCustParameters(fieldname, s);
}
}
return true;
}
/**
* Check if the given name is allowed for requests. This is used to ensure
* that system columns are not changed by form requests.
*
* @param name
* the name to check for allowance.
* @return true when field may be writen.
*/
private boolean isAllowedName(String name) {
name = name.toLowerCase();
if (name.startsWith("agn")) {
return false;
}
if (name.equals("customer_id") || name.equals("change_date")) {
return false;
}
if (name.equals("timestamp") || name.equals("creation_date")) {
return false;
}
return true;
}
/**
* Updates customer data by analyzing given HTTP-Request-Parameters
*
* @return true on success
* @param suffix
* Suffix appended to Database-Column-Names when searching for
* corresponding request parameters
* @param req
* Map containing all HTTP-Request-Parameters as key-value-pair.
*/
@Override
public boolean importRequestParameters(Map<String, Object> requestParameters, String suffix) {
CaseInsensitiveMap<Object> caseInsensitiveParameters = new CaseInsensitiveMap<Object>(requestParameters);
if (suffix == null) {
suffix = "";
}
Iterator<String> e = custDBStructure.keySet().iterator();
while (e.hasNext()) {
String aName = e.next();
String name = aName.toUpperCase();
if (!isAllowedName(aName)) {
continue;
}
String colType = (String) custDBStructure.get(aName);
if (colType.equalsIgnoreCase("DATE")) {
copyDate(caseInsensitiveParameters, aName, suffix);
} else if (caseInsensitiveParameters.get(name + suffix) != null) {
String aValue = (String) caseInsensitiveParameters.get(name + suffix);
if (name.equalsIgnoreCase("EMAIL")) {
if (aValue.length() == 0) {
aValue = " ";
}
aValue = aValue.toLowerCase();
aValue = aValue.trim();
} else if (name.length() > 4) {
if (name.substring(0, 4).equals("SEC_")
|| name.equals("FIRSTNAME")
|| name.equals("LASTNAME")) {
if (!isSecure(aValue)) {
return false;
}
}
}
if (name.equalsIgnoreCase("DATASOURCE_ID")) {
if (getCustParameters(aName) == null) {
setCustParameters(aName, aValue);
}
} else {
setCustParameters(aName, aValue);
}
}
}
return true;
}
/**
* Updates internal Datastructure for Mailinglist-Bindings of this customer
* by analyzing HTTP-Request-Parameters
*
* @return true on success
* @param tafWriteBack
* if true, eventually existent TAF-Information will be written
* back to source-customer
* @param params
* Map containing all HTTP-Request-Parameters as key-value-pair.
* @param doubleOptIn
* true means use Double-Opt-In
*/
@Override
public boolean updateBindingsFromRequest(Map<String, Object> params, boolean doubleOptIn, boolean tafWriteBack, String remoteAddr) {
HttpServletRequest request = (HttpServletRequest) params.get("_request");
@SuppressWarnings("unchecked")
Map<String, Object> requestParameters = (Map<String, Object>) params.get("requestParameters");
String postfix = null;
int mailinglistID = 0;
int mediatype = 0;
int subscribeStatus = 0;
String tmpName = null;
boolean changeit = false;
Map<Integer, BindingEntry> mList = null;
BindingEntry aEntry = null;
int mailingID = 0;
try {
Integer tmpNum = (Integer) params.get("mailingID");
mailingID = tmpNum.intValue();
} catch (Exception e) {
mailingID = 0;
}
for (String aName : requestParameters.keySet()) {
postfix = "";
if (aName.startsWith("agnSUBSCRIBE")) {
mediatype = 0;
mailinglistID = 0;
subscribeStatus = 0;
aEntry = null;
if (aName.length() > "agnSUBSCRIBE".length()) {
postfix = aName.substring("agnSUBSCRIBE".length());
}
try {
subscribeStatus = Integer.parseInt((String) requestParameters.get(aName));
} catch (Exception e1) {
subscribeStatus = 0;
}
tmpName = "agnMAILINGLIST" + postfix;
try {
mailinglistID = Integer.parseInt((String) requestParameters.get(tmpName));
} catch (Exception e1) {
mailinglistID = 0;
}
tmpName = "agnMEDIATYPE" + postfix;
try {
mediatype = Integer.parseInt((String) requestParameters.get(tmpName));
} catch (Exception e1) {
mediatype = 0;
}
// find BindingEntry or create new one
mList = listBindings.get(mailinglistID);
if (mList != null) {
aEntry = mList.get(mediatype);
}
if (aEntry != null) {
changeit = false;
// put changes in db
int oldStatus = aEntry.getUserStatus();
switch (oldStatus) {
case BindingEntry.USER_STATUS_ADMINOUT:
case BindingEntry.USER_STATUS_BOUNCED:
case BindingEntry.USER_STATUS_OPTOUT:
if (subscribeStatus == 1) {
changeit = true;
}
break;
case BindingEntry.USER_STATUS_WAITING_FOR_CONFIRM:
case BindingEntry.USER_STATUS_ACTIVE:
if (subscribeStatus == 0) {
changeit = true;
}
break;
}
if (changeit) {
switch (subscribeStatus) {
case 0:
aEntry.setUserStatus(BindingEntry.USER_STATUS_OPTOUT);
if (mailingID != 0) {
aEntry.setUserRemark("Opt-Out-Mailing: "
+ mailingID);
aEntry.setExitMailingID(mailingID);
} else {
aEntry.setUserRemark("User-Opt-Out: "
+ request.getRemoteAddr());
aEntry.setExitMailingID(0);
}
break;
case 1:
if (!doubleOptIn) {
aEntry.setUserStatus(BindingEntry.USER_STATUS_ACTIVE);
aEntry.setUserRemark("Opt-In-IP: "
+ request.getRemoteAddr());
} else {
aEntry.setUserStatus(BindingEntry.USER_STATUS_WAITING_FOR_CONFIRM);
aEntry.setUserRemark("Opt-In-IP: "
+ request.getRemoteAddr());
}
break;
}
aEntry.updateStatusInDB(companyID);
}
} else {
if (subscribeStatus == 1) {
aEntry = bindingEntryFactory.newBindingEntry();
aEntry.setCustomerID(customerID);
aEntry.setMediaType(mediatype);
aEntry.setMailinglistID(mailinglistID);
aEntry.setUserType(BindingEntry.USER_TYPE_WORLD);
aEntry.setRemoteAddr(remoteAddr);
if (!doubleOptIn) {
aEntry.setUserStatus(BindingEntry.USER_STATUS_ACTIVE);
aEntry.setUserRemark("Opt-In-IP: "
+ request.getRemoteAddr());
if (tafWriteBack) { // only if there was never a
// binding for adress...
tellFriendWriteback(); // make
// taf-writeback to
// originating
// customer
}
} else {
aEntry.setUserStatus(BindingEntry.USER_STATUS_WAITING_FOR_CONFIRM);
aEntry.setUserRemark("Opt-In-IP: "
+ request.getRemoteAddr());
}
aEntry.insertNewBindingInDB(companyID);
if (mList == null) {
mList = new HashMap<Integer, BindingEntry>();
listBindings.put(mailinglistID, mList);
}
mList.put(mediatype, aEntry);
}
}
}
}
return true;
}
@Override
public boolean updateBindingsFromRequest(Map<String, Object> params, boolean doubleOptIn, boolean tafWriteBack) {
return updateBindingsFromRequest(params, doubleOptIn, tafWriteBack, null);
}
/**
* Iterates through already loaded Mailinglist-Informations and checks if
* subscriber is active on at least one mailinglist
*
* @return true if subscriber is active on a mailinglist
*/
@Override
public boolean isActiveSubscriber() {
if (listBindings != null) {
for (Map<Integer, BindingEntry> listBindingItem : listBindings.values()) {
for (BindingEntry bindingEntry : listBindingItem.values()) {
if (bindingEntry.getUserStatus() == BindingEntry.USER_STATUS_ACTIVE) {
return true;
}
}
}
}
return false;
}
/**
* Checks if E-Mail-Adress given in customerData-HashMap is valid
*
* @return true if E-Mail-Adress is valid
*/
@Override
public boolean emailValid() {
String email = (String) custParameters.get("email");
if (email == null) {
return false;
}
email = email.trim();
if (email == null) {
return false;
}
if (!Pattern.matches("[^<@]+@[^<@.]+[.][^<@]+", email)) {
return false;
}
return true;
}
/**
* Writes TAF-Info back to source-customer record for tracking purposes
*
* @return always true
*/
private boolean tellFriendWriteback() {
boolean result = true;
Recipient sourceCust = null;
int custID = 0;
int tafNum = 0;
String tmp = null;
String custStr = null;
// add check if fields exist in db-structure!
if (!custDBStructure.containsKey("AGN_TAF_SOURCE")
|| !custDBStructure.containsKey("AGN_TAF_NUMBER")
|| !custDBStructure.containsKey("AGN_TAF_CUSTOMER_IDS")) {
return true;
}
if (getCustParameters("AGN_TAF_SOURCE") != null) {
try {
custID = Integer.parseInt((String) getCustParameters("AGN_TAF_SOURCE"));
} catch (Exception e) {
custID = 0;
}
}
if (custID != 0) {
sourceCust = recipientFactory.newRecipient();
sourceCust.setCompanyID(companyID);
sourceCust.setCustomerID(custID);
sourceCust.loadCustDBStructure();
sourceCust.setCustParameters(recipientDao.getCustomerDataFromDb(companyID, customerID));
if (sourceCust.getCustParameters("AGN_TAF_CUSTOMER_IDS") != null) {
tmp = sourceCust.getCustParameters("AGN_TAF_CUSTOMER_IDS");
} else {
tmp = "";
}
custStr = " " + customerID + ";";
if (tmp.indexOf(custStr) == -1) {
tmp = tmp + custStr;
sourceCust.setCustParameters("AGN_TAF_CUSTOMER_IDS", tmp);
try {
tafNum = Integer.parseInt((String) sourceCust
.getCustParameters("AGN_TAF_NUMBER"));
} catch (Exception e) {
tafNum = 0;
}
tafNum++;
sourceCust.setCustParameters("AGN_TAF_NUMBER",
Integer.toString(tafNum));
recipientDao.updateInDB(sourceCust);
}
}
return result;
}
/**
* String representation for easier debugging
*/
@Override
public String toString() {
return
"(" + companyID + "/" + customerID + ")" +
" Lastname: " + (custParameters == null ? "" : custParameters.get("lastname")) +
" Firstname: " + (custParameters == null ? "" : custParameters.get("firstname")) +
" Gender: " + (custParameters == null ? "" : custParameters.get("gender")) +
" Email: " + (custParameters == null ? "" : custParameters.get("email")) +
" Mailtype: " + (custParameters == null ? "" : custParameters.get("mailtype")) +
" Bindings: " + (listBindings == null ? 0 : listBindings.size()) +
" ChangeFlag: " + changeFlag;
}
}