ImportHandler.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.util;

import org.apache.log4j.Logger;
import org.itracker.core.resources.ITrackerResources;
import org.itracker.model.*;
import org.itracker.model.util.IssueUtilities;
import org.itracker.model.util.UserUtilities;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import java.util.*;


/**
 * This class provides functionality needed to parse an XML document to be imported into
 * an ITracker instance, into a set of data models.
 */
public class ImportHandler extends DefaultHandler implements ImportExportTags {

    private final Logger logger;
    private List<AbstractEntity> items;
    private StringBuffer tagBuffer;
    private SAXException endException;

    private AbstractEntity parentModel;
    private AbstractEntity childModel;
    private List<Object> itemList;
    private String tempStorage;

    public ImportHandler() {
        this.logger = Logger.getLogger(getClass());
        this.items = new ArrayList<>();
        this.endException = null;
    }

    public AbstractEntity[] getModels() {
        return items.toArray(new AbstractEntity[items.size()]);
    }

    @Override
    public void startDocument() {
        logger.debug("Started import xml parsing.");
    }

    @Override
    public void endDocument() {
        logger.debug("Completed import xml parsing.");
    }

    @Override
    public void startElement(String uri, String name, String qName, Attributes atts) throws SAXException {
        logger.debug("Parsing import tag " + qName);

        if (endException != null) {
            throw endException;
        }


        tempStorage = "";
        try {
            if (TAG_COMPONENT.equals(qName)) {
                String id = atts.getValue(ATTR_SYSTEMID);
                if (id == null) {
                    throw new SAXException("Attribute " + ATTR_SYSTEMID + " was null for component.");
                }

                // FIXME: pass component name instead of import/export ID. 
                childModel = new Component((Project) parentModel, atts.getValue(ATTR_ID));
                childModel.setId(new Integer(id));
            } else if (TAG_COMPONENTS.equals(qName)) {
                itemList = new ArrayList<>();
            } else if (TAG_CONFIGURATION.equals(qName)) {
                parentModel = new SystemConfiguration();
            } else if (TAG_CUSTOM_FIELD.equals(qName)) {
                String id = atts.getValue(ATTR_SYSTEMID);
                if (id == null) {
                    throw new SAXException("Attribute " + ATTR_SYSTEMID + " was null for issue.");
                }

                childModel = new CustomField();
                childModel.setId(new Integer(id));
            } else if (TAG_CUSTOM_FIELD_OPTION.equals(qName)) {
                tempStorage = ITrackerResources.unescapeUnicodeString(atts.getValue(ATTR_VALUE));
            } else if (TAG_CUSTOM_FIELDS.equals(qName)) {
                itemList = new ArrayList<>();
            } else if (TAG_HISTORY_ENTRY.equals(qName)) {
                String creatorId = atts.getValue(ATTR_CREATOR_ID);
                String date = atts.getValue(ATTR_DATE);
                String status = atts.getValue(ATTR_STATUS);
                if (creatorId == null) {
                    throw new SAXException("Attribute " + ATTR_CREATOR_ID + " was null for issue history.");
                } else if (date == null) {
                    throw new SAXException("Attribute date was null for issue history.");
                }

                childModel = new IssueHistory();
                ((IssueHistory) childModel).setUser((User) findModel(creatorId));
                ((IssueHistory) childModel).setStatus(status != null && !status.equals("") ? Integer.parseInt(status) : IssueUtilities.HISTORY_STATUS_AVAILABLE);
                childModel.setCreateDate(getDateValue(date, qName));
                tagBuffer = new StringBuffer();
            } else if (TAG_ISSUE.equals(qName)) {
                String id = atts.getValue(ATTR_SYSTEMID);
                if (id == null) {
                    throw new SAXException("Attribute " + ATTR_SYSTEMID + " was null for issue.");
                }

                parentModel = new Issue();
                parentModel.setId(new Integer(id));
            } else if (TAG_ISSUE_ATTACHMENT.equals(qName)) {
                childModel = new IssueAttachment();
            } else if (TAG_ISSUE_ATTACHMENTS.equals(qName)) {
                itemList = new ArrayList<Object>();
            } else if (TAG_ISSUE_COMPONENTS.equals(qName)) {
                itemList = new ArrayList<Object>();
            } else if (TAG_ISSUE_FIELD.equals(qName)) {
                String id = atts.getValue(ATTR_ID);
                if (id == null) {
                    throw new SAXException("Attribute " + ATTR_SYSTEMID + " was null for issue field.");
                }
                childModel = new IssueField((Issue) parentModel, (CustomField) findModel(id));
            } else if (TAG_ISSUE_FIELDS.equals(qName)) {
                itemList = new ArrayList<>();
            } else if (TAG_ISSUE_HISTORY.equals(qName)) {
                itemList = new ArrayList<>();
            } else if (TAG_ISSUE_VERSIONS.equals(qName)) {
                itemList = new ArrayList<>();
            } else if (TAG_PROJECT.equals(qName)) {
                String id = atts.getValue(ATTR_SYSTEMID);
                if (id == null) {
                    throw new SAXException("Attribute " + ATTR_SYSTEMID + " was null for project.");
                }

                parentModel = new Project();
                parentModel.setId(new Integer(id));
            } else if (TAG_PROJECT_FIELDS.equals(qName)) {
                itemList = new ArrayList<>();
            } else if (TAG_PROJECT_OWNERS.equals(qName)) {
                itemList = new ArrayList<>();
            } else if (TAG_RESOLUTION.equals(qName)) {
                String value = atts.getValue(ATTR_VALUE);
                String order = atts.getValue(ATTR_ORDER);

                childModel = new Configuration(Configuration.Type.resolution, value, Integer.parseInt(order));
                tagBuffer = new StringBuffer();
            } else if (TAG_SEVERITY.equals(qName)) {
                String value = atts.getValue(ATTR_VALUE);
                String order = atts.getValue(ATTR_ORDER);

                childModel = new Configuration(Configuration.Type.severity, value, Integer.parseInt(order));
                tagBuffer = new StringBuffer();
            } else if (TAG_STATUS.equals(qName)) {
                String value = atts.getValue(ATTR_VALUE);
                String order = atts.getValue(ATTR_ORDER);

                childModel = new Configuration(Configuration.Type.status, value, Integer.parseInt(order));
                tagBuffer = new StringBuffer();
            } else if (TAG_USER.equals(qName)) {
                String id = atts.getValue(ATTR_SYSTEMID);
                if (id == null) {
                    throw new SAXException("Attribute " + ATTR_SYSTEMID + " was null for user.");
                }

                parentModel = new User();
                parentModel.setId(new Integer(id));
            } else if (TAG_VERSION.equals(qName)) {
                String id = atts.getValue(ATTR_SYSTEMID);
                if (id == null) {
                    throw new SAXException("Attribute " + ATTR_SYSTEMID + " was null for version.");
                }

                // FIXME: pass version number instead of import/export ID. 
                childModel = new Version((Project) parentModel, atts.getValue(ATTR_ID));
                childModel.setId(new Integer(id));
            } else if (TAG_VERSIONS.equals(qName)) {
                itemList = new ArrayList<>();
            } else {
                tagBuffer = new StringBuffer();
            }
        } catch (NumberFormatException nfe) {
            throw new SAXException("Attribute in " + qName + " did not contain a numeric value.");
        }
    }

