podd.tab.TabImporter.java Source code

Java tutorial

Introduction

Here is the source code for podd.tab.TabImporter.java

Source

/*
 * Copyright (c) 2009 - 2010. School of Information Technology and Electrical
 * Engineering, The University of Queensland.  This software is being developed
 * for the "Phenomics Ontoogy Driven Data Management Project (PODD)" project.
 * PODD is a National e-Research Architecture Taskforce (NeAT) project
 * co-funded by ANDS and ARCS.
 *
 * PODD is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * PODD 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with PODD.  If not, see <http://www.gnu.org/licenses/>.
 */

package podd.tab;

import static podd.util.common.vocabulary.PoddModelNamespace.PODD_MODEL;
import static podd.util.common.vocabulary.PoddNamespaceHelper.PODD_NAMESPACE_HELPER;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import org.apache.commons.httpclient.HttpStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import podd.client.NamedFile;
import podd.client.PoddClient;
import podd.client.PoddClientException;
import podd.client.PoddWebServiceException;
import podd.client.entity.PoddEntity;
import podd.client.owl.PoddObjectProperty;
import podd.client.tab.TabColumn;
import podd.client.tab.TabGenerator;
import podd.dataaccess.PoddObjectDAO;
import podd.dataaccess.UserDAO;
import podd.exception.DataAccessException;
import podd.exception.OntologyHandlingException;
import podd.model.entity.PoddObject;
import podd.model.user.User;
import podd.util.common.Constants;
import podd.util.common.vocabulary.PoddModelNamespace;
import podd.util.common.vocabulary.PoddUserNamespace;
import podd.util.owl.OntologyRegistry;
import edu.emory.mathcs.backport.java.util.Collections;
import fedora.common.rdf.RDFName;

/**
 * Imports and processes a batch of PODD Objects for submission via the Web Services
 * 
 * All objects must have an ID attribute. The ID attribute can be an actual poddObject ID, for example "poddObject:1234", or a generic string name (that doesn't start with 'poddObject') such that other objects in the TAB file can make a reference to it.
 * "Parent ID": I've introduced a new property in the TAB file called "Parent ID". This is used to reference either an existing podd object (if it starts with 'poddObject'), or reference a new podd object that is also in the TAB file identified by the ID attribute. The relationship between the parent and the child is done by appending "HAS" in front of the object type of the child. There are special cases to this rule, and this is handled on a case by case basis such as "HAS_PRINCIPAL_INVESTIGATOR" and all the event types.
 * All "refersTo" attributes should also make reference to either an existing "poddObject" or a new podd object that is also in the TAB file identified by the ID attribute.
 * The ordering of the Classes is irrelevant. So it doesn't matter if the Project class is in the beginning or the end of the TAB file. I've written the TAB importer in such a way that it is smart enough to follow up the chain of "Parent ID"s and "refersTo"s to know the order in which importing should be done. 
 * All blank lines will be ignored
 * All lines starting with '#' will be ignored and can be treated as comments if necessary
    
 * Only need to provide property 'containedBy' when defining parent child relationships, and there's no need to explicity identify the relationship property such as hasMaterial
 * because it is specified during submission to the webservice in PoddWebService class
 * Only embedded objects need to have the parent child relationship explicitly defined.
 * 
 * @author Philip Wu
 *
 */
public class TabImporter {

    private static final Logger LOGGER = LoggerFactory.getLogger(TabImporter.class);
    private static final boolean _INFO = LOGGER.isInfoEnabled();
    private static final boolean _DEBUG = LOGGER.isDebugEnabled();
    private static final boolean _TRACE = LOGGER.isTraceEnabled();

    public static final String DELIMITER = TabGenerator.DELIMITER; // TAB

    public static final String PODD_OBJECT = "poddObject";

    public static final String LIST_DELIMITER = "\\" + TabGenerator.LIST_DELIMITER;
    public static final String HAS = "Has";
    public static final String COMMENT = "#";

    public static final String FILE_SEPARATOR = System.getProperty("file.separator");

    /** 
     * Maps the Column index and its Object type to a PoddEntity
     * The key is string of ObjectType + Column index
     */
    private Map<String, PoddEntityWrapper> columnPoddEntityMap = new HashMap<String, PoddEntityWrapper>();

    /**
     * Maps the id of the PoddEntity to the PoddEntity object
     * This is mainly used to find references to other objects based on its title. 
     */
    private Map<String, PoddEntityWrapper> idPoddEntityMap = new HashMap<String, PoddEntityWrapper>();
    /**
     * Map parent ID to a list of embedded objects that it contains
     */
    private Map<String, List<PoddEntityWrapper>> parentIdEmbeddedChildMap = new HashMap<String, List<PoddEntityWrapper>>();
    /**
     * The podd server where to submit the podd objects to
     */
    private PoddClient ws;
    /**
     * Username to be used for web service access
     */
    private String username;
    /**
     * Password associated with username
     */
    private String password;

    /**
     * A list of property handlers
     */
    private List<PropertyHandler> propertyHandlers = new ArrayList<PropertyHandler>();

    /**
     * Attachments mapping file name to file item
     */
    private Map<String, File> attachments = new HashMap<String, File>();

    /**
     * A list of validation handlers
     */
    private List<ValidationHandler> validationHandlers = new ArrayList<ValidationHandler>();

    /**
     * The input stream of the TAB file
     */
    private InputStream tabStream;

    /**
     * Map the temp IDs to the newly created podd object IDs
     */
    //private LinkedHashMap<String, URI> tempIdMap = new LinkedHashMap<String, URI>();

    /**
     * The list of newly created objects in the same order they were created in
     */
    private List<URI> processingOrder = new ArrayList<URI>();
    /**
     * Keeps track of the number of objects created or updated so far
     */
    private int numObjectsCompleted;
    /**
     * The total number of objects in the TAB file
     */
    private int totalNumObjects;
    /**
     * Is the tab importing process completed?
     */
    private boolean completed;
    /**
     * The ontology registry
     */
    private OntologyRegistry ontologyRegistry;
    /**
     * User DAO
     */
    private UserDAO userDao;
    /**
     * Podd Object DAO
     */
    private PoddObjectDAO objectDao;

    /**
     * List of erross to be reported.
     * Userful for asynchronous requests to obtain the status of the tab import
     */
    private String error;
    /**
     * Maximum number of characters allowed for a field
     */
    private int columnLength;

    /**
     * Constructor for TabImporter to invoke web services on the given podd server
     * @param poddServer
     */
    public TabImporter(String poddServer) {
        ws = new PoddClient(poddServer);
        initialize();
    }

    /**
     * Constructor for TabImporter to use the given credentials
     * @param poddServer
     * @param username
     * @param password
     */
    public TabImporter(String poddServer, String username, String password) {
        this(poddServer);
        this.username = username;
        this.password = password;

    }

