de.iteratec.iteraplan.businesslogic.service.legacyExcel.ExcelImportServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for de.iteratec.iteraplan.businesslogic.service.legacyExcel.ExcelImportServiceImpl.java

Source

/*
 * iteraplan is an IT Governance web application developed by iteratec, GmbH
 * Copyright (C) 2004 - 2014 iteratec, GmbH
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License version 3 as published by
 * the Free Software Foundation with the addition of the following permission
 * added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED
 * WORK IN WHICH THE COPYRIGHT IS OWNED BY ITERATEC, ITERATEC DISCLAIMS THE
 * WARRANTY OF NON INFRINGEMENT  OF THIRD PARTY RIGHTS.
 *
 * 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.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program; if not, see http://www.gnu.org/licenses or write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301 USA.
 *
 * You can contact iteratec GmbH headquarters at Inselkammerstr. 4
 * 82008 Munich - Unterhaching, Germany, or at email address info@iteratec.de.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU Affero General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU Affero General Public License
 * version 3, these Appropriate Legal Notices must retain the display of the
 * "iteraplan" logo. If the display of the logo is not reasonably
 * feasible for technical reasons, the Appropriate Legal Notices must display
 * the words "Powered by iteraplan".
 */
package de.iteratec.iteraplan.businesslogic.service.legacyExcel;

import static de.iteratec.iteraplan.businesslogic.exchange.legacyExcel.importer.ImportWorkbook.getProcessingLog;

import java.io.InputStream;
import java.io.PrintWriter;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang.StringUtils;

import com.google.common.base.Objects;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

import de.iteratec.iteraplan.businesslogic.exchange.legacyExcel.ExcelConstants;
import de.iteratec.iteraplan.businesslogic.exchange.legacyExcel.exporter.sheets.ExcelSheet;
import de.iteratec.iteraplan.businesslogic.exchange.legacyExcel.importer.BuildingBlockHolder;
import de.iteratec.iteraplan.businesslogic.exchange.legacyExcel.importer.CellValueHolder;
import de.iteratec.iteraplan.businesslogic.exchange.legacyExcel.importer.ExcelImportUtilities;
import de.iteratec.iteraplan.businesslogic.exchange.legacyExcel.importer.LandscapeData;
import de.iteratec.iteraplan.businesslogic.exchange.legacyExcel.importer.LandscapeData.BuildingBlockAttributes;
import de.iteratec.iteraplan.businesslogic.exchange.legacyExcel.importer.LandscapeData.BuildingBlockRelations;
import de.iteratec.iteraplan.businesslogic.exchange.legacyExcel.importer.LandscapeDataWorkbook;
import de.iteratec.iteraplan.businesslogic.exchange.legacyExcel.importer.ObjectRelatedPermissionsData;
import de.iteratec.iteraplan.businesslogic.exchange.legacyExcel.importer.ObjectRelatedPermissionsWorkbook;
import de.iteratec.iteraplan.businesslogic.exchange.legacyExcel.importer.ProcessingLog;
import de.iteratec.iteraplan.businesslogic.exchange.legacyExcel.importer.ProcessingLog.Level;
import de.iteratec.iteraplan.businesslogic.service.AttributeTypeService;
import de.iteratec.iteraplan.businesslogic.service.AttributeValueService;
import de.iteratec.iteraplan.businesslogic.service.BuildingBlockService;
import de.iteratec.iteraplan.businesslogic.service.BuildingBlockServiceLocator;
import de.iteratec.iteraplan.businesslogic.service.BusinessMappingService;
import de.iteratec.iteraplan.businesslogic.service.InformationSystemInterfaceService;
import de.iteratec.iteraplan.businesslogic.service.InformationSystemReleaseService;
import de.iteratec.iteraplan.businesslogic.service.TechnicalComponentReleaseService;
import de.iteratec.iteraplan.businesslogic.service.UserService;
import de.iteratec.iteraplan.common.Constants;
import de.iteratec.iteraplan.common.Logger;
import de.iteratec.iteraplan.common.MessageAccess;
import de.iteratec.iteraplan.common.UserContext;
import de.iteratec.iteraplan.common.error.IteraplanBusinessException;
import de.iteratec.iteraplan.common.error.IteraplanErrorMessages;
import de.iteratec.iteraplan.common.error.IteraplanTechnicalException;
import de.iteratec.iteraplan.model.AbstractHierarchicalEntity;
import de.iteratec.iteraplan.model.ArchitecturalDomain;
import de.iteratec.iteraplan.model.BuildingBlock;
import de.iteratec.iteraplan.model.BuildingBlockFactory;
import de.iteratec.iteraplan.model.BuildingBlockType;
import de.iteratec.iteraplan.model.BusinessDomain;
import de.iteratec.iteraplan.model.BusinessFunction;
import de.iteratec.iteraplan.model.BusinessMapping;
import de.iteratec.iteraplan.model.BusinessObject;
import de.iteratec.iteraplan.model.BusinessProcess;
import de.iteratec.iteraplan.model.BusinessUnit;
import de.iteratec.iteraplan.model.Direction;
import de.iteratec.iteraplan.model.InformationSystem;
import de.iteratec.iteraplan.model.InformationSystemDomain;
import de.iteratec.iteraplan.model.InformationSystemInterface;
import de.iteratec.iteraplan.model.InformationSystemRelease;
import de.iteratec.iteraplan.model.InfrastructureElement;
import de.iteratec.iteraplan.model.Isr2BoAssociation;
import de.iteratec.iteraplan.model.Product;
import de.iteratec.iteraplan.model.Project;
import de.iteratec.iteraplan.model.Tcr2IeAssociation;
import de.iteratec.iteraplan.model.TechnicalComponent;
import de.iteratec.iteraplan.model.TechnicalComponentRelease;
import de.iteratec.iteraplan.model.Transport;
import de.iteratec.iteraplan.model.TransportInfo;
import de.iteratec.iteraplan.model.TypeOfBuildingBlock;
import de.iteratec.iteraplan.model.attribute.AttributeType;
import de.iteratec.iteraplan.model.attribute.AttributeTypeGroup;
import de.iteratec.iteraplan.model.attribute.AttributeTypeGroupPermissionEnum;
import de.iteratec.iteraplan.model.attribute.AttributeValue;
import de.iteratec.iteraplan.model.attribute.AttributeValueAssignment;
import de.iteratec.iteraplan.model.attribute.TypeOfAttribute;
import de.iteratec.iteraplan.model.interfaces.HierarchicalEntity;
import de.iteratec.iteraplan.model.user.User;

/**
 * Allows importing specially formatted Excel files (workbooks) into the database
 */
@SuppressWarnings({ "PMD.TooManyMethods", "PMD.ExcessiveImports" })
public class ExcelImportServiceImpl implements ExcelImportService {

    private static final Logger LOGGER = Logger.getIteraplanLogger(ExcelImportServiceImpl.class);
    private static final Logger FRONTEND_LOG = Logger.getIteraplanLogger("ExcelImportProcessingLog");
    /** List of all single-value attribute types: Date, Number and Text */
    private static final Set<TypeOfAttribute> SINGLE_VALUE_ATS = Sets.newEnumSet(
            Sets.newHashSet(TypeOfAttribute.DATE, TypeOfAttribute.NUMBER, TypeOfAttribute.TEXT),
            TypeOfAttribute.class);

    private final Pattern businessObjectPattern = Pattern.compile("(<->|->|<-|-)\\s(.+)");

    private AttributeTypeService attributeTypeService;
    private AttributeValueService attributeValueService;

    private UserService userService;
    private BuildingBlockServiceLocator buildingBlockServiceLocator;

    /** {@inheritDoc} */
    public LandscapeData readLandscapeData(InputStream is, PrintWriter logStream) {
        ProcessingLog userLog = new ProcessingLog(getLogLevel(), logStream);
        LandscapeDataWorkbook importer = new LandscapeDataWorkbook(userLog);

        try {
            LandscapeData landscapeData = importer.doImport(is);

            return landscapeData;
        } catch (Exception e) {
            getProcessingLog().error("Import failed with exception: ", e);
            LandscapeDataWorkbook.removeProcessingLog();
            // rethrow as IteraplanException to cancel the transaction and redirect to an appropriate error display page
            throw new IteraplanBusinessException(IteraplanErrorMessages.INTERNAL_ERROR, e);
        }
    }

    /** {@inheritDoc} */
    public void importLandscapeData(LandscapeData landscapeData) {
        try {
            Map<String, AttributeType> allAttributeTypes = loadAllAttributeTypes();

            importContentToDaos(landscapeData.getBuildingBlocks());
            importAttributes(landscapeData.getAttributes(), allAttributeTypes);
            importRelations(landscapeData.getRelations(), landscapeData.getLocale(), allAttributeTypes);
            getProcessingLog().info("Import completed. The changed are now being commited to the database...");
        } catch (Exception e) {
            getProcessingLog().error("Import failed with exception: ", e);
            // rethrow as IteraplanException to cancel the transaction and redirect to an appropriate error display page
            throw new IteraplanBusinessException(IteraplanErrorMessages.INTERNAL_ERROR, e);
        } finally {
            LandscapeDataWorkbook.removeProcessingLog();
        }
    }

    /** {@inheritDoc} */
    public void importObjectRelatedPermissions(InputStream is, PrintWriter logWriter) {
        ProcessingLog userLog = new ProcessingLog(getLogLevel(), logWriter);
        ObjectRelatedPermissionsWorkbook importer = new ObjectRelatedPermissionsWorkbook(userLog);

        try {
            List<ObjectRelatedPermissionsData> permissionsData = importer.doImport(is);

            userLog.info("Saving object related permissions");
            for (ObjectRelatedPermissionsData objectUserPermissions : permissionsData) {
                CellValueHolder id = objectUserPermissions.getId();
                CellValueHolder name = objectUserPermissions.getName();
                TypeOfBuildingBlock type = objectUserPermissions.getTypeOfBuildingBlock();

                Integer parseId = parseId(id);
                String parseName = name.getAttributeValue();
                BuildingBlock bb = getDbCopy(parseId, parseName, type);
                if (bb != null) {
                    LOGGER.debug("BB: {0} Type: {1}", bb.getNonHierarchicalName(), type);

                    if (StringUtils.equals(AbstractHierarchicalEntity.TOP_LEVEL_NAME,
                            bb.getNonHierarchicalName())) {
                        userLog.warn(
                                "[{0}] The building block {1} with ID {2} is virtual. It is not allowed to assign object "
                                        + "related permissions for such elements",
                                id.getCellRef(), bb.getNonHierarchicalName(), bb.getId());
                        continue;
                    }

                    Set<User> users = this.loadOrCreateUsers(objectUserPermissions.getUsers());
                    if (users != null) {
                        bb.addOwningUserEntities(users);
                        getServiceFor(bb.getTypeOfBuildingBlock()).saveOrUpdate(bb);
                        String bbType = MessageAccess.getString(type.toString());
                        userLog.info(
                                "[{0}] The object related permissions for building block {1} of type {2} for users {3} were saved",
                                id.getCellRef(), bb.getNonHierarchicalName(), bbType, users);
                    }
                } else {
                    String bbType = MessageAccess.getString(type.toString());
                    userLog.warn("[{0}] The building block with ID {1} and type {2} was not found in database",
                            id.getCellRef(), id.getAttributeValue(), bbType);
                }
            }
        } catch (Exception e) {
            userLog.error("Import failed with exception: ", e);
            // rethrow as IteraplanException to cancel the transaction and redirect to an appropriate error display page
            throw new IteraplanBusinessException(IteraplanErrorMessages.INTERNAL_ERROR, e);
        } finally {
            LandscapeDataWorkbook.removeProcessingLog();
        }
    }

    private Map<String, AttributeType> loadAllAttributeTypes() {
        List<AttributeType> allAttributeTypes = attributeTypeService.loadElementList();
        Map<String, AttributeType> attributesMap = Maps.newHashMap();
        for (AttributeType attributeType : allAttributeTypes) {
            attributesMap.put(attributeType.getName(), attributeType);
        }

        return attributesMap;
    }