    @Override
    public void endElement(String uri, String name, String qName) throws SAXException {
        logger.debug("Completing import tag " + qName);

        try {
            if (TAG_ISSUE.equals(qName) || TAG_PROJECT.equals(qName) || TAG_USER.equals(qName) || TAG_CONFIGURATION.equals(qName)) {
                items.add((AbstractEntity) parentModel.clone());
                parentModel = null;
                childModel = null;
                itemList = null;
            } else if (TAG_RESOLUTION.equals(qName) || TAG_SEVERITY.equals(qName) || TAG_STATUS.equals(qName)) {
                ((Configuration) childModel).setName(getBuffer());
                items.add((AbstractEntity) childModel.clone());
                ((SystemConfiguration) parentModel).addConfiguration((Configuration) childModel);
                childModel = null;
            } else if (TAG_COMPONENT.equals(qName) || TAG_VERSION.equals(qName) || TAG_CUSTOM_FIELD.equals(qName)) {
                // Add to both so we can search the the models for components, customfields, and versions later
                // Make sure they are double added when processed
                items.add((AbstractEntity) childModel.clone());
                itemList.add(childModel.clone());
                childModel = null;
            } else if (TAG_HISTORY_ENTRY.equals(qName)) {
                ((IssueHistory) childModel).setDescription(getBuffer());
                itemList.add(childModel.clone());
                childModel = null;
            } else if (TAG_ISSUE_ATTACHMENT.equals(qName)) {
                itemList.add(childModel.clone());
                childModel = null;
            } else if (TAG_ISSUE_FIELD.equals(qName)) {
                itemList.add(childModel.clone());
                childModel = null;
            } else if (TAG_COMPONENTS.equals(qName)) {
                List<Component> itemListArray = new ArrayList<>();
                for (Object anItemList : itemList) {
                    itemListArray.add((Component) anItemList);
                }
                ((Project) parentModel).setComponents(itemListArray);
            } else if (TAG_COMPONENT_DESCRIPTION.equals(qName)) {
                ((Component) childModel).setDescription(getBuffer());
            } else if (TAG_COMPONENT_ID.equals(qName)) {
                if (itemList == null) {
                    itemList = new ArrayList<>();
                }
                itemList.add(findModel(getBuffer()));
            } else if (TAG_COMPONENT_NAME.equals(qName)) {
                ((Component) childModel).setName(getBuffer());
            } else if (TAG_CONFIGURATION_VERSION.equals(qName)) {
                ((SystemConfiguration) parentModel).setVersion(getBuffer());
            } else if (TAG_CREATE_DATE.equals(qName)) {
                parentModel.setCreateDate(getDateValue(getBuffer(), qName));
            } else if (TAG_CREATOR.equals(qName)) {
                ((Issue) parentModel).setCreator((User) findModel(getBuffer()));
            } else if (TAG_CUSTOM_FIELDS.equals(qName)) {
                List<CustomField> itemListArray = new ArrayList<>();
                for (Object anItemList : itemList) {
                    itemListArray.add((CustomField) anItemList);
                }
                ((SystemConfiguration) parentModel).setCustomFields(itemListArray);
            } else if (TAG_CUSTOM_FIELD_DATEFORMAT.equals(qName)) {
                ((CustomField) childModel).setDateFormat(getBuffer());
            } else if (TAG_CUSTOM_FIELD_LABEL.equals(qName)) {
//                ((CustomField) childModel).setName(getBuffer());
//            	TODO handle configuration for setting the BASE label
            } else if (TAG_CUSTOM_FIELD_OPTION.equals(qName)) {
                ((CustomField) childModel).addOption(tempStorage, getBuffer());
            } else if (TAG_CUSTOM_FIELD_REQUIRED.equals(qName)) {
                ((CustomField) childModel).setRequired(("true".equalsIgnoreCase(getBuffer())));
            } else if (TAG_CUSTOM_FIELD_SORTOPTIONS.equals(qName)) {
                ((CustomField) childModel).setSortOptionsByName(("true".equalsIgnoreCase(getBuffer())));
            } else if (TAG_CUSTOM_FIELD_TYPE.equals(qName)) {
                final String s = getBuffer();
                try {
                    // fallback for old export type as int-code
                    int i = Integer.valueOf(s);
                    ((CustomField) childModel).setFieldType(CustomField.Type.valueOf(i));
                } catch (RuntimeException e) {
                    try {
                        ((CustomField) childModel).setFieldType(CustomField.Type.valueOf(s));
                    } catch (RuntimeException re) {
                        throw new SAXException("Could not convert string buffer to type value.");
                    }
                }
            } else if (TAG_EMAIL.equals(qName)) {
                ((User) parentModel).setEmail(getBuffer());
            } else if (TAG_FIRST_NAME.equals(qName)) {
                ((User) parentModel).setFirstName(getBuffer());
            } else if (TAG_ISSUE_ATTACHMENTS.equals(qName)) {
                List<IssueAttachment> itemListArray = new ArrayList<>();
                for (Object anItemList : itemList) {
                    itemListArray.add((IssueAttachment) anItemList);
                }
                ((Issue) parentModel).setAttachments(itemListArray);
            } else if (TAG_ISSUE_ATTACHMENT_CREATOR.equals(qName)) {
                ((IssueAttachment) childModel).setUser((User) findModel(getBuffer()));
            } else if (TAG_ISSUE_ATTACHMENT_DESCRIPTION.equals(qName)) {
                ((IssueAttachment) childModel).setDescription(getBuffer());
            } else if (TAG_ISSUE_ATTACHMENT_FILENAME.equals(qName)) {
                ((IssueAttachment) childModel).setFileName(getBuffer());
            } else if (TAG_ISSUE_ATTACHMENT_ORIGFILE.equals(qName)) {
                ((IssueAttachment) childModel).setOriginalFileName(getBuffer());
            } else if (TAG_ISSUE_ATTACHMENT_SIZE.equals(qName)) {
                ((IssueAttachment) childModel).setSize(getBufferAsLong());
            } else if (TAG_ISSUE_ATTACHMENT_TYPE.equals(qName)) {
                ((IssueAttachment) childModel).setType(getBuffer());
            } else if (TAG_ISSUE_COMPONENTS.equals(qName)) {
                List<Component> itemListArray = new ArrayList<>();
                for (Object anItemList : itemList) {
                    itemListArray.add((Component) anItemList);
                }
                ((Issue) parentModel).setComponents(itemListArray);
            } else if (TAG_ISSUE_DESCRIPTION.equals(qName)) {
                ((Issue) parentModel).setDescription(getBuffer());
            } else if (TAG_ISSUE_FIELDS.equals(qName)) {
                List<IssueField> itemListArray = new ArrayList<>();
                for (int i = 0; i < itemList.size(); i++) {
                    itemListArray.add(i, (IssueField) itemList.get(i));
                }
                ((Issue) parentModel).setFields(itemListArray);
            } else if (TAG_ISSUE_HISTORY.equals(qName)) {
                List<IssueHistory> itemListArray = new ArrayList<>();
                for (int i = 0; i < itemList.size(); i++) {
                    itemListArray.add(i, (IssueHistory) itemList.get(i));
                }
                ((Issue) parentModel).setHistory(itemListArray);
            } else if (TAG_ISSUE_PROJECT.equals(qName)) {
                ((Issue) parentModel).setProject((Project) findModel(getBuffer()));
            } else if (TAG_ISSUE_RESOLUTION.equals(qName)) {
                ((Issue) parentModel).setResolution(getBuffer());
            } else if (TAG_ISSUE_SEVERITY.equals(qName)) {
                ((Issue) parentModel).setSeverity(getBufferAsInt());
            } else if (TAG_ISSUE_STATUS.equals(qName)) {
                ((Issue) parentModel).setStatus(getBufferAsInt());
            } else if (TAG_ISSUE_VERSIONS.equals(qName)) {
                List<Version> itemListArray = new ArrayList<>();
                for (int i = 0; i < itemList.size(); i++) {
                    itemListArray.add(i, (Version) itemList.get(i));
                }
                ((Issue) parentModel).setVersions(itemListArray);
            } else if (TAG_LAST_MODIFIED.equals(qName)) {
                parentModel.setLastModifiedDate(getDateValue(getBuffer(), qName));
            } else if (TAG_LAST_NAME.equals(qName)) {
                ((User) parentModel).setLastName(getBuffer());
            } else if (TAG_LOGIN.equals(qName)) {
                ((User) parentModel).setLogin(getBuffer());
            } else if (TAG_OWNER.equals(qName)) {
                ((Issue) parentModel).setOwner((User) findModel(getBuffer()));
            } else if (TAG_PROJECT_NAME.equals(qName)) {
                ((Project) parentModel).setName(getBuffer());
            } else if (TAG_PROJECT_DESCRIPTION.equals(qName)) {
                ((Project) parentModel).setDescription(getBuffer());
            } else if (TAG_PROJECT_FIELDS.equals(qName)) {
                List<CustomField> itemListArray = new ArrayList<>();
                for (int i = 0; i < itemList.size(); i++) {
                    itemListArray.add(i, (CustomField) itemList.get(i));
                }
                ((Project) parentModel).setCustomFields(itemListArray);
            } else if (TAG_PROJECT_FIELD_ID.equals(qName)) {
                itemList.add(findModel(getBuffer()));
            } else if (TAG_PROJECT_OPTIONS.equals(qName)) {
                ((Project) parentModel).setOptions(getBufferAsInt());
            } else if (TAG_PROJECT_OWNERS.equals(qName)) {
                List<User> itemListArray = new ArrayList<>();
                for (int i = 0; i < itemList.size(); i++) {
                    itemListArray.add(i, (User) itemList.get(i));
                }
                ((Project) parentModel).setOwners(itemListArray);
            } else if (TAG_PROJECT_OWNER_ID.equals(qName)) {
                itemList.add(findModel(getBuffer()));
            } else if (TAG_PROJECT_STATUS.equals(qName)) {
                // By default activate the project
                ((Project) parentModel).setStatus(Status.ACTIVE);
                try {
                    ((Project) parentModel).setStatus(Status.valueOf(getBuffer().toUpperCase()));
                } catch (RuntimeException re) {
                    // ok, it is active.
                }
            } else if (TAG_SUPER_USER.equals(qName)) {
                ((User) parentModel).setSuperUser(("true".equalsIgnoreCase(getBuffer())));
            } else if (TAG_TARGET_VERSION_ID.equals(qName)) {
                ((Issue) parentModel).setTargetVersion((Version) findModel(getBuffer()));
            } else if (TAG_USER_STATUS.equals(qName)) {
                // By default lock the user
                ((User) parentModel).setStatus(UserUtilities.STATUS_LOCKED);
                // TODO: Make status Enum
                String currBuffer = getBuffer();
                try {
                    ((User)parentModel).setStatus(Integer.parseInt(currBuffer));
                } catch (RuntimeException re) {
                    HashMap<String, String> userStatuses = UserUtilities.getStatusNames(EXPORT_LOCALE);
                    for (String key : userStatuses.keySet()) {
                        String keyValue = userStatuses.get(key);
                        if (keyValue != null && keyValue.equalsIgnoreCase(currBuffer)) {
                            ((User) parentModel).setStatus(Integer.parseInt(key));
                            break;
                        }
                    }
                }
            } else if (TAG_VERSIONS.equals(qName)) {
                List<Version> itemListArray = new ArrayList<>();
                for (int i = 0; i < itemList.size(); i++) {
                    itemListArray.add(i, (Version) itemList.get(i));
                }
                ((Project) parentModel).setVersions(itemListArray);
            } else if (TAG_VERSION_DESCRIPTION.equals(qName)) {
                ((Version) childModel).setDescription(getBuffer());
            } else if (TAG_VERSION_ID.equals(qName)) {
                if (itemList == null) {
                    itemList = new ArrayList<>();
                }
                itemList.add(findModel(getBuffer()));
            } else if (TAG_VERSION_NUMBER.equals(qName)) {
                ((Version) childModel).setVersionInfo(getBuffer());
            }
        } catch (RuntimeException e) {
            logger.debug("endElement: RuntimeException importing data.", e);
            endException = new SAXException("Error processing tag " + qName + ": " + e.getMessage());
            throw endException;
        } catch (SAXException e) {
            logger.debug("endElement: SAXException importing data.", e);
            throw e;
        } catch (CloneNotSupportedException e) {
            logger.debug("endElement: CloneNotSupportedException importing data.", e);
            endException = new SAXException("Error processing tag " + qName + ": " + e.getMessage());
            throw endException;
        }
        tagBuffer = null;
    }

