Batch processing of IDOCs with splitting

OOTB hybris supports splitting of DEBMAS IDOCs by KTOKD. It means that different IDOCs can be processed with different mapping services. But in case of IDOC contains set of DEBMAS with different KTOKD values, all of them would be processed with same mapping service regardless of real KTOKD value. Unfortunately implementation of proper fix requires core changes in datahub IDOC processing, but there is an easier way to fix that issue with performance trade-off.

Issue can be observed on IDOCs with such structure:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<?xml version="1.0" encoding="UTF-8"?>
<ZDEBMAS02>
    <IDOC BEGIN="1">
        <E1KNA1M SEGMENT="1">
            <KTOKD>Z002</KTOKD>
        </E1KNA1M>
    </IDOC>
    <IDOC BEGIN="1">
        <E1KNA1M SEGMENT="1">
            <KTOKD>Z001</KTOKD>
        </E1KNA1M>
    </IDOC>
    ...
</ZDEBMAS02>

And OOTB approach:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
    <!-- Maps received IDOCs of type: "DEBMAS" by value of attribute "KTOKD" to corresponding mapping service -->
<int-xml:xpath-router id="splitKTOKD" input-channel="DEBMAS" evaluate-as-string="true" resolution-required="false"
                      default-output-channel="DEBMAS-NOTSUPPORTED-KTOKD">
    <int-xml:xpath-expression id="splitKTOKDExpression" expression="//KTOKD"/>
    <int-xml:mapping value="Z001" channel="DEBMAS-Z001"/>
    <int-xml:mapping value="Z002" channel="DEBMAS-Z002"/>
</int-xml:xpath-router>

<int:service-activator id="blogcompanyDEBMASCompanyServiceActivator" input-channel="DEBMAS-Z001"
                       output-channel="rawFragmentDataInputChannel" ref="sapcompanyDEBMASCompanyMappingService"
                       method="map"/>

<int:service-activator id="blogcompanyDEBMASCompanyAddressServiceActivator" input-channel="DEBMAS-Z002"
                       output-channel="rawFragmentDataInputChannel"
                       ref="sapcompanyDEBMASCompanyAddressMappingService"
                       method="map"/>

        <!-- IDoc inbound mapping services -->
<bean id="sapcompanyDEBMASCompanyMappingService" class="com.hybris.datahub.sapidocintegration.IDOCMappingService">
<property name="rawFragmentDataExtensionSource" value="blogcompany"/>
<property name="rawFragmentDataFeedName" value="SAPCOMPANY_INBOUND_FEED"/>
<property name="rawFragmentDataType" value="RawDEBMASCompany"/>
</bean>

<bean id="sapcompanyDEBMASCompanyAddressMappingService"
      class="com.hybris.datahub.sapidocintegration.IDOCMappingService">
<property name="rawFragmentDataExtensionSource" value="blogcompany"/>
<property name="rawFragmentDataFeedName" value="SAPCOMPANY_INBOUND_FEED"/>
<property name="rawFragmentDataType" value="RawDEBMASAddress"/>
</bean>

In current example all IDOCs would be processed with mapping service mapped to KTOKD = Z001 (sapcompanyDEBMASCompanyMappingService), which will lead to creation of wrong data for IDOCs with KTOKD = Z002.

All IDOCs are processed with default IDOCMappingService, which parses xml with SAXParser, creates map of all possible data values from current xml raw data type definition, reduces map by removing duplicates and creates RawFragmentData in DB. The only way to change behaviour of IDOCMappingService is to override customizeMaps method, which is executed before reduceMaps and RawFragmentData creation. In this method map could be filterer with predefined KTOKD value, so at least wrong data would not be created from batch IDOC.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package com.blog.datahub.company.sapidocintegration;

import com.hybris.datahub.sapidocintegration.IDOCMappingService;

import java.util.Iterator;
import java.util.List;
import java.util.Map;

public class FilteredIDOCMappingService extends IDOCMappingService {

    private Map<String, String> filterMap;

    @Override
    protected void customizeMaps(List<Map<String, String>> maps) {
        super.customizeMaps(maps);
        for (Map.Entry<String, String> filter : filterMap.entrySet()) {
            filterMaps(maps, filter);
        }
    }


    private void filterMaps(List<Map<String, String>> maps, Map.Entry<String, String> filter) {
        Iterator<Map<String, String>> iterator = maps.iterator();
        while (iterator.hasNext()) {
            Map<String, String> nextMap = iterator.next();
            String value = nextMap.get(filter.getKey());
            if (!filter.getValue().equalsIgnoreCase(value)) {
                iterator.remove();
            }
        }
    }

    public void setFilterMap(Map<String, String> filterMap) {
        this.filterMap = filterMap;
    }
}

This implementation can be used with spring definition:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
    <!-- IDoc inbound mapping services -->
<util:map id="z001Filter">
    <entry key="E1KNA1M-KTOKD" value="Z001"/>
