de.iteratec.iteraplan.businesslogic.exchange.visio.informationflow.VisioInformationFlowExport.java Source code

Java tutorial

Introduction

Here is the source code for de.iteratec.iteraplan.businesslogic.exchange.visio.informationflow.VisioInformationFlowExport.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.exchange.visio.informationflow;

import java.awt.Color;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
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.Pattern;

import javax.xml.parsers.ParserConfigurationException;

import net.sourceforge.gxl.GXLDocument;
import net.sourceforge.gxl.GXLEdge;
import net.sourceforge.gxl.GXLGraph;
import net.sourceforge.gxl.GXLGraphElement;
import net.sourceforge.gxl.GXLNode;
import net.sourceforge.gxl.GXLString;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.xml.sax.SAXException;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

import de.iteratec.graph.layout.CenterPositionLayout;
import de.iteratec.graph.layout.LayoutOperation;
import de.iteratec.graph.layout.RadialComponentLayout;
import de.iteratec.graph.layout.SpringForceLayout;
import de.iteratec.gxl.GXLUtil;
import de.iteratec.gxl2visio.GxlToVisioConverter;
import de.iteratec.gxl2visio.exceptions.GraphStructureException;
import de.iteratec.iteraplan.businesslogic.exchange.common.Coordinates;
import de.iteratec.iteraplan.businesslogic.exchange.common.dimension.LineDimension;
import de.iteratec.iteraplan.businesslogic.exchange.common.informationflow.InformationFlowGeneralHelper;
import de.iteratec.iteraplan.businesslogic.exchange.common.informationflow.VisioInformationFlowTemplateParser;
import de.iteratec.iteraplan.businesslogic.exchange.visio.VisioDimensionExport;
import de.iteratec.iteraplan.businesslogic.exchange.visio.legend.VisioAttributeLegend;
import de.iteratec.iteraplan.businesslogic.exchange.visio.legend.VisioNamesLegend;
import de.iteratec.iteraplan.businesslogic.reports.query.options.GraphicalReporting.GraphicalExportBaseOptions;
import de.iteratec.iteraplan.businesslogic.reports.query.options.GraphicalReporting.Dimension.ColorDimensionOptionsBean;
import de.iteratec.iteraplan.businesslogic.reports.query.options.GraphicalReporting.Dimension.LineDimensionOptionsBean;
import de.iteratec.iteraplan.businesslogic.reports.query.options.GraphicalReporting.InformationFlow.IInformationFlowOptions;
import de.iteratec.iteraplan.businesslogic.reports.query.options.GraphicalReporting.InformationFlow.InformationFlowOptionsBean;
import de.iteratec.iteraplan.businesslogic.service.AttributeTypeService;
import de.iteratec.iteraplan.businesslogic.service.AttributeValueService;
import de.iteratec.iteraplan.businesslogic.service.InformationSystemReleaseService;
import de.iteratec.iteraplan.businesslogic.service.VisioExportServiceImpl;
import de.iteratec.iteraplan.common.Constants;
import de.iteratec.iteraplan.common.GeneralHelper;
import de.iteratec.iteraplan.common.Logger;
import de.iteratec.iteraplan.common.MessageAccess;
import de.iteratec.iteraplan.common.UserContext;
import de.iteratec.iteraplan.common.error.IteraplanErrorMessages;
import de.iteratec.iteraplan.common.error.IteraplanTechnicalException;
import de.iteratec.iteraplan.common.util.DateUtils;
import de.iteratec.iteraplan.common.util.InchConverter;
import de.iteratec.iteraplan.common.util.StringUtil;
import de.iteratec.iteraplan.model.BusinessObject;
import de.iteratec.iteraplan.model.Direction;
import de.iteratec.iteraplan.model.InformationSystemDomain;
import de.iteratec.iteraplan.model.InformationSystemInterface;
import de.iteratec.iteraplan.model.InformationSystemRelease;
import de.iteratec.iteraplan.model.InformationSystemRelease.TypeOfStatus;
import de.iteratec.iteraplan.model.Isr2BoAssociation;
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.user.TypeOfFunctionalPermission;
import de.iteratec.visio.model.Document;
import de.iteratec.visio.model.Page;
import de.iteratec.visio.model.Shape;
import de.iteratec.visio.model.exceptions.MasterNotFoundException;
import de.iteratec.visio.model.exceptions.NoSuchElementException;

/**
 * Contains the builder algorithm for the creation of a Visio information flow diagram. This class
 * generates a GXL graph which is then converted to a {@link Document} using the gxl2visio library.
 */
@SuppressWarnings("PMD.ExcessiveImports")
public class VisioInformationFlowExport extends VisioDimensionExport {

    private static final String BO_CATEGORY_NAME = MessageAccess
            .getStringOrNull(Constants.BB_BUSINESSOBJECT_PLURAL);
    private static final String IS_CATEGORY_NAME = MessageAccess
            .getStringOrNull(Constants.BB_INFORMATIONSYSTEMRELEASE_PLURAL);

    private static final String GXL_SCHEMA_URI = "http://www.iteratec.de/iteraplan/1/nofile#";

    public static final String PROP_ID = "Id";
    public static final String PROP_APP_VERSION = "ApplicationVersion";
    public static final String PROP_APP_NAME = "ApplicationName";
    public static final String PROP_APP_NAME_FULL = "ApplicationNameFull";
    public static final String PROP_APP_INFORMATION_OBJECTS = "InformationObjects";
    public static final String PROP_APP_INFORMATION_OBJECTS_FULL = "InformationObjectsFull";
    public static final String PROP_STATUS = "PlanningState";
    public static final String PROP_DESCRIPTION = "Description";
    public static final String PROP_START_DATE = "StartDate";
    public static final String PROP_END_DATE = "EndDate";
    public static final String PROP_COLOR_RED = "Red";
    public static final String PROP_COLOR_GREEN = "Green";
    public static final String PROP_COLOR_BLUE = "Blue";
    public static final String PROP_APP_BASE_COMPONENTS = "BaseComponents";
    public static final String PROP_APP_INS_DOMAIN = "ApplicationDomain";
    public static final String PROP_ATTRIBUTES = "ApplicationAttributes";

    public static final String PROP_LINE_TYPE = "LineStyle";
    public static final String PROP_FLOW_INFORMATION_OBJECTS = "InformationObjects";

