From 43c55009b043540d5fab54898ba28f871ca97958 Mon Sep 17 00:00:00 2001
From: Joze RIHTARSIC <joze.rihtarsic@ext.ec.europa.eu>
Date: Thu, 29 Nov 2018 04:21:28 +0100
Subject: [PATCH] - update sml unit tests

---
 .../smp/data/ui/ParticipantSMLRecord.java     |  57 +++++
 .../smp/data/ui/enums/SMLAction.java          |  22 ++
 .../smp/services/SMLIntegrationService.java   |  58 ++++-
 .../services/ui/UIServiceGroupService.java    | 141 +++++++++--
 .../ec/edelivery/smp/sml/SmlConnector.java    |   5 +-
 .../SMLIntegrationServiceIntegrationTest.java |  37 ++-
 ...ntegrationServiceNoSMLIntegrationTest.java |   6 +
 ...GroupServiceUpdateListIntegrationTest.java | 221 ++++++++++++++++++
 .../smp/smlintegration/SmlConnectorTest.java  |   9 +-
 .../edelivery/smp/testutil/TestDBUtils.java   |   4 +
 .../edelivery/smp/testutil/TestROUtils.java   |   9 +
 .../smp-keystore_multiple_domains.jks         | Bin 26112 -> 28718 bytes
 12 files changed, 529 insertions(+), 40 deletions(-)
 create mode 100644 smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/ParticipantSMLRecord.java
 create mode 100644 smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/enums/SMLAction.java
 create mode 100644 smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/ui/UIServiceGroupServiceUpdateListIntegrationTest.java

diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/ParticipantSMLRecord.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/ParticipantSMLRecord.java
new file mode 100644
index 000000000..f673f479c
--- /dev/null
+++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/ParticipantSMLRecord.java
@@ -0,0 +1,57 @@
+package eu.europa.ec.edelivery.smp.data.ui;
+
+import eu.europa.ec.edelivery.smp.data.model.DBDomain;
+import eu.europa.ec.edelivery.smp.data.model.DBServiceGroupDomain;
+import eu.europa.ec.edelivery.smp.data.ui.enums.EntityROStatus;
+import eu.europa.ec.edelivery.smp.data.ui.enums.SMLAction;
+
+import java.io.Serializable;
+
+public class ParticipantSMLRecord implements Serializable {
+
+    private SMLAction status = SMLAction.REGISTER;
+
+    private String participantIdentifier;
+    private String participantScheme;
+    private DBDomain domain;
+
+    public ParticipantSMLRecord(SMLAction status, String participantId,String participantScheme, DBDomain domain  ) {
+        this.status = status;
+        this.participantIdentifier = participantId;
+        this.participantScheme = participantScheme;
+        this.domain = domain;
+
+    }
+
+    public SMLAction getStatus() {
+        return status;
+    }
+
+    public void setStatus(SMLAction status) {
+        this.status = status;
+    }
+
+    public String getParticipantIdentifier() {
+        return participantIdentifier;
+    }
+
+    public void setParticipantId(String participantId) {
+        this.participantIdentifier = participantId;
+    }
+
+    public String getParticipantScheme() {
+        return participantScheme;
+    }
+
+    public void setParticipantScheme(String participantScheme) {
+        this.participantScheme = participantScheme;
+    }
+
+    public DBDomain getDomain() {
+        return domain;
+    }
+
+    public void setDomain(DBDomain domain) {
+        this.domain = domain;
+    }
+}
diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/enums/SMLAction.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/enums/SMLAction.java
new file mode 100644
index 000000000..402b37625
--- /dev/null
+++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/enums/SMLAction.java
@@ -0,0 +1,22 @@
+package eu.europa.ec.edelivery.smp.data.ui.enums;
+
+
+/**
+ * Enumeraton of Resourceobject statuse .
+ * @author Joze Rihtarsic
+ * @since 4.1
+ */
+public enum SMLAction {
+    REGISTER(0),
+    UNREGISTER(1);
+
+    int actionNumber;
+
+    SMLAction(int actionNumber) {
+        this.actionNumber = actionNumber;
+    }
+
+    public int getAction() {
+        return actionNumber;
+    }
+}
diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/SMLIntegrationService.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/SMLIntegrationService.java
index 438799d5a..e5c22634d 100644
--- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/SMLIntegrationService.java
+++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/SMLIntegrationService.java
@@ -7,6 +7,7 @@ import eu.europa.ec.edelivery.smp.data.dao.ServiceGroupDao;
 import eu.europa.ec.edelivery.smp.data.model.DBDomain;
 import eu.europa.ec.edelivery.smp.data.model.DBServiceGroup;
 import eu.europa.ec.edelivery.smp.data.model.DBServiceGroupDomain;
+import eu.europa.ec.edelivery.smp.data.ui.ParticipantSMLRecord;
 import eu.europa.ec.edelivery.smp.exceptions.SMPRuntimeException;
 import eu.europa.ec.edelivery.smp.logging.SMPLogger;
 import eu.europa.ec.edelivery.smp.logging.SMPLoggerFactory;
