gt:       final FlowProcessType exReq = flow.getExecuteRequest();
context: /*******************************************************************************
   * Copyright (c) 2013 - 2020 QPark Consulting  S.a r.l.
   *
   * This program and the accompanying materials are made available under the
   * terms of the Eclipse Public License v1.0.
   * The Eclipse Public License is available at
   * http://www.eclipse.org/legal/epl-v10.html.
   ******************************************************************************/
  package com.samples.domain.flow.flows.doc.test;
  
  import java.io.File;
  import java.io.FileNotFoundException;
  import java.io.FileOutputStream;
  import java.io.IOException;
  import java.util.ArrayList;
  import java.util.Collection;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.List;
  import java.util.Map;
  import java.util.Objects;
  import java.util.Optional;
  import java.util.Set;
  import java.util.TreeMap;
  import java.util.TreeSet;
  import java.util.concurrent.ConcurrentHashMap;
  import java.util.concurrent.atomic.AtomicInteger;
  import java.util.stream.Collectors;
  import org.apache.poi.hssf.usermodel.HSSFCell;
  import org.apache.poi.hssf.usermodel.HSSFCellStyle;
  import org.apache.poi.hssf.usermodel.HSSFRow;
  import org.apache.poi.hssf.usermodel.HSSFSheet;
  import org.apache.poi.hssf.usermodel.HSSFWorkbook;
  import org.apache.poi.ss.usermodel.CellStyle;
  import org.apache.poi.ss.usermodel.Font;
  import org.apache.poi.ss.util.CellRangeAddress;
  import com.qpark.eip.model.docmodel.ComplexType;
  import com.qpark.eip.model.docmodel.DataType;
  import com.qpark.eip.model.docmodel.ElementType;
  import com.qpark.eip.model.docmodel.FieldMappingType;
  import com.qpark.eip.model.docmodel.FieldType;
  import com.qpark.eip.model.docmodel.FlowFilterType;
  import com.qpark.eip.model.docmodel.FlowMapInOutType;
  import com.qpark.eip.model.docmodel.FlowProcessType;
  import com.qpark.eip.model.docmodel.FlowRuleType;
  import com.qpark.eip.model.docmodel.FlowSubRequestType;
  import com.qpark.eip.model.docmodel.FlowType;
  import com.qpark.eip.model.docmodel.InterfaceMappingType;
  import com.qpark.eip.model.docmodel.OperationType;
  import com.qpark.eip.model.docmodel.RequestResponseDataType;
  import com.qpark.eip.model.docmodel.ServiceType;
  
  /**
   * Basic methods to create an Excel sheet with Service Bus information about
   * Services, Flows, Mappings and so on.
   *
   * @author sneuroh
   */
  
  public class DocClient {
    /**
     * Inner class for Flow Step Information
     *
     * @author sneuroh
     */
    private class FlowStepInfo {
      /** UUID */
      String UUID;
      /** inbound or outbound */
      String direction;
      /** name */
      String name;
      /** rule, mapping or subrequest */
      String type;
      /** Input or output types */
      String io;
      /** Description of the Flow Step */
      String descr;
      /** Mapping input types of the InterfaceTypes (if any) */
      String mappingInputTypes = "";
  
      @Override
      public String toString() {
        final StringBuffer sb = new StringBuffer(256);
        sb.append(this.name).append("(").append(this.direction).append("/").append(this.type)
            .append(")");
        return sb.toString();
      }
    }
  
    private static String DIR_FLOW_IN = "Inbound";
    private static String DIR_FLOW_OUT = "Outbound";
    private static String STEP_RULE = "Rule";
    private static String STEP_SUB = "Information Request";
    private static String STEP_MAP = "Mapping";
    private HSSFWorkbook wb;
    private HSSFCellStyle headerStyle;
    private HSSFCellStyle bodyStyle;
    private DomainDocProvider provider;
    private Map<String, String> ctIdMap = new ConcurrentHashMap<>(256);
    private CellStyle frequencyStyle;
    private HSSFCellStyle wordWrapStyle;
  
    /**
     * Constructor
     */
    public DocClient(final DomainDocProvider provider) {
      this.wb = new HSSFWorkbook();
      this.provider = provider;
      this.headerStyle = this.createHeaderStyle();
      this.bodyStyle = this.createBodyStyle();
      this.wordWrapStyle = this.createdWordWrapStyle();
    }
  
    /**
     * Add a sheet with ComplexType information
     *
     * @param complexTypeUUIDSet
     *            Set of ComplexType UUID
     */
    public void addDataTypeSheet(final Collection<String> complexTypeUUIDSet) {
      final HSSFSheet sheet = this.createSheet("ComplexTypes");
      this.createDataTypeSheet(sheet, complexTypeUUIDSet);
      sheet.createFreezePane(0, 1, 0, 1);
      sheet.setAutoFilter(new CellRangeAddress(0, sheet.getRow(sheet.getLastRowNum()).getRowNum(), 0,
          sheet.getRow(sheet.getFirstRowNum()).getLastCellNum()));
    }
  
    /**
     * Add information about a Flow Filter to the flowInfo (Filter to be
     * replaced by rule)
     *
     * @param flowInfo
     *            List of FlowStepInfo (UUID, name, type, direction)
     * @param filter
     *            List of Flow Filters
     * @param direction
     *            inbound or outbound
     */
    private void addFlowFilter(final List<FlowStepInfo> flowInfo, final List<FlowFilterType> filter,
        final String direction) {
      FlowStepInfo e = null;
      FlowFilterType ffT = null;
      RequestResponseDataType rrdT = null;
      for (final FlowFilterType element : filter) {
        ffT = element;
        e = new FlowStepInfo();
        e.UUID = ffT.getId();
        e.name = ffT.getName();
        e.type = STEP_RULE;
        e.direction = direction;
  
        rrdT = ffT.getFilterInOut();
        final ComplexType flowInput = this.getComplexType(rrdT.getRequestId());
        final ComplexType flowOutput = this.getComplexType(rrdT.getResponseId());
  
        e.io = this.getComplexTypeFieldElements(flowInput) + "\n"
            + this.getComplexTypeFieldElements(flowOutput);
        e.descr =
            ffT.getFilterInFieldDescription() + "\n" + "\n" + ffT.getFilterOutFieldDescription();
        flowInfo.add(e);
      }
    }
  
    /**
     * Add information about a Flow Mappings to the flowInfo
     *
     * @param flowInfo
     *            List of FlowStepInfo (UUID, name, type, direction)
     * @param map
     *            List of Flow mappings
     * @param direction
     *            inbound or outbound
     */
    private void addFlowMapping(final List<FlowStepInfo> flowInfo, final List<FlowMapInOutType> map,
        final String direction, final List<InterfaceMappingType> interfaceTypes) {
      FlowStepInfo e = null;
      RequestResponseDataType rrdT = null;
      for (final FlowMapInOutType fmioT : map) {
        e = new FlowStepInfo();
        e.UUID = fmioT.getId();
        e.name = fmioT.getName();
        e.type = STEP_MAP;
        e.direction = direction;
  
        rrdT = fmioT.getMapInOut();
        final ComplexType flowInput = this.getComplexType(rrdT.getRequestId());
        final ComplexType flowOutput = this.getComplexType(rrdT.getResponseId());
  
        String flowInputInfo = "()";
        String flowOutputInfo = "()";
        if (flowInput != null) {
          flowInputInfo = this.getComplexTypeFieldElements(flowInput);
        }
        if (flowOutput != null) {
          flowOutputInfo = this.getComplexTypeFieldElements(flowOutput);
        }
  
        e.io = flowInputInfo + "\n" + flowOutputInfo;
        e.descr = fmioT.getMapInFieldDescription() + "\n" + "\n" + fmioT.getMapOutFieldDescription();
  
        final Set<String> mappingInputTypes = new TreeSet<>();
        interfaceTypes.stream().filter(inf -> fmioT.getInterfaceMappingId().contains(inf.getId()))
            .forEach(inf -> {
              mappingInputTypes.addAll(this.getComplexTypeNames(inf.getFieldMappingInputType()));
            });
        final StringBuffer sb = new StringBuffer(512);
        mappingInputTypes.stream().forEach(ctName -> sb.append(ctName));
        e.mappingInputTypes = sb.toString();
  
        flowInfo.add(e);
      }
    }
  
    /**
     * Add a row to a Flow description sheet for a FlowStepInfo object
     *
     * @param sheet
     *            Sheet
     * @param rownum
     *            row number
     * @param flowName
     *            Flow name
     * @param stepInfo
     *            FlowStepInfo object
     */
    private void addFlowRow(final HSSFSheet sheet, final int rownum, final String flowName,
        final FlowStepInfo stepInfo) {
      final HSSFRow row = sheet.createRow(rownum);
      // Flow
      this.createCell(row, 0, flowName, this.bodyStyle);
      // Direction
      if (Objects.nonNull(stepInfo)) {
        this.createCell(row, 1, stepInfo.direction, this.bodyStyle);
        // Input/Output
        this.createCell(row, 2, stepInfo.io, this.wordWrapStyle);
        // Type
        this.createCell(row, 3, stepInfo.type, this.wordWrapStyle);
        // Processing Step
        this.createCell(row, 4, stepInfo.name, this.bodyStyle);
        // Description
        this.createCell(row, 5, stepInfo.descr, this.wordWrapStyle);
        // Description
        this.createCell(row, 6, stepInfo.mappingInputTypes, this.bodyStyle);
      } else {
        this.createCell(row, 1, "[missing]", this.bodyStyle);
        // Input/Output
        this.createCell(row, 2, "[missing]", this.bodyStyle);
        // Type
        this.createCell(row, 3, "[missing]", this.bodyStyle);
        // Processing Step
        this.createCell(row, 4, "[missing]", this.bodyStyle);
        // Description
        this.createCell(row, 5, "[missing]", this.bodyStyle);
        // Description
        this.createCell(row, 6, "[missing]", this.bodyStyle);
      }
  
    }
  
    /**
     * Add information about a Flow Filter to the flowInfo (Filter to be
     * replaced by rule)
     *
     * @param flowInfo
     *            List of FlowStepInfo (UUID, name, type, direction)
     * @param filter
     *            List of Flow Filters
     * @param direction
     *            inbound or outbound
     */
    private void addFlowRule(final List<FlowStepInfo> flowInfo, final List<FlowRuleType> filter,
        final String direction) {
      FlowStepInfo e = null;
      FlowRuleType frT = null;
      RequestResponseDataType rrdT = null;
      for (final FlowRuleType element : filter) {
        frT = element;
        e = new FlowStepInfo();
        e.UUID = frT.getId();
        e.name = frT.getName();
        e.type = STEP_RULE;
        e.direction = direction;
  
        rrdT = frT.getRuleInOut();
        final ComplexType flowInput = this.getComplexType(rrdT.getRequestId());
        final ComplexType flowOutput = this.getComplexType(rrdT.getResponseId());
  
        e.io = this.getComplexTypeFieldElements(flowInput) + "\n"
            + this.getComplexTypeFieldElements(flowOutput);
        e.descr = frT.getRuleInFieldDescription() + "\n" + "\n" + frT.getRuleOutFieldDescription();
        flowInfo.add(e);
      }
    }
  
    /**
     * Add a sheet with Flow information
     *
     * @param flowPattern
     *            String pattern for flow lookup (contains)
     */
    public void addFlowSheet(final String flowPattern) {
      final HSSFSheet sheet = this.createSheet("Flows");
      this.createFlowSheet(sheet, flowPattern);
      sheet.createFreezePane(0, 1, 0, 1);
      sheet.setAutoFilter(new CellRangeAddress(0, sheet.getRow(sheet.getLastRowNum()).getRowNum(), 0,
          sheet.getRow(sheet.getFirstRowNum()).getLastCellNum()));
    }
  
    /**
     * Add information about a Flow SubRequest to the flowInfo
     *
     * @param flowInfo
     *            List of FlowStepInfo (UUID, name, type, direction)
     * @param sub
     *            List of Flow SubRequests
     * @param direction
     *            inbound or outbound
     */
    private void addFlowSubRequest(final List<FlowStepInfo> flowInfo,
        final List<FlowSubRequestType> sub, final String direction) {
      FlowStepInfo e = null;
      FlowSubRequestType fsrT = null;
      RequestResponseDataType rrdT = null;
      for (final FlowSubRequestType element : sub) {
        fsrT = element;
        e = new FlowStepInfo();
        e.UUID = fsrT.getId();
        e.name = fsrT.getName();
        e.type = STEP_SUB;
        e.direction = direction;
  
        rrdT = fsrT.getSubRequestInOut();
        final ComplexType flowInput = this.getComplexType(rrdT.getRequestId());
        final ComplexType flowOutput = this.getComplexType(rrdT.getResponseId());
  
        String flowInputInfo = "()";
        String flowOutputInfo = "()";
        if (flowInput != null) {
          flowInputInfo = this.getComplexTypeFieldElements(flowInput);
        }
        if (flowOutput != null) {
          flowOutputInfo = this.getComplexTypeFieldElements(flowOutput);
        }
  
        e.io = flowInputInfo + "\n" + flowOutputInfo;
        e.descr = fsrT.getSubRequestFieldDescription() + "\n" + "\n"
            + fsrT.getSubResponseFieldDescription();
  
        flowInfo.add(e);
      }
    }
  
    /**
     * Add a sheet with Interface type information for a flow identified by a
     * flow pattern string
     *
     * @param packagePattern
     *            flowPattern String pattern for flow lookup (contains)
     */
    public void addInterfaceMappingSheet(final List<String> packagePattern) {
      final HSSFSheet sheet = this.createSheet("Mappings");
      this.createInterfaceMappingSheet(sheet, packagePattern);
      sheet.createFreezePane(0, 1, 0, 1);
      sheet.setAutoFilter(new CellRangeAddress(0, sheet.getRow(sheet.getLastRowNum()).getRowNum(), 0,
          sheet.getRow(sheet.getFirstRowNum()).getLastCellNum()));
    }
  
    /**
     * Add a sheet with Interface type information for a flow identified by a
     * flow pattern string
     *
     * @param flowPattern
     *            flowPattern String pattern for flow lookup (contains)
     */
    public void addInterfaceMappingSheet(final String flowPattern) {
      final HSSFSheet sheet = this.createSheet("Mappings");
      this.createInterfaceMappingSheet(sheet, flowPattern);
      sheet.createFreezePane(0, 1, 0, 1);
      sheet.setAutoFilter(new CellRangeAddress(0, sheet.getRow(sheet.getLastRowNum()).getRowNum(), 0,
          sheet.getRow(sheet.getFirstRowNum()).getLastCellNum()));
    }
  
    /**
     * Add a sheet with Service information
     *
     * @param serviceIds
     *            List of Service UUIDs
     */
    public void addServiceSheet(final List<String> serviceIds, final Set<String> ctIds) {
      this.addServiceSheet(serviceIds, ".*", ctIds);
    }
  
    /**
     * Add a sheet with Service information
     *
     * @param serviceIds
     *            List of Service UUIDs
     */
    public void addServiceSheet(final List<String> serviceIds, final String operationPattern) {
      this.addServiceSheet(serviceIds, operationPattern, new HashSet<String>());
    }
  
    /**
     * Add a sheet with Service information
     *
     * @param serviceIds
     *            List of ComplexType UUIDs
     */
    public void addServiceSheet(final List<String> serviceIds, final String operationPattern,
        final Set<String> ctIds) {
      final HSSFSheet sheet = this.createSheet("Service");
      this.createServiceSheet(sheet, serviceIds, operationPattern, ctIds);
      sheet.createFreezePane(0, 1, 0, 1);
      sheet.setAutoFilter(new CellRangeAddress(0, sheet.getRow(sheet.getLastRowNum()).getRowNum(), 0,
          sheet.getRow(sheet.getFirstRowNum()).getLastCellNum()));
  
    }
  
    /**
     * Auto size the columns of the given sheet.
     *
     * @param sheet
     *            the sheet.
     */
    private void autoSizeColumn(final HSSFSheet sheet) {
      if (sheet != null && sheet.getRow(0) != null) {
        for (int j = 0; j < sheet.getRow(0).getPhysicalNumberOfCells(); j++) {
          sheet.autoSizeColumn(j);
        }
      }
    }
  
    public void autoSizeColumns() {
      for (int i = 0; i < this.wb.getNumberOfSheets(); i++) {
        this.autoSizeColumn(this.wb.getSheetAt(i));
      }
    }
  
    /**
     * @return body style
     */
    private HSSFCellStyle createBodyStyle() {
      final HSSFCellStyle style = this.wb.createCellStyle();
      // style.setWrapText(true);
      return style;
    }
  
    /**
     * Create a cell in the sheet row at index and set the value and apply style
     *
     * @param row
     *            row
     * @param index
     *            index in row
     * @param value
     *            value
     * @param style
     *            cell style
     * @return cell created
     */
    private HSSFCell createCell(final HSSFRow row, final int index, final String value,
        final HSSFCellStyle style) {
      final HSSFCell cell = row.createCell(index);
      if (Objects.isNull(value)) {
        cell.setCellValue("[missing]");
      } else {
        cell.setCellValue(value.trim());
      }
      cell.setCellStyle(style);
      return cell;
    }
  
    /**
     * Create a sheet with Complex Type information
     *
     * @param sheet
     * @param ctIds
     *            List
     */
    private void createDataTypeSheet(final HSSFSheet sheet, final Collection<String> ctIds) {
  
      int rownum = 0;
      final HSSFRow rowHeader = sheet.createRow(rownum);
  
      this.createCell(rowHeader, 0, "Package name", this.headerStyle);
      this.createCell(rowHeader, 1, "Type name", this.headerStyle);
      this.createCell(rowHeader, 2, "Inherited from", this.headerStyle);
      this.createCell(rowHeader, 3, "Cardinality", this.headerStyle);
      this.createCell(rowHeader, 4, "Field name", this.headerStyle);
      this.createCell(rowHeader, 5, "Field type", this.headerStyle);
      this.createCell(rowHeader, 6, "Field description", this.headerStyle);
      this.createCell(rowHeader, 7, "Type description", this.headerStyle);
      this.createCell(rowHeader, 8, "Type namespace", this.headerStyle);
  
      this.setColumnWidth(sheet, 0, 10);
      this.setColumnWidth(sheet, 1, 50);
      this.setColumnWidth(sheet, 2, 10);
      this.setColumnWidth(sheet, 5, 30);
      this.setColumnWidth(sheet, 3, 30);
      this.setColumnWidth(sheet, 4, 15);
      this.setColumnWidth(sheet, 6, 50);
      this.setColumnWidth(sheet, 7, 50);
      this.setColumnWidth(sheet, 8, 30);
  
      rownum++;
      final AtomicInteger rowNum = new AtomicInteger(rownum);
      for (final String ctId : ctIds) {
        final ComplexType ct = this.getComplexType(ctId);
        if (ct != null) {
          final List<FieldType> fields = ct.getField();
          if (Objects.nonNull(fields)) {
            fields.stream().filter(f -> Objects.nonNull(f))
                .sorted((f1, f2) -> Integer.compare(f1.getSequenceNumber(), f2.getSequenceNumber()))
                .forEach(f -> {
                  final HSSFRow row = sheet.createRow(rowNum.getAndIncrement());
  
                  final List<String> descendends = new ArrayList<>();
                  this.getComplexTypeNamesParents(ct, descendends);
  
                  this.createCell(row, 0, ct.getJavaPackageName(), this.wordWrapStyle);
                  this.createCell(row, 1, this.getRealShortName(ct), this.bodyStyle);
                  this.createCell(row, 2,
                      descendends.stream()
                          .filter(d -> !d.equals("anyType {http://www.w3.org/2001/XMLSchema}"))
                          .collect(Collectors.joining("\n")).trim(),
                      this.bodyStyle);
                  this.createCell(row, 3, f.getCardinality(), this.bodyStyle);
                  this.createCell(row, 4, f.getName(), this.bodyStyle);
                  this.createCell(row, 5,
                      Optional.ofNullable(this.getDataType(f.getFieldTypeDefinitionId()))
                          .map(dt -> dt.getName()).orElse("unknown"),
                      this.bodyStyle);
                  this.createCell(row, 6, f.getDescription(), this.wordWrapStyle);
                  this.createCell(row, 7, ct.getDescription(), this.wordWrapStyle);
                  this.createCell(row, 8, ct.getNamespace(), this.bodyStyle);
                });
          }
        }
      }
    }
  
    /**
     * @return the {@link CellStyle} supporting 4 digit frequencies.
     */
    public HSSFCellStyle createdWordWrapStyle() {
      final HSSFCellStyle style = this.wb.createCellStyle();
      style.setWrapText(true);
      return style;
    }
  
    /**
     * Setup a the sheet with Flow information
     *
     * @param sheet
     *            Sheet
     * @param flowPattern
     *            String pattern for flow lookup (contains)
     */
    private void createFlowSheet(final HSSFSheet sheet, final String flowPattern) {
      final List<FlowType> flowList = this.provider.getFlow(flowPattern);
  
      int rownum = 0;
      HSSFRow row = sheet.createRow(rownum);
  
      this.createCell(row, 0, "Flow", this.headerStyle);
      this.createCell(row, 1, "Direction", this.headerStyle);
      this.createCell(row, 2, "Input/Output", this.headerStyle);
      this.createCell(row, 3, "Type", this.headerStyle);
      this.createCell(row, 4, "Processing Step", this.headerStyle);
      this.createCell(row, 5, "Description", this.headerStyle);
      this.createCell(row, 6, "Mapping Input Types", this.headerStyle);
      rownum++;
  
      this.setColumnWidth(sheet, 0, 40);
      this.setColumnWidth(sheet, 1, 9);
      this.setColumnWidth(sheet, 2, 50);
      this.setColumnWidth(sheet, 3, 10);
      this.setColumnWidth(sheet, 4, 30);
      this.setColumnWidth(sheet, 5, 45);
      this.setColumnWidth(sheet, 6, 80);
  
      // sheet.getcolumn
      for (final FlowType flow : flowList) {
        final List<InterfaceMappingType> interfaceTypes = this.provider.getFlowMapping(flow.getId());
        final String flowName = flow.getShortName();
        row = sheet.createRow(rownum);
        this.createCell(row, 0, flow.getShortName(), this.bodyStyle);
  
        // Combination of the Flow messages
        flow.getInvokeFlowDefinition();
  
  