    /**
     * Constructor specifying the PODD server to submit the objects to and the TAB details
     * @param poddServer
     * @param tab
     */
    public TabImporter(String poddServer, Tab tab) {
        this(poddServer);

        this.tabStream = tab.getTabStream();
        if (tab.getAttachments() != null) {
            this.attachments.putAll(tab.getAttachments());
        }
    }

    /**
     * Add a cookie with the following values. This allows us to use the web service without
     * having to actually login.
     * @param domain
     * @param name
     * @param value
     */
    public void addCookie(String domain, String name, String value, String path, int version) {
        ws.addCookie(domain, name, value, path, version);
    }

    /**
     * Initialize
     */
    public void initialize() {

        // Initialize all the different property handlers. Order is important
        propertyHandlers.add(new ParentReferencePropertyHandler());
        propertyHandlers.add(new IdPropertyHandler());
        propertyHandlers.add(new ReferenceIdPropertyHandler());
        propertyHandlers.add(new TitlePropertyHandler());
        propertyHandlers.add(new DescriptionPropertyHandler());
        propertyHandlers.add(new FilePropertyHandler());
        propertyHandlers.add(new FileDescriptionPropertyHandler());
        propertyHandlers.add(new PrincipalInvestigatorPropertyHandler(ws));
        propertyHandlers.add(new DefaultPropertyHandler());
    }

    public InputStream getTabStream() {
        return tabStream;
    }

    public void setTabStream(InputStream tabStream) {
        this.tabStream = tabStream;
    }

    public void setUserDao(UserDAO userDao) {
        this.userDao = userDao;
    }

    public void setObjectDao(PoddObjectDAO objectDao) {
        this.objectDao = objectDao;
    }

    public Map<String, File> getAttachments() {
        return attachments;
    }

    public void setColumnLength(int columnLength) {
        this.columnLength = columnLength;
    }

    /**
     * Adds an attachment to be submitted along with the TAB file
     * @param file
     */
    public void addAttachment(File file) {
        if (file != null)
            addAttachment(file.getName(), file);
    }

    /**
     * Adds an attachment
     * @param filename   The filename to use to label this file
     * @param file
     */
    public void addAttachment(String filename, File file) {
        if (attachments == null)
            attachments = new HashMap<String, File>();

        if (file != null)
            attachments.put(filename, file);
    }

    /**
     * Adds a validation handler to be invoked before invoking the web services for submission
     * @param validationHandler
     */
    public void addValidationHandler(ValidationHandler validationHandler) {
        if (validationHandlers == null)
            validationHandlers = new ArrayList<ValidationHandler>();

        if (validationHandler != null)
            validationHandlers.add(validationHandler);

    }

    /**
     * Retrieve all the errors reported by tabImporter
     * @return
     */
    public String getError() {
        return error;
    }

    /**
     * Maps all the temporary IDs to the newly constructed IDs
     * @return
     */
    public Map<String, URI> getTempIdMap() {
        Map<String, URI> map = new HashMap<String, URI>();

        for (Map.Entry<String, PoddEntityWrapper> entry : this.idPoddEntityMap.entrySet()) {
            String tempId = entry.getKey();
            PoddEntityWrapper pew = entry.getValue();

            map.put(tempId, pew.getPoddEntity().getId());
        }

        return map;
        //return tempIdMap;
    }

    public int getNumObjectsCompleted() {
        return numObjectsCompleted;
    }

    public int getTotalNumObjects() {
        return totalNumObjects;
    }

    public void setOntologyRegistry(OntologyRegistry ontologyRegistry) {
        this.ontologyRegistry = ontologyRegistry;
    }

    /**
     * Returns the order in which the objects were processed
     * @return
     */
    public List<URI> getProcessingOrder() {
        return processingOrder;
    }

    /**
     * Returns the top level object
     * @return
     */
    public URI getTopLevelObject() {
        if (processingOrder.size() > 0)
            return processingOrder.get(0);

        return null;
    }

    /**
     * Is tab importing completed
     * @return
     */
    public boolean isCompleted() {
        /*
        if (getTotalNumObjects()  == 0 )
           return false;
        else if (getNumObjectsCompleted() == getTotalNumObjects())
           return true;
            
        return false;*/
        return completed;

    }

    /**
     * Reset the internal state
     */
    public void reset() {
        error = null;
        processingOrder.clear();
    }

    /**
     * The internal method for importing a TAB
     * @throws PoddTabException
     * @throws PoddWebServiceException
     */
    private void importTabInternal() throws PoddTabException, PoddWebServiceException {

        completed = false;

        try {
            // Validate that we have credentials
            if (username == null && password == null && !ws.hasCookies())
                throw new PoddTabException("No credentials have been supplied and no cookies have been added");

            if (_INFO) {
                LOGGER.info("Importing tab");
                if (_DEBUG) {
                    LOGGER.debug("tabStream=" + tabStream);
                }
            }

            if (tabStream == null)
                return;

            long beforePreparePoddEntities = System.currentTimeMillis();

            preparePoddEntities(tabStream);

            long afterPreparePoddEntities = System.currentTimeMillis();

            if (_INFO) {
                LOGGER.info("TabImporter preparePoddEntities timing="
                        + (afterPreparePoddEntities - beforePreparePoddEntities));
            }

            long beforeValidatePoddEntities = System.currentTimeMillis();

            // Validate all the podd entities
            for (PoddEntityWrapper prpe : columnPoddEntityMap.values()) {
                validatePoddEntityWrapper(prpe);

                // Give the custom validation handlers a chance to validate in their own way
                if (validationHandlers != null && validationHandlers.size() > 0) {
                    for (ValidationHandler validationHandler : validationHandlers) {
                        validationHandler.validate(prpe.getPoddEntity());
                    }
                }

                // Increment the total number of objects affected
                totalNumObjects++;
            }

            long afterValidatePoddEntities = System.currentTimeMillis();

            if (_INFO) {
                LOGGER.info("TabImporter validatePoddEntities timing="
                        + (afterValidatePoddEntities - beforeValidatePoddEntities));
            }

            long beforePopulateWithEmbeddedChildren = System.currentTimeMillis();

            // Populate the PoddEntities with their embedded children
            populateWithEmbeddedChildren();

            long afterPopulateWithEmbeddedChildren = System.currentTimeMillis();

            if (_INFO) {
                LOGGER.info("TabImporter populateWithEmbeddedChildren timing="
                        + (afterPopulateWithEmbeddedChildren - beforePopulateWithEmbeddedChildren));
            }

            long beforeLogin = System.currentTimeMillis();

            // Login to the web service
            if (username != null && password != null)
                ws.login(username, password);

            long afterLogin = System.currentTimeMillis();

            if (_INFO) {
                LOGGER.info("TabImporter webserviceLogin timing=" + (afterLogin - beforeLogin));
            }

            // Build a tree of dependencies by adding listeners to the dependencies
            //assignSubmitListeners();

            long beforeSubmitTopDown = System.currentTimeMillis();

            // Now begin to submit all the PoddObjects in a top down order
            submitTopDown();

            long afterSubmitTopDown = System.currentTimeMillis();

            if (_INFO) {
                LOGGER.info("TabImporter submitTopDown timing=" + (afterSubmitTopDown - beforeSubmitTopDown));
            }

            long beforeSubmitAttachments = System.currentTimeMillis();

            // Submit all files based on the PoddEntity's ID
            submitAttachments();

            long afterSubmitAttachments = System.currentTimeMillis();

            if (_INFO) {
                LOGGER.info("TabImporter submitAttachments timing="
                        + (afterSubmitAttachments - beforeSubmitAttachments));
            }

        } finally {
            completed = true;
        }

        if (_INFO) {
            LOGGER.info("Done importing");
        }
    }