    private static final String VISIO_SHAPE_NAME_APPLICATION_CP_S_BO_BC = "Application-cp-s-Bo-Bc";
    private static final String VISIO_SHAPE_NAME_APPLICATION_CP_S_BO = "Application-cp-s-Bo";
    private static final String VISIO_SHAPE_NAME_APPLICATION_CP_S_BC = "Application-cp-s-Bc";
    private static final String VISIO_SHAPE_NAME_APPLICATION_CP_S_SMALL = "Application-cp-s-Small";
    private static final String VISIO_SHAPE_NAME_APPLICATION_CP_M_BO_BC = "Application-cp-m-Bo-Bc";
    private static final String VISIO_SHAPE_NAME_APPLICATION_CP_M_BO = "Application-cp-m-Bo";
    private static final String VISIO_SHAPE_NAME_APPLICATION_CP_M_BC = "Application-cp-m-Bc";
    private static final String VISIO_SHAPE_NAME_APPLICATION_CP_M_SMALL = "Application-cp-m-Small";
    private static final String VISIO_SHAPE_NAME_APPLICATION_CP_L_BO_BC = "Application-cp-l-Bo-Bc";
    private static final String VISIO_SHAPE_NAME_APPLICATION_CP_L_BO = "Application-cp-l-Bo";
    private static final String VISIO_SHAPE_NAME_APPLICATION_CP_L_BC = "Application-cp-l-Bc";
    private static final String VISIO_SHAPE_NAME_APPLICATION_CP_L_SMALL = "Application-cp-l-Small";
    private static final String VISIO_SHAPE_NAME_COLOR_INDEX_SQUARE = "Color-Index-Square";
    private static final String VISIO_SHAPE_NAME_FLOW = "InformationFlow";
    private static final String VISIO_SHAPE_NAME_FLOW_BIDIRECTIONAL = "InformationFlowBidirectional";
    private static final String VISIO_SHAPE_NAME_FLOW_NODIRECTION = "InformationFlowNoDirection";
    private static final String VISIO_SHAPE_NAME_DIMCONTENT = "Legend-Dimension-Content";
    private static final String VISIO_SHAPE_NAME_DIMHEADER = "Legend-Dimension-Header";
    private static final String VISIO_SHAPE_NAME_ATTRHEADER = "Legend-Attribute-Header";
    private static final String VISIO_SHAPE_NAME_ATTRCONTENT = "Legend-Attribute-Content";

    private static final String VISIO_TEMPLATE_FILE = "/VisioInformationFlowTemplate.vdx";
    private static final String VISIO_DIAGRAM_TITLE = "iteraplan information flow diagram";

    private static final double LEGEND_DESCRIPTION_WIDTH_CM = 14.5;
    private static final double LEGEND_STANDARD_CELL_WIDTH_CM = 6.5;

    private static final double MARGIN_IN = InchConverter.cmToInches(1.5);

    private static final double APPLICATION_WIDTH = InchConverter.cmToInches(2);
    private static final double APPLICATION_PADDING_SIDE = InchConverter.cmToInches(0.4);

    private static final double APPLICATION_CP_S_LIMIT = 25;
    private static final double APPLICATION_CP_M_LIMIT = 60;

    private static final Pattern IS_ONLY_WHITESPACE_PATTERN = Pattern.compile("\\s*");

    private final InformationSystemReleaseService isrService;

    private GxlToVisioConverter visioDocumentCreator;
    private GXLGraph graph;

    private final Map<String, NodeWithProperties> isIdToGxlNode;

    private final List<InformationSystemRelease> isReleases;
    private final List<Integer> isReleaseIds;
    private final Set<InformationSystemInterface> isInterfaces;
    private final Map<String, BusinessObject> businessObjects;

    private LineDimension lineDimension;
    private int[] lineCaptionSelected;
    private Integer lineCaptionAttributeId;

    private static final Logger LOGGER = Logger.getIteraplanLogger(VisioInformationFlowExport.class);

    private static final boolean IS_LEFT_END_SEARCHED_BB = false;

    private final IInformationFlowOptions informationFlowOptions;

    /**
     * Constructor.
     * @param isReleases List of IS release.
     * @param isInterfaces a set of interfaces, which should be shown on the diagram (as edges). If this is null, all interfaces will be shown.
     * @param currentLocale The current Locale.
     */
    public VisioInformationFlowExport(List<InformationSystemRelease> isReleases,
            Set<InformationSystemInterface> isInterfaces, Map<String, BusinessObject> businessObjects,
            Locale currentLocale, IInformationFlowOptions informationFlowOptions,
            AttributeTypeService attributeTypeService, AttributeValueService attributeValueService,
            InformationSystemReleaseService isrService) {

        super(attributeTypeService, attributeValueService);
        this.isrService = isrService;
        // copy List of IS releases:
        this.isReleases = Lists.newArrayList();
        this.isReleaseIds = Lists.newArrayList();
        this.isIdToGxlNode = Maps.newHashMap();
        for (InformationSystemRelease isRelease : isReleases) {
            this.isReleases.add(isRelease);
            this.isReleaseIds.add(isRelease.getId());
        }
        this.isInterfaces = isInterfaces;
        setLocale(currentLocale);
        this.informationFlowOptions = informationFlowOptions;
        this.businessObjects = businessObjects;
    }

    private void init() {
        LOGGER.debug("entering init...");

        InputStream visioTemplateStream = null;
        try {
            // initialize VisioDocumentCreator
            visioTemplateStream = VisioExportServiceImpl.class.getResourceAsStream(VISIO_TEMPLATE_FILE);
            visioDocumentCreator = new GxlToVisioConverter(visioTemplateStream, VISIO_DIAGRAM_TITLE);

            // initialize GraphDocument and add GraphType:
            graph = new GXLGraph("informationFlow");
            GXLDocument graphDocument = new GXLDocument();
            graphDocument.getDocumentElement().add(graph);

            this.setTargetPage(visioDocumentCreator.getDocument().getPage(0));

        } catch (IOException iex) {
            throw new IteraplanTechnicalException(IteraplanErrorMessages.INTERNAL_ERROR, iex);
        } catch (NoSuchElementException nsex) {
            throw new IteraplanTechnicalException(IteraplanErrorMessages.INTERNAL_ERROR, nsex);
        } catch (ParserConfigurationException pce) {
            throw new IteraplanTechnicalException(IteraplanErrorMessages.INTERNAL_ERROR, pce);
        } catch (SAXException saxe) {
            throw new IteraplanTechnicalException(IteraplanErrorMessages.INTERNAL_ERROR, saxe);
        } finally {
            if (visioTemplateStream != null) {
                try {
                    visioTemplateStream.close();
                } catch (IOException e) {
                    LOGGER.error("Failed to close the visio template stream.");
                }
            }
        }

        LOGGER.debug("leaving init...");
    }

