/*********************************************************************************
* 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.io.File;
import java.io.IOException;
import java.io.LineNumberReader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.agnitas.beans.ColumnMapping;
import org.agnitas.beans.ImportProfile;
import org.agnitas.beans.ProfileField;
import org.agnitas.beans.impl.ColumnMappingImpl;
import org.agnitas.dao.ImportProfileDao;
import org.agnitas.dao.ProfileFieldDao;
import org.agnitas.dao.RecipientDao;
import org.agnitas.util.AgnUtils;
import org.agnitas.util.CaseInsensitiveMap;
import org.agnitas.util.CsvColInfo;
import org.agnitas.util.CsvTokenizer;
import org.agnitas.util.ImportUtils;
import org.agnitas.util.importvalues.Charset;
import org.agnitas.util.importvalues.Separator;
import org.agnitas.util.importvalues.TextRecognitionChar;
import org.agnitas.web.forms.ImportProfileColumnsForm;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
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;
/**
* Action that handles import profile column mapping management
*
* @author Vyacheslav Stepanov
*/
public class ImportProfileColumnsAction extends ImportBaseFileAction {
public static final int ACTION_UPLOAD = ACTION_LAST + 1;
public static final int ACTION_ADD_COLUMN = ACTION_LAST + 2;
public static final int ACTION_SKIP = ACTION_LAST + 3;
public static final int ACTION_REMOVE_MAPPING = ACTION_LAST + 4;
/**
* 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: resets all form data and loads import profile with columns of recipient table.<br>
* Also adds column mappings from uploaded csv-file using import profile settings for parsing and
* forwards to view page.
* <br><br>
* ACTION_UPLOAD: adds column mappings from uploaded csv-file using import profile settings for parsing and
* forwards to view page.
* <br><br>
* ACTION_ADD_COLUMN: creates a completely new mapping and adds it to profile mappings list. By default database
* column for new mapping is not set. Forwards to view page.
* <br><br>
* ACTION_REMOVE_MAPPING: removes existing column mapping from import profile and forwards to view page.
* <br><br>
* ACTION_SAVE: updates import profile with mappings in database and forwards to mappings view page.<br>
* Before updating import profile method <code>checkErrorsOnSave</code> is called. In default
* implementation (OpenEMM) just returns false. It can be overridden by sub classes of
* ImportProfileColumnsAction for additional validation.
* <br><br>
* Any other ACTION_* would cause a forward to mappings view page.
* <br>
* @param mapping The ActionMapping used to select this instance
* @param form The optional ActionForm bean for this request (if any)
* @param request The HTTP request we are processing. <br>
* If the request parameter "add" is set - changes action to ACTION_ADD_COLUMN.<br>
* If the request parameter "removeMapping" is set - changes action to ACTION_REMOVE_MAPPING.
* @param res The HTTP response we are creating
* @throws java.io.IOException if an input/output error occurs
* @throws javax.servlet.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 request,
HttpServletResponse res)
throws IOException, ServletException {
super.execute(mapping, form, request, res);
// Validate the request parameters specified by the user
ImportProfileColumnsForm aForm;
ActionMessages messages = new ActionMessages();
ActionMessages errors = new ActionMessages();
ActionForward destination = null;
if (!AgnUtils.isUserLoggedIn(request)) {
return mapping.findForward("logon");
}
if (form != null) {
aForm = (ImportProfileColumnsForm) form;
} else {
aForm = new ImportProfileColumnsForm();
}
AgnUtils.logger().info("Action: " + aForm.getAction());
if (fileUploadPerformed) {
aForm.setAction(ACTION_UPLOAD);
}
if (fileRemovePerformed) {
aForm.setAction(ACTION_SKIP);
}
if (AgnUtils.parameterNotEmpty(request, "add")) {
aForm.setAction(ACTION_ADD_COLUMN);
}
if (ImportUtils.hasNoEmptyParameterStartsWith(request, "removeMapping")) {
aForm.setAction(ACTION_REMOVE_MAPPING);
}
try {
switch (aForm.getAction()) {
case ImportProfileColumnsAction.ACTION_VIEW:
aForm.reset(mapping, request);
aForm.resetFormData();
loadImportProfile(aForm, request);
loadDbColumns(aForm, request);
if (aForm.getHasFile()) {
addColumnsFromFile(aForm, request);
}
aForm.setAction(ImportProfileColumnsAction.ACTION_SAVE);
destination = mapping.findForward("view");
break;
case ImportProfileColumnsAction.ACTION_UPLOAD:
addColumnsFromFile(aForm, request);
aForm.setAction(ImportProfileColumnsAction.ACTION_SAVE);
destination = mapping.findForward("view");
break;
case ImportProfileColumnsAction.ACTION_ADD_COLUMN:
addColumn(aForm);
aForm.setAction(ImportProfileColumnsAction.ACTION_SAVE);
destination = mapping.findForward("view");
break;
case ImportProfileColumnsAction.ACTION_REMOVE_MAPPING:
removeMapping(aForm, request);
aForm.setAction(ImportProfileColumnsAction.ACTION_SAVE);
destination = mapping.findForward("view");
break;
case ImportProfileColumnsAction.ACTION_SAVE:
if (!checkErrorsOnSave(aForm, errors)) {
saveMappings(aForm);
messages.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage("default.changes_saved"));
}
aForm.setAction(ImportProfileColumnsAction.ACTION_SAVE);
destination = mapping.findForward("view");
break;
default:
aForm.setAction(ImportProfileColumnsAction.ACTION_SAVE);
destination = mapping.findForward("view");
}
} catch (Exception e) {
AgnUtils.logger().error("execute: " + e + "\n" + AgnUtils.getStackTrace(e));
errors.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage("error.exception"));
}
// Report any errors we have discovered back to the original form
if (!errors.isEmpty()) {
saveErrors(request, errors);
}
if (!messages.isEmpty()) {
saveMessages(request, messages);
}
return destination;
}
/**
* In default implementation (OpenEMM) just returns false. It can be overridden by sub classes of
* ImportProfileColumnsAction for additional validation.
*
* @param aForm form
* @param errors <code>ActionMessages</code> object to put errors to
* @return true if errors are found
*/
protected boolean checkErrorsOnSave(ImportProfileColumnsForm aForm, ActionMessages errors) {
return false;
}
/**
* Handle user action to remove column mapping
*
* @param aForm a form
* @param request request
*/
private void removeMapping(ImportProfileColumnsForm aForm, HttpServletRequest request) {
String value = ImportUtils.getNotEmptyValueFromParameter(request, "removeMapping_");
Integer columnIndex = Integer.valueOf(value);
List<ColumnMapping> mappingList = aForm.getProfile().getColumnMapping();
mappingList.remove(columnIndex.intValue());
}
/**
* Saves column mappings done by user on edit-page
*
* @param aForm a form
*/
private void saveMappings(ImportProfileColumnsForm aForm) {
ImportProfileDao profileDao = (ImportProfileDao) getWebApplicationContext().getBean("ImportProfileDao");
profileDao.updateImportProfile(aForm.getProfile());
}
/**
* Handles column adding that user performed on edit-page
*
* @param aForm a form
*/
private void addColumn(ImportProfileColumnsForm aForm) {
String newColumn = aForm.getNewColumn();
ColumnMappingImpl mapping = new ColumnMappingImpl();
mapping.setFileColumn(newColumn);
mapping.setDatabaseColumn("");
mapping.setProfileId(aForm.getProfileId());
mapping.setMandatory(false);
aForm.getProfile().getColumnMapping().add(mapping);
aForm.setNewColumn("");
}
/**
* Loads columns of recipient table
*
* @param aForm a form
* @param request request
* @throws Exception
*/
private void loadDbColumns(ImportProfileColumnsForm aForm, HttpServletRequest request) throws Exception {
RecipientDao recipientDao = (RecipientDao) getWebApplicationContext().getBean("RecipientDao");
CaseInsensitiveMap<CsvColInfo> dbColumns = recipientDao.readDBColumns(AgnUtils.getCompanyID(request));
ImportUtils.removeHiddenColumns(dbColumns);
final Set keySet = dbColumns.keySet();
List<String> lKeySet = new ArrayList();
for(String tempString : (String[]) keySet.toArray(new String[0])){
lKeySet.add(tempString);
}
Collections.sort(lKeySet);
aForm.setDbColumns(lKeySet.toArray(new String[0]));
// load DB columns default values
ProfileFieldDao profileFieldDao = (ProfileFieldDao) getWebApplicationContext().getBean("ProfileFieldDao");
List<ProfileField> profileFields = profileFieldDao.getProfileFields(AgnUtils.getCompanyID(request));
for(ProfileField profileField : profileFields) {
aForm.getDbColumnsDefaults().put(profileField.getColumn(), profileField.getDefaultValue());
}
}
/**
* Adds column mappings from uploaded csv-file using import profile settings
* for parsing csv file
*
* @param aForm a form
* @param request request
*/
private void addColumnsFromFile(ImportProfileColumnsForm aForm, HttpServletRequest request) {
RecipientDao recipientDao = (RecipientDao) getWebApplicationContext().getBean("RecipientDao");
if (aForm.getProfile() == null) {
loadImportProfile(aForm, request);
}
ImportProfile profile = aForm.getProfile();
List<ColumnMapping> columnMappings = profile.getColumnMapping();
File file = getCurrentFile(request);
if (file == null) {
return;
}
try {
Map dbColumns = recipientDao.readDBColumns(AgnUtils.getCompanyID(request));
String profileCharset = Charset.getValue(profile.getCharset());
String fileString = FileUtils.readFileToString(file, profileCharset);
LineNumberReader aReader = new LineNumberReader(new StringReader(fileString));
String firstLine = aReader.readLine();
firstLine = firstLine.trim();
CsvTokenizer tokenizer = new CsvTokenizer(firstLine,
Separator.getValue(profile.getSeparator()),
TextRecognitionChar.getValue(profile.getTextRecognitionChar()));
String[] newCsvColumns = tokenizer.toArray();
List<ColumnMapping> newMappings = new ArrayList<ColumnMapping>();
for (String newCsvColumn : newCsvColumns) {
if (!aForm.columnExists(newCsvColumn, columnMappings) && !StringUtils.isEmpty(newCsvColumn)) {
ColumnMapping newMapping = new ColumnMappingImpl();
newMapping.setProfileId(profile.getId());
newMapping.setMandatory(false);
newMapping.setFileColumn(newCsvColumn);
if (dbColumns.get(newCsvColumn) != null) {
String dbColumn = getInsensetiveMapKey(dbColumns, newCsvColumn);
if (dbColumn == null) {
// if dbColumn is NULL mapping is invalid
continue;
}
newMapping.setDatabaseColumn(dbColumn);
String defaultValue = aForm.getDbColumnsDefaults().get(dbColumn);
if (defaultValue != null) {
newMapping.setDefaultValue(defaultValue);
}
}
newMappings.add(newMapping);
}
}
profile.getColumnMapping().addAll(newMappings);
} catch (IOException e) {
AgnUtils.logger().error("Error reading csv-file: " + e + "\n" + AgnUtils.getStackTrace(e));
} catch (Exception e) {
AgnUtils.logger().error("Error reading csv-file: " + e + "\n" + AgnUtils.getStackTrace(e));
}
}
/**
* Method returns the key of map that is case-insensitive-equals to column param
*
* @param map insensitive map
* @param column the column that is compared to keys of map
* @return the key of map
*/
private String getInsensetiveMapKey(Map map, String column) {
for (Object keyObject : map.keySet()) {
String key = (String) keyObject;
if (key.equalsIgnoreCase(column)) {
return key;
}
}
return null;
}
/**
* Loads import profile from DB using Dao
*
* @param aForm a form
* @param request request
*/
private void loadImportProfile(ImportProfileColumnsForm aForm, HttpServletRequest request) {
ImportProfileDao profileDao = (ImportProfileDao) getWebApplicationContext().getBean("ImportProfileDao");
ImportProfile profile = profileDao.getImportProfileById(aForm.getProfileId());
aForm.setProfile(profile);
}
}