</util:map>

<bean id="sapcompanyDEBMASCompanyMappingService"
      class="com.blog.datahub.company.sapidocintegration.FilteredIDOCMappingService">
<property name="rawFragmentDataExtensionSource" value="blogcompany"/>
<property name="rawFragmentDataFeedName" value="SAPCOMPANY_INBOUND_FEED"/>
<property name="rawFragmentDataType" value="RawDEBMASCompany"/>
<property name="filterMap" ref="z001Filter"/>
</bean>

<util:map id="z002Filter">
<entry key="E1KNA1M-KTOKD" value="Z002"/>
</util:map>
<bean id="sapcompanyDEBMASCompanyAddressMappingService"
      class="com.blog.datahub.company.sapidocintegration.FilteredIDOCMappingService">
<property name="rawFragmentDataExtensionSource" value="blogcompany"/>
<property name="rawFragmentDataFeedName" value="SAPCOMPANY_INBOUND_FEED"/>
<property name="rawFragmentDataType" value="RawDEBMASAddress"/>
<property name="filterMap" ref="z002Filter"/>
</bean>

Such implementation will resolve issue with creation of wrong data in Hybris, but it will lead to another issue with missing data. To fix this issue xml file should be processed by few mapping services. For that would be used technical workarond with one mapping service, which will sequentially execute predefined list of mapper services (each mapping service could be executed in separate thread to improve performance).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.blog.datahub.company.sapidocintegration;

import com.hybris.datahub.dto.integration.RawFragmentData;
import com.hybris.datahub.sapidocintegration.IDOCMappingService;

import java.util.LinkedList;
import java.util.List;

public class MultipleIDOCMappingService {

    private List<IDOCMappingService> mappingServices;


    public List<RawFragmentData> map(final String xml) {
        List<RawFragmentData> fragments = new LinkedList<>();
        mappingServices.forEach(service -> fragments.addAll(service.map(xml)));
        return fragments;
    }

    public void setMappingServices(List<IDOCMappingService> mappingServices) {
        this.mappingServices = mappingServices;
    }
}

And final spring definition:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
    <!-- Maps received IDOCs of type: "DEBMAS" by value of attribute "KTOKD" to corresponding mapping service -->
<int-xml:xpath-router id="splitKTOKD" input-channel="DEBMAS" evaluate-as-string="true" resolution-required="false"
                      default-output-channel="DEBMAS-NOTSUPPORTED-KTOKD">
    <int-xml:xpath-expression id="splitKTOKDExpression" expression="//KTOKD"/>
    <int-xml:mapping value="Z001" channel="DEBMAS-Z"/>
    <int-xml:mapping value="Z002" channel="DEBMAS-Z"/>
</int-xml:xpath-router>

<int:service-activator id="blogcompanyDEBMASServiceActivator" input-channel="DEBMAS-Z"
                       output-channel="rawFragmentDataInputChannel"
                       ref="sapcompanyDEBMASMappingService"
                       method="map"/>

<bean id="sapcompanyDEBMASMappingService"
      class="com.blog.datahub.company.sapidocintegration.MultipleIDOCMappingService">
<property name="mappingServices" ref="sapcompanyDEBMASMappingServices"/>
</bean>

<util:list id="sapcompanyDEBMASMappingServices">
<ref bean="sapcompanyDEBMASCompanyMappingService"/>
<ref bean="sapcompanyDEBMASCompanyAddressMappingService"/>
</util:list>

        <!-- IDoc inbound mapping services -->
<util:map id="z001Filter">
<entry key="E1KNA1M-KTOKD" value="Z001"/>
</util:map>

<bean id="sapcompanyDEBMASCompanyMappingService"
      class="com.blog.datahub.company.sapidocintegration.FilteredIDOCMappingService">
<property name="rawFragmentDataExtensionSource" value="blogcompany"/>
<property name="rawFragmentDataFeedName" value="SAPCOMPANY_INBOUND_FEED"/>
<property name="rawFragmentDataType" value="RawDEBMASCompany"/>
<property name="filterMap" ref="z001Filter"/>
</bean>

<util:map id="z002Filter">
<entry key="E1KNA1M-KTOKD" value="Z002"/>
</util:map>
<bean id="sapcompanyDEBMASCompanyAddressMappingService"
      class="com.blog.datahub.company.sapidocintegration.FilteredIDOCMappingService">
<property name="rawFragmentDataExtensionSource" value="blogcompany"/>
<property name="rawFragmentDataFeedName" value="SAPCOMPANY_INBOUND_FEED"/>
<property name="rawFragmentDataType" value="RawDEBMASAddress"/>
<property name="filterMap" ref="z002Filter"/>
</bean>

As a result MultipleIDOCMappingService combine all RawFragmentData received from predefined list of FilteredIDOCMappingService. The trade off this approach is performance, because the same IDOC is parsed by each mapping service from list.

comments powered by Disqus