    @Override
    public Document createDiagram() {

        init();

        if (informationFlowOptions.isUseNamesLegend()) {
            setVisioNamesLegend(new VisioNamesLegend(this.getTargetPage()));
        }

        setColorDimension(createColorDimension(informationFlowOptions.getColorOptionsBean(),
                TypeOfBuildingBlock.INFORMATIONSYSTEMRELEASE));

        lineDimension = createLineDimension(informationFlowOptions.getLineOptionsBean(),
                TypeOfBuildingBlock.INFORMATIONSYSTEMRELEASE);

        lineCaptionSelected = informationFlowOptions.getSelectionType();
        lineCaptionAttributeId = informationFlowOptions.getLineCaptionSelectedAttributeId();

        addMissingParentNodes();
        addResultNodes();
        groupNodes();

        for (GXLNode node : GXLUtil.getNodes(graph)) {
            setIsrNodeTexts(node);
        }

        addEdges();

        try {
            LOGGER.debug("trying to add graph to gxl2visio converter");

            List<LayoutOperation> layoutOperations = determineLayoutOperations();

            Rectangle2D graphAreaBounds = visioDocumentCreator.addGraph(graph, layoutOperations);
            // correct the position of the bounding box, since it's returned incorrectly by the "addGraph"-method
            graphAreaBounds.setRect(0, 0, graphAreaBounds.getWidth(), graphAreaBounds.getHeight());

            Shape title = createDiagramTitle(
                    MessageAccess.getStringOrNull("graphicalExport.informationflow.title", getLocale()));
            List<Shape> queryInfo = createQueryInfo(graphAreaBounds);

            Rectangle2D legendsBox = createLegends(graphAreaBounds);

            setTitlePos(graphAreaBounds, title, queryInfo);
            setQueryInfoPos(queryInfo, title.getPinX(), title.getPinY() - getQueryInfoHeight(queryInfo));

            Point2D adjustment = adjustPage(graphAreaBounds, title, queryInfo, legendsBox);
            legendsBox = new Rectangle2D.Double(legendsBox.getX() + adjustment.getX(),
                    legendsBox.getY() + adjustment.getY(), legendsBox.getWidth(), legendsBox.getHeight());

            createGeneratedInformation(this.getTargetPage().getWidth());
            createLogos(0, 0, this.getTargetPage().getWidth(), this.getTargetPage().getHeight());

            if (informationFlowOptions.isUseNamesLegend()) {
                createNamesLegend(legendsBox);
            }

        } catch (GraphStructureException gex) {
            throw new IteraplanTechnicalException(IteraplanErrorMessages.INTERNAL_ERROR, gex);
        } catch (MasterNotFoundException e) {
            throw new IteraplanTechnicalException(IteraplanErrorMessages.INTERNAL_ERROR, e);
        }
        return visioDocumentCreator.getDocument();
    }

    private void createNamesLegend(Rectangle2D legendsBox) throws MasterNotFoundException {
        double frameX = legendsBox.getX();
        double frameY = legendsBox.getY() - getTargetPage().getHeight() + MARGIN_IN;
        double frameWidth = legendsBox.getWidth();
        double frameHeight = getTargetPage().getHeight() - legendsBox.getHeight() - DISTANCE_TO_MARGIN_INCHES;
        createNamesLegend(frameX, frameY, frameWidth, frameHeight, informationFlowOptions.isNakedExport(),
                MessageAccess.getStringOrNull("graphicalExport.informationflow.title", getLocale()));
    }

    private void setTitlePos(Rectangle2D graphAreaBounds, Shape title, List<Shape> queryInfo) {
        double titleTopY = graphAreaBounds.getY() + graphAreaBounds.getHeight() + DISTANCE_TO_MARGIN_INCHES * 2.8
                + title.getHeight() + getQueryInfoHeight(queryInfo);
        setTitlePosAndSize(title, graphAreaBounds.getX(), titleTopY, null);
    }

    /**
     * Adjusts the page size to the containing shapes, with a margin, and also moves all shapes on the page
     * so their positions are actually within the page bounds. 
     */
    private Point2D adjustPage(Rectangle2D graphAreaBounds, Shape title, List<Shape> queryInfo,
            Rectangle2D legendsBox) {
        double pageWidth = graphAreaBounds.getWidth() + 3 * MARGIN_IN + legendsBox.getWidth();
        double graphAreaPlusTitleHeight = graphAreaBounds.getHeight() + title.getHeight()
                + getQueryInfoHeight(queryInfo) + MARGIN_IN;
        double pageHeight = Math.max(graphAreaPlusTitleHeight, legendsBox.getHeight()) + 2 * MARGIN_IN;
        getTargetPage().setSize(pageWidth, pageHeight);

        double deltaY = pageHeight - graphAreaPlusTitleHeight - MARGIN_IN;
        for (Shape shape : getTargetPage().getShapes()) {
            shape.setPosition(shape.getPinX() + MARGIN_IN, shape.getPinY() + deltaY);
        }

        return new Point2D.Double(MARGIN_IN, deltaY);
    }

    private Rectangle2D createLegends(Rectangle2D bounds) throws MasterNotFoundException {
        Page page = getTargetPage();
        double legendsBlockLeftX = bounds.getWidth() + MARGIN_IN;
        double legendsBlockTopY = bounds.getY() + bounds.getHeight() + MARGIN_IN;

        Coordinates position = new Coordinates(legendsBlockLeftX, legendsBlockTopY);

        Shape descriptionLegend = createDescriptionLegend(informationFlowOptions, page, position);
        double legendsBlockWidth = descriptionLegend.getWidth();
        double legendsBlockHeight = descriptionLegend.getHeight();

        position.incX(InchConverter.cmToInches(LEGEND_DESCRIPTION_WIDTH_CM - LEGEND_STANDARD_CELL_WIDTH_CM));
        position.incY(-(MARGIN_IN + descriptionLegend.getHeight()));
        legendsBlockHeight += MARGIN_IN;

        ColorDimensionOptionsBean colorsBean = informationFlowOptions.getColorOptionsBean();
        if (GraphicalExportBaseOptions.NOTHING_SELECTED != colorsBean.getDimensionAttributeId().intValue()) {
            double colorLegendHeight = createColorLegend(colorsBean, page, position,
                    VISIO_SHAPE_NAME_COLOR_INDEX_SQUARE, getFieldValueFromDimension(getColorDimension()),
                    TypeOfBuildingBlock.INFORMATIONSYSTEMRELEASE);
            position.incY(-(MARGIN_IN + colorLegendHeight));
            legendsBlockHeight += colorLegendHeight + MARGIN_IN;
        }

        LineDimensionOptionsBean lineTypeBean = informationFlowOptions.getLineOptionsBean();
        if (GraphicalExportBaseOptions.NOTHING_SELECTED != lineTypeBean.getDimensionAttributeId().intValue()) {
            legendsBlockHeight += createLineTypeLegend(lineTypeBean, page, position, VISIO_SHAPE_NAME_LINE_FIELD,
                    getFieldValueFromDimension(lineDimension), TypeOfBuildingBlock.INFORMATIONSYSTEMRELEASE);
        }
        return new Rectangle2D.Double(legendsBlockLeftX, legendsBlockTopY, legendsBlockWidth, legendsBlockHeight);
    }