@@ -126,7 +127,10 @@ public class SMLIntegrationService {
     }
 
     /**
-     * Method in transaction update servicegroupDomain status and registers participant to SML.
+     * Method in transaction update servicegroupDomain status and unregisters participant to SML.
+     * Method is meant for unregistering participants which are still in database. If they are delete
+     * then this method should not be used.
+     *
      * If registration fails  - transaction is rolled back
      *
      * @param participantId - Participant schema
@@ -146,27 +150,63 @@ public class SMLIntegrationService {
 
         DBServiceGroupDomain serviceGroupDomain = getAndValidateServiceGroupDomain(participantId, participantSchema, domainCode, BUS_SML_UNREGISTER_SERVICE_GROUP_FAILED);
 
-        ParticipantIdentifierType normalizedParticipantId = caseSensitivityNormalizer
-                .normalizeParticipantIdentifier(participantSchema,
-                        participantId);
-
         // unregister only  registered participants
         if (serviceGroupDomain.isSmlRegistered()) {
             // update value
             serviceGroupDomain.setSmlRegistered(false);
             serviceGroupDao.updateServiceGroupDomain(serviceGroupDomain);
-            smlConnector.unregisterFromDns(normalizedParticipantId, serviceGroupDomain.getDomain());
+            unregisterParticipantFromSML(participantId, participantSchema, serviceGroupDomain.getDomain());
             LOG.businessDebug(BUS_SML_UNREGISTER_SERVICE_GROUP, participantId, participantSchema, domainCode);
         } else  {
             LOG.businessWarn(BUS_SML_UNREGISTER_SERVICE_GROUP_ALREADY_REGISTERED, participantId, participantSchema, domainCode );
         }
     }
 
+    /**
+     * Method unregisters participant from SML. It does not check if Participant is in database or of is unregistered
+     *
+     * @param participantId - Participant schema
+     * @param participantSchema - Participant schema
+     * @param domain - register to domain
+     */
+
+    public boolean unregisterParticipantFromSML(String participantId, String participantSchema, DBDomain domain){
+        LOG.businessDebug(BUS_SML_UNREGISTER_SERVICE_GROUP, participantId, participantSchema, domain.getDomainCode());
+
+        ParticipantIdentifierType normalizedParticipantId = caseSensitivityNormalizer
+                .normalizeParticipantIdentifier(participantSchema,
+                        participantId);
+
+        // unregister only registered participants
+         return smlConnector.unregisterFromDns(normalizedParticipantId, domain);
+
+    }
+
+    /**
+     * Method registers participant to SML. It does not check if Participant is in database or of is already registered
+     *
+     * @param participantId - Participant schema
+     * @param participantSchema - Participant schema
+     * @param domain - register to domain
+     */
+
+    public boolean registerParticipantToSML(String participantId, String participantSchema, DBDomain domain){
+        LOG.businessDebug(BUS_SML_UNREGISTER_SERVICE_GROUP, participantId, participantSchema, domain.getDomainCode());
+
+        ParticipantIdentifierType normalizedParticipantId = caseSensitivityNormalizer
+                .normalizeParticipantIdentifier(participantSchema,
+                        participantId);
+
+        // unregister only  registered participants
+        return smlConnector.registerInDns(normalizedParticipantId, domain);
+
+    }
+
     private DBServiceGroupDomain getAndValidateServiceGroupDomain(String participantId, String participantSchema, String domainCode, SMPMessageCode messageCode) {
         // retrieve participant (session must be on - lazy loading... )
         Optional<DBServiceGroup> optionalServiceGroup = serviceGroupDao.findServiceGroup(participantId, participantSchema);
         if (!optionalServiceGroup.isPresent()){
-            String msg = "Service group not exists!";
+            String msg = "Service group not exists anymore !";
             LOG.businessError(messageCode, participantId, participantId, domainCode, msg);
             throw new SMPRuntimeException(SG_NOT_EXISTS,  participantId, participantSchema);
         }
@@ -179,4 +219,8 @@ public class SMLIntegrationService {
         }
         return optionalServiceGroupDomain.get();
     }
+
+    public boolean isSmlIntegrationEnabled(){
+        return smlConnector.isSmlIntegrationEnabled();
+    }
 }
diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIServiceGroupService.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIServiceGroupService.java
index 74a5e8cfb..c67e2b4a1 100644
--- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIServiceGroupService.java
+++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIServiceGroupService.java
@@ -10,9 +10,11 @@ import eu.europa.ec.edelivery.smp.data.dao.UserDao;
 import eu.europa.ec.edelivery.smp.data.model.*;
 import eu.europa.ec.edelivery.smp.data.ui.*;
 import eu.europa.ec.edelivery.smp.data.ui.enums.EntityROStatus;
+import eu.europa.ec.edelivery.smp.data.ui.enums.SMLAction;
 import eu.europa.ec.edelivery.smp.exceptions.SMPRuntimeException;
 import eu.europa.ec.edelivery.smp.logging.SMPLogger;
 import eu.europa.ec.edelivery.smp.logging.SMPLoggerFactory;
+import eu.europa.ec.edelivery.smp.services.SMLIntegrationService;
 import eu.europa.ec.edelivery.smp.services.ui.filters.ServiceGroupFilter;
 import eu.europa.ec.smp.api.exceptions.XmlInvalidAgainstSchemaException;
 import org.apache.commons.lang3.StringUtils;
@@ -35,7 +37,6 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
-import java.util.stream.Collectors;
 
 import static eu.europa.ec.edelivery.smp.data.ui.ServiceGroupValidationRO.ERROR_CODE_INVALID_EXTENSION;
 import static eu.europa.ec.edelivery.smp.data.ui.ServiceGroupValidationRO.ERROR_CODE_OK;
@@ -56,7 +57,10 @@ public class UIServiceGroupService extends UIServiceBase<DBServiceGroup, Service
     UserDao userDao;
 
     @Autowired
-    private CaseSensitivityNormalizer caseSensitivityNormalizer;
+    CaseSensitivityNormalizer caseSensitivityNormalizer;
+
+    @Autowired
+    SMLIntegrationService smlIntegrationService;
 
 
     @Override
@@ -127,27 +131,91 @@ public class UIServiceGroupService extends UIServiceBase<DBServiceGroup, Service
     }
 
     @Transactional