    void importContentToDaos(Collection<BuildingBlockHolder> buildingBlocks) {
        LOGGER.debug("Saving building blocks to DB");

        for (BuildingBlockHolder buildingBlockHolder : buildingBlocks) {
            BuildingBlock buildingBlock = buildingBlockHolder.getBuildingBlock();
            String buildingBlockCellCoords = ExcelImportUtilities
                    .getCellRef(buildingBlockHolder.getBuildingBlockCell());
            LOGGER.debug("Cell [{0}] BB: {1} Type: {2}", buildingBlockCellCoords,
                    buildingBlock.getNonHierarchicalName(), buildingBlock.getTypeOfBuildingBlock());

            if (isRelationBuildingBlock(buildingBlockHolder)) {
                continue;
            }

            try {
                saveBuildingBlock(buildingBlockHolder);
            } catch (IteraplanBusinessException e) {
                String bbType = MessageAccess.getString(buildingBlock.getTypeOfBuildingBlock().toString());
                LOGGER.warn("Saving building block " + buildingBlock.getNonHierarchicalName() + " from cell [" + 0
                        + "] failed", e);
                getProcessingLog().warn(
                        "Cell [{0}]  Building block {1} of type {2} could not be saved, row {3} is ignored. Error was: {4}",
                        buildingBlockCellCoords, buildingBlock.getNonHierarchicalName(), bbType,
                        ExcelImportUtilities.getCellRow(buildingBlockHolder.getBuildingBlockCell()),
                        e.getMessage());
            }
        }
    }

    protected void saveBuildingBlock(BuildingBlockHolder buildingBlockHolder) {
        BuildingBlock buildingBlock = buildingBlockHolder.getBuildingBlock();
        if (buildingBlock instanceof InformationSystemRelease) {
            saveIsr(buildingBlockHolder);
            return;
        } else if (buildingBlock instanceof TechnicalComponentRelease) {
            saveTcr(buildingBlockHolder);
            return;
        }
        // else: it's a non-release building block and we can process it generically

        TypeOfBuildingBlock typeOfBuildingBlock = buildingBlock.getTypeOfBuildingBlock();
        BuildingBlockService<BuildingBlock, Integer> service = getServiceFor(typeOfBuildingBlock);

        if (service == null) {
            LOGGER.error("Can't save Building Block from cell [{0}]: Unknown Building Block Type, skipping",
                    ExcelImportUtilities.getCellRef(buildingBlockHolder.getBuildingBlockCell()));
            return;
        }

        addParentToHierarchicalEntity(buildingBlock, service);
        BuildingBlock savedEntity = service.saveOrUpdate(buildingBlock);
        // store the potentially modified DB ID into the building block object for later reference
        buildingBlock.setId(savedEntity.getId());
    }

    /**
     * Adds the parent to new hierarchical entities if required. The parent will be the root element of the specified building
     * block.
     * 
     * @param buildingBlock the building block to add the parent for
     * @param service the building block service for getting the root element
     */
    @SuppressWarnings({ "rawtypes", "unchecked" })
    private void addParentToHierarchicalEntity(BuildingBlock buildingBlock,
            BuildingBlockService<BuildingBlock, Integer> service) {
        if (buildingBlock instanceof AbstractHierarchicalEntity<?>) {
            AbstractHierarchicalEntity hEntity = (AbstractHierarchicalEntity) buildingBlock;
            HierarchicalEntity<?> parent = hEntity.getParent();

            if (parent == null && !hEntity.isTopLevelElement()) {
                BuildingBlock root = service.getFirstElement();
                hEntity.addParent((HierarchicalEntity) root);
            }
        }
    }

    /**
     * Returns {@code true} if the specified {@code buildingBlock} is a relation building block.
     * The relation building blocks will be imported in {@link #importRelations(Collection, Locale)}.
     * 
     * @param buildingBlock the building block to check
     * @return {@code true} if the specified {@code buildingBlock} is a relation building block
     */
    private boolean isRelationBuildingBlock(BuildingBlockHolder buildingBlock) {
        if (buildingBlock.getBuildingBlock() instanceof BusinessMapping
                || buildingBlock.getBuildingBlock() instanceof InformationSystemInterface) {
            LOGGER.debug(
                    "Programming error: There should be no BM/ISI in the ImportContent/CoreAttributes, since they can't be created without their relations");

            return true;
        }

        return false;
    }

    private void importRelations(Collection<BuildingBlockRelations> buildingBlockRelSet, Locale locale,
            Map<String, AttributeType> allAttributeTypes) {
        for (BuildingBlockRelations buildingBlockRel : buildingBlockRelSet) {
            Map<String, CellValueHolder> entries = buildingBlockRel.getContent();
            importSubscribedUsers(buildingBlockRel.getBuildingBlock(), entries, locale);

            BuildingBlock buildingBlock = handleSpecialRelations(locale, buildingBlockRel, allAttributeTypes);
            if (buildingBlock == null) {
                continue;
            }

            // All non-ISI/BMs can have their names printed in this way
            getProcessingLog().info("Saving relations from row {0} for: {1}", buildingBlockRel.getRowNum(),
                    buildingBlock.getNonHierarchicalName());

            try {
                saveRelations(locale, buildingBlockRel, buildingBlock, allAttributeTypes);
            } catch (IteraplanBusinessException e) {
                String bbType = MessageAccess.getString(buildingBlock.getTypeOfBuildingBlock().toString());
                LOGGER.warn("saving relations for building block " + buildingBlock.getNonHierarchicalName()
                        + " from row " + buildingBlockRel.getRowNum() + " failed", e);
                getProcessingLog().warn(
                        "Row {0}  Relations for building block {1} of type {2} could not be saved, this row. Error was: {4}",
                        buildingBlockRel.getRowNum(), buildingBlock.getNonHierarchicalName(), bbType,
                        e.getMessage());
            }
        }
    }

    private void saveRelations(Locale locale, BuildingBlockRelations buildingBlockRel, BuildingBlock buildingBlock,
            Map<String, AttributeType> allAttributeTypes) {
        Map<String, CellValueHolder> entries = buildingBlockRel.getContent();

        if (buildingBlock instanceof ArchitecturalDomain) {
            createBuildingBlockRelations((ArchitecturalDomain) buildingBlock, entries, locale);
        } else if (buildingBlock instanceof BusinessDomain) {
            createBuildingBlockRelations((BusinessDomain) buildingBlock, entries, locale);
        } else if (buildingBlock instanceof BusinessFunction) {
            createBuildingBlockRelations((BusinessFunction) buildingBlock, entries, locale);
        } else if (buildingBlock instanceof BusinessObject) {
            createBuildingBlockRelations((BusinessObject) buildingBlock, entries, locale);
        } else if (buildingBlock instanceof BusinessProcess) {
            createBuildingBlockRelations((BusinessProcess) buildingBlock, entries, locale);
        } else if (buildingBlock instanceof BusinessUnit) {
            createBuildingBlockRelations((BusinessUnit) buildingBlock, entries, locale);
        } else if (buildingBlock instanceof InformationSystemDomain) {
            createBuildingBlockRelations((InformationSystemDomain) buildingBlock, entries, locale);
        } else if (buildingBlock instanceof InformationSystemRelease) {
            createBuildingBlockRelations((InformationSystemRelease) buildingBlock, entries, locale);
        } else if (buildingBlock instanceof InformationSystemInterface) {
            Map<String, CellValueHolder> attributes = buildingBlockRel.getAttributes();
            createBuildingBlockRelationsISI((InformationSystemInterface) buildingBlock, entries, attributes, locale,
                    allAttributeTypes);
        } else if (buildingBlock instanceof InfrastructureElement) {
            createBuildingBlockRelations((InfrastructureElement) buildingBlock, entries, locale);
        } else if (buildingBlock instanceof Product) {
            createBuildingBlockRelations((Product) buildingBlock, entries, locale);
        } else if (buildingBlock instanceof Project) {
            createBuildingBlockRelations((Project) buildingBlock, entries, locale);
        } else if (buildingBlock instanceof TechnicalComponentRelease) {
            createBuildingBlockRelations((TechnicalComponentRelease) buildingBlock, entries, locale);
        } else {
            LOGGER.error("Can't update BB relations: Unknown BBT {0}, skipping",
                    buildingBlock.getBuildingBlockType());
        }
    }

    /**
     * Imports the subscribed users for the specified {@code buildingBlock}.
     * 
     * @param buildingBlock the building block to subcribe users for
     * @param relations the relation field values containing field names associated with the value
     * @param locale the import locale
     */
    private void importSubscribedUsers(BuildingBlock buildingBlock, Map<String, CellValueHolder> relations,
            Locale locale) {
        CellValueHolder subscribedUserCellValueHolder = relations
                .get(MessageAccess.getStringOrNull(Constants.SUBSCRIBED_USERS, locale));
        String subscribedUsersContent = "";
        if (subscribedUserCellValueHolder == null) {
            return;
        }
        subscribedUsersContent = subscribedUserCellValueHolder.getAttributeValue();

        String subscribedUserNames = StringUtils.defaultString(subscribedUsersContent);
        String[] subscribedUsersLogins = ExcelImportUtilities.getSplittedArray(subscribedUserNames,
                ExcelSheet.IN_LINE_SEPARATOR.trim());

        Set<String> validLoginNames = Sets.newHashSet();
        for (String login : subscribedUsersLogins) {
            if (StringUtils.isNotEmpty(login)) {
                validLoginNames.add(login);
            }
        }

        Set<User> subscribedUsers = this.loadOrCreateUsers(validLoginNames);
        buildingBlock.getSubscribedUsers().clear();
        buildingBlock.getSubscribedUsers().addAll(subscribedUsers);
    }

    /**
     * Checks if the given BuildingBlock needs special treatment and creates the special Relations, if necessary.
     * @param locale
     *          Locale
     * @param buildingBlockRel
     *          the given BuildingBlockRelation
     * @return null, if special treatment was necessary, a dbCopy of the BuildingBlock for further treatment otherwise
     */
    private BuildingBlock handleSpecialRelations(Locale locale, BuildingBlockRelations buildingBlockRel,
            Map<String, AttributeType> allAttributeTypes) {
        BuildingBlock buildingBlock = buildingBlockRel.getBuildingBlock();
        Map<String, CellValueHolder> entries = buildingBlockRel.getContent();
        Map<String, CellValueHolder> attributes = buildingBlockRel.getAttributes();

        // ISI/BM are special relationship BBs (and their "name" cannot at this stage be printed so easily)
        if (buildingBlock instanceof InformationSystemInterface) {
            createBuildingBlockRelationsISI((InformationSystemInterface) buildingBlock, entries, attributes, locale,
                    allAttributeTypes);
            return null;
        } else if (buildingBlock instanceof BusinessMapping) {
            createBuildingBlockRelationsBM(entries, attributes, locale, allAttributeTypes);
            return null;
        }

        // BuildingBlock not imported?
        BuildingBlock dbCopy = getDbCopyById(buildingBlock.getId(), buildingBlock.getTypeOfBuildingBlock());
        if (dbCopy == null) {
            getProcessingLog().debug("Element {0} wasn't imported; Neither will its relations be.",
                    buildingBlock.getNonHierarchicalName());
        }
        return dbCopy;
    }

    /**
     * Loads the users with the login names specified in {@code userLoginNames} set. If the user does not exist, it will be created (see
     * {@link UserService#createUser(String)}).
     * 
     * @param userLoginNames
     *          the set containing user login names
     * @return the set of user entities
     */
    private Set<User> loadOrCreateUsers(Set<String> userLoginNames) {
        Set<User> result = Sets.newHashSet();

        for (String loginName : userLoginNames) {
            User user = userService.getUserByLoginIfExists(loginName);

            if (user == null) {
                getProcessingLog().info("The user {0} does not exist. Creating new one", loginName);
                user = userService.createUser(loginName);
            }

            result.add(user);
        }

        return result;
    }

