From 357cb8f835f7fa21f46c3bb60f5175c4dbadbf87 Mon Sep 17 00:00:00 2001
From: RIHTARSIC Joze <joze.rihtarsic@ext.ec.europa.eu>
Date: Thu, 22 Jun 2023 07:54:39 +0200
Subject: [PATCH] small upodates and fixes

---
 .../_search-table.component-theme.scss        |  4 +-
 .../domain-sml-integration-panel.component.ts |  2 -
 .../extension-panel.component.ts              |  1 +
 .../admin-extension/extension.component.html  |  2 +-
 .../edelivery/smp/data/dao/ResourceDao.java   | 46 -------------------
 .../ec/edelivery/smp/data/model/DBDomain.java |  2 +-
 .../smp/exceptions/SMLErrorMessages.java      |  4 +-
 .../edelivery/smp/services/DomainService.java | 32 +++++++++++--
 .../smp/services/SMLIntegrationService.java   | 16 ++++---
 .../smp/sml/SmlConnectorTestConstants.java    |  6 +--
 .../mysql5innoDb-4.2_to_5.0-step_01.sql       | 11 +----
 .../oracle10g-4.2_to_5.0-step_01.sql          |  3 --
 .../database-scripts/mysql5innodb.ddl         |  3 --
 .../smp-setup/database-scripts/oracle10g.ddl  |  3 --
 14 files changed, 49 insertions(+), 86 deletions(-)

diff --git a/smp-angular/src/app/common/search-table/_search-table.component-theme.scss b/smp-angular/src/app/common/search-table/_search-table.component-theme.scss
index 003ecf557..65deaee76 100644
--- a/smp-angular/src/app/common/search-table/_search-table.component-theme.scss
+++ b/smp-angular/src/app/common/search-table/_search-table.component-theme.scss
@@ -8,8 +8,8 @@
     background-color: smp.get-theme-color($theme, primary, 800, 0.1) !important;
   }
 