-    public void updateServiceGroupList(List<ServiceGroupRO> lst) {
+    public List<ParticipantSMLRecord> updateServiceGroupList(List<ServiceGroupRO> lst) {
         boolean suc = false;
+        List<ParticipantSMLRecord> lstRecords = new ArrayList<>();
         for (ServiceGroupRO dRo : lst) {
             if (dRo.getStatus() == EntityROStatus.NEW.getStatusNumber()) {
-                addNewServiceGroup(dRo);
+                lstRecords.addAll(addNewServiceGroup(dRo));
             } else if (dRo.getStatus() == EntityROStatus.UPDATED.getStatusNumber()) {
-                updateServiceGroup(dRo);
+                lstRecords.addAll(updateServiceGroup(dRo));
             } else if (dRo.getStatus() == EntityROStatus.REMOVE.getStatusNumber()) {
-                DBServiceGroup upd = getDatabaseDao().find(dRo.getId());
-                serviceGroupDao.removeServiceGroup(upd);
+                lstRecords.addAll(removeServiceGroup(dRo));
             }
         }
+        // register/unregister participants from domain
+        processSMLRecords(lstRecords);
+
+
+        return lstRecords;
+    }
+
+    /**
+     * Final process of SML records. If participant is to be unregistered it does not update status to database because
+     * it should not be there anymore! For registering it update status!
+     * @param lstRecords
+     */
+    public void processSMLRecords( List<ParticipantSMLRecord> lstRecords){
+        if (!smlIntegrationService.isSmlIntegrationEnabled()){
+            return;
+        }
+        for (ParticipantSMLRecord record: lstRecords){
+            if (record.getStatus()== SMLAction.REGISTER){
+                boolean result = smlIntegrationService.registerParticipantToSML(record.getParticipantIdentifier(),
+                        record.getParticipantScheme(), record.getDomain());
+
+                updateServiceGroupDomainStatus(result, record);
+            }else if (record.getStatus()== SMLAction.UNREGISTER){
+                boolean result = smlIntegrationService.unregisterParticipantFromSML(record.getParticipantIdentifier(),
+                        record.getParticipantScheme(), record.getDomain());
+                // no need to update database because record is deleted
+                updateServiceGroupDomainStatus(result, record);
+
+            }
+        }
+    }
+
+    protected void updateServiceGroupDomainStatus(boolean smlActionStatus, ParticipantSMLRecord record){
+        Optional<DBServiceGroupDomain> optionalServiceGroupDomain = serviceGroupDao.findServiceGroupDomain(record.getParticipantIdentifier(),
+                record.getParticipantScheme(), record.getDomain().getDomainCode());
+        if (optionalServiceGroupDomain.isPresent()) {
+            DBServiceGroupDomain serviceGroupDomain = optionalServiceGroupDomain.get();
+            if (serviceGroupDomain.isSmlRegistered()!= smlActionStatus){
+                serviceGroupDomain.setSmlRegistered(smlActionStatus);
+                serviceGroupDao.updateServiceGroupDomain(serviceGroupDomain);
+            }
+
+        }
+    }
+
+    /**
+     * Remove service group
+     * @param dRo
+     * @return
+     */
+    public List<ParticipantSMLRecord> removeServiceGroup(ServiceGroupRO dRo){
+        List<ParticipantSMLRecord> participantSMLRecordList = new ArrayList<>();
+
+        DBServiceGroup dbServiceGroup = getDatabaseDao().find(dRo.getId());
+        // first update domains
+        List<DBServiceGroupDomain> dbServiceGroupDomainList = dbServiceGroup.getServiceGroupDomains();
+        dbServiceGroupDomainList.forEach(dro -> {
+                participantSMLRecordList.add( new ParticipantSMLRecord(SMLAction.UNREGISTER, dro.getServiceGroup().getParticipantIdentifier(),
+                        dro.getServiceGroup().getParticipantScheme(),dro.getDomain()));
+        });
+        serviceGroupDao.removeServiceGroup(dbServiceGroup);
+        return participantSMLRecordList;
     }
 
+
+
     /**
      * Method validates and converts UI resource object entity to database entity and persists it to database
      *
      * @param serviceGroupRO
      */
-    private void addNewServiceGroup(ServiceGroupRO serviceGroupRO) {
-        // normalize indentifiers
+    protected List<ParticipantSMLRecord> addNewServiceGroup(ServiceGroupRO serviceGroupRO) {
+        // normalize identifiers
         normalizeIdentifiers(serviceGroupRO);
 
         DBServiceGroup dbServiceGroup = new DBServiceGroup();
@@ -159,7 +227,7 @@ public class UIServiceGroupService extends UIServiceBase<DBServiceGroup, Service
 
         // first update domains
         // validate (if domains are added only once) and  create domain list for service group.
-        createDomainsForNewServiceGroup(serviceGroupRO, dbServiceGroup);
+        List<ParticipantSMLRecord> listOfActions = createDomainsForNewServiceGroup(serviceGroupRO, dbServiceGroup);
 
 
         // sort service metadata by domain
@@ -181,6 +249,7 @@ public class UIServiceGroupService extends UIServiceBase<DBServiceGroup, Service
             dbServiceGroup.setExtension(buff);
         }
         getDatabaseDao().persistFlushDetach(dbServiceGroup);
+        return listOfActions;
     }
 
     private void normalizeIdentifiers(ServiceGroupRO sgo) {
@@ -202,19 +271,26 @@ public class UIServiceGroupService extends UIServiceBase<DBServiceGroup, Service
      * @param serviceGroupRO
      * @param dbServiceGroup
      */
-    protected void createDomainsForNewServiceGroup(ServiceGroupRO serviceGroupRO, DBServiceGroup dbServiceGroup) {
+    protected List<ParticipantSMLRecord> createDomainsForNewServiceGroup(ServiceGroupRO serviceGroupRO, DBServiceGroup dbServiceGroup) {
+
+        List<ParticipantSMLRecord> participantSMLRecordList = new ArrayList<>();
         // first update domains
-        List<ServiceGroupDomainRO> serviceGroupDomainROList = validateDomainList(serviceGroupRO);
+        List<ServiceGroupDomainRO> serviceGroupDomainROList = serviceGroupRO.getServiceGroupDomains();
         // validate (if domains are added only once) and  create domain list for service group.
         serviceGroupDomainROList.forEach(dro -> {
             // everting ok  find domain and add it to service group
             Optional<DBDomain> dmn = domainDao.getDomainByCode(dro.getDomainCode());
             if (dmn.isPresent()) {
-                dbServiceGroup.addDomain(dmn.get());
+                DBServiceGroupDomain domain =  dbServiceGroup.addDomain(dmn.get());
+                participantSMLRecordList.add( new ParticipantSMLRecord(SMLAction.REGISTER,
+                        serviceGroupRO.getParticipantIdentifier(),
+                        serviceGroupRO.getParticipantScheme(),
+                        domain.getDomain()));
             } else {
                 throw new SMPRuntimeException(DOMAIN_NOT_EXISTS, dro.getDomainCode());
             }
         });
+        return participantSMLRecordList;
     }
 
 
@@ -223,8 +299,9 @@ public class UIServiceGroupService extends UIServiceBase<DBServiceGroup, Service
      *
      * @param serviceGroupRO
      */
-    protected void updateServiceGroup(ServiceGroupRO serviceGroupRO) {
-        // normalize indentifiers
+    protected List<ParticipantSMLRecord> updateServiceGroup(ServiceGroupRO serviceGroupRO) {
+
+        // normalize identifiers
         normalizeIdentifiers(serviceGroupRO);
         // find and validate service group
         DBServiceGroup dbServiceGroup = findAndValidateServiceGroup(serviceGroupRO);
@@ -233,7 +310,7 @@ public class UIServiceGroupService extends UIServiceBase<DBServiceGroup, Service
         updateUsersOnServiceGroup(serviceGroupRO, dbServiceGroup);
 
         // update domain
-        updateDomainsForServiceGroup(serviceGroupRO, dbServiceGroup);
+        List<ParticipantSMLRecord> participantSMLRecordList =  updateDomainsForServiceGroup(serviceGroupRO, dbServiceGroup);
 
         //update service metadata
         List<ServiceMetadataRO> serviceMetadataROList = serviceGroupRO.getServiceMetadata();
@@ -304,6 +381,7 @@ public class UIServiceGroupService extends UIServiceBase<DBServiceGroup, Service
 
         // persist it to database
         getDatabaseDao().update(dbServiceGroup);
+        return participantSMLRecordList;
     }
 
     /**
@@ -312,9 +390,12 @@ public class UIServiceGroupService extends UIServiceBase<DBServiceGroup, Service
      * @param serviceGroupRO
      * @param dbServiceGroup
      */
-    protected void updateDomainsForServiceGroup(ServiceGroupRO serviceGroupRO, DBServiceGroup dbServiceGroup) {
+    protected List<ParticipantSMLRecord> updateDomainsForServiceGroup(ServiceGroupRO serviceGroupRO, DBServiceGroup dbServiceGroup) {
+        List<ParticipantSMLRecord> participantSMLRecordList = new ArrayList<>();
+
         // / validate (if domains are added only once) and  create domain list for service group.
-        List<ServiceGroupDomainRO> serviceGroupDomainROList = validateDomainList(serviceGroupRO);
+       // List<ServiceGroupDomainRO> serviceGroupDomainROList = validateDomainList(serviceGroupRO);
+        List<ServiceGroupDomainRO> serviceGroupDomainROList = serviceGroupRO.getServiceGroupDomains();
         // copy array list of old domains and then put them back. Domain not added back will be deleted by hibernate
         // ...
         List<DBServiceGroupDomain> lstOldSGDomains = new ArrayList<>();
@@ -327,22 +408,34 @@ public class UIServiceGroupService extends UIServiceBase<DBServiceGroup, Service
             if (dsg != null) {
                 // put it back - no need to call addDomain
                 dbServiceGroup.getServiceGroupDomains().add(dsg);
-                // remove from list
+                // remove from old domain list
                 lstOldSGDomains.remove(dsg);
             } else {
-                // everting ok  find domain and add it to service group
+                // new domain  - find dbDomain and add it to service group
                 Optional<DBDomain> dmn = domainDao.getDomainByCode(serviceGroupDomainRO.getDomainCode());
                 if (dmn.isPresent()) {
-                    dbServiceGroup.addDomain(dmn.get());
+
+                    DBServiceGroupDomain sgd =  dbServiceGroup.addDomain(dmn.get());
+                    participantSMLRecordList.add(new ParticipantSMLRecord( SMLAction.REGISTER,
+                            sgd.getServiceGroup().getParticipantIdentifier(),
+                            sgd.getServiceGroup().getParticipantScheme(),
+                            sgd.getDomain()));
                 } else {
                     throw new SMPRuntimeException(DOMAIN_NOT_EXISTS, serviceGroupDomainRO.getDomainCode());
                 }
             }
         });
-        // remove references
+        // remove old domains
         lstOldSGDomains.forEach(dbServiceGroupDomain -> {
+            participantSMLRecordList.add(new ParticipantSMLRecord( SMLAction.UNREGISTER,
+                    dbServiceGroupDomain.getServiceGroup().getParticipantIdentifier(),
+                    dbServiceGroupDomain.getServiceGroup().getParticipantScheme(),
+                    dbServiceGroupDomain.getDomain()));
+
             dbServiceGroupDomain.setServiceGroup(null);
+
         });
+        return participantSMLRecordList;
     }
 
     /**
@@ -350,7 +443,7 @@ public class UIServiceGroupService extends UIServiceBase<DBServiceGroup, Service
      *
      * @param serviceGroupRO
      * @return
-     */
+     *
     protected List<ServiceGroupDomainRO> validateDomainList(ServiceGroupRO serviceGroupRO) {
         List<ServiceGroupDomainRO> serviceGroupDomainROList = serviceGroupRO.getServiceGroupDomains();
         // validate (if domains are added only once) and  create domain list for service group.
@@ -365,7 +458,7 @@ public class UIServiceGroupService extends UIServiceBase<DBServiceGroup, Service
             }
         });
         return serviceGroupDomainROList;
-    }
+    }*/
 
     /**
      * Update users on service group. Method is OK for update and add new domain
diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/sml/SmlConnector.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/sml/SmlConnector.java
index e82629674..34f175055 100644
--- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/sml/SmlConnector.java
+++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/sml/SmlConnector.java
@@ -93,14 +93,15 @@ public class SmlConnector implements ApplicationContextAware {
     }
 
 
-    public void unregisterFromDns(ParticipantIdentifierType normalizedParticipantId, DBDomain domain) {
+    public boolean unregisterFromDns(ParticipantIdentifierType normalizedParticipantId, DBDomain domain) {
         if (!smlIntegrationEnabled) {
-            return;
+            return false;
         }
         log.info("Removing Participant from BDMSL: {} ", asString(normalizedParticipantId));
         try {
             ServiceMetadataPublisherServiceForParticipantType smlRequest = toBusdoxParticipantId(normalizedParticipantId, domain.getSmlSmpId());
             getClient(domain).delete(smlRequest);
+            return true;
         } catch (Exception e) {
             throw new SMPRuntimeException(ErrorCode.SML_INTEGRATION_EXCEPTION,e, ExceptionUtils.getRootCauseMessage(e));
         }
diff --git a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/SMLIntegrationServiceIntegrationTest.java b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/SMLIntegrationServiceIntegrationTest.java
index 87c3b4849..855903ada 100644
--- a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/SMLIntegrationServiceIntegrationTest.java
+++ b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/SMLIntegrationServiceIntegrationTest.java
@@ -107,7 +107,7 @@ public class SMLIntegrationServiceIntegrationTest extends AbstractServiceIntegra
     }
 
     @Test
-    public void registerParticipantToSML() throws NotFoundFault, UnauthorizedFault, InternalErrorFault, BadRequestFault {
+    public void registerParticipant() throws NotFoundFault, UnauthorizedFault, InternalErrorFault, BadRequestFault {
         /* given (init database - check setup)
          * Domain: TEST_DOMAIN_CODE_1
          * Users: USERNAME_1, USER_CERT_2
@@ -125,7 +125,7 @@ public class SMLIntegrationServiceIntegrationTest extends AbstractServiceIntegra
     }
 
     @Test
-    public void unRegisterParticipantFromSML() throws NotFoundFault, UnauthorizedFault, InternalErrorFault, BadRequestFault {
+    public void unRegisterParticipant() throws NotFoundFault, UnauthorizedFault, InternalErrorFault, BadRequestFault {
         /* given (init database - check setup)
          * Domain: TEST_DOMAIN_CODE_1
          * Users: USERNAME_1, USER_CERT_2
@@ -143,7 +143,7 @@ public class SMLIntegrationServiceIntegrationTest extends AbstractServiceIntegra
     }
 
     @Test
-    public void registerParticipantToSML_NotExists(){
+    public void registerParticipant_NotExists(){
         expectedExeption.expect(SMPRuntimeException.class);
         String notExistsId = TEST_SG_ID_1+"NotExists";
         expectedExeption.expectMessage("ServiceGroup not found (part. id: '"+TEST_SG_ID_1+"NotExists', part. sch.: '"+TEST_SG_SCHEMA_1+"')!");
@@ -153,7 +153,7 @@ public class SMLIntegrationServiceIntegrationTest extends AbstractServiceIntegra
     }
 
     @Test
-    public void registerParticipantToSML_NotOnDomain(){
+    public void registerParticipant_NotOnDomain(){
         expectedExeption.expect(SMPRuntimeException.class);
         expectedExeption.expectMessage("Service group not registered for domain (domain: "+TEST_DOMAIN_CODE_2+", part. id: '"+TEST_SG_ID_1+"', part. sch.: '"+TEST_SG_SCHEMA_1+"')!");
 
@@ -161,6 +161,35 @@ public class SMLIntegrationServiceIntegrationTest extends AbstractServiceIntegra
         testInstance.registerParticipant(TEST_SG_ID_1,TEST_SG_SCHEMA_1,TEST_DOMAIN_CODE_2 );
     }
 
+    @Test
+    public void isSmlIntegrationEnabled(){
+        assertTrue(testInstance.isSmlIntegrationEnabled());
+    }
+
+    @Test
+    public void registerParticipantToSML() throws NotFoundFault, UnauthorizedFault, InternalErrorFault, BadRequestFault {
+        DBDomain testDomain01 = domainDao.getDomainByCode(TEST_DOMAIN_CODE_1).get();
+        // when
+        testInstance.registerParticipantToSML(TEST_SG_ID_1,TEST_SG_SCHEMA_1,testDomain01 );
+
+        //then -- expect on call
+        assertEquals(1, integrationMock.getParticipantManagmentClientMocks().size());
+        verify(integrationMock.getParticipantManagmentClientMocks().get(0)).create(any());
+        Mockito.verifyNoMoreInteractions(integrationMock.getParticipantManagmentClientMocks().toArray());
+    }
+
+    @Test
+    public void unregisterParticipantFromSML() throws NotFoundFault, UnauthorizedFault, InternalErrorFault, BadRequestFault {
+        DBDomain testDomain01 = domainDao.getDomainByCode(TEST_DOMAIN_CODE_1).get();
+        // when
+        testInstance.unregisterParticipantFromSML(TEST_SG_ID_1,TEST_SG_SCHEMA_1,testDomain01 );
+
+        //then -- expect on call
+        assertEquals(1, integrationMock.getParticipantManagmentClientMocks().size());
+        verify(integrationMock.getParticipantManagmentClientMocks().get(0)).delete(any());
+        Mockito.verifyNoMoreInteractions(integrationMock.getParticipantManagmentClientMocks().toArray());
+    }
+
 
 
 }
diff --git a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/SMLIntegrationServiceNoSMLIntegrationTest.java b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/SMLIntegrationServiceNoSMLIntegrationTest.java
index 22465e8fc..3d20fd6a9 100644
--- a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/SMLIntegrationServiceNoSMLIntegrationTest.java
+++ b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/SMLIntegrationServiceNoSMLIntegrationTest.java
@@ -26,6 +26,7 @@ import org.springframework.transaction.annotation.Transactional;
 
 import static eu.europa.ec.edelivery.smp.testutil.TestConstants.*;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 
 /**
  * Purpose of class is to test ServiceGroupService base methods
@@ -87,4 +88,9 @@ public class SMLIntegrationServiceNoSMLIntegrationTest extends AbstractServiceIn
         testInstance.registerParticipant(TEST_SG_ID_1, TEST_SG_SCHEMA_1, TEST_DOMAIN_CODE_1);
     }
 
+    @Test
+    public void isSmlIntegrationEnabled(){
+        assertFalse(testInstance.isSmlIntegrationEnabled());
+    }
+
 }
diff --git a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/ui/UIServiceGroupServiceUpdateListIntegrationTest.java b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/ui/UIServiceGroupServiceUpdateListIntegrationTest.java
new file mode 100644
index 000000000..03ad6459d
--- /dev/null
+++ b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/ui/UIServiceGroupServiceUpdateListIntegrationTest.java
@@ -0,0 +1,221 @@
+package eu.europa.ec.edelivery.smp.services.ui;
+
+
+import eu.europa.ec.edelivery.smp.config.SmlIntegrationConfiguration;
+import eu.europa.ec.edelivery.smp.data.model.DBDomain;
+import eu.europa.ec.edelivery.smp.data.model.DBServiceGroup;
+import eu.europa.ec.edelivery.smp.data.model.DBUser;
+import eu.europa.ec.edelivery.smp.data.ui.ParticipantSMLRecord;
+import eu.europa.ec.edelivery.smp.data.ui.ServiceGroupDomainRO;
+import eu.europa.ec.edelivery.smp.data.ui.ServiceGroupRO;
+import eu.europa.ec.edelivery.smp.data.ui.ServiceResult;
+import eu.europa.ec.edelivery.smp.data.ui.enums.EntityROStatus;
+import eu.europa.ec.edelivery.smp.data.ui.enums.SMLAction;
+import eu.europa.ec.edelivery.smp.services.AbstractServiceIntegrationTest;
+import eu.europa.ec.edelivery.smp.testutil.TestConstants;
+import eu.europa.ec.edelivery.smp.testutil.TestDBUtils;
+import eu.europa.ec.edelivery.smp.testutil.TestROUtils;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.UUID;
+
+import static eu.europa.ec.edelivery.smp.testutil.TestConstants.TEST_DOMAIN_CODE_1;
+import static eu.europa.ec.edelivery.smp.testutil.TestConstants.TEST_DOMAIN_CODE_2;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+
+/**
+ * Purpose of class is to test ServiceGroupService base methods
+ *
+ * @author Joze Rihtarsic
+ * @since 4.1
+ */
+@ContextConfiguration(classes = {UIServiceGroupService.class, UIServiceMetadataService.class, SmlIntegrationConfiguration.class})
+@TestPropertySource(properties = {"bdmsl.integration.enabled=true"})
+public class UIServiceGroupServiceUpdateListIntegrationTest extends AbstractServiceIntegrationTest {
+
+    @Rule
+    public ExpectedException expectedExeption = ExpectedException.none();
+
+    @Autowired
+    protected UIServiceGroupService testInstance;
+
+    @Autowired
+    protected UIServiceMetadataService uiServiceMetadataService;
+
+    @Before
+    public void setup() {
+        integrationMock.reset();
+        prepareDatabaseForMultipeDomainEnv();
+    }
+
+    @Autowired
+    SmlIntegrationConfiguration integrationMock;
+
+    protected void insertDataObjectsForOwner(int size, DBUser owner) {
+        for (int i = 0; i < size; i++) {
+            insertServiceGroup(String.format("%4d", i), true, owner);
+        }
+    }
+
+    protected void insertDataObjects(int size) {
+        insertDataObjectsForOwner(size, null);
+    }
+
+    protected DBServiceGroup insertServiceGroup(String id, boolean withExtension, DBUser owner) {
+        DBServiceGroup d = TestDBUtils.createDBServiceGroup(String.format("0007:%s:utest", id), TestConstants.TEST_SG_SCHEMA_1, withExtension);
+        if (owner != null) {
+            d.getUsers().add(owner);
+        }
+        serviceGroupDao.persistFlushDetach(d);
+        return d;
+    }
+
+    @Test
+    public void addNewServiceGroupTestSMLRecords() {
+        // given
+        DBDomain dbDomain1 = domainDao.getDomainByCode(TEST_DOMAIN_CODE_1).get();
+        DBDomain dbDomain2 = domainDao.getDomainByCode(TEST_DOMAIN_CODE_2).get();
+        ServiceGroupRO serviceGroupRO = TestROUtils.createROServiceGroupForDomains(UUID.randomUUID().toString(), UUID.randomUUID().toString(),
+                dbDomain1, dbDomain2);
+        // When
+        List<ParticipantSMLRecord> lst = testInstance.addNewServiceGroup(serviceGroupRO);
+        // then
+        assertEquals(2, lst.size());
+        assertEquals(SMLAction.REGISTER, lst.get(0).getStatus());
+        assertEquals(SMLAction.REGISTER, lst.get(1).getStatus());
+        assertEquals(dbDomain1, lst.get(0).getDomain());
+        assertEquals(dbDomain2, lst.get(1).getDomain());
+        assertEquals(lst.get(0).getParticipantIdentifier(), lst.get(1).getParticipantIdentifier());
+        assertEquals(serviceGroupRO.getParticipantIdentifier(), lst.get(0).getParticipantIdentifier());
+        assertEquals(serviceGroupRO.getParticipantScheme(), lst.get(0).getParticipantScheme());
+    }
+
+    @Test
+    @Transactional
+    public void removeServiceGroupTestSMLRecords() {
+        // given
+        ServiceResult<ServiceGroupRO> res = testInstance.getTableList(-1, -1, null, null, null);
+        assertFalse(res.getServiceEntities().isEmpty());
+        ServiceGroupRO roToDelete = res.getServiceEntities().get(0);
+        assertFalse(roToDelete.getServiceGroupDomains().isEmpty());
+
+        // When
+        List<ParticipantSMLRecord> lst = testInstance.removeServiceGroup(roToDelete);
+        // then
+        assertEquals(roToDelete.getServiceGroupDomains().size(), lst.size());
+        lst.forEach(val -> {
+            assertEquals(SMLAction.UNREGISTER, val.getStatus());
+        });
+
+    }
+
+    @Test
+    @Transactional
+    public void updateServiceGroupTestSMLRecordsRemoveDomain() {
+
+        // given
+        DBDomain dbDomain1 = domainDao.getDomainByCode(TEST_DOMAIN_CODE_1).get();
+        DBDomain dbDomain2 = domainDao.getDomainByCode(TEST_DOMAIN_CODE_2).get();
+        DBServiceGroup dbServiceGroup = TestDBUtils.createDBServiceGroupRandom();
+        dbServiceGroup.addDomain(dbDomain1);
+        dbServiceGroup.addDomain(dbDomain2);
+        serviceGroupDao.persistFlushDetach(dbServiceGroup);
+        ServiceGroupRO roToUpdate = testInstance.getServiceGroupById(dbServiceGroup.getId());
+        // when
+        ServiceGroupDomainRO dro = roToUpdate.getServiceGroupDomains().remove(0);
+        List<ParticipantSMLRecord> lst = testInstance.updateServiceGroup(roToUpdate);
+        // then
+        assertEquals(1, lst.size());
+        assertEquals(SMLAction.UNREGISTER, lst.get(0).getStatus());
+        assertEquals(dro.getDomainCode(), lst.get(0).getDomain().getDomainCode());
+        assertEquals(roToUpdate.getParticipantIdentifier(), lst.get(0).getParticipantIdentifier());
+        assertEquals(roToUpdate.getParticipantScheme(), lst.get(0).getParticipantScheme());
+    }
+
+    @Test
+    @Transactional
+    public void updateServiceGroupTestSMLRecordsAddDomain() {
+
+        // given
+        DBDomain dbDomain1 = domainDao.getDomainByCode(TEST_DOMAIN_CODE_1).get();
+        DBDomain dbDomain2 = domainDao.getDomainByCode(TEST_DOMAIN_CODE_2).get();
+        DBServiceGroup dbServiceGroup = TestDBUtils.createDBServiceGroupRandom();
+        dbServiceGroup.addDomain(dbDomain1);
+        serviceGroupDao.persistFlushDetach(dbServiceGroup);
+        ServiceGroupRO roToUpdate = testInstance.getServiceGroupById(dbServiceGroup.getId());
+        // when
+        ServiceGroupDomainRO sgr = new ServiceGroupDomainRO();
+        sgr.setDomainCode(dbDomain2.getDomainCode());
+        sgr.setSmlSubdomain(dbDomain2.getSmlSubdomain());
+        sgr.setDomainId(dbDomain2.getId());
+        roToUpdate.getServiceGroupDomains().add(sgr);
+        List<ParticipantSMLRecord> lst = testInstance.updateServiceGroup(roToUpdate);
+        // then
+        assertEquals(1, lst.size());
+        assertEquals(SMLAction.REGISTER, lst.get(0).getStatus());
+        assertEquals(sgr.getDomainCode(), lst.get(0).getDomain().getDomainCode());
+        assertEquals(roToUpdate.getParticipantIdentifier(), lst.get(0).getParticipantIdentifier());
+    }
+
+    @Test
+    @Transactional
+    public void updateListSMLRecordsAddDomain() {
+
+        // given
+        DBDomain dbDomain1 = domainDao.getDomainByCode(TEST_DOMAIN_CODE_1).get();
+        DBDomain dbDomain2 = domainDao.getDomainByCode(TEST_DOMAIN_CODE_2).get();
+        DBServiceGroup dbServiceGroup1 = TestDBUtils.createDBServiceGroupRandom();
+        DBServiceGroup dbServiceGroup2 = TestDBUtils.createDBServiceGroupRandom();
+        dbServiceGroup1.addDomain(dbDomain1);
+        dbServiceGroup2.addDomain(dbDomain1);
+        serviceGroupDao.persistFlushDetach(dbServiceGroup1);
+        serviceGroupDao.persistFlushDetach(dbServiceGroup2);
+        ServiceGroupRO serviceGroupROAdd = TestROUtils.createROServiceGroupForDomains(UUID.randomUUID().toString(), UUID.randomUUID().toString(),
+                dbDomain1, dbDomain2);
+        ServiceGroupRO serviceGroupROUpdate = testInstance.getServiceGroupById(dbServiceGroup1.getId());
+        ServiceGroupRO serviceGroupRORemove = testInstance.getServiceGroupById(dbServiceGroup2.getId());
+        serviceGroupROAdd.setStatus(EntityROStatus.NEW.getStatusNumber());
+        serviceGroupRORemove.setStatus(EntityROStatus.REMOVE.getStatusNumber());
+        serviceGroupROUpdate.setStatus(EntityROStatus.UPDATED.getStatusNumber());
+        serviceGroupROUpdate.getServiceGroupDomains().clear();
+        ServiceGroupDomainRO sgr = new ServiceGroupDomainRO();
+        sgr.setDomainCode(dbDomain2.getDomainCode());
+        sgr.setSmlSubdomain(dbDomain2.getSmlSubdomain());
+        sgr.setDomainId(dbDomain2.getId());
+        serviceGroupROUpdate.getServiceGroupDomains().add(sgr);
+
+        List<ServiceGroupRO> lstRo = Arrays.asList(serviceGroupROAdd, serviceGroupRORemove, serviceGroupROUpdate);
+
+        List<ParticipantSMLRecord> lst = testInstance.updateServiceGroupList(lstRo);
+        // then
+        assertEquals(5, lst.size());
+        assertEquals(SMLAction.REGISTER, lst.get(0).getStatus());
+        assertEquals(serviceGroupROAdd.getParticipantIdentifier(), lst.get(0).getParticipantIdentifier());
+        assertEquals(SMLAction.REGISTER, lst.get(1).getStatus());
+        assertEquals(serviceGroupROAdd.getParticipantIdentifier(), lst.get(1).getParticipantIdentifier());
+
+
+        assertEquals(SMLAction.UNREGISTER, lst.get(2).getStatus());
+        assertEquals(serviceGroupRORemove.getParticipantIdentifier(), lst.get(2).getParticipantIdentifier());
+
+        assertEquals(SMLAction.REGISTER, lst.get(3).getStatus());
+        assertEquals(dbDomain2.getDomainCode(), lst.get(3).getDomain().getDomainCode());
+        assertEquals(serviceGroupROUpdate.getParticipantIdentifier(), lst.get(3).getParticipantIdentifier());
+        assertEquals(SMLAction.UNREGISTER, lst.get(4).getStatus());
+        assertEquals(dbDomain1.getDomainCode(), lst.get(4).getDomain().getDomainCode());
+
+        assertEquals(5, integrationMock.getParticipantManagmentClientMocks().size());
+    }
+
+}
diff --git a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/smlintegration/SmlConnectorTest.java b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/smlintegration/SmlConnectorTest.java
index e5d5fe42b..7d23f47b5 100644
--- a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/smlintegration/SmlConnectorTest.java
+++ b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/smlintegration/SmlConnectorTest.java
@@ -33,6 +33,7 @@ import java.util.ArrayList;
 import java.util.List;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.verify;
 import static org.springframework.beans.factory.config.ConfigurableBeanFactory.SCOPE_PROTOTYPE;
