ImportDataVerifyAction.java

/*
 * This software was designed and created by Jason Carroll.
 * Copyright (c) 2002, 2003, 2004 Jason Carroll.
 * The author can be reached at jcarroll@cowsultants.com
 * ITracker website: http://www.cowsultants.com
 * ITracker forums: http://www.cowsultants.com/phpBB/index.php
 *
 * This program is free software; you can redistribute it and/or modify
 * it only under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

package org.itracker.web.actions.admin.configuration;

import org.apache.commons.beanutils.PropertyUtils;
import org.apache.log4j.Logger;
import org.apache.struts.action.*;
import org.apache.struts.upload.FormFile;
import org.itracker.ImportExportException;
import org.itracker.core.resources.ITrackerResources;
import org.itracker.model.*;
import org.itracker.model.util.SystemConfigurationUtilities;
import org.itracker.model.util.UserUtilities;
import org.itracker.persistence.dao.NoSuchEntityException;
import org.itracker.services.ConfigurationService;
import org.itracker.services.ProjectService;
import org.itracker.services.UserService;
import org.itracker.web.actions.base.ItrackerBaseAction;
import org.itracker.web.util.Constants;
import org.itracker.web.util.ImportExportUtilities;
import org.itracker.web.util.LoginUtilities;
import org.itracker.web.util.ServletContextUtils;

import javax.naming.InitialContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.List;


/**
 * Performs a verification on the import data to ensure that it contains no errors,
 * applies any import options, and also updates the data to reuse any current system
 * data if needed.  It also collects statistics on the import data to display to the user
 * before the import is actually processed.
 * <br><br>
 * When reusing existing system data.  The following criteria is used to determine if
 * a piece of data matches an existing system resource:<br>
 * User - the login<br>
 * Project - the project name<br>
 * Status, Severity, Resolution - the name of the item as defined in the language root/base locale<br>
 * Custom Fields - the label name of the custom field as defined in the language root/base locale<br>
 */
public class ImportDataVerifyAction extends ItrackerBaseAction {
    private static final Logger log = Logger.getLogger(ImportDataVerifyAction.class);

    private static final int UPDATE_STATUS = 1;
    private static final int UPDATE_SEVERITY = 2;
    private static final int UPDATE_RESOLUTION = 3;

    public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        ActionMessages errors = new ActionMessages();


        if (!LoginUtilities.hasPermission(PermissionType.USER_ADMIN, request, response)) {
            return mapping.findForward("unauthorized");
        }