    /**
     * Loads the users with the login names specified in {@code userLoginNames} set. If the user does not exist, it will be created (see
     * {@link UserService#createUser(String)}).
     * 
     * @param userLoginNames
     *          the set containing user login names
     * @return the set of user entities
     */
    private Set<User> loadOrCreateUsers(CellValueHolder users) {
        String usersContent = users.getAttributeValue();
        if (StringUtils.isBlank(usersContent)) {
            getProcessingLog().error("[{0}] The users are not specified, skipping", users.getCellRef());
            return null;
        }
        String[] splittedArray = ExcelImportUtilities.getSplittedArray(usersContent,
                ExcelSheet.IN_LINE_SEPARATOR.trim());

        Set<String> userLoginNames = Sets.newHashSet(splittedArray);
        Set<User> result = Sets.newHashSet();

        for (String loginName : userLoginNames) {
            User user = userService.getUserByLoginIfExists(loginName);

            if (user == null) {
                getProcessingLog().info("[{0}] The user {1} does not exist. Creating new one", users.getCellRef(),
                        loginName);
                user = userService.createUser(loginName);
            }

            result.add(user);
        }

        return result;
    }

    private void importAttributes(Collection<BuildingBlockAttributes> buildingBlockAttSet,
            Map<String, AttributeType> allAttributeTypes) {
        for (BuildingBlockAttributes buildingBlockAtt : buildingBlockAttSet) {
            BuildingBlock buildingBlock = buildingBlockAtt.getBuildingBlock();
            try {
                importAttributesForBB(buildingBlock, buildingBlockAtt.getAttributes(), allAttributeTypes);
            } catch (IteraplanBusinessException e) {
                String bbType = MessageAccess.getString(buildingBlock.getTypeOfBuildingBlock().toString());
                LOGGER.warn("saving attributes for building block " + buildingBlock.getNonHierarchicalName()
                        + " from row " + buildingBlockAtt.getRowNum() + " failed", e);
                getProcessingLog().warn(
                        "Row {0}  Error saving attributes for building block {0} of type {1}; skipping this row!",
                        buildingBlockAtt.getRowNum(), buildingBlock.getNonHierarchicalName(), bbType);
            }
        }
    }

    /**
     * Imports attributes into the database for a given BB
     * 
     * @param bb
     *          For most BBs, this should just be the BB that was read in from the ImportContent with its proper Database ID filled in (The Database
     *          copy of which will be automatically retrieved by Id; if it can't be found by ID, it will just be skipped), ie: for those BBTs, this
     *          parameter does not need to be the Database/WorkingCopy. However, for ISI/BM, it must already be the Database/WorkingCopy.
     * @param entries
     *          Mappings of Attribute Name + Group -> Attribute Value
     */
    private void importAttributesForBB(BuildingBlock bb, Map<String, CellValueHolder> entries,
            Map<String, AttributeType> allAttributeTypes) {
        if (bb.getId() == null) {
            return;
        }

        CellValueHolder anyCell = Iterables.getFirst(entries.values(), null);
        String rowNum = (anyCell == null ? "empty" : ExcelImportUtilities.getCellRow(anyCell.getOriginCell()));
        getProcessingLog().info("saving attributes from row [{0}] for: {1}", rowNum, bb.getNonHierarchicalName());
        LOGGER.debug("{0} has these attributes: {1}", bb.getNonHierarchicalName(), entries);

        for (Entry<String, CellValueHolder> entry : entries.entrySet()) {
            final String name = getAttributeName(entry.getKey());
            final CellValueHolder cellValueHolder = entry.getValue();
            final AttributeType attrType = allAttributeTypes.get(name);

            if (attrType != null && isAttributeTypeEnabledForBB(bb, attrType)) {
                AttributeTypeGroup atg = attrType.getAttributeTypeGroup();
                if (!UserContext.getCurrentPerms().userHasAttrTypeGroupPermission(atg,
                        AttributeTypeGroupPermissionEnum.READ_WRITE)) {
                    getProcessingLog().error(
                            "You have no permission to edit attributes in the group {0}. The attribute {1} in cell [{2}] will not be saved.",
                            atg.getName(), attrType.getName(), cellValueHolder.getCellRef());
                    continue;
                }

                try {
                    Set<AttributeValue> avSet = ExcelImportUtilities.createAttributeValue(attrType,
                            entry.getValue());

                    if (SINGLE_VALUE_ATS.contains(attrType.getTypeOfAttribute())) {
                        if (!avSet.isEmpty()) {
                            AttributeValue firstAV = Iterables.get(avSet, 0);

                            // This is a hack to avoid ObjectDeletedExceptions
                            // Usually this should be fixed in another place, but the according changes could have
                            // undesired side-effects throughout the rest of iteraplan (where attribute value unsetting
                            // seems to work just fine)
                            // a local change here is the least invasive change, and since this code is scheduled
                            // to be retired anyway, there is basically no technical debt.
                            if (firstAV == null) {
                                AttributeValueAssignment ava = bb.getAssignmentForId(attrType.getId());
                                if (ava != null) {
                                    ava.getAttributeValue().getAttributeValueAssignments().remove(ava);
                                    ava.removeReferences();
                                }
                            }
                            // end of hack

                            attributeValueService.setValue(bb, firstAV, attrType);
                        }
                    } else {

                        // This is a hack to avoid ObjectDeletedExceptions
                        // Usually this should be fixed in another place, but the according changes could have
                        // undesired side-effects throughout the rest of iteraplan (where attribute value unsetting
                        // seems to work just fine)
                        // a local change here is the least invasive change, and since this code is scheduled
                        // to be retired anyway, there is basically no technical debt.
                        if (avSet.isEmpty()) {
                            Set<AttributeValueAssignment> avas = Sets
                                    .newHashSet(bb.getAssignmentsForId(attrType.getId()));
                            for (AttributeValueAssignment ava : avas) {
                                ava.getAttributeValue().getAttributeValueAssignments().remove(ava);
                                ava.removeReferences();
                            }
                        }
                        // end of hack

                        attributeValueService.setReferenceValues(bb, avSet, attrType.getId());
                    }
                } catch (IllegalArgumentException ex) {
                    getProcessingLog().warn(
                            "Cell [{0}]  Ignoring value \"{1}\" for attribute type \"{2}\" due to error.",
                            cellValueHolder.getCellRef(), cellValueHolder.getAttributeValue(), attrType.getName());
                }
            } else {
                getProcessingLog().warn(
                        "The attribute {0} does not exist in the database or is not supported by {1} building block. The value in cell [{2}] thus cannot be imported",
                        entry.getKey(), bb, ExcelImportUtilities.getCellRef(entry.getValue().getOriginCell()));
            }
        }
        attributeValueService.saveOrUpdateAttributeValues(bb);
    }

    private boolean isAttributeTypeEnabledForBB(BuildingBlock bb, final AttributeType attrType) {
        for (BuildingBlockType bbt : attrType.getBuildingBlockTypes()) {
            if (bbt.getTypeOfBuildingBlock().equals(bb.getTypeOfBuildingBlock())) {
                return true;
            }
        }

        return false;
    }

    /**
     * Returns the attribute name from the specified attribute header name, read from Excel file. The header names can contain the groups, specified
     * between {@code '('} and {@code ')'}. For example {@code 'Complexity (Default Attribute Group)'}.
     * 
     * @param attrHeadName the attribute header name
     * @return the attribute name
     */
    private String getAttributeName(String attrHeadName) {
        String groupSeparator = "(";
        if (attrHeadName.contains(groupSeparator)) {
            String fullName[] = ExcelImportUtilities.getSplittedArray(attrHeadName, "(");
            return fullName[0].trim();
        }

        return attrHeadName.trim();
    }

    private void saveTcr(BuildingBlockHolder tcrHolder) {
        BuildingBlock bb = tcrHolder.getBuildingBlock();
        if (!(bb instanceof TechnicalComponentRelease)) {
            throw new IllegalArgumentException("Can only process Technical Component Releases");
        }

        TechnicalComponentRelease tcr = (TechnicalComponentRelease) bb;
        TechnicalComponent newTc = tcr.getTechnicalComponent();

        boolean doesTCExist = getTechnicalComponentReleaseService().isDuplicateTechnicalComponent(newTc.getName(),
                newTc.getId());
        if (doesTCExist) {
            String tcrNameCellRef = ExcelImportUtilities.getCellRef(tcrHolder.getBuildingBlockCell());
            TechnicalComponent tc = (TechnicalComponent) getBuildingBlockByName(newTc.getTypeOfBuildingBlock(),
                    newTc.getName(), tcrNameCellRef);
            newTc.getReleases().clear();
            tc.addRelease(tcr);
        }

        if (tcr.getId() == null) {
            try {
                getTechnicalComponentReleaseService().validateDuplicate(tcr.getTechnicalComponent(), tcr);
            } catch (IteraplanBusinessException e) {
                int errorCode = e.getErrorCode();
                if (errorCode == IteraplanErrorMessages.DUPLICATE_RELEASE) {
                    getProcessingLog().error("The Technical Component Release {0} already exists in DB. Skipping",
                            tcr.getNonHierarchicalName());
                    return;
                } else {
                    throw e;
                }
            }
        }

        getTechnicalComponentReleaseService().saveOrUpdate(tcr);
    }

    private void saveIsr(BuildingBlockHolder isrHolder) {
        BuildingBlock bb = isrHolder.getBuildingBlock();
        if (!(bb instanceof InformationSystemRelease)) {
            throw new IllegalArgumentException("Can only process Information System Releases");
        }

        InformationSystemRelease isr = (InformationSystemRelease) bb;
        final InformationSystem newIs = isr.getInformationSystem();
        final boolean doesISExist = getInformationSystemReleaseService()
                .isDuplicateInformationSystem(newIs.getName(), newIs.getId());

        if (doesISExist) {
            String isrNameCellRef = ExcelImportUtilities.getCellRef(isrHolder.getBuildingBlockCell());
            final InformationSystem is = (InformationSystem) getBuildingBlockByName(newIs.getTypeOfBuildingBlock(),
                    newIs.getName(), isrNameCellRef);
            newIs.getReleases().clear();
            is.addRelease(isr);
        }

        if (isr.getId() == null) {
            try {
                getInformationSystemReleaseService().validateDuplicate(isr.getInformationSystem(), isr);
            } catch (IteraplanBusinessException e) {
                int errorCode = e.getErrorCode();
                if (errorCode == IteraplanErrorMessages.DUPLICATE_RELEASE) {
                    getProcessingLog().error("The Information System Release {0} already exists in DB. Skipping",
                            isr.getNonHierarchicalName());
                    return;
                } else {
                    throw e;
                }
            }
        }

        getInformationSystemReleaseService().saveOrUpdate(isr);
    }

    /**
     * Returns Building Block by the specified {@code id} and {@code type}. If the specified {@code id} equals {@code null} or the building block will
     * be not found, {@code null} will be returned.
     * 
     * @param id
     *          the building block id
     * @param type
     *          the building block type
     * @return the building block instance or {@code null}, if such building block does not exist
     */
    private BuildingBlock getDbCopyById(Integer id, TypeOfBuildingBlock type) {
        if (id == null) {
            return null;
        }

        return getServiceFor(type).loadObjectByIdIfExists(id);
    }