    private Shape createDescriptionLegend(IInformationFlowOptions infoFlowOptions, Page page,
            final Coordinates position) throws MasterNotFoundException {

        String descriptionTypeName = InformationFlowGeneralHelper.getDescriptionTypeName(getAttributeTypeService(),
                lineCaptionSelected, lineCaptionAttributeId, informationFlowOptions, getLocale());

        // Create container
        Shape visioLegendContainter = page
                .createNewShape(VisioAttributeLegend.VISIO_SHAPE_NAME_LEGEND_GROUP_CONTAINER);

        double legendHeight;
        double legendWidth;
        int rowCount = 4;

        // create dimension column
        // ***********************
        Shape shape = visioLegendContainter.createNewInnerShape(VISIO_SHAPE_NAME_DIMHEADER);

        shape.setFieldValue(MessageAccess.getStringOrNull("graphicalReport.headline", getLocale()));

        legendHeight = rowCount * shape.getHeight();

        double pinYHead = legendHeight - shape.getHeight();
        double pinXHead = 0;
        double heightHead = shape.getHeight();

        shape.setPosition(pinXHead, pinYHead);

        shape = visioLegendContainter.createNewInnerShape(VISIO_SHAPE_NAME_DIMCONTENT);
        shape.setFieldValue(MessageAccess.getStringOrNull("reports.color", getLocale()));

        shape.setPosition(pinXHead, pinYHead - heightHead);

        double widthFirstRow = shape.getWidth();
        double pinY = shape.getPinY();
        double height = shape.getHeight();
        legendWidth = widthFirstRow;

        shape = visioLegendContainter.createNewInnerShape(VISIO_SHAPE_NAME_DIMCONTENT);
        shape.setFieldValue(MessageAccess.getStringOrNull("reports.lineType", getLocale()));
        shape.setPosition(pinXHead, pinY - height);

        shape = visioLegendContainter.createNewInnerShape(VISIO_SHAPE_NAME_DIMCONTENT);
        shape.setFieldValue(MessageAccess.getStringOrNull("reports.lineCaption", getLocale()));
        shape.setPosition(pinXHead, pinY - 2 * height);

        // create attribute column
        // ***********************
        shape = visioLegendContainter.createNewInnerShape(VISIO_SHAPE_NAME_ATTRHEADER);
        shape.setFieldValue(InformationFlowGeneralHelper.headerAttributeOrBuilding(getLocale()));

        double pinXWithOffset = pinXHead + widthFirstRow;

        shape.setPosition(pinXWithOffset, pinYHead);
        double pinY2 = shape.getPinY();
        double height2 = shape.getHeight();

        shape = visioLegendContainter.createNewInnerShape(VISIO_SHAPE_NAME_ATTRCONTENT);

        String colorAttributeName = InformationFlowGeneralHelper
                .replaceBlank(getFieldValueFromDimension(createColorDimension(infoFlowOptions.getColorOptionsBean(),
                        TypeOfBuildingBlock.INFORMATIONSYSTEMRELEASE)));
        shape.setFieldValue(colorAttributeName);
        shape.setPosition(pinXWithOffset, pinY2 - height2);

        shape = visioLegendContainter.createNewInnerShape(VISIO_SHAPE_NAME_ATTRCONTENT);
        String lineAttributeName = InformationFlowGeneralHelper
                .replaceBlank(getFieldValueFromDimension(createLineDimension(infoFlowOptions.getLineOptionsBean(),
                        TypeOfBuildingBlock.INFORMATIONSYSTEMRELEASE)));
        shape.setFieldValue(lineAttributeName);
        shape.setPosition(pinXWithOffset, pinY2 - 2 * height2);

        shape = visioLegendContainter.createNewInnerShape(VISIO_SHAPE_NAME_ATTRCONTENT);
        shape.setFieldValue(descriptionTypeName);
        shape.setPosition(pinXWithOffset, pinY2 - 3 * height2);

        legendWidth = legendWidth + shape.getWidth();

        visioLegendContainter.setSize(legendWidth, legendHeight);
        visioLegendContainter.setPosition(position.getX(), position.getY() - legendHeight);

        return visioLegendContainter;
    }

    private List<LayoutOperation> determineLayoutOperations() {
        List<LayoutOperation> layoutOps = Lists.newArrayList();

        if (informationFlowOptions.getSelectedTemplateFile() != null) {
            VisioInformationFlowTemplateParser parser = new VisioInformationFlowTemplateParser(
                    informationFlowOptions.getSelectedTemplateFile());
            parser.parseTemplate();
            layoutOps.add(new CopyFromTemplateLayout(parser.getNodeMap()));
            layoutOps.add(new CenterPositionLayout());
        } else {
            layoutOps.add(new RadialComponentLayout());
        }

        layoutOps.add(new SpringForceLayout(SpringForceLayout.DEFAULT_DIN_A_CONFIG));
        return layoutOps;
    }

    private List<Shape> createQueryInfo(Rectangle2D bounds) throws MasterNotFoundException {
        List<Shape> queryInfo = null;
        if (informationFlowOptions.isShowSavedQueryInfo()) {
            Coordinates pos = new Coordinates(0, 0);
            double widthInCm = InchConverter.inchesToCm(bounds.getWidth());
            queryInfo = createSavedQueryInfo(pos, widthInCm, 10, informationFlowOptions.getSavedQueryInfo(),
                    informationFlowOptions.getServerUrl());
        }
        return queryInfo;
    }