    /**
     * Import the TAB file given as an input stream
     * @param is
     * @throws PoddTabException
     * @throws PoddWebServiceException
     */
    public void importTab() throws PoddTabException, PoddWebServiceException {
        try {
            importTabInternal();
        } catch (PoddTabException e) {
            // Record any exceptions and throw them back out
            LOGGER.warn("Recording error: " + e.getMessage());
            this.error = e.getMessage();
            throw e;
        } catch (PoddWebServiceException e) {
            // Record any exceptions and throw them back out
            LOGGER.warn("Recording error: " + e.getMessage());
            this.error = e.getMessage();
            throw e;
        }

    }

    /**
     * Rollback any objects that have created. 
     * TODO: But how to rollback objects that have been edited.
     * @return
     */
    public boolean rollbackCreatedObjects() {
        if (_INFO) {
            LOGGER.info("Rolling back created objects");
        }
        try {
            if (null != processingOrder) {
                List<URI> reversedProcessingOrder = new ArrayList<URI>();
                reversedProcessingOrder.addAll(processingOrder);
                Collections.reverse(reversedProcessingOrder);

                for (URI uri : reversedProcessingOrder) {

                    String poddObjectId = uri.getFragment();

                    if (_INFO) {
                        LOGGER.info("Deleting podd object that was created: " + poddObjectId);
                    }

                    PoddObject object = objectDao.load(poddObjectId);
                    if (object != null) {
                        objectDao.forceDelete(object);
                    }
                }
            }
        } catch (DataAccessException e) {
            LOGGER.error("Found exception", e);
            e.printStackTrace();
            return false;
        }

        if (_INFO) {
            LOGGER.info("Rollback successful");
        }

        return true;
    }

    /**
     * Populate the podd objects that have embedded children with the embedded children
     * @throws PoddTabException 
     */
    private void populateWithEmbeddedChildren() throws PoddTabException {

        if (idPoddEntityMap != null) {

            for (Map.Entry<String, PoddEntityWrapper> entry : idPoddEntityMap.entrySet()) {
                String id = entry.getKey();
                PoddEntityWrapper pew = entry.getValue();
                if (!pew.getIsEmbedded()) {

                    List<PoddEntityWrapper> embeddedChildren = this.parentIdEmbeddedChildMap.get(id);
                    if (embeddedChildren != null) {
                        for (PoddEntityWrapper embeddedChild : embeddedChildren) {
                            // Assign all of the embedded object IDs if they don't already have it
                            try {
                                if (embeddedChild.getPoddEntity().getId() == null) {
                                    // Randomly generate an ID
                                    URI idURI = new URI(
                                            PoddModelNamespace.PODD_MODEL.uri + UUID.randomUUID().toString());
                                    embeddedChild.getPoddEntity().setId(idURI);
                                }
                            } catch (URISyntaxException e) {
                                e.printStackTrace();
                                LOGGER.error("Found exception", e);
                                throw new PoddTabException(e);
                            }

                            // Also explicity add the relationship property between the parent and its embedded child
                            URI childObjectTypeURI = embeddedChild.getPoddEntity().getObjectTypeURI();
                            String childObjectType = childObjectTypeURI.getFragment();
                            URI relationshipURI = this.getModelURI(childObjectType, true);

                            // Add the relationship property to the PoddEntity
                            PoddObjectProperty objProperty = new PoddObjectProperty();
                            objProperty.setProperty(relationshipURI);
                            objProperty.setValue(embeddedChild.getPoddEntity().getId().toString());
                            pew.getPoddEntity().addProperty(objProperty);

                            pew.addEmbeddedChild(embeddedChild);
                        }
                    }
                }
            }
        }
    }

    /**
     * Create and populate PoddEntities for submission
     * @param is
     * @throws PoddTabException
     */
    public void preparePoddEntities(InputStream is) throws PoddTabException {

        BufferedReader br = new BufferedReader(new InputStreamReader(is, Charset.forName("UTF-8")));

        try {
            int lineNumber = 1;
            String line = null;
            ontologyRegistry.openSession();
            while ((line = br.readLine()) != null) {
                // Skip comments or blank lines
                if (line.startsWith(COMMENT) || line.trim().length() == 0)
                    continue;
                String[] split = line.split(DELIMITER);

                if (split.length < TabColumn.OBJECT_PROPERTY.ordinal() + 1)
                    throw new PoddTabException("Line number " + lineNumber + " has missing parameters", true);

                handleProperty(split, lineNumber);

                lineNumber++;
            }
        } catch (IOException e) {
            LOGGER.error("Found exception", e);
            e.printStackTrace();
            throw new PoddTabException(e);
        } finally {
            try {
                br.close();
            } catch (IOException e) {
                LOGGER.error("Found exception", e);
                e.printStackTrace();
            }
            ontologyRegistry.closeSession();
        }

    }

    /**
     * Test to ensure that the string is not empty or null
     * @param value
     * @return
     */
    public boolean isEmpty(String value) {
        if (value == null || value.trim().length() == 0)
            return true;

        return false;
    }