    /**
     * Returns Building Block by the specified {@code name}, {@code id} and {@code type}.
     * The Building Block is first searched for by {@code name}, then by {@code id}. If inconsistencies
     * are detected, e.g. the retrieved Building Block has not the specified {@code id}, {@code null}
     * is returned. If both name and id are {@code null} or the building block cannot be found,
     * {@code null} will be returned.
     * 
     * @param id
     *          the building block id
     * @param name
     *          the name of the building block
     * @param type
     *          the building block type
     * @return the building block instance or {@code null}, if such building block does not exist
     */
    private BuildingBlock getDbCopy(Integer id, String name, TypeOfBuildingBlock type) {
        String bbName = StringUtils.defaultIfBlank(name, null);
        if (id == null && bbName == null) {
            return null;
        }

        if (bbName != null && getServiceFor(type).doesObjectWithDifferentIdExist(null, bbName)) {
            BuildingBlock bb = getServiceFor(type).findByNames(Sets.newHashSet(bbName)).get(0);
            if (id == null || bb.getId().equals(id)) {
                return bb;
            } else {
                String bbType = MessageAccess.getString(type.toString());
                getProcessingLog().warn("ID {0} and name {1} do not match for type {2}, skipping", id, bbName,
                        bbType);
                return null;
            }
        } else if (id != null) {
            BuildingBlock bb = getServiceFor(type).loadObjectByIdIfExists(id);

            if (bb != null && type.equals(bb.getTypeOfBuildingBlock())) {
                return bb;
            } else {
                return null;
            }
        }

        return null;
    }

    private Integer parseId(CellValueHolder id) {
        try {
            return Integer.valueOf((int) Double.parseDouble(id.getAttributeValue()));

        } catch (NumberFormatException e) {
            getProcessingLog().debug("[{0}] Could not understand Id {1}", id.getCellRef(), id.getAttributeValue());
            LOGGER.error(e.getMessage(), e);
            return null;
        }
    }

    @SuppressWarnings("unchecked")
    private <T extends HierarchicalEntity<T>> void setParentRelation(AbstractHierarchicalEntity<T> bb,
            CellValueHolder hierarchyNameCellValueHolder) {
        if (hierarchyNameCellValueHolder != null) {
            String hierarchyName = hierarchyNameCellValueHolder.getAttributeValue();
            String hierarchyCellRef = ExcelImportUtilities.getCellRef(hierarchyNameCellValueHolder.getOriginCell());
            String parentNameFromHierarchicalName = getParentNameFromHierarchicalName(hierarchyName,
                    bb.getNonHierarchicalName(), hierarchyCellRef);
            BuildingBlock parentNew = getBuildingBlockByName(bb.getTypeOfBuildingBlock(),
                    parentNameFromHierarchicalName, hierarchyCellRef);

            if (parentNew != null && !Objects.equal(parentNew, bb.getParent())) {
                bb.addParent((T) parentNew);
            }
        }
    }

    void createBuildingBlockRelations(BusinessFunction bf, Map<String, CellValueHolder> relations, Locale locale) {
        // Parent
        CellValueHolder hierarchyNameCellValueHolder = relations
                .get(getHierarchyHeaderFor(Constants.BB_BUSINESSFUNCTION_PLURAL, locale));
        setParentRelation(bf, hierarchyNameCellValueHolder);

        // Import BusinessDomains
        CellValueHolder bdCellValueHolder = relations
                .get(MessageAccess.getStringOrNull(Constants.BB_BUSINESSDOMAIN_PLURAL, locale));
        if (bdCellValueHolder != null) {
            Set<BusinessDomain> bds = loadBuildingBlocksAsSet(TypeOfBuildingBlock.BUSINESSDOMAIN,
                    bdCellValueHolder);
            bf.removeBusinessDomains();
            bf.addBusinessDomains(bds);
        }

        CellValueHolder boCellValueHolder = relations
                .get(MessageAccess.getStringOrNull(Constants.BB_BUSINESSOBJECT_PLURAL, locale));
        if (boCellValueHolder != null) {
            Set<BusinessObject> bos = loadBuildingBlocksAsSet(TypeOfBuildingBlock.BUSINESSOBJECT,
                    boCellValueHolder);
            bf.removeBusinessObjects();
            bf.addBusinessObjects(bos);
        }

        getServiceFor(TypeOfBuildingBlock.BUSINESSFUNCTION).saveOrUpdate(bf);
    }

    void createBuildingBlockRelations(ArchitecturalDomain ad, Map<String, CellValueHolder> relations,
            Locale locale) {
        CellValueHolder hierarchyNameCellValueHolder = relations
                .get(getHierarchyHeaderFor(Constants.BB_ARCHITECTURALDOMAIN_PLURAL, locale));
        setParentRelation(ad, hierarchyNameCellValueHolder);

        CellValueHolder tcrCellValueHolder = relations
                .get(MessageAccess.getStringOrNull(Constants.BB_TECHNICALCOMPONENTRELEASE_PLURAL, locale));
        if (tcrCellValueHolder != null) {
            Set<TechnicalComponentRelease> tcrs = loadBuildingBlocksAsSet(
                    TypeOfBuildingBlock.TECHNICALCOMPONENTRELEASE, tcrCellValueHolder);
            ad.removeTechnicalComponentReleases();
            ad.addTechnicalComponentReleases(tcrs);
        }

        getServiceFor(TypeOfBuildingBlock.ARCHITECTURALDOMAIN).saveOrUpdate(ad);
    }

    /**
     * Gets the header key to retrieve the BM string
     * 
     * @param bmLayout
     *          The 3 BBTypes, in order, used to build the header key
     */
    private String getBusinessMappingHeaderKey(TypeOfBuildingBlock[] bmLayout, Locale locale) {

        assert bmLayout.length == 3;

        String bm = MessageAccess.getStringOrNull(Constants.BB_BUSINESSMAPPING_PLURAL, locale);
        StringBuilder strBuilder = new StringBuilder(bm);
        strBuilder.append(ExcelSheet.UNIT_OPENER);

        for (int i = 0; i < 3; i++) {
            switch (bmLayout[i]) {
            case BUSINESSPROCESS:
                strBuilder.append(MessageAccess.getStringOrNull(Constants.BB_BUSINESSPROCESS, locale));
                break;
            case BUSINESSUNIT:
                strBuilder.append(MessageAccess.getStringOrNull(Constants.BB_BUSINESSUNIT, locale));
                break;
            case PRODUCT:
                strBuilder.append(MessageAccess.getStringOrNull(Constants.BB_PRODUCT, locale));
                break;
            case INFORMATIONSYSTEMRELEASE:
                strBuilder.append(MessageAccess.getStringOrNull(Constants.BB_INFORMATIONSYSTEMRELEASE, locale));
                break;
            default:
                throw new IteraplanTechnicalException();
            }

            // No UnitSeparator after the 3rd BBT Name
            if (i < 2) {
                strBuilder.append(ExcelSheet.UNIT_SEPARATOR);
            }
        }

        strBuilder.append(ExcelSheet.UNIT_CLOSER);
        return strBuilder.toString();
    }

    /**
     * Validates, checks for duplicates, and finally saves a BM.
     * 
     * @return The businessMapping, if it was either created or if it already existed; Else: null
     */
    private BusinessMapping saveBusinessMapping(InformationSystemRelease isr, BusinessProcess bp, BusinessUnit bu,
            Product product) {
        String happyName = "Business Mapping [" + isr + "][" + product + "][" + bu + "][" + bp + "]";
        getProcessingLog().debug("Saving {0}", happyName);

        if ((product == null) || (bu == null) || (bp == null) || (isr == null)) {
            getProcessingLog().error(happyName + " contains missing elements! Not importing.");
            return null;
        }

        if (product.isTopLevelElement() && bu.isTopLevelElement() && bp.isTopLevelElement()) {
            getProcessingLog().error(happyName + " consists of only top level elements! Not importing.");
            return null;
        }

        // Confirm not duplicate
        BusinessMapping oldBm = getBusinessMappingService()
                .getBusinessMappingByRelatedBuildingBlockIds(product.getId(), bu.getId(), bp.getId(), isr.getId());
        if (oldBm != null) {
            getProcessingLog().debug("Such a BusinessMapping already exists, not saving again.");
            return oldBm;
        }

        // Save
        BusinessMapping bm = BuildingBlockFactory.createBusinessMapping();
        bm.addProduct(product);
        bm.addBusinessUnit(bu);
        bm.addBusinessProcess(bp);
        bm.addInformationSystemRelease(isr);
        getBusinessMappingService().saveOrUpdate(bm);

        return bm;

    }