        try {
            FormFile file = (FormFile) PropertyUtils.getSimpleProperty(form, "importFile");
            ImportDataModel model = new ImportDataModel();
            AbstractEntity[] importData = ImportExportUtilities.importIssues(new InputStreamReader(file.getInputStream()));
            AbstractEntity[] existingModel = new AbstractEntity[importData.length];

            model.setReuseUsers((Boolean) PropertyUtils.getSimpleProperty(form, "optionreuseusers"));
            model.setReuseProjects((Boolean) PropertyUtils.getSimpleProperty(form, "optionreuseprojects"));
            model.setReuseConfig((Boolean) PropertyUtils.getSimpleProperty(form, "optionreuseconfig"));
            model.setCreatePasswords((Boolean) PropertyUtils.getSimpleProperty(form, "optioncreatepasswords"));
            model.setData(importData, existingModel);

            InitialContext ic = new InitialContext();
            checkConfig(model, ic);
            log.debug(model.toString());
            checkUsers(model, ic);
            log.debug(model.toString());
            checkProjects(model, ic);
            log.debug(model.toString());
            checkIssues(model, ic);
            log.debug(model.toString());

            HttpSession session = request.getSession(true);
            session.setAttribute(Constants.IMPORT_DATA_KEY, model);
        } catch (ImportExportException iee) {
            if (iee.getType() == ImportExportException.TYPE_INVALID_LOGINS) {
                log.error("Invalid logins found while verifying import data.");
                errors.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage("itracker.web.error.importexport.invalidlogins", iee.getMessage()));
            } else if (iee.getType() == ImportExportException.TYPE_INVALID_STATUS) {
                log.error("Invalid status found while verifying import data.");
                errors.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage("itracker.web.error.importexport.invalidstatus", iee.getMessage()));
            } else {
                log.error("Exception while verifying import data.", iee);
                errors.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage("itracker.web.error.system"));
            }
        } catch (Exception e) {
            log.error("Exception while verifying import data.", e);
            errors.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage("itracker.web.error.system"));
        }

        if (!errors.isEmpty()) {
            saveErrors(request, errors);
            return mapping.getInputForward();
        }

        return mapping.findForward("importdataverify");
    }

    private void checkConfig(ImportDataModel model, InitialContext ic) throws ImportExportException {
        try {
            int maxSeverityValue = 1;
            int maxResolutionValue = 1;

            ConfigurationService configurationService = getITrackerServices().getConfigurationService();

            List<Configuration> statuses = configurationService.getConfigurationItemsByType(Configuration.Type.status, ImportExportUtilities.EXPORT_LOCALE);
            List<Configuration> severities = configurationService.getConfigurationItemsByType(Configuration.Type.severity, ImportExportUtilities.EXPORT_LOCALE);
            List<Configuration> resolutions = configurationService.getConfigurationItemsByType(Configuration.Type.resolution, ImportExportUtilities.EXPORT_LOCALE);
            List<CustomField> fields = configurationService.getCustomFields();

            for (int i = 0; i < severities.size(); i++) {
                maxSeverityValue = Math.max(maxSeverityValue, Integer.parseInt(severities.get(i).getValue()));
            }
            for (int i = 0; i < resolutions.size(); i++) {
                maxResolutionValue = Math.max(maxResolutionValue, Integer.parseInt(resolutions.get(i).getValue()));
            }

            AbstractEntity[] importData = model.getData();
            for (int i = 0; i < importData.length; i++) {
                if (importData[i] instanceof Configuration) {
                    // Need to check to see if it finds a matching name.  If so change value.
                    // For status, if it finds a matching value but not name, this is an error.
                    // Otherwise, just change the value for the resolution and severity.  Then iterate
                    // through the issues and update the old value to the new one since they are all stored
                    // as strings/ints, not the id to the config item.
                    Configuration configItem = (Configuration) importData[i];
                    if (configItem.getType() == Configuration.Type.status) {
                        boolean found = false;
                        for (int j = 0; j < statuses.size(); j++) {
                            if (model.getReuseConfig() && statuses.get(j).getName().equalsIgnoreCase(configItem.getName())) {
                                // Matching status, update issues
                                updateIssues(importData, UPDATE_STATUS, configItem.getValue(), statuses.get(j).getValue());
                                model.setExistingModel(i, configItem);
                                model.addVerifyStatistic(ImportExportUtilities.IMPORT_STAT_STATUSES, ImportExportUtilities.IMPORT_STAT_REUSED);
                                found = true;
                                break;
                            } else if (statuses.get(j).getValue().equalsIgnoreCase(configItem.getValue())) {
                                // Found a matching status value, and the name didn't match
                                throw new ImportExportException(configItem.getValue(), ImportExportException.TYPE_INVALID_STATUS);
                            }
                        }
                        if (!found) {
                            model.addVerifyStatistic(ImportExportUtilities.IMPORT_STAT_STATUSES, ImportExportUtilities.IMPORT_STAT_NEW);
                        }
                    } else if (configItem.getType() == Configuration.Type.severity) {
                        boolean found = false;
                        if (model.getReuseConfig()) {
                            for (int j = 0; j < severities.size(); j++) {
                                if (severities.get(j).getName().equalsIgnoreCase(configItem.getName())) {
                                    // Matching severity, update issues
                                    updateIssues(importData, UPDATE_SEVERITY, configItem.getValue(), severities.get(j).getValue());
                                    model.setExistingModel(i, configItem);
                                    model.addVerifyStatistic(ImportExportUtilities.IMPORT_STAT_SEVERITIES, ImportExportUtilities.IMPORT_STAT_REUSED);
                                    found = true;
                                    break;
                                }
                            }
                        }
                        if (!found) {
                            updateIssues(importData, UPDATE_SEVERITY, configItem.getValue(), Integer.toString(++maxSeverityValue));
                            configItem.setValue(Integer.toString(maxSeverityValue));
                            model.addVerifyStatistic(ImportExportUtilities.IMPORT_STAT_SEVERITIES, ImportExportUtilities.IMPORT_STAT_NEW);
                        }
                    } else if (configItem.getType() == Configuration.Type.resolution) {
                        boolean found = false;
                        if (model.getReuseConfig()) {
                            for (int j = 0; j < resolutions.size(); j++) {
                                if (resolutions.get(j).getName().equalsIgnoreCase(configItem.getName())) {
                                    // Matching resolution, update issues
                                    updateIssues(importData, UPDATE_RESOLUTION, configItem.getValue(), resolutions.get(j).getValue());
                                    model.setExistingModel(i, configItem);
                                    model.addVerifyStatistic(ImportExportUtilities.IMPORT_STAT_RESOLUTIONS, ImportExportUtilities.IMPORT_STAT_REUSED);
                                    found = true;
                                    break;
                                }
                            }
                        }
                        if (!found) {
                            updateIssues(importData, UPDATE_RESOLUTION, configItem.getValue(), Integer.toString(++maxResolutionValue));
                            configItem.setValue(Integer.toString(maxResolutionValue));
                            model.addVerifyStatistic(ImportExportUtilities.IMPORT_STAT_RESOLUTIONS, ImportExportUtilities.IMPORT_STAT_NEW);
                        }
                    }
                } else if (importData[i] instanceof CustomField) {
                    boolean found = false;
                    CustomField field = (CustomField) importData[i];
                    if (model.getReuseFields()) {
                        for (int j = 0; j < fields.size(); j++) {
                            if (fields.get(j).getFieldType() == field.getFieldType() /*&& fields.get(j).getName().equalsIgnoreCase(field.getName())*/) {
                                // Matching custom field.  Set id, but don't need to update issues
                                // since it contains the customfield model
                                field.setId(fields.get(j).getId());
                                model.setExistingModel(i, field);
                                model.addVerifyStatistic(ImportExportUtilities.IMPORT_STAT_FIELDS, ImportExportUtilities.IMPORT_STAT_REUSED);
                                found = true;
                                break;
                            }
                        }
                    }
                    if (!found) {
                        model.addVerifyStatistic(ImportExportUtilities.IMPORT_STAT_FIELDS, ImportExportUtilities.IMPORT_STAT_NEW);
                    }
                }
            }
        } catch (ImportExportException iee) {
            throw iee;
        } catch (Exception e) {
            log.error("Error verifiying import data.", e);
            throw new ImportExportException(e.getMessage());
        }
    }

    private void checkUsers(ImportDataModel model, InitialContext ic) throws ImportExportException {
        String invalidLogins = null;

        try {
            UserService userService = getITrackerServices().getUserService();

            AbstractEntity[] importData = model.getData();

            for (int i = 0; i < importData.length; i++) {
                if (importData[i] instanceof User) {
                    User user = (User) importData[i];
                    User existingUser;
                    try {
                        existingUser = userService.getUserByLogin(user.getLogin());
                    } catch (NoSuchEntityException e) {
                        existingUser = null;
                    }
                    if (existingUser != null) {
                        if (model.getReuseUsers()) {
                            user.setId(existingUser.getId());
                            model.setExistingModel(i, existingUser);
                            model.addVerifyStatistic(ImportExportUtilities.IMPORT_STAT_USERS, ImportExportUtilities.IMPORT_STAT_REUSED);
                            log.debug("Reusing existing user " + user.getLogin() + "(" + user.getId() + ") during import.");
                        } else {
                            log.debug("Existing user " + existingUser.getLogin() + "(" + existingUser.getId() + ") during import.  Adding to invalid login list.");
                            invalidLogins = (invalidLogins == null ?
                                    existingUser.getLogin() :
                                    new StringBuffer(invalidLogins).append(", ").append(existingUser.getLogin()).toString());
                        }
                    } else {
                        model.addVerifyStatistic(ImportExportUtilities.IMPORT_STAT_USERS, ImportExportUtilities.IMPORT_STAT_NEW);
                    }
                }
            }
        } catch (Exception e) {
            log.error("Error verifying import data.", e);
            throw new ImportExportException(e.getMessage());
        }

        if (invalidLogins != null) {
            throw new ImportExportException(invalidLogins, ImportExportException.TYPE_INVALID_LOGINS);
        }
    }

    private void checkProjects(ImportDataModel model, InitialContext ic) throws ImportExportException {
        try {
            ProjectService projectService = ServletContextUtils.getItrackerServices().getProjectService();

            List<Project> existingProjects = projectService.getAllProjects();

            AbstractEntity[] importData = model.getData();

            for (int i = 0; i < importData.length; i++) {
                if (importData[i] instanceof Project) {
                    if (!model.getReuseProjects()) {
                        model.addVerifyStatistic(ImportExportUtilities.IMPORT_STAT_PROJECTS, ImportExportUtilities.IMPORT_STAT_NEW);
                        continue;
                    }

                    Project project = (Project) importData[i];
                    for (int j = 0; j < existingProjects.size(); j++) {
                        log.debug("Project Name: " + project.getName() + "  Existing Project: " + existingProjects.get(j).getName());
                        log.debug("Project Name: " + ITrackerResources.escapeUnicodeString(project.getName(), false) + "  Existing Project: " + ITrackerResources.escapeUnicodeString(existingProjects.get(j).getName(), false));
                        if (project.getName() != null && project.getName().equalsIgnoreCase(existingProjects.get(j).getName())) {
                            project.setId(existingProjects.get(j).getId());
                            model.setExistingModel(i, project);
                            model.addVerifyStatistic(ImportExportUtilities.IMPORT_STAT_PROJECTS, ImportExportUtilities.IMPORT_STAT_REUSED);

                            log.debug("Reusing existing project " + project.getName() + "(" + project.getId() + ") during import.");
                            break;
                        }
                    }
                    model.addVerifyStatistic(ImportExportUtilities.IMPORT_STAT_PROJECTS, ImportExportUtilities.IMPORT_STAT_NEW);

                }
            }
        } catch (Exception e) {
            log.error("Error verifiying import data.", e);
            throw new ImportExportException(e.getMessage());
        }
    }

    private void checkIssues(ImportDataModel model, InitialContext ic) throws ImportExportException {
        AbstractEntity[] importData = model.getData();

        for (int i = 0; i < importData.length; i++) {
            if (importData[i] instanceof Issue) {
                model.addVerifyStatistic(ImportExportUtilities.IMPORT_STAT_ISSUES, ImportExportUtilities.IMPORT_STAT_NEW);
            }
        }
    }

    private void updateIssues(AbstractEntity[] models, int updateType, String currentValue, String newValue) throws ImportExportException {
        if (models == null || currentValue == null || newValue == null) {
            return;
        }

        try {
            for (int i = 0; i < models.length; i++) {
                if (models[i] instanceof Issue) {
                    Issue issue = (Issue) models[i];
                    if (updateType == UPDATE_STATUS && issue.getStatus() == Integer.parseInt(currentValue)) {
                        issue.setStatus(Integer.parseInt(newValue));
                    } else if (updateType == UPDATE_SEVERITY && issue.getSeverity() == Integer.parseInt(currentValue)) {
                        issue.setSeverity(Integer.parseInt(newValue));
                    } else if (updateType == UPDATE_RESOLUTION && currentValue.equalsIgnoreCase(issue.getResolution())) {
                        issue.setResolution(newValue);
                    }
                }
            }
        } catch (Exception e) {
            log.debug("Unable to update configuration data in issues.", e);
            throw new ImportExportException("Unable to update configuration data in issues: " + e.getMessage());
        }
    }
}