    public void validatePoddEntityWrapper(PoddEntityWrapper wrapper) throws PoddTabException {

        long start = System.currentTimeMillis();
        try {
            if (wrapper == null || wrapper.getPoddEntity() == null)
                return;

            PoddEntity poddEntity = wrapper.getPoddEntity();
            StringBuilder sbPrefix = new StringBuilder();
            if (poddEntity.getId() != null)
                sbPrefix.append("Object with ID, " + poddEntity.getId() + " with");
            else if (wrapper.getReferenceId() != null)
                sbPrefix.append("Object with temporary reference, " + wrapper.getReferenceId() + " with");
            sbPrefix.append(" object type " + poddEntity.getObjectTypeURI());

            String prefix = sbPrefix.toString();

            // Perform the following validation on new podd objects and not existing podd objects
            if (poddEntity.getId() == null) {

                if (poddEntity.getObjectTypeURI().equals(PoddModelNamespace.PODD_MODEL.PROJECT.getURI())) {
                    try {
                        validateProject(poddEntity);
                    } catch (PoddTabException e) {
                        // Include prefix when throwing errors
                        throw new PoddTabException(prefix + " with " + e.getMessage());
                    }
                } else if (wrapper.getParentReference() == null || wrapper.getParentReference().length() == 0) {
                    throw new PoddTabException("Parent ID must exist for new objects", true);
                }

                // Only podd concepts are required to have a title
                if (this.isSubClassOfPoddConcept(poddEntity.getObjectTypeURI())) {
                    // Validate that the title property has been set
                    // Title is only required for new podd objects
                    if (wrapper.getPoddEntity().getName() == null
                            || wrapper.getPoddEntity().getName().trim().length() == 0) {
                        throw new PoddTabException(prefix + " is missing required Title property");
                    }
                }
            }

            // Check the length of the title
            if (wrapper.getPoddEntity().getName() != null
                    && (wrapper.getPoddEntity().getName().length() > columnLength
                            || (wrapper.getPoddEntity().getDescription() != null
                                    && wrapper.getPoddEntity().getDescription().length() > columnLength))) {
                throw new PoddTabException(
                        prefix + " has properties that are longer than " + columnLength + " characters long");
            }

            // Validate that all parentIds and refersTo can be found
            String newParentReference = wrapper.getNewParentReference();
            if (newParentReference != null && !idPoddEntityMap.containsKey(newParentReference))
                throw new PoddTabException(
                        prefix + " has a parent reference that doesn't exist: " + newParentReference, true);

            // Validate refersTo
            if (wrapper.getPoddEntity().getProperties() != null) {
                List<String> allRefersTo = wrapper.getAllRefersTo();

                for (String refersToID : allRefersTo) {
                    // If the refersTo is not an existing podd object and is not in the collection of ID map, then report a failed integrity check
                    if (isPoddObjectID(refersToID)) {
                        // Check that the podd object exists using the web service
                        try {
                            boolean exists = ws.poddObjectExists(ws.constructObjectURI(refersToID));
                            if (!exists) {
                                throw new PoddTabException(
                                        prefix + " has a refersTo reference that doesn't exist: " + refersToID,
                                        true);
                            }
                        } catch (PoddWebServiceException e) {
                            LOGGER.error("Found exception", e);
                        }
                    } else if (!idPoddEntityMap.containsKey(refersToID)) {
                        throw new PoddTabException(
                                prefix + " has a refersTo reference that doesn't exist: " + refersToID, true);
                    }
                }
            }
        } finally {
            if (_INFO) {
                long end = System.currentTimeMillis();
                LOGGER.info("validatePoddEntityWrapper timing=" + (end - start));
            }
        }
    }

    /**
     * Validate a Project's properties
     * @param poddEntity
     * @throws PoddTabException
     */
    private void validateProject(PoddEntity poddEntity) throws PoddTabException {
        final URI[] USER_PROPERTIES = new URI[] { PoddModelNamespace.PODD_MODEL.HAS_PRINCIPAL_INVESTIGATOR.getURI(),
                PoddModelNamespace.PODD_MODEL.HAS_PROJECT_ADMINISTRATOR.getURI(),
                PoddModelNamespace.PODD_MODEL.HAS_PROJECT_MEMBER.getURI(),
                PoddModelNamespace.PODD_MODEL.HAS_PROJECT_OBSERVER.getURI() };

        // TODO: At the moment we only assume one user for each property
        for (URI uri : USER_PROPERTIES) {
            validateUserProperty(poddEntity, uri);
        }

    }

    /**
     * Given the podd entity and uri of the property, extract the username and validate that the user exists
     * @param poddEntity
     * @param propertyUri
     * @throws PoddTabException
     */
    private void validateUserProperty(PoddEntity poddEntity, URI propertyUri) throws PoddTabException {
        String userUri = poddEntity.getPropertyValue(propertyUri);
        try {
            if (userUri != null) {
                // HACK FIXME: Should not be relying on URI fragments to contain meaningful information
                String username = (new URI(userUri)).getFragment();
                if (username != null) {
                    // Check that the user name exists
                    if (!userExists(username))
                        throw new PoddTabException("username " + username + " does not exist");
                }
            }
        } catch (URISyntaxException e) {
            LOGGER.error("Found exception", e);
            e.printStackTrace();
            throw new PoddTabException(e);
        }
    }

    /**
     * Check does the username exist?
     * @param username
     * @return
     * @throws PoddTabException
     */
    private boolean userExists(String username) throws PoddTabException {

        try {
            User user = userDao.loadByUserName(username);
            if (user == null)
                return false;
            else
                return true;
        } catch (DataAccessException e) {
            e.printStackTrace();
            LOGGER.error("Found exception", e);
            throw new PoddTabException(e.getMessage());
        }

    }

    /**
     * Clean model short names from tab input
     * @param modelName   Unsanitzed user input
     * @return
     */
    private String sanitizeModelName(String modelName) {
        if (modelName == null)
            return null;

        String sanitized = modelName.replaceAll(" ", ""); // remove all spaces
        sanitized = sanitized.trim();
        return sanitized;
    }

    /**
     * Gets the URI of the model short name
     * @param modelName
     * @return
     */
    private URI getModelURI(String modelName, boolean isProperty) throws PoddTabException {
        if (modelName == null || modelName.trim().length() == 0)
            return null;

        modelName = sanitizeModelName(modelName);

        // Get the first character 
        StringBuilder sbFirstChar = new StringBuilder();
        sbFirstChar.append(modelName.charAt(0));
        String firstChar = sbFirstChar.toString();

        String lowerModelName = firstChar.toLowerCase() + modelName.substring(1);
        String upperModelName = firstChar.toUpperCase() + modelName.substring(1);

        RDFName rdfName = null;
        if (!isProperty) {

            // Try 2 different ways to get the URI. First way is to have the first letter lowercase. Second way is to have the first letter uppercase
            rdfName = PODD_NAMESPACE_HELPER.getRDFName(lowerModelName);
            if (rdfName == null) {
                // Second attempt
                rdfName = PODD_NAMESPACE_HELPER.getRDFName(upperModelName);
            }
        } else {
            // Is a property
            String hasModelName = "has" + modelName;
            rdfName = PODD_NAMESPACE_HELPER.getRDFName(hasModelName);

            if (rdfName == null) { // Could be a refersTo property
                rdfName = PODD_NAMESPACE_HELPER.getRDFName(lowerModelName);
            }

        }

        if (rdfName == null)
            throw new PoddTabException("The following '" + modelName + "' could not be mapped to a PODD Model",
                    true);
        return rdfName.getURI();
    }