    @SuppressWarnings("PMD.ExcessiveMethodLength")
    void createBuildingBlockRelations(InformationSystemRelease isr, Map<String, CellValueHolder> relations,
            Locale locale) {
        // Parent
        CellValueHolder hierarchyNameCellValueHolder = relations
                .get(getHierarchyHeaderFor(Constants.BB_INFORMATIONSYSTEMRELEASE_PLURAL, locale));
        // cannot use setParentRelation(isr, hierarchyNameCellValueHolder) here, because of type incompatibility
        if (hierarchyNameCellValueHolder != null) {
            String hierarchyName = hierarchyNameCellValueHolder.getAttributeValue();
            String hierarchyCellRef = ExcelImportUtilities.getCellRef(hierarchyNameCellValueHolder.getOriginCell());
            String parentNameFromHierarchicalName = getParentNameFromHierarchicalName(hierarchyName,
                    isr.getNonHierarchicalName(), hierarchyCellRef);
            BuildingBlock parentNew = getBuildingBlockByName(isr.getTypeOfBuildingBlock(),
                    parentNameFromHierarchicalName,
                    ExcelImportUtilities.getCellRef(hierarchyNameCellValueHolder.getOriginCell()));
            if (parentNew != null) {
                isr.addParent((InformationSystemRelease) parentNew);
            }
        }

        CellValueHolder predecessorsCellValueHolder = relations
                .get(MessageAccess.getStringOrNull(Constants.ASSOC_PREDECESSORS, locale));
        if (predecessorsCellValueHolder != null) {
            Set<InformationSystemRelease> preds = loadBuildingBlocksAsSet(
                    TypeOfBuildingBlock.INFORMATIONSYSTEMRELEASE, predecessorsCellValueHolder);
            isr.removePredecessors();
            isr.addPredecessors(preds);
        }

        CellValueHolder successorsCellValueHolder = relations
                .get(MessageAccess.getStringOrNull(Constants.ASSOC_SUCCESSORS, locale));
        if (successorsCellValueHolder != null) {
            Set<InformationSystemRelease> succs = loadBuildingBlocksAsSet(
                    TypeOfBuildingBlock.INFORMATIONSYSTEMRELEASE, successorsCellValueHolder);
            isr.removeSuccessors();
            isr.addSuccessors(succs);
        }

        CellValueHolder bfCellValueHolder = relations
                .get(MessageAccess.getStringOrNull(Constants.BB_BUSINESSFUNCTION_PLURAL, locale));
        if (bfCellValueHolder != null) {
            Set<BusinessFunction> bfs = loadBuildingBlocksAsSet(TypeOfBuildingBlock.BUSINESSFUNCTION,
                    bfCellValueHolder);
            isr.removeBusinessFunctions();
            isr.addBusinessFunctions(bfs);
        }

        // Interfaces (not imported, just warn user)
        // Reason: We can't uniquely identify an Interface by its "name", since multiple ISIs with the same 2 ISRs can exist
        CellValueHolder interfacesCellValueHolder = relations.get(MessageAccess
                .getStringOrNull("reporting.excel.header.informationSystemRelease.interfacesTo", locale));
        if (interfacesCellValueHolder != null) {
            String interfaces = interfacesCellValueHolder.getAttributeValue();
            String ifaceCellRef = ExcelImportUtilities.getCellRef(interfacesCellValueHolder.getOriginCell());
            if (!StringUtils.isEmpty(interfaces)) {
                getProcessingLog().warn(
                        "{0} has interfaces specified in cell [{1}] in the InformationSystemRelease sheet. These were not imported. Only Interfaces listed in the Interfaces sheet are imported.",
                        isr.getNonHierarchicalName(), ifaceCellRef);
            }
        }

        CellValueHolder boCellValueHolder = relations
                .get(MessageAccess.getStringOrNull(Constants.BB_BUSINESSOBJECT_PLURAL, locale));
        if (boCellValueHolder != null) {
            Set<BusinessObject> bos = loadBuildingBlocksAsSet(TypeOfBuildingBlock.BUSINESSOBJECT,
                    boCellValueHolder);
            Set<Isr2BoAssociation> associations = Sets.newHashSetWithExpectedSize(bos.size());
            Set<Isr2BoAssociation> existentAssociations = isr.getBusinessObjectAssociations();
            for (BusinessObject bo : bos) {
                Isr2BoAssociation assoc = BuildingBlockFactory.createIsr2BoAssociation(isr, bo);
                // try to find out whether that association exists already, and if yes, use the existing object
                for (Isr2BoAssociation connectedAssoc : existentAssociations) {
                    if (assoc.equals(connectedAssoc)) {
                        assoc = connectedAssoc;
                        break;
                    }
                }
                associations.add(assoc);
            }
            buildingBlockServiceLocator.getIsr2BoAssociationService().saveAssociations(associations);
            isr.connectIsr2BoAssociations(associations);
        }

        // Business Mapping
        TypeOfBuildingBlock[] orderOfBMContent = { TypeOfBuildingBlock.BUSINESSPROCESS, TypeOfBuildingBlock.PRODUCT,
                TypeOfBuildingBlock.BUSINESSUNIT };
        CellValueHolder allBMCellValueHolder = relations.get(getBusinessMappingHeaderKey(orderOfBMContent, locale));
        if (allBMCellValueHolder != null) {
            String allBMs = allBMCellValueHolder.getAttributeValue();
            String bmCellRef = ExcelImportUtilities.getCellRef(allBMCellValueHolder.getOriginCell());
            if (StringUtils.isNotEmpty(allBMs)) {
                getProcessingLog().warn(
                        "Information System {0} has business mappings specified in cell [{1}]. These were not imported. Only Business Mappings listed in the Business Mappings sheet are imported.",
                        isr.getNonHierarchicalName(), bmCellRef);
            }
        }

        CellValueHolder isdCellValueHolder = relations
                .get(MessageAccess.getStringOrNull(Constants.BB_INFORMATIONSYSTEMDOMAIN_PLURAL, locale));
        if (isdCellValueHolder != null) {
            Set<InformationSystemDomain> isds = loadBuildingBlocksAsSet(TypeOfBuildingBlock.INFORMATIONSYSTEMDOMAIN,
                    isdCellValueHolder);
            isr.removeInformationSystemDomains();
            isr.addInformationSystemDomains(isds);
        }

        CellValueHolder ieCellValueHolder = relations
                .get(MessageAccess.getStringOrNull(Constants.BB_INFRASTRUCTUREELEMENT_PLURAL, locale));
        if (ieCellValueHolder != null) {
            Set<InfrastructureElement> ies = loadBuildingBlocksAsSet(TypeOfBuildingBlock.INFRASTRUCTUREELEMENT,
                    ieCellValueHolder);
            isr.removeInfrastructureElements();
            isr.addInfrastructureElements(ies);
        }

        CellValueHolder projectsCellValueHolder = relations
                .get(MessageAccess.getStringOrNull(Constants.BB_PROJECT_PLURAL, locale));
        if (projectsCellValueHolder != null) {
            Set<Project> projs = loadBuildingBlocksAsSet(TypeOfBuildingBlock.PROJECT, projectsCellValueHolder);
            isr.removeProjects();
            isr.addProjects(projs);
        }

        CellValueHolder tcrCellValueHolder = relations
                .get(MessageAccess.getStringOrNull(Constants.BB_TECHNICALCOMPONENTRELEASE_PLURAL, locale));
        if (tcrCellValueHolder != null) {
            Set<TechnicalComponentRelease> tcrs = loadBuildingBlocksAsSet(
                    TypeOfBuildingBlock.TECHNICALCOMPONENTRELEASE, tcrCellValueHolder);
            isr.removeTechnicalComponentReleases();
            isr.addTechnicalComponentReleases(tcrs);
        }

        // Uses ISs
        CellValueHolder isCellValueHolder = relations.get(MessageAccess.getStringOrNull(
                ExcelConstants.HEADER_INFORMATIONSYSTEMRELEASE_SHEET_BASECOMPONENTS_COLUMN, locale));
        if (isCellValueHolder != null) {
            Set<InformationSystemRelease> iss = loadBuildingBlocksAsSet(
                    TypeOfBuildingBlock.INFORMATIONSYSTEMRELEASE, isCellValueHolder);
            isr.removeBaseComponents();
            isr.addBaseComponents(iss);
        }

        getInformationSystemReleaseService().saveOrUpdate(isr);
    }

    /**
     * Get the header text for the key of BBT (plural)
     */
    private String getHierarchyHeaderFor(String constantBbtKey, Locale locale) {
        return MessageAccess.getStringOrNull(constantBbtKey, locale) + ' '
                + MessageAccess.getStringOrNull("global.hierarchical", locale);
    }

    void createBuildingBlockRelations(BusinessDomain bd, Map<String, CellValueHolder> relations, Locale locale) {
        // Parent
        CellValueHolder hierarchyNameCellValueHolder = relations
                .get(getHierarchyHeaderFor(Constants.BB_BUSINESSDOMAIN_PLURAL, locale));
        setParentRelation(bd, hierarchyNameCellValueHolder);

        CellValueHolder bfCellValueHolder = relations
                .get(MessageAccess.getStringOrNull(Constants.BB_BUSINESSFUNCTION_PLURAL, locale));
        if (bfCellValueHolder != null) {
            Set<BusinessFunction> bfs = loadBuildingBlocksAsSet(TypeOfBuildingBlock.BUSINESSFUNCTION,
                    bfCellValueHolder);
            bd.removeBusinessFunctions();
            bd.addBusinessFunctions(bfs);
        }

        CellValueHolder boCellValueHolder = relations
                .get(MessageAccess.getStringOrNull(Constants.BB_BUSINESSOBJECT_PLURAL, locale));
        if (boCellValueHolder != null) {
            Set<BusinessObject> bos = loadBuildingBlocksAsSet(TypeOfBuildingBlock.BUSINESSOBJECT,
                    boCellValueHolder);
            bd.removeBusinessObjects();
            bd.addBusinessObjects(bos);
        }

        CellValueHolder bpCellValueHolder = relations
                .get(MessageAccess.getStringOrNull(Constants.BB_BUSINESSPROCESS_PLURAL, locale));
        if (bpCellValueHolder != null) {
            Set<BusinessProcess> bps = loadBuildingBlocksAsSet(TypeOfBuildingBlock.BUSINESSPROCESS,
                    bpCellValueHolder);
            bd.removeBusinessProcesses();
            bd.addBusinessProcesses(bps);
        }

        CellValueHolder productsCellValueHolder = relations
                .get(MessageAccess.getStringOrNull(Constants.BB_PRODUCT_PLURAL, locale));
        if (productsCellValueHolder != null) {
            Set<Product> prods = loadBuildingBlocksAsSet(TypeOfBuildingBlock.PRODUCT, productsCellValueHolder);
            bd.removeProducts();
            bd.addProducts(prods);
        }

        CellValueHolder buCellValueHolder = relations
                .get(MessageAccess.getStringOrNull(Constants.BB_BUSINESSUNIT_PLURAL, locale));
        if (buCellValueHolder != null) {
            Set<BusinessUnit> bus = loadBuildingBlocksAsSet(TypeOfBuildingBlock.BUSINESSUNIT, buCellValueHolder);
            bd.removeBusinessUnits();
            bd.addBusinessUnits(bus);
        }

        getServiceFor(TypeOfBuildingBlock.BUSINESSDOMAIN).saveOrUpdate(bd);
    }

    void createBuildingBlockRelations(BusinessObject bo, Map<String, CellValueHolder> relations, Locale locale) {
        // Parent
        CellValueHolder hierarchyNameCellValueHolder = relations
                .get(getHierarchyHeaderFor(Constants.BB_BUSINESSOBJECT_PLURAL, locale));
        setParentRelation(bo, hierarchyNameCellValueHolder);

        CellValueHolder specializationsCellValueHolder = relations
                .get(MessageAccess.getStringOrNull(Constants.ASSOC_SPECIALISATION, locale));
        if (specializationsCellValueHolder != null) {
            Set<BusinessObject> specials = loadBuildingBlocksAsSet(TypeOfBuildingBlock.BUSINESSOBJECT,
                    specializationsCellValueHolder);
            bo.removeSpecialisationRelations();
            bo.addSpecialisations(specials);
        }

        CellValueHolder bdCellValueHolder = relations
                .get(MessageAccess.getStringOrNull(Constants.BB_BUSINESSDOMAIN_PLURAL, locale));
        if (bdCellValueHolder != null) {
            Set<BusinessDomain> bds = loadBuildingBlocksAsSet(TypeOfBuildingBlock.BUSINESSDOMAIN,
                    bdCellValueHolder);
            bo.removeBusinessDomainRelations();
            bo.addBusinessDomains(bds);
        }

        CellValueHolder bfCellValueHolder = relations
                .get(MessageAccess.getStringOrNull(Constants.BB_BUSINESSFUNCTION_PLURAL, locale));
        if (bfCellValueHolder != null) {
            Set<BusinessFunction> bfs = loadBuildingBlocksAsSet(TypeOfBuildingBlock.BUSINESSFUNCTION,
                    bfCellValueHolder);
            bo.removeBusinessFunctionRelations();
            bo.addBusinessFunctions(bfs);
        }

        CellValueHolder isrCellValueHolder = relations
                .get(MessageAccess.getStringOrNull(Constants.BB_INFORMATIONSYSTEMRELEASE_PLURAL, locale));
        if (isrCellValueHolder != null) {
            Set<InformationSystemRelease> isrs = loadBuildingBlocksAsSet(
                    TypeOfBuildingBlock.INFORMATIONSYSTEMRELEASE, isrCellValueHolder);
            Set<Isr2BoAssociation> associations = Sets.newHashSetWithExpectedSize(isrs.size());
            Set<Isr2BoAssociation> existentAssociations = bo.getInformationSystemReleaseAssociations();
            for (InformationSystemRelease isr : isrs) {
                Isr2BoAssociation assoc = BuildingBlockFactory.createIsr2BoAssociation(isr, bo);
                // try to find out whether that association exists already, and if yes, use the existing object
                for (Isr2BoAssociation connectedAssoc : existentAssociations) {
                    if (assoc.equals(connectedAssoc)) {
                        assoc = connectedAssoc;
                        break;
                    }
                }
                buildingBlockServiceLocator.getIsr2BoAssociationService().saveAssociations(associations);
                associations.add(assoc);
            }
            bo.connectIsr2BoAssociations(associations);
        }

        getServiceFor(TypeOfBuildingBlock.BUSINESSOBJECT).saveOrUpdate(bo);

    }

    void createBuildingBlockRelations(BusinessProcess bp, Map<String, CellValueHolder> relations, Locale locale) {
        CellValueHolder hierarchyNameCellValueHolder = relations
                .get(getHierarchyHeaderFor(Constants.BB_BUSINESSPROCESS_PLURAL, locale));
        setParentRelation(bp, hierarchyNameCellValueHolder);

        CellValueHolder bdCellValueHolder = relations
                .get(MessageAccess.getStringOrNull(Constants.BB_BUSINESSDOMAIN_PLURAL, locale));
        if (bdCellValueHolder != null) {
            Set<BusinessDomain> bds = loadBuildingBlocksAsSet(TypeOfBuildingBlock.BUSINESSDOMAIN,
                    bdCellValueHolder);
            bp.removeBusinessDomainRelations();
            bp.addBusinessDomains(bds);
        }

        // Business Mapping
        TypeOfBuildingBlock[] orderOfBmContent = { TypeOfBuildingBlock.PRODUCT, TypeOfBuildingBlock.BUSINESSUNIT,
                TypeOfBuildingBlock.INFORMATIONSYSTEMRELEASE };
        CellValueHolder allBMCellValueHolder = relations.get(getBusinessMappingHeaderKey(orderOfBmContent, locale));
        if (allBMCellValueHolder != null) {
            String allBMs = allBMCellValueHolder.getAttributeValue();
            String bmCellRef = ExcelImportUtilities.getCellRef(allBMCellValueHolder.getOriginCell());
            if (!StringUtils.isEmpty(allBMs)) {
                getProcessingLog().warn(
                        "Business Process {0} has business mappings specified in cell [{1}]. These were not imported. Only Business Mappings listed in the Business Mappings sheet are imported.",
                        bp.getNonHierarchicalName(), bmCellRef);
            }
        }

        // save updated relations
        getServiceFor(TypeOfBuildingBlock.BUSINESSPROCESS).saveOrUpdate(bp);
    }