@@ -79,10 +80,11 @@ public class SmlConnectorTest {
 
     @Test
     public void testRegisterInDns() throws UnauthorizedFault, NotFoundFault, InternalErrorFault, BadRequestFault {
-    //when
-        smlConnector.registerInDns(PARTICIPANT_ID, DEFAULT_DOMAIN);
+        //when
+        boolean result = smlConnector.registerInDns(PARTICIPANT_ID, DEFAULT_DOMAIN);
 
         //then
+        assertTrue(result);
         assertEquals(1, smlClientMocks.size());
         verify(smlClientMocks.get(0)).create(any());
         Mockito.verifyNoMoreInteractions(smlClientMocks.toArray());
@@ -104,9 +106,10 @@ public class SmlConnectorTest {
     @Test
     public void testUnregisterFromDns() throws UnauthorizedFault, NotFoundFault, InternalErrorFault, BadRequestFault {
         //when
-        smlConnector.unregisterFromDns(PARTICIPANT_ID, DEFAULT_DOMAIN);
+        boolean result = smlConnector.unregisterFromDns(PARTICIPANT_ID, DEFAULT_DOMAIN);
 
         //then
+        assertTrue(result);
         assertEquals(1, smlClientMocks.size());
         verify(smlClientMocks.get(0)).delete(any());
         Mockito.verifyNoMoreInteractions(smlClientMocks.toArray());
diff --git a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/testutil/TestDBUtils.java b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/testutil/TestDBUtils.java
index 19a6f798e..6b8371403 100644
--- a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/testutil/TestDBUtils.java
+++ b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/testutil/TestDBUtils.java
@@ -57,6 +57,10 @@ public class TestDBUtils {
         return createDBServiceGroup(id, sch, true);
     }
 
+    public static DBServiceGroup createDBServiceGroupRandom() {
+        return createDBServiceGroup(UUID.randomUUID().toString(), UUID.randomUUID().toString(), true);
+    }
+
     public static DBServiceGroup createDBServiceGroup(String id, String sch, boolean withExtension) {
         DBServiceGroup grp = new DBServiceGroup();
         grp.setParticipantIdentifier(id);
diff --git a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/testutil/TestROUtils.java b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/testutil/TestROUtils.java
index 7be76dde9..f107f79b3 100644
--- a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/testutil/TestROUtils.java
+++ b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/testutil/TestROUtils.java
@@ -50,6 +50,15 @@ public class TestROUtils {
         return sgo;
     }
 
+    public static ServiceGroupRO createROServiceGroupForDomains(String id, String sch, DBDomain ... domains) {
+        ServiceGroupRO sgo =  createROServiceGroup(id, sch);
+        Arrays.asList(domains).forEach(domain -> {
+            ServiceGroupDomainRO sgd = createServiceGroupDomain(domain);
+            sgo.getServiceGroupDomains().add(sgd);
+        });
+        return sgo;
+    }
+
     public static ServiceGroupRO createROServiceGroup(String id, String sch) {
         return createROServiceGroup(id, sch, true);
     }
diff --git a/smp-webapp/src/test/resources/keystores/smp-keystore_multiple_domains.jks b/smp-webapp/src/test/resources/keystores/smp-keystore_multiple_domains.jks
index eedfa138dafe601321b05278cac7fcd2ce830b9c..22e4cc5e038d4501a4bf0d0d95e8d0d0b72d13e4 100644
GIT binary patch
delta 1425
zcmZoT!?^ANBhSCL|K2h%FfcJNFo;bK<d@p0wo{qYl7WFSJ?eZ*_T-1rGWGd8ZkbFl
z;+Ex8d=kd*UK07+dh!1KZ#g+7?!2>HtGD3eFX>jhU03(oMSjorH(*$B>Ih>|*NiI>
zx;2xRZ@GHE-K5t{#`MX{N1J`;{+itRpnF&I+bq)rOQtuQw+Y={Iy-TLOYwZWt1_9-
z7m4I7S+ge9@1s=neoOtj;13^{rG9z+v_3LZmw%<x>RcP|Td@Zf7b#v~p1+-md&a}t
zU%T8U>%HhLZL^)7eYKiTyEbG)VRn_H>-)3T9yVz&w)NLc$P}H~5WTu{f}DBe^9O!8
z%jc~XdzW#^o<(5wr_#GuVxF)XNGzC?emm=*%F@`m7bO^L=9+px({R{guzW+YNRi6L
z!*WmSd$;y)?eX1GV_LYw)zaj~q0sQ2+Be)wLx28}2(#NIVtA~<_(yu}_y2pA1V~L*
zKPPaIb#C>=eX5FA_WCmEWxp<OJ@0k$h9$fIF`pB2e*W1~cBe+8x%eK3W7CO0KXVLK
zH*N1wJ^uCdL}8bT<KG0PF4%4pdfEQd>^WhtQ+L-n9o4QE5tl2P>AbY~x-(<Zd!GnC
z+v5{wY+wJV^vkmCYbX2a)r<D;4|@LSiFEPBpR0DwueEA#|2*xP0%Op^`aemltq+S!
zuTp>2W6`#9*|p^7wXISITWt4S?Yz|)>$TOyVWx&+#AL_WVZXEZq?$i5M7`Y~es)pE
zxmV9$t!G)F?(vqtb5*^rz=eGb+qZ`t`Xn}S{sU3x=hltdHsN`zx|CUOYFm}v5T51T
zW#6ddp7eL)Gg-FPr`TeI85lTKANk(a=s39eg8IHWy_drNhA!HiUE|y>eYSm}n&{&)
z{exaTI&p_Ic3spvyCjIo;`zg4C-%+R>2CE#MMeC^8KcPW-&8NW{k7}kb>Sk>TPb}1
z{Jj$Q&6;v2r|Cky^n=ZbA^uF0`He+4-&a1T%VlH$O0coT*Zsi>_J^tVW3QRBCK)A#
zC2zd^?$cJLhV}Bs%M31NT}+nn{M4+wDCKs(+mGm!Dz8nlHqX;6^nbkc|Ic})dR_JL
z`))5HHbq$;NHz#QGvSe~>9Wr?tSVFNX2w+t`Q)`Z#o2$#y*<l(Q<nAJ5U!PnUzaR@
zCtY}<ZpWRO>h{+k)?5mh<#MdPwfn@H*geTz)1G&pyw~<epK<!;#yL~>MB6r{c$m6;
zKjp}v7+f-C`*ORQ;|Cd(rg1Cqw7(V#eZ_ct=ib<mpC^pCmD(rX-rk_5c)M=$MhDlL
z)e>8-)GfSUQP=*dRJPt?dY7Vy_M=xu+wLe%d1|<+>dV^u!IH1_^v;%Qifpv!<E>Xp
zO4xMv$J6e2wHb?6zdOzPl$A$-ec$=D_jU)VICq@8q91&4<$bgDZCQn#*Z#io=#Mc~
zdwk-_k?YSVOqxIASLX3gzaHt?&v>ZnqamTO?T>lAaQ@m12d_i*FZ(@b98=}4x9~6D
z;#PbxPyC0c3R~)=7v0SBdYZ1z``X7?;@Zj{QlIIiRlIKQg<Q4LI_c7<Wxhf)eL8~#
z6XmqLoc5KmFL*C`dfk~%#gkfN&Zj&TQ$A|;<YZvX)y&VGmh0a9$hhTx|LOIm{<=kr
zs;=9AX#7-@cGZu6dfBCmMLJt{pV`f%q;N61^6_c8A5M4d9?U&(Pjb_<AEy*<xyUpK
zY<#+X!&CRr`j%Gd7^XE#W&}MeS+?@9`<X{ZyYm(9pI`j)!}9V}quXKGf)^dOyG%Y&
zeZaYn_h&C>wZc6azNNp4PI9j5kbZx|eV_QU`|&p<vWx!3xy_xhci)bsc2Di9s^+@#
zSpQ5g?>+d2q4f6uz5kcQbZ*#b+Pg<>L)M;@6DRps{*B7Av~PJJT$k{Ti~rzai<hlG
urN3;seqpm>C?vt!bBS(V7icFU;&5>5`;$#O*S2mj&XeeHVb?f2jROD>X~x9>

delta 58
zcmZ4YfU)5WBhSCL|K2h%FfcJNFo;YJ<d@p0wo`fYH;r?;n{P+ki-?$Azvd_to69(9
Qwy3v!?&gi@cUK4j0H}NyHvj+t

-- 
GitLab