    /**
     * Performs a check to see if the parentReference is based on the title of a new object, or the poddObject ID of an existing object
     * @param poddObjectId
     * @return
     */
    public static boolean isPoddObjectID(String poddObjectId) throws PoddTabException {

        if (poddObjectId == null || poddObjectId.trim().length() == 0)
            return false;

        if (poddObjectId.startsWith(PODD_OBJECT) || poddObjectId.startsWith(Constants.PODD_OBJECT_NAMESPACE)) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * Create and populate all the Podd Entities for the current line
     * @param split
     * @param lineNumber
     * @throws PoddTabException
     */
    private void handleProperty(String[] split, int lineNumber) throws PoddTabException {
        String shortNameObjectType = getObjectType(split, lineNumber);
        URI objectTypeURI = getModelURI(shortNameObjectType, false);

        String shortNameObjectProperty = getObjectProperty(split, lineNumber);

        //Sanitize
        shortNameObjectProperty = shortNameObjectProperty.trim();
        try {
            for (int i = TabColumn.VALUES.ordinal(); i < split.length; i++) {
                String propertyValue = split[i];

                if (propertyValue != null && propertyValue.trim().length() > 0) {
                    propertyValue = propertyValue.trim();
                    PoddEntityWrapper prpe = fetchCreatePoddEntity(objectTypeURI, i);

                    // If the object class is not a subclass of PoddConcept, then we assume it to be an embedded type
                    if (!isSubClassOfPoddConcept(objectTypeURI)) {
                        prpe.setIsEmbedded(Boolean.TRUE);

                        List<PoddEntityWrapper> embeddedChildren = parentIdEmbeddedChildMap
                                .get(prpe.getParentReference());

                        if (embeddedChildren == null) {
                            embeddedChildren = new ArrayList<PoddEntityWrapper>();
                            parentIdEmbeddedChildMap.put(prpe.getParentReference(), embeddedChildren);

                            // Also store the full URI as the key if it represents a podd object
                            if (this.isPoddObjectID(prpe.getParentReference())) {
                                parentIdEmbeddedChildMap.put(ws.constructObjectURI(prpe.getParentReference()),
                                        embeddedChildren);
                            }

                        }

                        embeddedChildren.add(prpe);
                    }

                    // Check to the range to see if it can have multiple children with the same relationship
                    String[] propertyValueList = propertyValue.split(LIST_DELIMITER);
                    for (String propertyValueItem : propertyValueList) {
                        if (propertyHandlers != null) {
                            for (PropertyHandler propertyHandler : propertyHandlers) {
                                if (propertyHandler.handleProperty(objectTypeURI, shortNameObjectProperty,
                                        propertyValueItem, prpe, lineNumber)) {
                                    break; // stop processing all the other property handlers
                                }
                            }
                        }
                    }
                }
            } // end for
        } catch (PoddTabException e) {
            // Wrap the PoddTabException that was caught with the line number
            throw new PoddTabException(e.getMessage() + " on line number " + lineNumber, e.isUserError());
        }
    }

    /**
     * Helper method to test if the object URI represents a subclass of PODD_CONCEPT
     * @param subclassURI
     * @return
     * @throws PoddTabException
     */
    private boolean isSubClassOfPoddConcept(URI subclassURI) throws PoddTabException {
        try {
            return ontologyRegistry.isSubClassOf(subclassURI, PODD_MODEL.PODD_CONCEPT.getURI());
        } catch (OntologyHandlingException e) {
            LOGGER.error("Found exception", e);
            e.printStackTrace();
            throw new PoddTabException(e);
        }
    }

    /**
     * Get the object type as a short name
     * @param split
     * @return
     */
    private String getObjectType(String[] split, int lineNumber) throws PoddTabException {
        if (split.length < TabColumn.OBJECT_TYPE.ordinal() + 1)
            throw new PoddTabException("Line number " + lineNumber + " is missing Object type", true);

        String shortNameObjectType = split[TabColumn.OBJECT_TYPE.ordinal()];
        return shortNameObjectType.trim();
    }

    /**
     * Get the object property 
     * @param split
     * @return
     * @throws PoddTabException
     */
    private String getObjectProperty(String[] split, int lineNumber) throws PoddTabException {
        if (split.length < TabColumn.OBJECT_PROPERTY.ordinal() + 1)
            throw new PoddTabException("Line number " + lineNumber + " is missing Object property", true);
        String shortNameObjectProperty = split[TabColumn.OBJECT_PROPERTY.ordinal()];
        return shortNameObjectProperty.trim();
    }

    /**
     * Creates a key for accessing the map of PoddEntities
     * @param objectName
     * @param columnIndex
     * @return
     */
    private String getPoddEntityKey(String objectName, int columnIndex) {
        return objectName + columnIndex;
    }

    /**
     * Fetches or Creates a ParentReferencePoddEntity given its object name and the column index
     * @param objectName
     * @param columnIndex
     * @return
     */
    private PoddEntityWrapper fetchCreatePoddEntity(URI objectTypeURI, int columnIndex) {
        String key = getPoddEntityKey(objectTypeURI.getFragment(), columnIndex);
        PoddEntityWrapper prpe = columnPoddEntityMap.get(key);
        if (prpe == null) {
            prpe = new PoddEntityWrapper();

            PoddEntity poddEntity = new PoddEntity();
            poddEntity.setObjectTypeURI(objectTypeURI);

            prpe.setPoddEntity(poddEntity);

            columnPoddEntityMap.put(key, prpe);
        }

        return prpe;
    }

    /**
     * For all the PoddEntityWrappers assign submit listeners to all their dependencies
     * @throws PoddTabException
     */
    private void assignSubmitListeners() throws PoddTabException {

        if (columnPoddEntityMap.values() != null) {

            for (PoddEntityWrapper peWrapper : columnPoddEntityMap.values()) {
                assignSubmitListener(peWrapper);
            }
        }
    }

    /**
     * Assigns a submit listener to all its dependencies
     * @param peWrapper
     * @throws PoddTabException
     */
    private void assignSubmitListener(PoddEntityWrapper peWrapper) throws PoddTabException {

        if (peWrapper == null)
            return;

        PoddEntitySubmitListener listener = new PoddEntitySubmitListener(peWrapper);

        // If this object also has a parent, then submit the parent first before submitting this one
        if (peWrapper.hasUncreatedParent()) {
            // Add submit listener to the parent 
            addSubmitListener(listener, peWrapper.getNewParentReference());
        }

        // Add submitListeners to refersTo objects
        for (String refersToID : peWrapper.getAllRefersTo()) {
            addSubmitListener(listener, refersToID);
        }

    }

    /**
     * Adds a SubmitListener to the referenceID with the given PODD Entity Wrapper object
     * @param peWrapper
     * @param referenceID
     * @throws PoddTabException
     */
    private void addSubmitListener(SubmitListener listener, String referenceID) throws PoddTabException {

        PoddEntityWrapper referenceWrapper = idPoddEntityMap.get(referenceID);
        /*
        if (referenceWrapper == null)
           throw new PoddTabException("Podd Object with ID "+referenceID+" cannot be found.", true);
        */
        if (referenceWrapper != null)
            referenceWrapper.addSubmitListener(listener);

    }

    private void submitInParallel() throws PoddTabException {

    }

    /**
     * Submits all the podd objects in top down order, submitted the parents first before the children
     * @throws PoddTabException
     */
    private void submitTopDown() throws PoddTabException {

        if (_INFO) {
            LOGGER.info("Entering submitTopDown");
        }

        if (columnPoddEntityMap.values() != null) {

            for (PoddEntityWrapper peWrapper : columnPoddEntityMap.values()) {
                if (!peWrapper.isSubmitted()) {
                    // If the parent has not been created yet, then create the parent first
                    if (peWrapper.getNewParentReference() != null) {
                        URI parentURI = submitReference(peWrapper.getNewParentReference());

                        // Map the temporary ID to newly created podd object ID
                        //this.tempIdMap.put(prpe.getNewParentReference(), parentURI);
                        peWrapper.getPoddEntity().setParentURI(parentURI);
                    }

                    // Check it's references
                    populateRefersToReference(peWrapper);

                    submitPoddEntity(peWrapper);
                }

                // Increment the total number of objects completed
                numObjectsCompleted++;
            }
        }

        if (_INFO) {
            LOGGER.info("Exiting submitTopDown");
        }
    }

    /**
     * Submit the parent, using recursion if the parent also has a new parent
     * @param referenceId
     * @return
     * @throws PoddTabException
     */
    private URI submitReference(String referenceId) throws PoddTabException {

        if (referenceId == null)
            return null;

        PoddEntityWrapper peWrapper = idPoddEntityMap.get(referenceId);
        if (peWrapper == null)
            throw new PoddTabException("Podd Object with ID " + referenceId + " cannot be found.", true);

        // If it's not already submitted
        if (!peWrapper.isSubmitted()) {
            // If this object also has a parent, then submit the parent first before submitting this one
            if (peWrapper.hasUncreatedParent()) {
                URI referenceURI = submitReference(peWrapper.getNewParentReference());

                // Map the temporary ID to newly created podd object ID
                //this.tempIdMap.put(referenceId, referenceURI);

                // Once the parent has been submitted, assign the returned parentId to the current PoddObject
                peWrapper.getPoddEntity().setParentURI(referenceURI);
            }

            // If this object has any RefersTo references, then submit those references first as well
            populateRefersToReference(peWrapper);

            return submitPoddEntity(peWrapper);
        }

        // If its already been submitted, then it should already have an ID assigned
        return peWrapper.getPoddEntity().getId();
    }

    /**
     * Populates all RefersTo properties with poddObject IDs
     * RefersTo properties can be represented as a delimited list, so we must break them out 
     * into individual refersTo, and add them back as single refersTo properties
     * 
     * @param poddEntity
     * @throws PoddTabException
     */
    private void populateRefersToReference(PoddEntityWrapper pew) throws PoddTabException {
        PoddEntity poddEntity = pew.getPoddEntity();
        if (poddEntity.getProperties() != null) {

            // Stores all the refernces that were broken out from the list
            List<PoddObjectProperty> refersToList = new ArrayList<PoddObjectProperty>();

            List<String> allRefersTo = pew.getAllRefersTo();

            for (String referToID : allRefersTo) {

                URI referenceURI = null;
                if (!isPoddObjectID(referToID)) {
                    // Must be a reference to a new object. Submit the new object first
                    referenceURI = submitReference(referToID);
                } else {
                    // Convert the string poddObjectId to its full URI form
                    try {
                        referenceURI = new URI(ws.constructObjectURI(referToID));
                    } catch (URISyntaxException e) {
                        LOGGER.error("Found exception", e);
                        e.printStackTrace();
                    }
                }

                if (referenceURI != null) {

                    URI propertyURI = pew.getPropertyByRefersToID(referToID);

                    PoddObjectProperty singleRefersTo = new PoddObjectProperty();
                    singleRefersTo.setProperty(propertyURI);
                    singleRefersTo.setValue(referenceURI.toString());
                    refersToList.add(singleRefersTo);
                }
            }

            // Clear all the old Refers to properties
            pew.clearAllRefersTo();

            // Now repopulate with the new refers to properties
            // Adding back in the refersTo that were separated out
            poddEntity.getProperties().addAll(refersToList);
        }
    }

    /**
     * Submits all the attachments for all the podd objects already submitted
     * @throws PoddTabException
     */
    private void submitAttachments() throws PoddTabException {
        //LOGGER.info("attachments: "+attachments);
        if (columnPoddEntityMap.values() != null) {

            // Pull out all the filenames and use them to retrieve the files
            for (PoddEntityWrapper prpe : columnPoddEntityMap.values()) {
                List<NamedFile> namedFiles = new ArrayList<NamedFile>();
                URI owner = prpe.getPoddEntity().getId();

                if (prpe.getFilenames() != null) {
                    for (String filename : prpe.getFilenames()) {
                        File file = attachments.get(filename);
                        if (file != null) {
                            NamedFile namedFile = new NamedFile(file, filename);
                            namedFiles.add(namedFile);
                        }
                    }
                }

                if (owner != null && namedFiles.size() > 0) {

                    // Validate that the Files have description
                    if (prpe.getFileDescription() == null || prpe.getFileDescription().length() == 0) {
                        StringBuilder sb = new StringBuilder();
                        for (NamedFile file : namedFiles) {
                            sb.append(", ");
                            sb.append(file.getFilename());
                        }

                        throw new PoddTabException(
                                "Missing file description for the following files:" + sb.toString(), true);
                    }

                    try {
                        LOGGER.info("Submitting attachments for ID: " + owner + " with the following files:");
                        for (NamedFile file : namedFiles) {
                            LOGGER.info("file: " + file.getFilename() + " exists=" + file.getFile().exists());
                        }

                        ws.attachFilesToPoddObject(owner.toString(), namedFiles, prpe.getFileDescription());

                    } catch (FileNotFoundException e) {
                        LOGGER.error("Found exception", e);
                        e.printStackTrace();
                        throw new PoddTabException(e);
                    } catch (PoddWebServiceException e) {
                        LOGGER.error("Found exception", e);
                        e.printStackTrace();
                        throw new PoddTabException(e);
                    }
                }

                namedFiles.clear();
            }

            // clean up the uploaded files                  
            for (File file : attachments.values()) {
                LOGGER.info("deleting " + file.getAbsolutePath());
                boolean deleted = file.delete();
                if (!deleted)
                    LOGGER.warn("Unable to delete file: " + file.getAbsolutePath());
            }
        }
    }

    /**
     * Submit the podd entity and update status variables
     * @param peWrapper
     * @return
     * @throws PoddTabException
     */
    private URI submitPoddEntity(PoddEntityWrapper peWrapper) throws PoddTabException {
        if (_INFO) {
            LOGGER.info("Entering submitPoddEntity");
        }

        // Submit the object

        // Only submit the object if it is not an embedded object
        if (!peWrapper.getIsEmbedded()) {
            synchronized (peWrapper) {
                try {
                    PoddEntity pe = peWrapper.getPoddEntity();
                    if (peWrapper.getPoddEntity().getId() != null) {
                        // Editing an existing podd object
                        // A Parent must be specified when editing for web services to work
                        // If no parent has been specified, then default to using the current parent

                        List<String> containedBys = ws.getField(PoddModelNamespace.PODD_MODEL.CONTAINED_BY.getURI(),
                                pe.getId());
                        if (containedBys != null && containedBys.size() > 0) {
                            LOGGER.info("Defaulting parentId for existing " + pe.getId() + " to "
                                    + containedBys.get(0));
                            pe.setParentURI(new URI(containedBys.get(0)));
                        }
                    }

                    String id = ws.createOrEditObject(pe);
                    URI uri = new URI(id);
                    peWrapper.setSubmitted(true);
                    peWrapper.getPoddEntity().setId(uri);
                    // Add to the creation order
                    processingOrder.add(uri);

                    if (_INFO) {
                        LOGGER.info("Exiting submitPoddEntity successfully");
                    }

                    return uri;
                } catch (PoddWebServiceException e) {
                    LOGGER.error("Found exception", e);
                    e.printStackTrace();
                    throw createPoddTabException(e);
                } catch (PoddClientException e) {
                    LOGGER.error("Found exception", e);
                    e.printStackTrace();
                    throw new PoddTabException(e);
                } catch (URISyntaxException e) {
                    LOGGER.error("Found exception", e);
                    e.printStackTrace();
                    throw new PoddTabException(e);
                }
            }
        } else {
            // Embedded objects do not have an URI
            LOGGER.info("Skipping embedded object: " + peWrapper.getPoddEntity().getObjectTypeURI());

            return null;
        }
    }

    // Convert a PoddwebServiceException to a Tab exception
    private PoddTabException createPoddTabException(PoddWebServiceException e) {

        boolean isUserError = false;
        switch (e.getStatus()) {
        case HttpStatus.SC_BAD_REQUEST:
            isUserError = true;
        }

        PoddTabException tabE = new PoddTabException(e, isUserError);
        return tabE;
    }

    /**
     * Handles ID properties
     * @author Philip Wu
     *
     */
    class IdPropertyHandler implements PropertyHandler {

        @Override
        public boolean handleProperty(URI objectTypeURI, String propertyName, String propertyValue,
                PoddEntityWrapper prpe, int lineNumber) throws PoddTabException {

            if (propertyName.equals(PROPERTY_ID)) {
                if (TabImporter.isPoddObjectID(propertyValue)) {
                    try {
                        URI uri = new URI(PoddClient.constructObjectURI(propertyValue));
                        prpe.getPoddEntity().setId(uri);
                    } catch (URISyntaxException e) {
                        LOGGER.error("Found exception", e);
                        e.printStackTrace();
                        throw new PoddTabException(e);
                    }
                }
                idPoddEntityMap.put(propertyValue, prpe);

                return true;
            }
            return false;
        }

    }

    /**
     * Handles ID properties
     * @author Philip Wu
     *
     */
    class ReferenceIdPropertyHandler implements PropertyHandler {

        @Override
        public boolean handleProperty(URI objectTypeURI, String propertyName, String propertyValue,
                PoddEntityWrapper prpe, int lineNumber) throws PoddTabException {

            if (propertyName.equals(PropertyHandler.PROPRETY_REFERENCE_ID)) {
                idPoddEntityMap.put(propertyValue, prpe);
                prpe.setReferenceId(propertyValue);
                return true;
            }
            return false;
        }

    }

    /**
     * Handle a File Property
     * Filenames can be in a comma delimited list
     * @author Philip Wu
     *
     */
    class FilePropertyHandler implements PropertyHandler {

        @Override
        public boolean handleProperty(URI objectTypeURI, String propertyName, String propertyValue,
                PoddEntityWrapper prpe, int lineNumber) throws PoddTabException {
            if (propertyName.equals(PROPERTY_FILE)) {

                if (propertyValue != null) {
                    String[] filenames = propertyValue.split(LIST_DELIMITER);
                    for (String filename : filenames) {
                        if (filename != null && filename.trim().length() > 0) {

                            filename = filename.trim();

                            // Validate that the file exists
                            boolean exists = attachments.containsKey(filename);
                            if (exists)
                                prpe.addFilename(filename);
                            else
                                throw new PoddTabException(
                                        "Line number " + lineNumber
                                                + " is referencing a file that could not be found: " + filename,
                                        true);
                        }
                    }
                }
                return true;
            }
            return false;
        }
    }

    /**
     * Handle a File Description Property
     * @author Philip Wu
     *
     */
    class FileDescriptionPropertyHandler implements PropertyHandler {

        @Override
        public boolean handleProperty(URI objectTypeURI, String propertyName, String propertyValue,
                PoddEntityWrapper prpe, int lineNumber) throws PoddTabException {

            if (propertyName.equals(PROPERTY_FILE_DESCRIPTION)) {
                prpe.setFileDescription(propertyValue);
                return true;
            }
            return false;
        }
    }

    /**
     * Handle Title Property
     * @author Philip Wu
     *
     */
    class TitlePropertyHandler implements PropertyHandler {

        @Override
        public boolean handleProperty(URI objectTypeURI, String propertyName, String propertyValue,
                PoddEntityWrapper prpe, int lineNumber) throws PoddTabException {
            if (propertyName.equals(PROPERTY_TITLE)) {
                prpe.getPoddEntity().setName(propertyValue);
                return true;
            }
            return false;
        }
    }

    /**
     * Handler Description Property
     * @author Philip Wu
     *
     */
    class DescriptionPropertyHandler implements PropertyHandler {

        @Override
        public boolean handleProperty(URI objectTypeURI, String propertyName, String propertyValue,
                PoddEntityWrapper prpe, int lineNumber) throws PoddTabException {

            if (propertyName.equals(PROPERTY_DESCRIPTION)) {
                prpe.getPoddEntity().setDescription(propertyValue);

                return true;
            }
            return false;
        }
    }

    /**
     * If no other Property handler takes an action on the property, then this is the Default property handler
     * A best-guess is made to determine the model URI based on the property name.
     * @author Philip Wu
     *
     */
    class DefaultPropertyHandler implements PropertyHandler {

        @Override
        public boolean handleProperty(URI objectTypeURI, String propertyName, String propertyValue,
                PoddEntityWrapper prpe, int lineNumber) throws PoddTabException {
            //LOGGER.info("handleProperty objectTypeURI="+objectTypeURI+" propertyName="+propertyName+" propertyValue="+propertyValue);
            //LOGGER.info("objectPropertyURI="+objectPropertyURI);
            URI objectPropertyURI = getModelURI(propertyName, true);
            // Validate the URIs
            if (objectPropertyURI == null)
                throw new PoddTabException(
                        "Line number " + lineNumber + " has unmapped Object property of " + propertyName);

            //LOGGER.info("isEnum="+ontologyRegistry.isReferenceToEnumType(objectPropertyURI));
            if (ontologyRegistry.isReferenceToEnumType(objectPropertyURI)) {
                //LOGGER.info("propertyName="+propertyName+ " propertyValue="+propertyValue+" is ENUM");
                // Remove all spaces from the property value 
                propertyValue = propertyValue.replaceAll(" ", "");
                RDFName enumName = PODD_NAMESPACE_HELPER.getRDFName(propertyValue);
                if (enumName != null) {
                    propertyValue = enumName.getURI().toString();
                } else {
                    throw new PoddTabException(
                            "Line number " + lineNumber + " has invalid property value. Property " + propertyName
                                    + " cannot have value " + propertyValue,
                            true);
                }
            }

            // Add the property to the PoddEntity
            PoddObjectProperty objProperty = new PoddObjectProperty();
            objProperty.setProperty(objectPropertyURI);
            objProperty.setValue(propertyValue);
            prpe.getPoddEntity().addProperty(objProperty);

            return true;
        }
    }

    /**
     * Handle the principal investgator property
     * @author Philip Wu
     *
     */
    class PrincipalInvestigatorPropertyHandler implements PropertyHandler {

        PoddClient poddClient;

        public PrincipalInvestigatorPropertyHandler(PoddClient poddClient) {
            this.poddClient = poddClient;
        }

        @Override
        public boolean handleProperty(URI objectTypeURI, String propertyName, String propertyValue,
                PoddEntityWrapper prpe, int lineNumber) throws PoddTabException {

            if (objectTypeURI.equals(PoddModelNamespace.PODD_MODEL.PROJECT.getURI())
                    && (propertyName.equals(PROPERTY_PRINCIPAL_INVESTIGATOR) || propertyName
                            .equals(PoddModelNamespace.PODD_MODEL.HAS_PRINCIPAL_INVESTIGATOR.localName))) {
                String username = propertyValue; // The property value is the username

                // Check that user name already exists
                try {
                    podd.client.entity.User user = ws.getUser(username);
                    if (user == null) {
                        throw new PoddTabException("The username " + username + " does not exist");
                    }
                } catch (PoddWebServiceException e) {
                    LOGGER.error("Found exception", e);
                    e.printStackTrace();
                    throw new PoddTabException(e);
                }

                PoddObjectProperty property = new PoddObjectProperty();
                property.setProperty(PoddModelNamespace.PODD_MODEL.HAS_PRINCIPAL_INVESTIGATOR.getURI());
                property.setValue(PoddUserNamespace.PODD_USER.uri + username);

                prpe.getPoddEntity().addProperty(property);

                return true;
            }

            return false;
        }
    }

    /**
     * Tab Import implementation of SubmitListener
     * @author Philip Wu
     *
     */
    class PoddEntitySubmitListener implements SubmitListener {

        private PoddEntityWrapper peWrapper;

        public PoddEntitySubmitListener(PoddEntityWrapper pew) {
            this.peWrapper = pew;
        }

        @Override
        public void handleSubmit(PoddEntityWrapper invoker) throws PoddTabException {

            // Check to see if the parent has already been submitted
            PoddEntityWrapper parentWrapper = idPoddEntityMap.get(peWrapper.getParentReference());

            // If the parent hasn't already been submitted, then wait for this listener to be invoked again
            if (!parentWrapper.isSubmitted())
                return;

            // Check to see if all of its refers to has been submitted
            for (String refersToID : peWrapper.getAllRefersTo()) {

                PoddEntityWrapper refersToWrapper = idPoddEntityMap.get(refersToID);
                if (!refersToWrapper.isSubmitted()) {
                    return;
                }
            }

            // Repopulate refers to
            repopulateRefersTo();

            // Now we are aready to submit the podd entity
            submitPoddEntity(peWrapper);

            // Now spawn new thread for each of its dependencies
            for (final SubmitListener listener : peWrapper.getSubmitListeners()) {

                // Parallel the submission process for speed improvement
                Thread t = new Thread(new Runnable() {

                    @Override
                    public void run() {
                        try {
                            listener.handleSubmit(peWrapper);
                        } catch (PoddTabException e) {
                            LOGGER.error("Found exception", e);
                            e.printStackTrace();
                        }
                    }

                });
                t.start();
            }

        }

        /**
         * At this stage we assume that all of the refers to have already been submitted and have their PoddObjectID has already been assigned
         * 
         * @param peWrapper
         */
        private void repopulateRefersTo() {

            // Stores all the refernces that were broken out from the list
            List<PoddObjectProperty> refersToList = new ArrayList<PoddObjectProperty>();

            List<String> allRefersTo = peWrapper.getAllRefersTo();

            for (String referToID : allRefersTo) {
                PoddEntityWrapper refersToWrapper = idPoddEntityMap.get(referToID);
                URI referenceURI = refersToWrapper.getPoddEntity().getId();

                URI propertyURI = peWrapper.getPropertyByRefersToID(referToID);

                PoddObjectProperty singleRefersTo = new PoddObjectProperty();
                singleRefersTo.setProperty(propertyURI);
                singleRefersTo.setValue(referenceURI.toString());
                refersToList.add(singleRefersTo);

            }

            // Clear all the old Refers to properties
            peWrapper.clearAllRefersTo();

            // Now repopulate with the new refers to properties
            // Adding back in the refersTo that were separated out
            peWrapper.getPoddEntity().getProperties().addAll(refersToList);

        }

    }

}