    void createBuildingBlockRelations(BusinessUnit bu, Map<String, CellValueHolder> relations, Locale locale) {
        // Parent
        CellValueHolder hierarchyNameCellValueHolder = relations
                .get(getHierarchyHeaderFor(Constants.BB_BUSINESSUNIT_PLURAL, locale));
        setParentRelation(bu, hierarchyNameCellValueHolder);

        CellValueHolder bdCellValueHolder = relations
                .get(MessageAccess.getStringOrNull(Constants.BB_BUSINESSDOMAIN_PLURAL, locale));
        if (bdCellValueHolder != null) {
            Set<BusinessDomain> bds = loadBuildingBlocksAsSet(TypeOfBuildingBlock.BUSINESSDOMAIN,
                    bdCellValueHolder);
            bu.removeBusinessDomainRelations();
            bu.addBusinessDomains(bds);
        }

        TypeOfBuildingBlock[] orderOfBmContent = { TypeOfBuildingBlock.BUSINESSPROCESS, TypeOfBuildingBlock.PRODUCT,
                TypeOfBuildingBlock.INFORMATIONSYSTEMRELEASE };
        CellValueHolder allBMCellValueHolder = relations.get(getBusinessMappingHeaderKey(orderOfBmContent, locale));
        if (allBMCellValueHolder != null) {
            String allBMs = allBMCellValueHolder.getAttributeValue();
            String bmCellRef = ExcelImportUtilities.getCellRef(allBMCellValueHolder.getOriginCell());
            if (StringUtils.isNotEmpty(allBMs)) {
                getProcessingLog().warn(
                        "Business Unit {0} has business mappings specified in cell [{1}]. These were not imported. Only Business Mappings listed in the Business Mappings sheet are imported.",
                        bu.getNonHierarchicalName(), bmCellRef);
            }
        }

        getServiceFor(TypeOfBuildingBlock.BUSINESSUNIT).saveOrUpdate(bu);
    }

    void createBuildingBlockRelations(InformationSystemDomain isd, Map<String, CellValueHolder> relations,
            Locale locale) {
        // Parent
        CellValueHolder hierarchyNameCellValueHolder = relations
                .get(getHierarchyHeaderFor(Constants.BB_INFORMATIONSYSTEMDOMAIN_PLURAL, locale));
        setParentRelation(isd, hierarchyNameCellValueHolder);

        CellValueHolder isrCellValueHolder = relations
                .get(MessageAccess.getStringOrNull(Constants.BB_INFORMATIONSYSTEMRELEASE_PLURAL, locale));
        if (isrCellValueHolder != null) {
            Set<InformationSystemRelease> isrs = loadBuildingBlocksAsSet(
                    TypeOfBuildingBlock.INFORMATIONSYSTEMRELEASE, isrCellValueHolder);
            isd.removeInformationSystemReleases();
            isd.addInformationSystemReleases(isrs);
        }

        getServiceFor(TypeOfBuildingBlock.INFORMATIONSYSTEMDOMAIN).saveOrUpdate(isd);
    }

    void createBuildingBlockRelationsBM(Map<String, CellValueHolder> relations,
            Map<String, CellValueHolder> attributes, Locale locale, Map<String, AttributeType> allAttributeTypes) {
        // Look up and retrieve the current version of the related BBs
        CellValueHolder bpHolder = relations
                .get(MessageAccess.getStringOrNull(Constants.BB_BUSINESSPROCESS_PLURAL, locale));
        String bpName = (bpHolder != null ? bpHolder.getAttributeValue() : "");
        String bpCellRef = bpHolder != null ? ExcelImportUtilities.getCellRef(bpHolder.getOriginCell()) : "";

        CellValueHolder productHolder = relations
                .get(MessageAccess.getStringOrNull(Constants.BB_PRODUCT_PLURAL, locale));
        String productName = (productHolder != null ? productHolder.getAttributeValue() : "");
        String productCellRef = productHolder != null
                ? ExcelImportUtilities.getCellRef(productHolder.getOriginCell())
                : "";

        CellValueHolder buHolder = relations
                .get(MessageAccess.getStringOrNull(Constants.BB_BUSINESSUNIT_PLURAL, locale));
        String buName = (buHolder != null ? buHolder.getAttributeValue() : "");
        String buCellRef = buHolder != null ? ExcelImportUtilities.getCellRef(buHolder.getOriginCell()) : "";

        CellValueHolder isrHolder = relations
                .get(MessageAccess.getStringOrNull(Constants.BB_INFORMATIONSYSTEMRELEASE_PLURAL, locale));
        String isrName = (isrHolder != null ? isrHolder.getAttributeValue() : "");
        String isrCellRef = isrHolder != null ? ExcelImportUtilities.getCellRef(isrHolder.getOriginCell()) : "";

        String bmRowNum = (isrHolder != null ? ExcelImportUtilities.getCellRow(isrHolder.getOriginCell()) : "");

        String happyName = "Business Mapping [" + bpName + "][" + productName + "][" + buName + "][" + isrName
                + "] from row [" + bmRowNum + "]";
        getProcessingLog().debug("Preparing to save {0}", happyName);

        BusinessProcess bp = (BusinessProcess) getBuildingBlockByNameOrTop(TypeOfBuildingBlock.BUSINESSPROCESS,
                bpName, bpCellRef);
        Product product = (Product) getBuildingBlockByNameOrTop(TypeOfBuildingBlock.PRODUCT, productName,
                productCellRef);
        BusinessUnit bu = (BusinessUnit) getBuildingBlockByNameOrTop(TypeOfBuildingBlock.BUSINESSUNIT, buName,
                buCellRef);
        InformationSystemRelease isr = (InformationSystemRelease) getBuildingBlockByName(
                TypeOfBuildingBlock.INFORMATIONSYSTEMRELEASE, isrName, isrCellRef);

        // All elements must be found (or at least replaced with Top Elements)
        if ((bp == null) || (product == null) || (bu == null) || (isr == null)) {
            getProcessingLog().error("Could not find related all BuildingBlocks for {0}. Not importing.",
                    happyName);
            return;
        }

        // load biz mapping if it exists already
        BusinessMapping bm = getBusinessMappingService()
                .getBusinessMappingByRelatedBuildingBlockIds(product.getId(), bu.getId(), bp.getId(), isr.getId());
        if (bm != null) {
            getProcessingLog().debug("{0} exists already, not saving again. Will try updating it.", happyName);
        } else {
            bm = saveBusinessMapping(isr, bp, bu, product);
        }

        if (bm == null) {
            getProcessingLog().warn("Could not create {0}. Giving up on this element.", happyName);
            return;
        }

        // Import Attributes
        getBusinessMappingService().saveOrUpdate(bm);
        importAttributesForBB(bm, attributes, allAttributeTypes);
    }

    void createBuildingBlockRelationsISI(InformationSystemInterface isi, Map<String, CellValueHolder> relations,
            Map<String, CellValueHolder> attributes, Locale locale, Map<String, AttributeType> allAttributeTypes) {
        CellValueHolder relAHolder = relations
                .get(MessageAccess.getStringOrNull(Constants.BB_INTERFACE_INFORMATIONSYSTEMRELEASE_A, locale));
        String strReleaseA = relAHolder != null ? relAHolder.getAttributeValue() : "";
        String releaseACellRef = relAHolder != null ? ExcelImportUtilities.getCellRef(relAHolder.getOriginCell())
                : "";

        CellValueHolder relBHolder = relations
                .get(MessageAccess.getStringOrNull(Constants.BB_INTERFACE_INFORMATIONSYSTEMRELEASE_B, locale));
        String strReleaseB = relBHolder != null ? relBHolder.getAttributeValue() : "";
        String releaseBCellRef = relBHolder != null ? ExcelImportUtilities.getCellRef(relBHolder.getOriginCell())
                : "";

        String isiRowNum = (relAHolder != null ? ExcelImportUtilities.getCellRow(relAHolder.getOriginCell()) : "");

        String interfaceDescription = "Interface '" + isi.getName() + "' [" + strReleaseA + "] "
                + isi.getInterfaceDirection() + " [" + strReleaseB + "] from row " + isiRowNum;
        getProcessingLog().info("Saving {0}", interfaceDescription);

        InformationSystemRelease releaseA = (InformationSystemRelease) getBuildingBlockByName(
                TypeOfBuildingBlock.INFORMATIONSYSTEMRELEASE, strReleaseA, releaseACellRef);
        InformationSystemRelease releaseB = (InformationSystemRelease) getBuildingBlockByName(
                TypeOfBuildingBlock.INFORMATIONSYSTEMRELEASE, strReleaseB, releaseBCellRef);

        if ((releaseA == null) || (releaseB == null)) {
            getProcessingLog().error("Could not find related BuildingBlocks for {0}. Not importing.",
                    interfaceDescription);
            return;
        }

        if (isi.getId() == null) {
            getProcessingLog().debug(" inserting {0}", interfaceDescription);
        } else {
            getProcessingLog().debug(" updating {0}", interfaceDescription);
        }
        isi.connect(releaseA, releaseB);

        // Import TechnicalComponentReleases
        CellValueHolder tcrCellValueHolder = relations
                .get(MessageAccess.getStringOrNull(Constants.BB_TECHNICALCOMPONENTRELEASE_PLURAL, locale));
        if (tcrCellValueHolder != null) {
            Set<TechnicalComponentRelease> tcrSet = loadBuildingBlocksAsSet(
                    TypeOfBuildingBlock.TECHNICALCOMPONENTRELEASE, tcrCellValueHolder);
            isi.removeTechnicalComponentReleases();
            isi.addTechnicalComponentReleases(tcrSet);
        }

        CellValueHolder boNamesCellValueHolder = relations
                .get(MessageAccess.getStringOrNull("interface.transport.businessObjects", locale));
        saveTransports(boNamesCellValueHolder, isi);

        getInformationSystemInterfaceService().saveOrUpdate(isi);
        importAttributesForBB(isi, attributes, allAttributeTypes);
    }

    private void saveTransports(CellValueHolder boNamesWithDirections, InformationSystemInterface isi) {
        if (boNamesWithDirections == null) {
            return;
        }
        String boNamesStr = boNamesWithDirections.getAttributeValue();
        if (StringUtils.isBlank(boNamesStr)) {
            return;
        }

        String[] boNamesParsed = ExcelImportUtilities.getSplittedArray(boNamesStr,
                ExcelSheet.IN_LINE_SEPARATOR.trim());
        String boNamesCellRef = ExcelImportUtilities.getCellRef(boNamesWithDirections.getOriginCell());

        for (String boNameWithDirection : Sets.newHashSet(boNamesParsed)) {
            if (StringUtils.isBlank(boNameWithDirection)) {
                continue;
            }

            Matcher matcher = businessObjectPattern.matcher(boNameWithDirection);
            if (matcher.find()) {
                final String direction = matcher.group(1);
                final String name = matcher.group(2);

                if (StringUtils.isNotBlank(name)) {
                    saveTransport(direction, name, boNamesCellRef, isi);
                } else {
                    getProcessingLog().warn("A Business Object name in cell [{0}] is blank: {1}", boNamesCellRef,
                            boNameWithDirection);
                }
            } else {
                getProcessingLog().warn(
                        "Could not understand the Business Object direction and name in cell [{0}]: {1}",
                        boNamesCellRef, boNameWithDirection);
            }
        }
    }