    /**
     * Adds necessary nodes to the graph for all parent IS releases that have a child IS release in
     * the List of IS release Ids, but are not contained in the List itself.
     */
    public void addMissingParentNodes() {
        LOGGER.debug("entering addMissingParentNodes...");

        List<Integer> missingIsReleaseIds = Lists.newArrayList();

        // gather Ids of IS release parent elements not contained in the List of
        // IS release Ids:
        for (InformationSystemRelease release : isReleases) {

            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("adding parent nodes for IS release: " + release.getNonHierarchicalName());
            }

            InformationSystemRelease parent = release.getParent();

            while (parent != null) {

                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("current parent: " + parent.getNonHierarchicalName());
                }

                Integer parentId = parent.getId();

                if (!isReleaseIds.contains(parentId) && !missingIsReleaseIds.contains(parentId)) {
                    missingIsReleaseIds.add(parentId);
                }
                parent = parent.getParent();
            }
        }

        LOGGER.debug("finished iterating through parents...");

        for (Integer id : missingIsReleaseIds) {
            InformationSystemRelease isRelease = isrService.loadObjectById(id);
            addIsrNode(isRelease);
        }

        LOGGER.debug("leaving addMissingParentNodes...");
    }

    /**
     * Adds all necessary result nodes to the graph for the List of IS releases.
     */
    public void addResultNodes() {
        LOGGER.debug("entering addResultNodes...");

        for (InformationSystemRelease isRelease : isReleases) {
            addIsrNode(isRelease);
        }

        LOGGER.debug("leaving addResultNodes...");
    }

    /**
     * Adds a given InformationSystemRelease to the resulting graph.
     * @param isRelease The InformationSystemRelease to add.
     */
    private void addIsrNode(InformationSystemRelease isRelease) {
        // Get the list of child business objects/isr2BoAssociations  filtered by a given Map of BusinessObjects 
        Set<Isr2BoAssociation> isr2BoAssociationSet = GeneralHelper
                .filterAbstractAssociationsByBusinessObjects(isRelease, businessObjects, IS_LEFT_END_SEARCHED_BB);
        // Get the list of child base components
        Set<InformationSystemRelease> bcSet = isRelease.getBaseComponents();

        String nodeId = String.valueOf(isRelease.getId());
        GXLNode node = new GXLNode(nodeId);

        // distinguish between IS releases with and without child business objects and base components
        // and create the graph node (shape) accordingly. We set the standard node type here. Later on
        // the node type might get changed (see method adjustNodeMasterShapes()) if the number of
        // interfaces exceeds certain limits.
        if ((!informationFlowOptions.isShowIsBusinessObjects() || CollectionUtils.isEmpty(isr2BoAssociationSet))
                && (!informationFlowOptions.isShowIsBaseComponents() || CollectionUtils.isEmpty(bcSet))) {
            // neither business objects nor base components
            node.setType(createGxlTypeURI(VISIO_SHAPE_NAME_APPLICATION_CP_S_SMALL));
        } else if (!informationFlowOptions.isShowIsBusinessObjects()
                || CollectionUtils.isEmpty(isr2BoAssociationSet)) {
            // no business objects (only base components)
            node.setType(createGxlTypeURI(VISIO_SHAPE_NAME_APPLICATION_CP_S_BC));
        } else if (!informationFlowOptions.isShowIsBaseComponents() || CollectionUtils.isEmpty(bcSet)) {
            // no base components (only business objects)
            node.setType(createGxlTypeURI(VISIO_SHAPE_NAME_APPLICATION_CP_S_BO));
        } else {
            // both
            node.setType(createGxlTypeURI(VISIO_SHAPE_NAME_APPLICATION_CP_S_BO_BC));
        }

        // The attributes of the node which are relevant for the name legend are set later on, when the
        // width of the shape can be estimated.
        String isName = isRelease.getInformationSystem().getName();
        if (!isOnlyWhitespace(isRelease.getVersion())) {
            isName += " # " + isRelease.getVersion();
        }

        NodeWithProperties nodeWrapper = new NodeWithProperties(node, StringUtil.encodeVisioXml(isName),
                GeneralHelper.makeConcatenatedNameStringForAssociationCollection(isr2BoAssociationSet, false, true,
                        false),
                GeneralHelper.makeConcatenatedNameStringForBbCollection(isRelease.getBaseComponents()));

        isIdToGxlNode.put(nodeId, nodeWrapper);

        addShapePropertiesToInsNode(isRelease, node, isName);

        graph.add(node);
    }

    /**
     * Escapes the passed text to make it appropriate for Visio, and sets it as a String attribute to the node or edge.
     */
    private void setTextPropertyToNode(GXLGraphElement nodeOrEdge, String property, String text) {
        String escapedText = StringUtil.encodeVisioXml(text);
        nodeOrEdge.setAttr(property, new GXLString(escapedText));
    }

    /**
     * Adds a defined set of custom properties to the GLXNode.
     * These properties will be exported to the Visio shapes as custom properties.
     * 
     * @param isRelease An InformationSystemRelease containing the attributes to add to the GLXNode.
     * @param node The corresponding graph node for the isRelease.
     * @param isName The formated name of the isRelease.
     */
    private void addShapePropertiesToInsNode(InformationSystemRelease isRelease, GXLNode node, String isName) {

        setTextPropertyToNode(node, PROP_ID, isRelease.getId().toString());
        setTextPropertyToNode(node, PROP_APP_NAME_FULL, isName);
        setTextPropertyToNode(node, PROP_APP_INFORMATION_OBJECTS_FULL,
                GeneralHelper.makeConcatenatedNameStringForBbCollection(isRelease.getBusinessObjects()));
        String urlToEntity = this.retrieveXLinkUrlForIdentityEntity(isRelease,
                this.informationFlowOptions.getServerUrl());
        setTextPropertyToNode(node, PROP_XREF_URL, urlToEntity);

        // create a string containing all INS Domains, separated by a semicolon
        StringBuilder isdAttribute = new StringBuilder();
        // check if the user has the permission to read the INS Domains:
        if (UserContext.getCurrentPerms()
                .userHasFunctionalPermission(TypeOfFunctionalPermission.INFORMATIONSYSTEMDOMAIN)) {
            for (InformationSystemDomain isd : isRelease.getInformationSystemDomains()) {
                isdAttribute.append(isd.getName() + "(" + isd.getId() + ");");
            }
        }
        setTextPropertyToNode(node, PROP_APP_INS_DOMAIN, isdAttribute.toString());

        // create a string containing all assigned attributes, separated by a semicolon
        StringBuilder attributesString = new StringBuilder();
        Set<AttributeValueAssignment> avas = isRelease.getAttributeValueAssignments();
        for (AttributeValueAssignment ava : avas) {
            AttributeValue av = ava.getAttributeValue();
            AttributeType at = av.getAbstractAttributeType();
            AttributeTypeGroup atg = at.getAttributeTypeGroup();
            // check if the user has the permission to read the attributeGroup:
            if (UserContext.getCurrentPerms().userHasAttrTypeGroupPermission(atg,
                    AttributeTypeGroupPermissionEnum.READ)) {
                attributesString.append("(" + at.getName() + ")=" + av.getLocalizedValueString(getLocale()) + ";");
            }
        }
        setTextPropertyToNode(node, PROP_ATTRIBUTES, attributesString.toString());

        setTextPropertyToNode(node, PROP_APP_VERSION, isRelease.getVersion());
        setTextPropertyToNode(node, PROP_STATUS, mapTypeOfStatusVisioExport(isRelease.getTypeOfStatus()));
        setTextPropertyToNode(node, PROP_DESCRIPTION, isRelease.getDescription());
        setTextPropertyToNode(node, PROP_START_DATE,
                DateUtils.formatAsStringToDefault(isRelease.runtimeStartsAt(), getLocale()));
        setTextPropertyToNode(node, PROP_END_DATE,
                DateUtils.formatAsStringToDefault(isRelease.runtimeEndsAt(), getLocale()));

        Color color = getColorDimension().getValue(isRelease);
        LOGGER.debug("Color for node: {0}", color);

        setTextPropertyToNode(node, PROP_COLOR_RED, String.valueOf(color.getRed()));
        setTextPropertyToNode(node, PROP_COLOR_GREEN, String.valueOf(color.getGreen()));
        setTextPropertyToNode(node, PROP_COLOR_BLUE, String.valueOf(color.getBlue()));
    }

    /**
     * Maps the Typeofstatus to Visio export Strings
     *
     * @param typeOfStatus
     *          The TypeofStatus to map.
     * @return The Visio String representation.
     */
    private String mapTypeOfStatusVisioExport(TypeOfStatus typeOfStatus) {
        String status = typeOfStatus.toString();
        int dotIndex = status.indexOf('_');
        return status.substring(dotIndex + 1);
    }

    /**
     * Adds all edges to the graph.
     */
    public void addEdges() {
        LOGGER.debug("entering addEdges()");

        List<InformationSystemInterface> interfaces = Lists.newArrayList();
        List<Integer> interfacesIds = Lists.newArrayList();

        // The map that stores the edge count (greater or equal than the interface count) for every
        // information system.
        // We use a one-value integer array in this map instead of an Integer object,
        // because Integer objects are immutable and would cost performance.
        Map<Integer, int[]> releaseIdToEdgeCount = Maps.newHashMap();

        for (InformationSystemRelease isRelease : isReleases) {
            releaseIdToEdgeCount.put(isRelease.getId(), new int[] { 0 });

            // filter interfaces
            Set<InformationSystemInterface> filteredInterfaces = isRelease.getAllConnections();
            if (isInterfaces != null) {
                filteredInterfaces = Sets.intersection(filteredInterfaces, isInterfaces);
            }

            // retrieves all connections of the isRelease and stores them in a List
            for (InformationSystemInterface iface : filteredInterfaces) {
                if (isReleaseIds.contains(iface.getInformationSystemReleaseA().getId())
                        && isReleaseIds.contains(iface.getInformationSystemReleaseB().getId())
                        && !interfacesIds.contains(iface.getId())) {
                    interfaces.add(iface);
                    interfacesIds.add(iface.getId());
                }
            }
        }

        // add edges for all found interfaces:
        for (InformationSystemInterface iface : interfaces) {
            // Edges are only added if both "from" and "to" IS release are in the List of IS release Ids.
            Integer isReleaseAId = iface.getInformationSystemReleaseA().getId();
            Integer isReleaseBId = iface.getInformationSystemReleaseB().getId();
            if (isReleaseIds.contains(isReleaseAId) && isReleaseIds.contains(isReleaseBId)) {
                int newEdgesCount = addEdgesForInterfaces(iface);
                releaseIdToEdgeCount.get(isReleaseAId)[0] += newEdgesCount;
                releaseIdToEdgeCount.get(isReleaseAId)[0] += newEdgesCount;
            }
        }

        // releaseId to edge count now contains the number of edges per node
        adjustNodeMasterShapes(releaseIdToEdgeCount);

        LOGGER.debug("leaving addEdges()");
    }

    private void adjustNodeMasterShapes(Map<Integer, int[]> releaseIdToEdgeCount) {
        for (Entry<Integer, int[]> entry : releaseIdToEdgeCount.entrySet()) {
            Integer releaseId = entry.getKey();
            String releaseIdStr = releaseId.toString();
            NodeWithProperties infoSysNodeProperties = isIdToGxlNode.get(releaseIdStr);
            GXLNode node = infoSysNodeProperties.getNode();
            // a single-value array
            int[] edgeCount = entry.getValue();

            if (edgeCount[0] > APPLICATION_CP_M_LIMIT) {
                // set type to large in accordance with the BO string
                if (isOnlyWhitespace(infoSysNodeProperties.getBusinessObjects())
                        && isOnlyWhitespace(infoSysNodeProperties.getBaseComponents())) {
                    // neither business objects nor base components
                    node.setType(createGxlTypeURI(VISIO_SHAPE_NAME_APPLICATION_CP_L_SMALL));
                } else if (!isOnlyWhitespace(infoSysNodeProperties.getBaseComponents())) {
                    // no business objects (only base components)
                    node.setType(createGxlTypeURI(VISIO_SHAPE_NAME_APPLICATION_CP_L_BC));
                } else if (!isOnlyWhitespace(infoSysNodeProperties.getBusinessObjects())) {
                    // no base components (only business objects)
                    node.setType(createGxlTypeURI(VISIO_SHAPE_NAME_APPLICATION_CP_L_BO));
                } else {
                    // both
                    node.setType(createGxlTypeURI(VISIO_SHAPE_NAME_APPLICATION_CP_L_BO_BC));
                }
            }
            if ((edgeCount[0] > APPLICATION_CP_S_LIMIT) && (edgeCount[0] <= APPLICATION_CP_M_LIMIT)) {
                // set the type to medium
                if (isOnlyWhitespace(infoSysNodeProperties.getBusinessObjects())
                        && isOnlyWhitespace(infoSysNodeProperties.getBaseComponents())) {
                    // no business objects and base components
                    node.setType(createGxlTypeURI(VISIO_SHAPE_NAME_APPLICATION_CP_M_SMALL));
                } else if (!isOnlyWhitespace(infoSysNodeProperties.getBaseComponents())) {
                    // no business objects (only base components)
                    node.setType(createGxlTypeURI(VISIO_SHAPE_NAME_APPLICATION_CP_M_BC));
                } else if (!isOnlyWhitespace(infoSysNodeProperties.getBusinessObjects())) {
                    // no base components (only business objects)
                    node.setType(createGxlTypeURI(VISIO_SHAPE_NAME_APPLICATION_CP_M_BO));
                } else {
                    // both
                    node.setType(createGxlTypeURI(VISIO_SHAPE_NAME_APPLICATION_CP_M_BO_BC));
                }
            }
        }
    }

    private static URI createGxlTypeURI(String typeIdentifier) {
        try {
            return new URI(GXL_SCHEMA_URI + typeIdentifier);
        } catch (URISyntaxException e) {
            // should never happen
            LOGGER.error(e.getMessage(), e);
            return null;
        }
    }

    private static boolean isOnlyWhitespace(String str) {
        return IS_ONLY_WHITESPACE_PATTERN.matcher(str).matches();
    }

    /**
     * Adds edges to the graph for all transports of a given Connection.
     * @param iface The connection to assign to the graph.
     * @return The count of edges.
     */
    @SuppressWarnings("boxing")
    private int addEdgesForInterfaces(InformationSystemInterface iface) {

        List<String> bidirectionalBOs = Lists.newArrayList();
        List<String> firstToSecondBOs = Lists.newArrayList();
        List<String> secondToFirstBOs = Lists.newArrayList();
        List<String> noDirectionBOs = Lists.newArrayList();

        addTransportedBusinessObjects(iface, bidirectionalBOs, firstToSecondBOs, secondToFirstBOs, noDirectionBOs);

        Map<String, String> edgeProps = Maps.newHashMap();

        edgeProps.put(PROP_ID, iface.getId().toString());
        edgeProps.put(PROP_XREF_URL,
                this.retrieveXLinkUrlForIdentityEntity(iface, this.informationFlowOptions.getServerUrl()));

        // TODO currently there is no status information implemented for interfaces:
        edgeProps.put(PROP_STATUS, "current");

        edgeProps.put(PROP_DESCRIPTION, iface.getDescription());

        StringBuilder attributesString = initAttributeString(iface.getAttributeValueAssignments());
        edgeProps.put(PROP_ATTRIBUTES, attributesString.toString());
        edgeProps.put(PROP_LINE_TYPE,
                String.valueOf(VisioDimensionExport.getVisioLinePattern(lineDimension.getValue(iface))));

        return calculateEdgeCount(edgeProps, iface, bidirectionalBOs, firstToSecondBOs, secondToFirstBOs,
                noDirectionBOs);
    }

    /**
     * @param edgeProps
     * @param noDirectionBOs
     * @param secondToFirstBOs
     * @param firstToSecondBOs
     * @param bidirectionalBOs
     * @param iface
     * 
     * @return the edge count
     */
    private int calculateEdgeCount(Map<String, String> edgeProps, InformationSystemInterface iface,
            List<String> bidirectionalBOs, List<String> firstToSecondBOs, List<String> secondToFirstBOs,
            List<String> noDirectionBOs) {
        List<Direction> directions = Lists.newArrayList();
        List<String> transportsLabels = Lists.newArrayList();

        if (ArrayUtils.contains(lineCaptionSelected, InformationFlowOptionsBean.LINE_DESCR_BUSINESS_OBJECTS)) {
            addTransportLabelAndDirection(transportsLabels, directions, bidirectionalBOs,
                    Direction.BOTH_DIRECTIONS);
            addTransportLabelAndDirection(transportsLabels, directions, noDirectionBOs, Direction.NO_DIRECTION);
            addTransportLabelAndDirection(transportsLabels, directions, firstToSecondBOs,
                    Direction.FIRST_TO_SECOND);
            addTransportLabelAndDirection(transportsLabels, directions, secondToFirstBOs,
                    Direction.SECOND_TO_FIRST);
            if (transportsLabels.isEmpty() && directions.isEmpty()) {
                transportsLabels.add("");
                directions.add(Direction.NO_DIRECTION);
            }
        } else {
            transportsLabels.add("");
            directions.add(iface.getInterfaceDirection());
        }

        int edgeCount = transportsLabels.size();

        List<String> commonTransportLabels = determineCommonLabels(iface);

        // add edges
        for (int i = 0; i < edgeCount; i++) {
            List<String> allEdgeLabels = Lists.newArrayList(commonTransportLabels);
            allEdgeLabels.add(transportsLabels.get(i));
            allEdgeLabels.removeAll(Lists.newArrayList(""));

            edgeProps.put(PROP_FLOW_INFORMATION_OBJECTS, StringUtils.join(allEdgeLabels, "; "));

            addEdgeWithDirection(iface, directions.get(i), edgeProps);
        }

        return edgeCount;
    }

    private List<String> determineCommonLabels(InformationSystemInterface iface) {
        List<String> commonTransportLabels = Lists.newArrayList();
        for (int lineCaption : this.lineCaptionSelected) {
            String prefix = "";
            switch (lineCaption) {

            case InformationFlowOptionsBean.LINE_DESCR_TECHNICAL_COMPONENTS:
                List<String> referencedTcrs = InformationFlowGeneralHelper.getReferencedTcReleaseNames(iface);
                if (!referencedTcrs.isEmpty()) {
                    prefix = MessageAccess.getStringOrNull(
                            "graphicalExport.informationflow.technicalComponent.abbreviation") + ": ";
                    commonTransportLabels.add(prefix + GeneralHelper
                            .makeConcatenatedStringWithSeparator(referencedTcrs, Constants.BUILDINGBLOCKSEP));
                }
                break;

            case InformationFlowOptionsBean.LINE_DESCR_ATTRIBUTES:
                List<String> resultValues = InformationFlowGeneralHelper
                        .getLabelDescrForAttribute(getAttributeTypeService(), iface, this.lineCaptionAttributeId);
                prefix = MessageAccess.getStringOrNull("graphicalExport.informationflow.attribute.abbreviation")
                        + ": ";
                commonTransportLabels.add(prefix + GeneralHelper.makeConcatenatedStringWithSeparator(resultValues,
                        Constants.BUILDINGBLOCKSEP));
                break;

            case InformationFlowOptionsBean.LINE_DESCR_DESCRIPTION:
                prefix = MessageAccess.getStringOrNull("graphicalExport.informationflow.description.abbreviation")
                        + ": ";
                commonTransportLabels.add(prefix + iface.getDescription());
                break;

            case InformationFlowOptionsBean.LINE_DESCR_NAME:
                if (!iface.getName().isEmpty()) {
                    prefix = MessageAccess.getStringOrNull("graphicalExport.informationflow.name.abbreviation")
                            + ": ";
                    commonTransportLabels.add(prefix + iface.getName());
                }
                break;

            default: // Business Objects handles separately
            }
        }
        return commonTransportLabels;
    }

    private void addTransportLabelAndDirection(List<String> businessObjectsLabels, List<Direction> directions,
            List<String> listOfObjects, Direction direction) {
        if (!listOfObjects.isEmpty()) {
            String prefix = MessageAccess
                    .getStringOrNull("graphicalExport.informationflow.businessObject.abbreviation") + ": ";
            businessObjectsLabels.add(prefix
                    + GeneralHelper.makeConcatenatedStringWithSeparator(listOfObjects, Constants.BUILDINGBLOCKSEP));
            directions.add(direction);
        }
    }

    /**
     * Prepares a string from the AttributeValueAssignment
     * 
     * @return StringBuilder for the string
     */
    private StringBuilder initAttributeString(Set<AttributeValueAssignment> avas) {
        StringBuilder attributesString = new StringBuilder();

        for (AttributeValueAssignment ava : avas) {
            AttributeValue av = ava.getAttributeValue();
            AttributeType at = av.getAbstractAttributeType();
            AttributeTypeGroup atg = at.getAttributeTypeGroup();

            // check if the user has the permission to read the attributeGroup:
            if (UserContext.getCurrentPerms().userHasAttrTypeGroupPermission(atg,
                    AttributeTypeGroupPermissionEnum.READ)) {
                attributesString.append('(');
                attributesString.append(at.getName());
                attributesString.append(")=");
                attributesString.append(av.getLocalizedValueString(getLocale()));
                attributesString.append(';');
            }
        }

        return attributesString;
    }

    /**
     * Adds edges to the graph with the given direction
     * @param iface
     * @param direction
     * @param edgeProps
     */
    private void addEdgeWithDirection(InformationSystemInterface iface, Direction direction,
            Map<String, String> edgeProps) {

        // The default is a shape with no direction.
        EdgeParameters edgeParams = new EdgeParameters(VISIO_SHAPE_NAME_FLOW_NODIRECTION, iface, edgeProps);

        if (Direction.BOTH_DIRECTIONS.equals(direction)) {
            edgeParams = new EdgeParameters(VISIO_SHAPE_NAME_FLOW_BIDIRECTIONAL, iface, edgeProps);
        } else if (Direction.FIRST_TO_SECOND.equals(direction)) {
            edgeParams = new EdgeParameters(VISIO_SHAPE_NAME_FLOW, iface, true, edgeProps);
        } else if (Direction.SECOND_TO_FIRST.equals(direction)) {
            edgeParams = new EdgeParameters(VISIO_SHAPE_NAME_FLOW, iface, false, edgeProps);
        }

        addEdge(edgeParams.masterFlow, edgeParams.releaseAId, edgeParams.releaseBId, edgeParams.edgeProps);
    }

    /**
     * 
     * @param connection
     * @param bidirectionalBOs
     * @param firstToSecondBOs
     * @param secondToFirstBOs
     * @param noDirectionBOs
     */
    private void addTransportedBusinessObjects(InformationSystemInterface connection, List<String> bidirectionalBOs,
            List<String> firstToSecondBOs, List<String> secondToFirstBOs, List<String> noDirectionBOs) {
        for (Transport transport : Lists.newArrayList(connection.getTransports())) {

            String transportedBusinessObject = transport.getBusinessObject().getName();
            TransportInfo transportInfo = transport.getTransportInfo();

            switch (transportInfo) {
            case BOTH_DIRECTIONS:
                bidirectionalBOs.add(transportedBusinessObject);
                break;
            case FIRST_TO_SECOND:
                firstToSecondBOs.add(transportedBusinessObject);
                break;
            case SECOND_TO_FIRST:
                secondToFirstBOs.add(transportedBusinessObject);
                break;
            default: // No Direction
                noDirectionBOs.add(transportedBusinessObject);
            }
        }
    }

    /**
     * Group the graph nodes in a hierarchical fashion (if necessary). For every node in the nodes[]
     * it is checked whether the corresponding IS release has a parent. If yes, the node is copied to
     * the list of children and removed from the graph later on.
     */
    public void groupNodes() {
        LOGGER.debug("entering groupNodes...");

        Set<GXLNode> nodes = GXLUtil.getNodes(graph);

        // nodesMap maps nodeIds to nodes:
        Map<Integer, GXLNode> nodesMap = Maps.newHashMap();
        for (GXLNode node : nodes) {
            Integer nodeId = Integer.valueOf(node.getID());
            nodesMap.put(nodeId, node);
        }

        // check for all nodes if their corresponding IS releases have parents:
        for (GXLNode node : nodes) {
            Integer nodeId = Integer.valueOf(node.getID());
            InformationSystemRelease isRelease = isrService.loadObjectById(nodeId);
            if (isRelease.getParent() != null) {
                Integer parentNodeId = isRelease.getParent().getId();
                GXLNode parentNode = nodesMap.get(parentNodeId);

                GXLGraph innerGraph;
                if (parentNode.getGraphCount() == 0) {
                    innerGraph = new GXLGraph("subsystems" + parentNodeId);
                    parentNode.add(innerGraph);
                } else {
                    innerGraph = parentNode.getGraphAt(0);
                }
                // move node into outer node
                graph.remove(node);
                innerGraph.add(node);
                nodesMap.put(nodeId, node);
            }
        }
        LOGGER.debug("leaving groupNodes...");
    }

    /**
     * Adds a new connection edge with properties to the resulting graph.
     * @param shapeName
     * @param fromId
     * @param toId
     * @param edgeProps
     */
    private void addEdge(String shapeName, Integer fromId, Integer toId, Map<String, String> edgeProps) {
        GXLEdge edge = new GXLEdge(String.valueOf(fromId), String.valueOf(toId));

        edge.setType(createGxlTypeURI(shapeName));

        for (Map.Entry<String, String> edgeProperty : edgeProps.entrySet()) {
            String prop = "";
            if (edgeProperty.getValue() != null) {
                prop = edgeProperty.getValue();
            }
            setTextPropertyToNode(edge, edgeProperty.getKey(), prop);
        }

        graph.add(edge);
    }

    /**
     * 
     * @param node
     * @return The resulting node width.
     */
    private double setIsrNodeTexts(GXLNode node) {

        double nodeWidth = 0;

        if (node.getGraphCount() == 0) {
            nodeWidth = APPLICATION_WIDTH;
        } else {
            for (GXLNode child : GXLUtil.getNodes(node.getGraphAt(0))) {
                setIsrNodeTexts(child);
            }
            nodeWidth = (int) Math.ceil(Math.sqrt(GXLUtil.getNodes(node.getGraphAt(0)).size()))
                    * (APPLICATION_WIDTH + APPLICATION_PADDING_SIDE) + APPLICATION_PADDING_SIDE;
        }

        NodeWithProperties nodeWithProperties = isIdToGxlNode.get(node.getID());
        String appName = nodeWithProperties.getAppName();
        String nodeBusinessObjects = nodeWithProperties.getBusinessObjects();
        String nodeBaseComponents = nodeWithProperties.getBaseComponents();

        String screenName = getScreenName(informationFlowOptions, appName, null, IS_CATEGORY_NAME, nodeWidth, 8,
                "");
        setTextPropertyToNode(node, PROP_APP_NAME, screenName);

        String screenBos = getScreenName(informationFlowOptions, nodeBusinessObjects, null, BO_CATEGORY_NAME,
                nodeWidth, 6, "");
        setTextPropertyToNode(node, PROP_APP_INFORMATION_OBJECTS, screenBos);

        String screenBcs = getScreenName(informationFlowOptions, nodeBaseComponents, null, IS_CATEGORY_NAME,
                nodeWidth, 6, "");
        setTextPropertyToNode(node, PROP_APP_BASE_COMPONENTS, screenBcs);

        return nodeWidth;
    }

    /**
     * Parameter class for set of edge parameter values and common computations, e.g. releases A and B
     * of the interface.
     */
    private static class EdgeParameters {
        private final String masterFlow;
        private Integer releaseAId;
        private Integer releaseBId;

        private final Map<String, String> edgeProps;

        protected EdgeParameters(String masterFlow, InformationSystemInterface connection,
                Map<String, String> edgeProps) {
            this.masterFlow = masterFlow;
            this.releaseAId = connection.getInformationSystemReleaseA().getId();
            this.releaseBId = connection.getInformationSystemReleaseB().getId();
            this.edgeProps = edgeProps;
        }

        /**
         * Constructor for directed, yet not bidirectional edges.
         *
         * @param masterFlow
         *          The master shape name
         * @param connection
         *          The connection object holding information for the edge
         * @param relAToRelB
         *          Determines whether the edge should be directed from A to B (true), or from B to A
         *          (false).
         * @param edgeProps
         *          The (visio-relevant) shape properties for the edge.
         */
        protected EdgeParameters(String masterFlow, InformationSystemInterface connection, boolean relAToRelB,
                Map<String, String> edgeProps) {
            this.masterFlow = masterFlow;
            if (relAToRelB) {
                this.releaseAId = connection.getInformationSystemReleaseA().getId();
                this.releaseBId = connection.getInformationSystemReleaseB().getId();
            } else {
                this.releaseAId = connection.getInformationSystemReleaseB().getId();
                this.releaseBId = connection.getInformationSystemReleaseA().getId();
            }
            this.edgeProps = edgeProps;
        }

    }

    private static class NodeWithProperties {
        private final GXLNode node;
        private final String appName;
        private final String businessObjects;
        private final String baseComponents;

        public NodeWithProperties(GXLNode node, String appName, String businessObjects, String baseComponents) {
            super();
            this.node = node;
            this.appName = appName;
            this.businessObjects = businessObjects;
            this.baseComponents = baseComponents;
        }

        public GXLNode getNode() {
            return node;
        }

        public String getAppName() {
            return appName;
        }

        public String getBusinessObjects() {
            return businessObjects;
        }

        public String getBaseComponents() {
            return baseComponents;
        }

    }
}