    @Override
    public void characters(char[] ch, int start, int length) {
        logger.debug("Read " + ch.length + " Start: " + start + " Length: " + length);
        logger.debug("String: " + new String(ch, start, length));
        if (tagBuffer != null) {
            tagBuffer.append(ITrackerResources.unescapeUnicodeString(new String(ch, start, length)));
        }
    }

    private String getBuffer() {
        if (tagBuffer == null) {
            return "";
        } else {
            return tagBuffer.toString();
        }
    }

    private int getBufferAsInt() throws SAXException {
        if (tagBuffer == null) {
            return -1;
        } else {
            try {
                return Integer.parseInt(tagBuffer.toString());
            } catch (NumberFormatException nfe) {
                throw new SAXException("Could not convert string buffer to int value.");
            }
        }
    }

    private long getBufferAsLong() throws SAXException {
        if (tagBuffer == null) {
            return -1;
        } else {
            try {
                return Long.parseLong(tagBuffer.toString());
            } catch (NumberFormatException nfe) {
                throw new SAXException("Could not convert string buffer to long value.");
            }
        }
    }

    private AbstractEntity findModel(String itemTypeId) {
        if (itemTypeId != null && !itemTypeId.equals("")) {
            for (AbstractEntity model : items) {
                if (getModelTypeIdString(model).equalsIgnoreCase(itemTypeId)) {
                    return model;
                }
            }
        }
        logger.debug("Unable to find model id " + itemTypeId + " during import.");
        return null;
    }

    private String getModelTypeIdString(AbstractEntity model) {
        String idString = "UNKNOWN";

        if (model != null && model.getId() != null) {
            String type = "";
            if (model instanceof Component) {
                type = TAG_COMPONENT;
            } else if (model instanceof CustomField) {
                type = TAG_CUSTOM_FIELD;
            } else if (model instanceof Issue) {
                type = TAG_ISSUE;
            } else if (model instanceof Project) {
                type = TAG_PROJECT;
            } else if (model instanceof User) {
                type = TAG_USER;
            } else if (model instanceof Version) {
                type = TAG_VERSION;
            }

            idString = type + model.getId();
        }

        return idString;
    }

    private Date getDateValue(String dateString, String qName) throws SAXException {
        if (dateString == null || "".equals(dateString)) {
            return new Date();
        }

        try {
            return DATE_FORMATTER.parse(dateString);
        } catch (Exception e) {
            throw new SAXException("Value in " + qName + " did not contain a valid date value.");
        }
    }
}