    private void saveTransport(String direction, String boName, String boCellCoordinates,
            InformationSystemInterface isi) {
        BusinessObject bo = (BusinessObject) getBuildingBlockByName(TypeOfBuildingBlock.BUSINESSOBJECT, boName,
                boCellCoordinates);
        if (bo != null) {
            LOGGER.debug("Related BB found: " + bo.getNonHierarchicalName());

            Transport t = createOrLoadTransport(isi, bo);
            setBusinessObjectDirection(t, direction);
        } else {
            getProcessingLog().warn("Related business object {0} from cell [{1}] not found, ignoring", boName,
                    boCellCoordinates);
        }
    }

    private Transport createOrLoadTransport(InformationSystemInterface isi, BusinessObject bo) {
        final Transport existingTransport = getExistingTransport(isi, bo);
        if (existingTransport != null) {
            return existingTransport;
        }

        final Transport newTransport = BuildingBlockFactory.createTransport();
        isi.addTransport(newTransport);
        newTransport.addBusinessObject(bo);

        return newTransport;
    }

    /**
     * Returns existing {@link Transport} in {@code isi} with the specified {@code bo}. If such business
     * object does not exist, {@code null} will be returned.
     * 
     * @param isi the information system interface
     * @param bo the business object
     * @return the existing transport or {@code null}
     */
    private Transport getExistingTransport(InformationSystemInterface isi, BusinessObject bo) {
        Set<Transport> transports = isi.getTransports();
        for (Transport transport : transports) {
            if (bo.equals(transport.getBusinessObject())) {
                return transport;
            }
        }

        return null;
    }

    /**
     * Sets the direction of the specified transport {@code t}. If the direction is known, it
     * will be set. Otherwise the direction will be set as "no direction".
     * 
     * @param t the transport to set the direction for
     * @param direction the direction. The direction can be one of the following values: '-', '->', "<-" or '<->'
     */
    private void setBusinessObjectDirection(Transport t, String direction) {
        TransportInfo transportInfo = TransportInfo.getByTextRepresentation(direction);
        switch (transportInfo) {
        case NO_DIRECTION:
            t.setDirection(Direction.NO_DIRECTION);
            break;
        case FIRST_TO_SECOND:
            t.setDirection(Direction.FIRST_TO_SECOND);
            break;
        case SECOND_TO_FIRST:
            t.setDirection(Direction.SECOND_TO_FIRST);
            break;
        case BOTH_DIRECTIONS:
            t.setDirection(Direction.BOTH_DIRECTIONS);
            break;
        default:
            LOGGER.warn("Could not understand the direction of the business object, probably a programming error");
            break;
        }
    }

    void createBuildingBlockRelations(InfrastructureElement ie, Map<String, CellValueHolder> relations,
            Locale locale) {
        // Parent
        CellValueHolder hierarchyNameCellValueHolder = relations
                .get(getHierarchyHeaderFor(Constants.BB_INFRASTRUCTUREELEMENT_PLURAL, locale));
        setParentRelation(ie, hierarchyNameCellValueHolder);

        CellValueHolder tcrNamesCellValueHolder = relations
                .get(MessageAccess.getStringOrNull(Constants.BB_TECHNICALCOMPONENTRELEASE_PLURAL, locale));
        if (tcrNamesCellValueHolder != null) {
            Set<TechnicalComponentRelease> tcrSet = loadBuildingBlocksAsSet(
                    TypeOfBuildingBlock.TECHNICALCOMPONENTRELEASE, tcrNamesCellValueHolder);
            Set<Tcr2IeAssociation> associations = Sets.newHashSetWithExpectedSize(tcrSet.size());
            Set<Tcr2IeAssociation> existentAssociations = ie.getTechnicalComponentReleaseAssociations();
            for (TechnicalComponentRelease tcr : tcrSet) {
                Tcr2IeAssociation assoc = BuildingBlockFactory.createTcr2IeAssociation(tcr, ie);
                // try to find out whether that association exists already, and if yes, use the existing object
                for (Tcr2IeAssociation connectedAssoc : existentAssociations) {
                    assoc = connectedAssoc;
                    break;
                }
                buildingBlockServiceLocator.getTcr2IeAssociationService().saveAssociations(associations);
                associations.add(assoc);
            }
            ie.connectTcr2IeAssociations(associations);
        }

        CellValueHolder isrCellValueHolder = relations
                .get(MessageAccess.getStringOrNull(Constants.BB_INFORMATIONSYSTEMRELEASE_PLURAL, locale));
        if (isrCellValueHolder != null) {
            Set<InformationSystemRelease> isrs = loadBuildingBlocksAsSet(
                    TypeOfBuildingBlock.INFORMATIONSYSTEMRELEASE, isrCellValueHolder);
            ie.removeInformationSystemReleases();
            ie.addInformationSystemReleases(isrs);
        }

        getServiceFor(TypeOfBuildingBlock.INFRASTRUCTUREELEMENT).saveOrUpdate(ie);
    }

    void createBuildingBlockRelations(Product product, Map<String, CellValueHolder> relations, Locale locale) {
        // Parent
        CellValueHolder hierarchyNameCellValueHolder = relations
                .get(getHierarchyHeaderFor(Constants.BB_PRODUCT_PLURAL, locale));
        setParentRelation(product, hierarchyNameCellValueHolder);

        CellValueHolder bdCellValueHolder = relations
                .get(MessageAccess.getStringOrNull(Constants.BB_BUSINESSDOMAIN_PLURAL, locale));
        if (bdCellValueHolder != null) {
            Set<BusinessDomain> bds = loadBuildingBlocksAsSet(TypeOfBuildingBlock.BUSINESSDOMAIN,
                    bdCellValueHolder);
            product.removeBusinessDomainRelations();
            product.addBusinessDomains(bds);
        }

        TypeOfBuildingBlock[] orderOfBmContent = { TypeOfBuildingBlock.BUSINESSPROCESS,
                TypeOfBuildingBlock.BUSINESSUNIT, TypeOfBuildingBlock.INFORMATIONSYSTEMRELEASE };
        CellValueHolder allBMCellValueHolder = relations.get(getBusinessMappingHeaderKey(orderOfBmContent, locale));
        if (allBMCellValueHolder != null) {
            String allBMs = allBMCellValueHolder.getAttributeValue();
            String bmCellRef = ExcelImportUtilities.getCellRef(allBMCellValueHolder.getOriginCell());
            if (StringUtils.isNotEmpty(allBMs)) {
                getProcessingLog().warn(
                        "Product {0} has business mappings specified in cell [{1}]. These were not imported. Only Business Mappings listed in the Business Mappings sheet are imported.",
                        product.getNonHierarchicalName(), bmCellRef);
            }
        }

        getServiceFor(TypeOfBuildingBlock.PRODUCT).saveOrUpdate(product);
    }

    void createBuildingBlockRelations(Project project, Map<String, CellValueHolder> relations, Locale locale) {
        // Parent
        CellValueHolder hierarchyNameCellValueHolder = relations
                .get(getHierarchyHeaderFor(Constants.BB_PROJECT_PLURAL, locale));
        setParentRelation(project, hierarchyNameCellValueHolder);

        CellValueHolder isrCellValueHolder = relations
                .get(MessageAccess.getStringOrNull(Constants.BB_INFORMATIONSYSTEMRELEASE_PLURAL, locale));
        if (isrCellValueHolder != null) {
            Set<InformationSystemRelease> isrs = loadBuildingBlocksAsSet(
                    TypeOfBuildingBlock.INFORMATIONSYSTEMRELEASE, isrCellValueHolder);
            project.removeInformationSystemReleases();
            project.addInformationSystemReleases(isrs);
        }

        getServiceFor(TypeOfBuildingBlock.PROJECT).saveOrUpdate(project);
    }

    /**
     * Returns the Parent name by passing a hierarchical name, or null if not found.
     * 
     * @param nameHierarchy
     * @param nameNonHierarchy
     *          If passed (not null), checks that this is the name contained in the other parameter, returning null on mismatch.
     */
    String getParentNameFromHierarchicalName(String nameHierarchy, String nameNonHierarchy,
            String cellCoordinates) {
        if (nameHierarchy == null) {
            return null;
        }

        // If there is at least one :, there is a parent
        final String hierarchySeparator = Constants.HIERARCHYSEP.trim();

        if (nameHierarchy.contains(hierarchySeparator)) {
            // Only verify the name if this parameter was passed
            if (nameNonHierarchy != null) {
                String nameNonHierarchyFound = StringUtils.substringAfterLast(nameHierarchy, hierarchySeparator);
                nameNonHierarchyFound = StringUtils.strip(nameNonHierarchyFound);

                // Check if the Name part of the Hierarchy is the one we expected
                if (!nameNonHierarchyFound.equalsIgnoreCase(nameNonHierarchy)) {
                    getProcessingLog().warn(
                            "Hierarchical Name \"{0}\" in cell [{1}] does not contain the name of the BuildingBlock it belongs to ({2}). Not setting parent!",
                            nameHierarchy, cellCoordinates, nameNonHierarchy);
                    return null;
                }
            }
            String allBeforeName = StringUtils.substringBeforeLast(nameHierarchy, hierarchySeparator);
            allBeforeName = StringUtils.strip(allBeforeName);

            // Strip grandparents+ if necessary
            if (allBeforeName.contains(hierarchySeparator)) {
                return StringUtils.substringAfterLast(allBeforeName, Constants.HIERARCHYSEP).trim();
            }
            return allBeforeName;
        }
        return null;
    }