-  .ngx-datatable .datatable-row-selected {
-    background-color: red;
+  .ngx-datatable .datatable-body-row.active {
+    background-color: smp.get-theme-color($theme, primary, 200) !important;
   }
 
   .ngx-datatable .datatable-body-row:hover,.ngx-datatable .datatable-row-odd:hover  {
diff --git a/smp-angular/src/app/system-settings/admin-domain/domain-sml-panel/domain-sml-integration-panel.component.ts b/smp-angular/src/app/system-settings/admin-domain/domain-sml-panel/domain-sml-integration-panel.component.ts
index 9e0a731fe..01cade896 100644
--- a/smp-angular/src/app/system-settings/admin-domain/domain-sml-panel/domain-sml-integration-panel.component.ts
+++ b/smp-angular/src/app/system-settings/admin-domain/domain-sml-panel/domain-sml-integration-panel.component.ts
@@ -226,7 +226,6 @@ export class DomainSmlIntegrationPanelComponent implements BeforeLeaveGuard {
         if (res) {
           if (res.success) {
             this.alertService.success("Domain [" + domain.domainCode + "] registered to sml!");
-            this.lookups.refreshDomainLookupForLoggedUser();
             domain.smlRegistered = true;
             this.domain = domain;
           } else {
@@ -253,7 +252,6 @@ export class DomainSmlIntegrationPanelComponent implements BeforeLeaveGuard {
         if (res) {
           if (res.success) {
             this.alertService.success("Domain [" + domain.domainCode + "] unregistered from sml!");
-            this.lookups.refreshDomainLookupForLoggedUser();
             domain.smlRegistered = false;
             this.domain = domain;
           } else {
diff --git a/smp-angular/src/app/system-settings/admin-extension/extension-panel/extension-panel.component.ts b/smp-angular/src/app/system-settings/admin-extension/extension-panel/extension-panel.component.ts
index 5a714e877..1fdb4c37a 100644
--- a/smp-angular/src/app/system-settings/admin-extension/extension-panel/extension-panel.component.ts
+++ b/smp-angular/src/app/system-settings/admin-extension/extension-panel/extension-panel.component.ts
@@ -36,6 +36,7 @@ export class ExtensionPanelComponent implements AfterViewInit {
 
   @Input() set extension(value: ExtensionRo) {
     this._extension = value;
+    this.resourceDefinitionSelected(null);
     this.resourceDefDataSource.data = value?.resourceDefinitions;
   }
 
diff --git a/smp-angular/src/app/system-settings/admin-extension/extension.component.html b/smp-angular/src/app/system-settings/admin-extension/extension.component.html
index d55cc075e..a7b9c0cd0 100644
--- a/smp-angular/src/app/system-settings/admin-extension/extension.component.html
+++ b/smp-angular/src/app/system-settings/admin-extension/extension.component.html
@@ -15,7 +15,7 @@
 <ng-template #searchExtensionPanel>
   <mat-form-field id="extension-filter">
     <mat-label>Filter by extension name</mat-label>
-    <input matInput (keyup)="applyFilter($event)" placeholder="Oasis SMP" #input>
+    <input matInput (keyup)="applyFilter($event)"  #input>
   </mat-form-field>
 
 
diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/dao/ResourceDao.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/dao/ResourceDao.java
index 613877f6f..fb01b3807 100644
--- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/dao/ResourceDao.java
+++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/dao/ResourceDao.java
@@ -163,48 +163,6 @@ public class ResourceDao extends BaseDao<DBResource> {
     }
 
 
-    /**
-     * Method returns ServiceGroupDomain for participant identifie and domain code. If there is no service group
-     * or service group registred to domain it returns empty Option.
-     * If more than one result returns IllegalStateException caused by database data inconsistency. Only one combination of
-     * participant identifier must be in the database.
-     *
-     * @param participantId participant identifier
-     * @param schema        participant identifier schema
-     * @param domainCode    domainCode
-     * @return DBResource
-     */
-    public Optional<DBDomainResourceDef> findServiceGroupDomain(String participantId, String schema, String domainCode) {
-
-        try {
-            TypedQuery<DBDomainResourceDef> query = memEManager.createNamedQuery("DBServiceGroupDomain.getServiceGroupDomain", DBDomainResourceDef.class);
-            query.setParameter("participantIdentifier", participantId);
-            query.setParameter("participantScheme", schema);
-            query.setParameter("domainCode", domainCode);
-            DBDomainResourceDef res = query.getSingleResult();
-            return Optional.of(res);
-        } catch (NoResultException e) {
-            return Optional.empty();
-        } catch (NonUniqueResultException e) {
-            throw new IllegalStateException(ErrorCode.ILLEGAL_STATE_SG_MULTIPLE_ENTRY.getMessage(participantId, schema));
-        }
-    }
-
-    public Optional<DBDomainResourceDef> findServiceGroupDomainForUserIdAndMetadataId(Long userId, Long serviceMetadataId) {
-
-        try {
-            TypedQuery<DBDomainResourceDef> query = memEManager.createNamedQuery("DBServiceGroupDomain.getOwnedServiceGroupDomainForUserIdAndServiceMetadataId", DBDomainResourceDef.class);
-            query.setParameter("userId", userId);
-            query.setParameter("serviceMetadataId", serviceMetadataId);
-            DBDomainResourceDef res = query.getSingleResult();
-            return Optional.of(res);
-        } catch (NoResultException e) {
-            return Optional.empty();
-        } catch (NonUniqueResultException e) {
-            throw new IllegalStateException(ErrorCode.ILLEGAL_STATE_SMD_ON_MULTIPLE_SGD.getMessage(serviceMetadataId, userId));
-        }
-    }
-
     public Long getResourceCountForDomainIdAndResourceDefId(Long domainId, Long resourceDefId) {
         TypedQuery<Long> query = memEManager.createNamedQuery(QUERY_RESOURCES_BY_DOMAIN_ID_RESOURCE_DEF_ID_COUNT, Long.class);
         query.setParameter(PARAM_DOMAIN_ID, domainId);
@@ -306,8 +264,4 @@ public class ResourceDao extends BaseDao<DBResource> {
         }
         return cq;
     }
-
-    public void updateServiceGroupDomain(DBDomainResourceDef serviceGroupDomain) {
-        memEManager.merge(serviceGroupDomain);
-    }
 }
diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/model/DBDomain.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/model/DBDomain.java
index da2edc068..f18e1df1c 100644
--- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/model/DBDomain.java
+++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/model/DBDomain.java
@@ -86,7 +86,7 @@ public class DBDomain extends BaseEntity {
     @ColumnDescription(comment = "Domain code used as http parameter in rest webservices")
     String domainCode;
 
-    @Column(name = "SML_SUBDOMAIN", length = CommonColumnsLengths.MAX_SML_SUBDOMAIN_LENGTH, unique = true)
+    @Column(name = "SML_SUBDOMAIN", length = CommonColumnsLengths.MAX_SML_SUBDOMAIN_LENGTH)
     @ColumnDescription(comment = "SML subdomain")
     String smlSubdomain;
     @Column(name = "SML_SMP_ID", length = CommonColumnsLengths.MAX_SML_SMP_ID_LENGTH)
diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/exceptions/SMLErrorMessages.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/exceptions/SMLErrorMessages.java
index 834a1a669..b2ec3f84b 100644
--- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/exceptions/SMLErrorMessages.java
+++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/exceptions/SMLErrorMessages.java
@@ -2,8 +2,8 @@ package eu.europa.ec.edelivery.smp.exceptions;
 
 public class SMLErrorMessages {
 
-    public static final String ERR_PARTICIPANT_ALREADY_EXISTS ="[ERR-106] The participant identifier '%s' does already exist for the scheme %s";
-    public static final String ERR_PARTICIPANT_NOT_EXISTS="[ERR-100] The participant identifier '%s' doesn't exist for the scheme %s";
+    public static final String ERR_PARTICIPANT_ALREADY_EXISTS ="[ERR-106] The participant identifier '%s' with scheme: '%s' already exist";
+    public static final String ERR_PARTICIPANT_NOT_EXISTS="[ERR-110] At least one of the participants doesn't exist in the list";
     public static final String ERR_DOMAIN_ALREADY_EXISTS="[ERR-106] The SMP '%s' already exists";
     public static final String ERR_DOMAIN_NOT_EXISTS="[ERR-100] The SMP '%s' doesn't exist";
 
diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/DomainService.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/DomainService.java
index 66e9ab52e..7c4116283 100644
--- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/DomainService.java
+++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/DomainService.java
@@ -13,7 +13,9 @@ import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
+import javax.transaction.Transactional;
 import javax.validation.constraints.NotNull;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Optional;
 import java.util.regex.Pattern;
@@ -85,23 +87,45 @@ public class DomainService {
      * @param domain
      */
 
+    @Transactional
     public void registerDomainAndParticipants(DBDomain domain) {
         LOG.info("Start registerDomainAndParticipants for domain:" + domain.getDomainCode());
         smlIntegrationService.registerDomain(domain);
 
         DBResourceFilter filter = DBResourceFilter.createBuilder().domain(domain).build();
         List<DBResource> resources = resourceDao.getResourcesForFilter(-1, -1, filter);
-        for (DBResource resource : resources) {
-            smlIntegrationService.registerParticipant(resource, domain);
+        List<DBResource> processed = new ArrayList<>();
+        try {
+            for (DBResource resource : resources) {
+                smlIntegrationService.registerParticipant(resource, domain);
+                processed.add(resource);
+            }
+        } catch (SMPRuntimeException exc) {
+            // rollback dns records
+            for (DBResource resource : processed) {
+                smlIntegrationService.unregisterParticipant(resource, domain);
+            }
+            throw exc;
         }
     }
 
+    @Transactional
     public void unregisterDomainAndParticipantsFromSml(DBDomain domain) {
 
         DBResourceFilter filter = DBResourceFilter.createBuilder().domain(domain).build();
         List<DBResource> resources = resourceDao.getResourcesForFilter(-1, -1, filter);
-        for (DBResource resource : resources) {
-            smlIntegrationService.unregisterParticipant(resource, domain);
+        List<DBResource> processed = new ArrayList<>();
+        try {
+            for (DBResource resource : resources) {
+                smlIntegrationService.unregisterParticipant(resource, domain);
+                processed.add(resource);
+            }
+        } catch (SMPRuntimeException exc) {
+            // rollback dns records
+            for (DBResource resource : processed) {
+                smlIntegrationService.registerParticipant(resource, domain);
+            }
+            throw exc;
         }
         smlIntegrationService.unRegisterDomain(domain);
     }
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 981a2b34b..1de3d8e46 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
@@ -96,6 +96,7 @@ public class SMLIntegrationService {
         if (!isSMLIntegrationEnabled()) {
             String msg = "SML integration is not enabled!";
             LOG.businessWarn(BUS_SML_REGISTER_SERVICE_GROUP_FAILED, resource.getIdentifierValue(), resource.getIdentifierScheme(), domain.getDomainCode(), msg);
+            return;
         }
         Identifier normalizedParticipantId = identifierService
                 .normalizeParticipant(resource.getIdentifierScheme(), resource.getIdentifierValue());
@@ -111,23 +112,23 @@ public class SMLIntegrationService {
         }
     }
 
-    public String getNaptrServiceForResource(DBResource resource){
-        LOG.info("Get naptr service for resource: [{}]", resource );
+    public String getNaptrServiceForResource(DBResource resource) {
+        LOG.info("Get naptr service for resource: [{}]", resource);
         if (resource == null
                 || resource.getDomainResourceDef() == null
                 || resource.getDomainResourceDef().getResourceDef() == null
-                || StringUtils.isBlank(resource.getDomainResourceDef().getResourceDef().getIdentifier())){
-            LOG.info("return null naptr service for resource: [{}]", resource );
+                || StringUtils.isBlank(resource.getDomainResourceDef().getResourceDef().getIdentifier())) {
+            LOG.info("return null naptr service for resource: [{}]", resource);
             return null;
         }
         String resDefIdentifier = resource.getDomainResourceDef().getResourceDef().getIdentifier();
-        LOG.info("return null naptr service for resource: [{}] and document type [{}]", resource, resDefIdentifier );
+        LOG.info("return null naptr service for resource: [{}] and document type [{}]", resource, resDefIdentifier);
         Map<String, String> map = configurationService.getCustomNaptrServicesMap();
 
-        if (map!=null &&  map.containsKey(resDefIdentifier) ) {
+        if (map != null && map.containsKey(resDefIdentifier)) {
             return map.get(resDefIdentifier);
         }
-        LOG.info("return null because configuration does not have document type and document type [{}]", resource, resDefIdentifier );
+        LOG.info("return null because configuration does not have document type and document type [{}]", resource, resDefIdentifier);
         return null;
 
     }
@@ -147,6 +148,7 @@ public class SMLIntegrationService {
         if (!isSMLIntegrationEnabled()) {
             String msg = "SML integration is not enabled!";
             LOG.businessWarn(BUS_SML_UNREGISTER_SERVICE_GROUP_FAILED, resource.getIdentifierValue(), resource.getIdentifierScheme(), domain.getDomainCode(), msg);
+            return;
         }
 
         // unregister only  registered participants
diff --git a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/sml/SmlConnectorTestConstants.java b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/sml/SmlConnectorTestConstants.java
index 2cc419692..7b602537a 100644
--- a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/sml/SmlConnectorTestConstants.java
+++ b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/sml/SmlConnectorTestConstants.java
@@ -16,10 +16,10 @@ public class SmlConnectorTestConstants {
         DEFAULT_DOMAIN.setSmlSmpId("SAMPLE-SMP-ID");
     }
 
-    protected static final String ERROR_UNEXPECTED_MESSAGE = "[ERR-106] Something unexpected happend";
+    protected static final String ERROR_UNEXPECTED_MESSAGE = "[ERR-106] Something unexpected happened";
     protected static final String ERROR_SMP_NOT_EXISTS = "[ERR-100] The SMP '" + DEFAULT_DOMAIN.getSmlSmpId() + "' doesn't exist";
     protected static final String ERROR_SMP_ALREADY_EXISTS = "[ERR-106] The SMP '" + DEFAULT_DOMAIN.getSmlSmpId() + "' already exists";
-    protected static final String ERROR_PI_ALREADY_EXISTS = "[ERR-106] The participant identifier 'sample:value' does already exist for the scheme sample:scheme";
-    protected static final String ERROR_PI_NO_EXISTS = "[ERR-100] The participant identifier 'sample:value' doesn't exist for the scheme sample:scheme";
+    protected static final String ERROR_PI_ALREADY_EXISTS = "[ERR-106] The participant identifier 'sample:value' with scheme: 'sample:scheme' already exist";
+    protected static final String ERROR_PI_NO_EXISTS = "[ERR-110] At least one of the participants doesn't exist in the list";
 
 }
diff --git a/smp-webapp/src/main/smp-setup/database-scripts/migration from 4.2 to 5.0/mysql5innoDb-4.2_to_5.0-step_01.sql b/smp-webapp/src/main/smp-setup/database-scripts/migration from 4.2 to 5.0/mysql5innoDb-4.2_to_5.0-step_01.sql
index e35175eef..f1a16884f 100644
--- a/smp-webapp/src/main/smp-setup/database-scripts/migration from 4.2 to 5.0/mysql5innoDb-4.2_to_5.0-step_01.sql	
+++ b/smp-webapp/src/main/smp-setup/database-scripts/migration from 4.2 to 5.0/mysql5innoDb-4.2_to_5.0-step_01.sql	
@@ -67,7 +67,7 @@ create table SMP_ALERT (
     MAIL_TO varchar(1024)  CHARACTER SET utf8 COLLATE utf8_bin,
     PROCESSED_TIME datetime,
     REPORTING_TIME datetime,
-    FOR_USERNAME varchar(256)  CHARACTER SET utf8 COLLATE utf8_bin,
+    FOR_USERNAME varchar(64)  CHARACTER SET utf8 COLLATE utf8_bin,
     primary key (ID)
 ) comment='SMP alerts' ENGINE=InnoDB DEFAULT CHARSET=utf8;
 
@@ -85,7 +85,7 @@ create table SMP_ALERT_AUD (
     MAIL_TO varchar(1024)  CHARACTER SET utf8 COLLATE utf8_bin,
     PROCESSED_TIME datetime,
     REPORTING_TIME datetime,
-    FOR_USERNAME varchar(256)  CHARACTER SET utf8 COLLATE utf8_bin,
+    FOR_USERNAME varchar(64)  CHARACTER SET utf8 COLLATE utf8_bin,
     primary key (ID, REV)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 
@@ -590,9 +590,6 @@ alter table SMP_DOCUMENT_VERSION
 alter table SMP_DOMAIN
    add constraint UK_djrwqd4luj5i7w4l7fueuaqbj unique (DOMAIN_CODE);
 
-alter table SMP_DOMAIN
-   add constraint UK_likb3jn0nlxlekaws0xx10uqc unique (SML_SUBDOMAIN);
-
 alter table SMP_DOMAIN_MEMBER
    add constraint SMP_DOM_MEM_IDX unique (FK_DOMAIN_ID, FK_USER_ID);
 
@@ -841,7 +838,3 @@ alter table SMP_USER_AUD
    add constraint FK2786r5minnkai3d22b191iiiq
    foreign key (REV)
    references SMP_REV_INFO (id);
-
--- --------------------------------------------------------------------
--- COPY DATA
--- --------------------------------------------------------------------
diff --git a/smp-webapp/src/main/smp-setup/database-scripts/migration from 4.2 to 5.0/oracle10g-4.2_to_5.0-step_01.sql b/smp-webapp/src/main/smp-setup/database-scripts/migration from 4.2 to 5.0/oracle10g-4.2_to_5.0-step_01.sql
index 20d1fef6c..3a0279a41 100644
--- a/smp-webapp/src/main/smp-setup/database-scripts/migration from 4.2 to 5.0/oracle10g-4.2_to_5.0-step_01.sql	
+++ b/smp-webapp/src/main/smp-setup/database-scripts/migration from 4.2 to 5.0/oracle10g-4.2_to_5.0-step_01.sql	
@@ -845,9 +845,6 @@ alter table SMP_DOCUMENT_VERSION
 alter table SMP_DOMAIN
    add constraint UK_djrwqd4luj5i7w4l7fueuaqbj unique (DOMAIN_CODE);
 
-alter table SMP_DOMAIN
-   add constraint UK_likb3jn0nlxlekaws0xx10uqc unique (SML_SUBDOMAIN);
-
 alter table SMP_DOMAIN_MEMBER
    add constraint SMP_DOM_MEM_IDX unique (FK_DOMAIN_ID, FK_USER_ID);
 
diff --git a/smp-webapp/src/main/smp-setup/database-scripts/mysql5innodb.ddl b/smp-webapp/src/main/smp-setup/database-scripts/mysql5innodb.ddl
index 3c5ee1665..8d9b3b89e 100644
--- a/smp-webapp/src/main/smp-setup/database-scripts/mysql5innodb.ddl
+++ b/smp-webapp/src/main/smp-setup/database-scripts/mysql5innodb.ddl
@@ -538,9 +538,6 @@ create index SMP_DOCVER_DOCUMENT_IDX on SMP_DOCUMENT_VERSION (FK_DOCUMENT_ID);
     alter table SMP_DOMAIN 
        add constraint UK_djrwqd4luj5i7w4l7fueuaqbj unique (DOMAIN_CODE);
 
-    alter table SMP_DOMAIN 
-       add constraint UK_likb3jn0nlxlekaws0xx10uqc unique (SML_SUBDOMAIN);
-
     alter table SMP_DOMAIN_MEMBER 
        add constraint SMP_DOM_MEM_IDX unique (FK_DOMAIN_ID, FK_USER_ID);
 
diff --git a/smp-webapp/src/main/smp-setup/database-scripts/oracle10g.ddl b/smp-webapp/src/main/smp-setup/database-scripts/oracle10g.ddl
index 0960c1663..bfcebe7fb 100644
--- a/smp-webapp/src/main/smp-setup/database-scripts/oracle10g.ddl
+++ b/smp-webapp/src/main/smp-setup/database-scripts/oracle10g.ddl
@@ -778,9 +778,6 @@ create index SMP_DOCVER_DOCUMENT_IDX on SMP_DOCUMENT_VERSION (FK_DOCUMENT_ID);
     alter table SMP_DOMAIN 
        add constraint UK_djrwqd4luj5i7w4l7fueuaqbj unique (DOMAIN_CODE);
 
-    alter table SMP_DOMAIN 
-       add constraint UK_likb3jn0nlxlekaws0xx10uqc unique (SML_SUBDOMAIN);
-
     alter table SMP_DOMAIN_MEMBER 
        add constraint SMP_DOM_MEM_IDX unique (FK_DOMAIN_ID, FK_USER_ID);
 
-- 
GitLab