    void createBuildingBlockRelations(TechnicalComponentRelease tcr, Map<String, CellValueHolder> relations,
            Locale locale) {
        CellValueHolder ieNamesCellValueHolder = relations
                .get(MessageAccess.getStringOrNull(Constants.BB_INFRASTRUCTUREELEMENT_PLURAL, locale));
        if (ieNamesCellValueHolder != null) {
            Set<InfrastructureElement> ieSet = loadBuildingBlocksAsSet(TypeOfBuildingBlock.INFRASTRUCTUREELEMENT,
                    ieNamesCellValueHolder);
            Set<Tcr2IeAssociation> associations = Sets.newHashSetWithExpectedSize(ieSet.size());
            Set<Tcr2IeAssociation> existentAssociations = tcr.getInfrastructureElementAssociations();
            for (InfrastructureElement ie : ieSet) {
                Tcr2IeAssociation assoc = BuildingBlockFactory.createTcr2IeAssociation(tcr, ie);
                // try to find out whether that association exists already, and if yes, use the existing object
                for (Tcr2IeAssociation connectedAssoc : existentAssociations) {
                    assoc = connectedAssoc;
                    break;
                }
                buildingBlockServiceLocator.getTcr2IeAssociationService().saveAssociations(associations);
                associations.add(assoc);
            }
            tcr.connectTcr2IeAssociations(associations);
        }

        // Interfaces (not imported, just warn user)
        // Reason: We can't uniquely identify an Interface by its "name", since multiple ISIs with the same 2 ISRs can exist
        CellValueHolder interfacesCellValueHolder = relations.get(MessageAccess
                .getStringOrNull(ExcelConstants.HEADER_TECHNICALCOMPONENTRELEASE_SHEET_INTERFACES_COLUMN, locale));
        if (interfacesCellValueHolder != null) {
            String interfaces = interfacesCellValueHolder.getAttributeValue();
            String cellRef = ExcelImportUtilities.getCellRef(interfacesCellValueHolder.getOriginCell());
            if (!StringUtils.isEmpty(interfaces)) {
                getProcessingLog().warn(
                        "Technical Component {0} has interfaces specified in cell [{1}]. These were not imported. Only interfaces listed in the Interface sheet are imported.",
                        tcr.getNonHierarchicalName(), cellRef);
            }
        }

        CellValueHolder isrCellValueHolder = relations
                .get(MessageAccess.getStringOrNull(Constants.BB_INFORMATIONSYSTEMRELEASE_PLURAL, locale));
        if (isrCellValueHolder != null) {
            Set<InformationSystemRelease> isrs = loadBuildingBlocksAsSet(
                    TypeOfBuildingBlock.INFORMATIONSYSTEMRELEASE, isrCellValueHolder);
            tcr.removeInformationSystemReleases();
            tcr.addInformationSystemReleases(isrs);
        }

        // import ArchitecturalDomains
        CellValueHolder adCellValueHolder = relations
                .get(MessageAccess.getStringOrNull(Constants.BB_ARCHITECTURALDOMAIN_PLURAL, locale));
        if (adCellValueHolder != null) {
            Set<ArchitecturalDomain> ads = loadBuildingBlocksAsSet(TypeOfBuildingBlock.ARCHITECTURALDOMAIN,
                    adCellValueHolder);
            tcr.removeArchitecturalDomains();
            tcr.addArchitecturalDomains(ads);
        }

        CellValueHolder tcrNamesCellValueHolder = relations.get(MessageAccess.getStringOrNull(
                ExcelConstants.HEADER_TECHNICALCOMPONENTRELEASE_SHEET_BASECOMPONENTS_COLUMN, locale));
        if (tcrNamesCellValueHolder != null) {
            Set<TechnicalComponentRelease> usedTcrs = loadBuildingBlocksAsSet(
                    TypeOfBuildingBlock.TECHNICALCOMPONENTRELEASE, tcrNamesCellValueHolder);
            tcr.removeBaseComponents();
            tcr.addBaseComponents(usedTcrs);
        }

        CellValueHolder predecessorsCellValueHolder = relations
                .get(MessageAccess.getStringOrNull(Constants.ASSOC_PREDECESSORS, locale));
        if (predecessorsCellValueHolder != null) {
            Set<TechnicalComponentRelease> preds = loadBuildingBlocksAsSet(
                    TypeOfBuildingBlock.TECHNICALCOMPONENTRELEASE, predecessorsCellValueHolder);
            tcr.removePredecessors();
            tcr.addPredecessors(preds);
        }

        CellValueHolder successorsCellValueHolder = relations
                .get(MessageAccess.getStringOrNull(Constants.ASSOC_SUCCESSORS, locale));
        if (successorsCellValueHolder != null) {
            Set<TechnicalComponentRelease> succs = loadBuildingBlocksAsSet(
                    TypeOfBuildingBlock.TECHNICALCOMPONENTRELEASE, successorsCellValueHolder);
            tcr.removeSuccessors();
            tcr.addSuccessors(succs);
        }

        getTechnicalComponentReleaseService().saveOrUpdate(tcr);
    }

    /**
     * Retrieves the building block objects, described by a list of building block names, from the database. Building block names that cannot be found
     * in the DB will be silently ignored; only a log message is written.
     * 
     * @param <T> the element type of the resulting set. This MUST correspond to the enum type passed in as <code>tob</code>.
     * @param tob the Building Block type of the listed building blocks.
     * @param buildingBlockList a semicolon-separated list of building block names.
     * @param cellCoordinates Excel cell coordinates of the cell where the building block list was read from, for logging purposes
     * @return the corresponding building block objects, as many as could be found in the DB.
     */
    @SuppressWarnings("unchecked")
    private <T extends BuildingBlock> Set<T> loadBuildingBlocksAsSet(TypeOfBuildingBlock tob,
            String buildingBlockList, String cellCoordinates) {
        if (StringUtils.isBlank(buildingBlockList)) {
            return Collections.emptySet();
        }

        Set<String> allNames = Sets.newHashSet(
                ExcelImportUtilities.getSplittedArray(buildingBlockList, ExcelSheet.IN_LINE_SEPARATOR.trim()));
        final Set<String> nonHierarchicalNames = getNonHierarchicalNames(allNames);
        List<BuildingBlock> loadedBbList = getServiceFor(tob).findByNames(nonHierarchicalNames);
        Set<T> foundBuildingBlocks = Sets.newHashSet((Collection<? extends T>) loadedBbList);

        checkFoundResults(tob, foundBuildingBlocks, nonHierarchicalNames, cellCoordinates);
        return foundBuildingBlocks;
    }

    /**
     * Like {@link #loadBuildingBlocksAsSet(TypeOfBuildingBlock, String, String)}, but extracts the cell value internally
     * @param tob the Building Block type of the listed building blocks.
     * @param buildingBlockList cell value holder for the list of BB names
     * @return the corresponding building block objects, as many as could be found in the DB.
     */
    private <T extends BuildingBlock> Set<T> loadBuildingBlocksAsSet(TypeOfBuildingBlock tob,
            CellValueHolder buildingBlockList) {
        String cellText = buildingBlockList.getAttributeValue();
        String cellRef = ExcelImportUtilities.getCellRef(buildingBlockList.getOriginCell());
        return loadBuildingBlocksAsSet(tob, cellText, cellRef);
    }

    /**
     * Checks if all building blocks are found. For missing building blocks a warning will be written to
     * the processing log.
     * 
     * @param tob the type of building block
     * @param foundBuildingBlocks the found building blocks
     * @param nonHierarchicalNames the set of non-hierarchical building block names
     * @param cellCoordinates Excel cell coordinates of the cell where the building block list was read from, for logging purposes
     */
    private void checkFoundResults(TypeOfBuildingBlock tob, Set<? extends BuildingBlock> foundBuildingBlocks,
            final Set<String> nonHierarchicalNames, String cellCoordinates) {
        Set<String> allNamesLower = convertToLowerCase(nonHierarchicalNames);
        Set<String> buildingBlockNames = getBuildingBlockNames(foundBuildingBlocks);
        for (String searchName : allNamesLower) {
            if (!buildingBlockNames.contains(searchName)) {
                String bbType = MessageAccess.getString(tob.toString());
                getProcessingLog().warn("Cell [{0}]: Related building block {1} (type {2}) not found, ignoring",
                        cellCoordinates, searchName, bbType);
            }
        }
    }

    /**
     * Returns the non-hierarchical building block names.
     * 
     * @param buildingBlocks the building blocks to get the names for
     * @return the set containing lower-case non-hierarchical names
     */
    private Set<String> getBuildingBlockNames(Set<? extends BuildingBlock> buildingBlocks) {
        Set<String> names = Sets.newHashSet();
        for (BuildingBlock buildingBlock : buildingBlocks) {
            names.add(StringUtils.lowerCase(buildingBlock.getNonHierarchicalName()));
        }

        return names;
    }

    private Set<String> convertToLowerCase(Set<String> names) {
        Set<String> result = Sets.newHashSet();
        for (String string : names) {
            result.add(string.toLowerCase());
        }

        return result;
    }

    private Set<String> getNonHierarchicalNames(Set<String> names) {
        Set<String> nonHierarchicalNames = Sets.newHashSet();
        for (String name : names) {
            if (name.indexOf(Constants.HIERARCHYSEP) > -1) {
                String nameNonHierarchy = name
                        .substring(name.lastIndexOf(Constants.HIERARCHYSEP) + Constants.HIERARCHYSEP.length());
                nonHierarchicalNames.add(nameNonHierarchy);
            } else {
                nonHierarchicalNames.add(name);
            }
        }

        return nonHierarchicalNames;
    }

    /**
     * Returns the building block service instance that is responsible for the given {@link TypeOfBuildingBlock}.
     * 
     * @param tob
     *          One of the user-visible building block types. Internal types, such as business mappings, transports and attributable relation types are
     *          not supported; null will be returned.
     * @return a service instance that can deal with the requested building block type, or <code>null</code> if <code>tob</code> is not supported.
     */
    private BuildingBlockService<BuildingBlock, Integer> getServiceFor(TypeOfBuildingBlock tob) {
        return buildingBlockServiceLocator.getService(tob);
    }

    /**
     * Works just like getBuildingBlockByName, except that it will try to return the top-level element if the desired element can't be found. Might
     * still return null if that can't be found either.
     */
    private BuildingBlock getBuildingBlockByNameOrTop(TypeOfBuildingBlock type, String name,
            String cellCoordinates) {
        BuildingBlock result = getBuildingBlockByName(type, name, cellCoordinates);
        if (result == null) {
            String bbType = MessageAccess.getString(type.toString());
            getProcessingLog().warn(
                    "Cell [{0}]: Element of type {1} with name {2} not found. Using Virtual Element instead.",
                    cellCoordinates, bbType, name);
            return getBuildingBlockByName(type, AbstractHierarchicalEntity.TOP_LEVEL_NAME, "none");
        }

        return result;
    }

    /**
     * Get a BuildingBlock by its Non-Hierarchical Name. Returns null if a BuildingBlock with exactly this name can't be found. (Hierarchical names are
     * automatically converted to be Non-Hierarchical)
     * 
     * @param type
     * @param name
     *          Name of BB. For ISRs/TCRs, in format: "BaseName # Version"
     * @return a BB with exactly this name, else null
     */
    protected BuildingBlock getBuildingBlockByName(TypeOfBuildingBlock type, String name, String cellCoordinates) {
        if (name == null) {
            return null;
        }

        Set<BuildingBlock> foundBuildingBlocks = loadBuildingBlocksAsSet(type, name, cellCoordinates);
        return Iterables.getFirst(foundBuildingBlocks, null);
    }

    private Level getLogLevel() {
        if (FRONTEND_LOG.isDebugEnabled()) {
            return Level.DEBUG;
        }
        if (FRONTEND_LOG.isInfoEnabled()) {
            return Level.INFO;
        }
        if (FRONTEND_LOG.isWarnEnabled()) {
            return Level.WARN;
        }
        return Level.ERROR;
    }

    @SuppressWarnings("PMD.UnnecessaryLocalBeforeReturn")
    private InformationSystemInterfaceService getInformationSystemInterfaceService() {
        // the compiler does not allow to cast directly, so we have to make this intermediate step
        BuildingBlockService<?, ?> x = getServiceFor(TypeOfBuildingBlock.INFORMATIONSYSTEMINTERFACE);
        return (InformationSystemInterfaceService) x;
    }

    @SuppressWarnings("PMD.UnnecessaryLocalBeforeReturn")
    private InformationSystemReleaseService getInformationSystemReleaseService() {
        // the compiler does not allow to cast directly, so we have to make this intermediate step
        BuildingBlockService<?, ?> x = getServiceFor(TypeOfBuildingBlock.INFORMATIONSYSTEMRELEASE);
        return (InformationSystemReleaseService) x;
    }

    @SuppressWarnings("PMD.UnnecessaryLocalBeforeReturn")
    private TechnicalComponentReleaseService getTechnicalComponentReleaseService() {
        // the compiler does not allow to cast directly, so we have to make this intermediate step
        BuildingBlockService<?, ?> x = getServiceFor(TypeOfBuildingBlock.TECHNICALCOMPONENTRELEASE);
        return (TechnicalComponentReleaseService) x;
    }

    @SuppressWarnings("PMD.UnnecessaryLocalBeforeReturn")
    private BusinessMappingService getBusinessMappingService() {
        // the compiler does not allow to cast directly, so we have to make this intermediate step
        BuildingBlockService<?, ?> x = getServiceFor(TypeOfBuildingBlock.BUSINESSMAPPING);
        return (BusinessMappingService) x;
    }

    public void setAttributeValueService(AttributeValueService attributeValueService) {
        this.attributeValueService = attributeValueService;
    }

    public void setAttributeTypeService(AttributeTypeService attributeTypeService) {
        this.attributeTypeService = attributeTypeService;
    }

    public void setUserService(UserService userService) {
        this.userService = userService;
    }

    public void setBuildingBlockServiceLocator(BuildingBlockServiceLocator buildingBlockServiceLocator) {
        this.buildingBlockServiceLocator = buildingBlockServiceLocator;
    }

}