From 392222974c97eff659af8c2fb4591f753ebe832a Mon Sep 17 00:00:00 2001
From: Joze RIHTARSIC <joze.RIHTARSIC@ext.ec.europa.eu>
Date: Sat, 28 May 2022 15:08:50 +0200
Subject: [PATCH] Fix reload cron expresion and small updates for alert table

---
 smp-angular/src/app/alert/alert-controller.ts | 18 +++-
 smp-angular/src/app/alert/alert-ro.model.ts   |  6 +-
 .../src/app/alert/alert.component.html        |  5 +-
 smp-angular/src/app/alert/alert.component.ts  | 22 ++++-
 smp-angular/src/app/app.module.ts             |  4 +
 .../object-properties-dialog.component.css    | 42 ++++++++
 .../object-properties-dialog.component.html   | 28 ++++++
 .../object-properties-dialog.component.ts     | 25 +++++
 .../password-change-dialog.component.ts       |  1 -
 .../src/app/property/property.component.html  |  4 -
 .../smp/config/DatabaseProperties.java        |  2 -
 .../smp/cron/SMPDynamicCronTrigger.java       | 22 +++--
 .../smp/data/dao/ConfigurationDao.java        |  3 +-
 .../ec/edelivery/smp/data/dao/UserDao.java    | 33 ++++++-
 .../ec/edelivery/smp/data/model/DBUser.java   |  3 +-
 .../ec/edelivery/smp/data/ui/AlertRO.java     |  7 ++
 .../smp/data/ui/enums/SMPPropertyEnum.java    |  2 +-
 .../edelivery/smp/services/AlertService.java  | 97 ++++++++++++-------
 .../smp/services/ui/UIAlertService.java       | 27 +++++-
 .../smp/services/ui/UIPropertyService.java    | 12 ++-
 .../ec/edelivery/smp/utils/PropertyUtils.java |  7 ++
 .../AbstractServiceIntegrationTest.java       |  4 +-
 .../smp/services/AlertServiceTest.java        |  6 +-
 .../ui/UIPropertyServiceIntegrationTest.java  | 27 +++---
 .../smp/utils/PropertyUtilsTest.java          |  4 +
 .../smp/config/SMPTaskSchedulerConfig.java    | 22 +++--
 ...PCronExpressionPropertyUpdateListener.java | 50 ++++++++--
 27 files changed, 382 insertions(+), 101 deletions(-)
 create mode 100644 smp-angular/src/app/common/dialogs/object-properties-dialog/object-properties-dialog.component.css
 create mode 100644 smp-angular/src/app/common/dialogs/object-properties-dialog/object-properties-dialog.component.html
 create mode 100644 smp-angular/src/app/common/dialogs/object-properties-dialog/object-properties-dialog.component.ts

diff --git a/smp-angular/src/app/alert/alert-controller.ts b/smp-angular/src/app/alert/alert-controller.ts
index b1a20b744..6009413b4 100644
--- a/smp-angular/src/app/alert/alert-controller.ts
+++ b/smp-angular/src/app/alert/alert-controller.ts
@@ -3,6 +3,7 @@ import {MatDialog, MatDialogConfig, MatDialogRef} from '@angular/material/dialog
 import {GlobalLookups} from "../common/global-lookups";
 import {SearchTableEntity} from "../common/search-table/search-table-entity.model";
 import {HttpClient} from "@angular/common/http";
+import {ObjectPropertiesDialogComponent} from "../common/dialogs/object-properties-dialog/object-properties-dialog.component";
 
 export class AlertController implements SearchTableController {
 
@@ -30,15 +31,30 @@ export class AlertController implements SearchTableController {
   }
 
   public showDetails(row: any) {
+    this.dialog.open(ObjectPropertiesDialogComponent, {
+      data: {
+        title: "Alert details!",
+        object: row.alertDetails,
+
+      }
+    });
   }
 
   public edit(row: any) {
+    this.dialog.open(ObjectPropertiesDialogComponent, {
+      data: {
+        title: "Alert details!",
+        object: row.alertDetails,
+
+      }
+    });
   }
 
+
   public delete(row: any) {
   }
 
   newDialog(config?: MatDialogConfig): MatDialogRef<any> {
-    return undefined;
+    return this.dialog.open(ObjectPropertiesDialogComponent, config);
   }
 }
diff --git a/smp-angular/src/app/alert/alert-ro.model.ts b/smp-angular/src/app/alert/alert-ro.model.ts
index aa8501219..4f83ab2fc 100644
--- a/smp-angular/src/app/alert/alert-ro.model.ts
+++ b/smp-angular/src/app/alert/alert-ro.model.ts
@@ -2,11 +2,13 @@ import {SearchTableEntity} from '../common/search-table/search-table-entity.mode
 
 export interface AlertRo extends SearchTableEntity {
   sid: string;
-  processed: boolean;
   alertType: string;
   alertStatus: string;
+  alertStatusDesc?:string;
   alertLevel: string;
-  processedTime: Date;
+  processedTime?: Date;
   reportingTime: Date;
+  mailTo?:string;
+  alertDetails?: Object;
 }
 
diff --git a/smp-angular/src/app/alert/alert.component.html b/smp-angular/src/app/alert/alert.component.html
index c751cd65b..b5a77433c 100644
--- a/smp-angular/src/app/alert/alert.component.html
+++ b/smp-angular/src/app/alert/alert.component.html
@@ -10,7 +10,7 @@
   [filter]="filter"
   [allowNewItems]="false"
   [allowDeleteItems]="false"
-  [allowEditItems]="false"
+  [allowEditItems]="true"
   [showActionButtons]="false"
 >
   <ng-template #additionalToolButtons>
@@ -24,4 +24,7 @@
   <ng-template #forUser  let-row="row" let-value="value" ngx-datatable-cell-template>
     <div class='truncate-text' title="{{value}} (Email:'{{row.mailTo}}')" >{{value}}</div>
   </ng-template>
+  <ng-template #credentialType  let-row="row" let-value="value" ngx-datatable-cell-template>
+    <div class='truncate-text'  >{{value['CREDENTIAL_TYPE']}}</div>
+  </ng-template>
 </smp-search-table>
diff --git a/smp-angular/src/app/alert/alert.component.ts b/smp-angular/src/app/alert/alert.component.ts
index 3afced83c..797401c5c 100644
--- a/smp-angular/src/app/alert/alert.component.ts
+++ b/smp-angular/src/app/alert/alert.component.ts
@@ -9,6 +9,7 @@ import {SmpConstants} from "../smp.constants";
 import {GlobalLookups} from "../common/global-lookups";
 import {SearchTableComponent} from "../common/search-table/search-table.component";
 import {SecurityService} from "../security/security.service";
+import {ObjectPropertiesDialogComponent} from "../common/dialogs/object-properties-dialog/object-properties-dialog.component";
 
 
 @Component({
@@ -23,6 +24,7 @@ export class AlertComponent implements AfterViewInit {
   @ViewChild('searchTable') searchTable: SearchTableComponent;
   @ViewChild('dateTimeColumn') dateTimeColumn:TemplateRef<any>;
   @ViewChild('truncateText') truncateText:TemplateRef<any>;
+  @ViewChild('credentialType') credentialType:TemplateRef<any>;
   @ViewChild('forUser') forUser:TemplateRef<any>;
 
 
@@ -63,6 +65,14 @@ export class AlertComponent implements AfterViewInit {
         maxWidth:200,
         showInitially: true,
       },
+      {
+        name: 'Credential type',
+        title: "Credential type.",
+        prop: 'alertDetails',
+        maxWidth:200,
+        cellTemplate: this.credentialType,
+        showInitially: true,
+      },
       {
         name: 'Alert type',
         title: "Alert type.",
@@ -90,7 +100,7 @@ export class AlertComponent implements AfterViewInit {
         title: "Alert level.",
         prop: 'alertLevel',
         showInitially: true,
-        maxWidth:100,
+        maxWidth:80,
 
       },
     ];
@@ -100,11 +110,19 @@ export class AlertComponent implements AfterViewInit {
 
 
   details(row: any) {
-    //this.alertController.showDetails(row);
+      this.dialog.open(ObjectPropertiesDialogComponent, {
+        data: {
+          title: "Alert details!",
+          object: row.alertDetails,
+
+        }
+      });
   }
 
   // for dirty guard...
   isDirty(): boolean {
     return this.searchTable.isDirty();
   }
+
+
 }
diff --git a/smp-angular/src/app/app.module.ts b/smp-angular/src/app/app.module.ts
index 79ebfde18..b99c52939 100644
--- a/smp-angular/src/app/app.module.ts
+++ b/smp-angular/src/app/app.module.ts
@@ -88,6 +88,8 @@ import {PropertyComponent} from "./property/property.component";
 import {PropertyDetailsDialogComponent} from "./property/property-details-dialog/property-details-dialog.component";
 import {MatCheckbox, MatCheckboxModule} from "@angular/material/checkbox";
 import {AutoFocusDirective} from "./common/directive/autofocus/auto-focus.directive";
+import {ObjectPropertiesDialogComponent} from "./common/dialogs/object-properties-dialog/object-properties-dialog.component";
+import {MatTableModule} from "@angular/material/table";
 
 
 @NgModule({
@@ -112,6 +114,7 @@ import {AutoFocusDirective} from "./common/directive/autofocus/auto-focus.direct
     CancelDialogComponent,
     ConfirmationDialogComponent,
     InformationDialogComponent,
+    ObjectPropertiesDialogComponent,
     RowLimiterComponent,
     DatePipe,
     CapitalizeFirstPipe,
@@ -167,6 +170,7 @@ import {AutoFocusDirective} from "./common/directive/autofocus/auto-focus.direct
     ReactiveFormsModule,
     SharedModule,
     MatExpansionModule,
+    MatTableModule,
   ],
   providers: [
     AuthenticatedGuard,
diff --git a/smp-angular/src/app/common/dialogs/object-properties-dialog/object-properties-dialog.component.css b/smp-angular/src/app/common/dialogs/object-properties-dialog/object-properties-dialog.component.css
new file mode 100644
index 000000000..5e50dc67f
--- /dev/null
+++ b/smp-angular/src/app/common/dialogs/object-properties-dialog/object-properties-dialog.component.css
@@ -0,0 +1,42 @@
+label:hover, label:active, input:hover + label, input:active + label {
+  color: #3f51b5;
+}
+
+.divTable {
+  display: table;
+  width: 100%;
+}
+
+.divTableRow {
+  display: table-row;
+}
+
+.divTableHeading {
+  background-color: #EEE;
+  display: table-header-group;
+}
+
+.divTableCell, .divTableHead {
+  /*border: 1px solid #999999;*/
+  display: table-cell;
+  padding: 3px 3px;
+  text-align: center;
+}
+
+.divTableHeading {
+  background-color: #EEE;
+  display: table-header-group;
+  font-weight: bold;
+}
+
+.divTableFoot {
+  background-color: #EEE;
+  display: table-footer-group;
+  font-weight: bold;
+}
+
+.divTableBody {
+  display: table-row-group;
+}
+
+
diff --git a/smp-angular/src/app/common/dialogs/object-properties-dialog/object-properties-dialog.component.html b/smp-angular/src/app/common/dialogs/object-properties-dialog/object-properties-dialog.component.html
new file mode 100644
index 000000000..15511a6d5
--- /dev/null
+++ b/smp-angular/src/app/common/dialogs/object-properties-dialog/object-properties-dialog.component.html
@@ -0,0 +1,28 @@
+<h1 mat-dialog-title>{{title}}</h1>
+<mat-dialog-content  style="height: 400px;width: 800px;">
+    <table mat-table style="width: 100%" [dataSource]="dataSource">
+
+
+      <!-- Name Column -->
+      <ng-container matColumnDef="key">
+        <th mat-header-cell *matHeaderCellDef> Key</th>
+        <td mat-cell *matCellDef="let element"> {{element[0]}} </td>
+      </ng-container>
+
+      <!-- Weight Column -->
+      <ng-container matColumnDef="value">
+        <th mat-header-cell *matHeaderCellDef> Value</th>
+        <td mat-cell *matCellDef="let element"> {{element[1]}} </td>
+      </ng-container>
+      <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
+      <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
+    </table>
+
+</mat-dialog-content>
+
+<mat-dialog-actions>
+  <button mat-raised-button color="primary" (click)="dialogRef.close(false)" id="nobuttondialog_id">
+    <mat-icon>close</mat-icon>
+    <span>Close</span>
+  </button>
+</mat-dialog-actions>
diff --git a/smp-angular/src/app/common/dialogs/object-properties-dialog/object-properties-dialog.component.ts b/smp-angular/src/app/common/dialogs/object-properties-dialog/object-properties-dialog.component.ts
new file mode 100644
index 000000000..5709f7bcb
--- /dev/null
+++ b/smp-angular/src/app/common/dialogs/object-properties-dialog/object-properties-dialog.component.ts
@@ -0,0 +1,25 @@
+import {Component, Inject} from '@angular/core';
+import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog';
+
+@Component({
+  selector: 'object-properties-dialog',
+  templateUrl: './object-properties-dialog.component.html',
+  styleUrls: ['./object-properties-dialog.component.css']
+})
+export class ObjectPropertiesDialogComponent {
+
+  title: string="Object properties";
+  object:Object
+  displayedColumns: string[] = ['key', 'value'];
+  dataSource : object[];
+
+  constructor(public dialogRef: MatDialogRef<ObjectPropertiesDialogComponent>,
+              @Inject(MAT_DIALOG_DATA) public data: any) {
+    //this.title=data.title;
+    this.object=data.row.alertDetails;
+    this.dataSource = Object.keys(this.object).map((key) => [key, this.object[key]]);
+
+
+
+  }
+}
diff --git a/smp-angular/src/app/common/dialogs/password-change-dialog/password-change-dialog.component.ts b/smp-angular/src/app/common/dialogs/password-change-dialog/password-change-dialog.component.ts
index 0098e1cef..c28cd854a 100644
--- a/smp-angular/src/app/common/dialogs/password-change-dialog/password-change-dialog.component.ts
+++ b/smp-angular/src/app/common/dialogs/password-change-dialog/password-change-dialog.component.ts
@@ -8,7 +8,6 @@ import {AlertMessageService} from "../../alert-message/alert-message.service";
 import {SecurityService} from "../../../security/security.service";
 import {InformationDialogComponent} from "../information-dialog/information-dialog.component";
 import {UserRo} from "../../../user/user-ro.model";
-import {SmpConstants} from "../../../smp.constants";
 
 @Component({
   selector: 'smp-password-change-dialog',
diff --git a/smp-angular/src/app/property/property.component.html b/smp-angular/src/app/property/property.component.html
index c2befd1b1..535e2cf45 100644
--- a/smp-angular/src/app/property/property.component.html
+++ b/smp-angular/src/app/property/property.component.html
@@ -29,9 +29,6 @@
   <ng-template #tableTitle>
     <span *ngIf="isServerRestartNeeded()" [class]="'alert-message'">Server restart is needed!</span>
   </ng-template>
-
-  tableTitle
-
   <ng-template #propertyValueTemplate
                let-row="row"
                let-value="value"
@@ -40,7 +37,6 @@
       [matTooltip]='row.desc'>{{ value }}</span>
     <span *ngIf="row.updateDate" style="display: block;font-size: 0.8em;color: darkorange">New Value: '{{ row.newValue +"." }}'
         <span *ngIf="!row.restartNeeded">
-          {{  jsonStringify(row)  }}
       Scheduled update time:  {{ row.updateDate | date:'yyyy-MM-dd HH:mm:ss' }}</span>
     </span>
 
diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/config/DatabaseProperties.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/config/DatabaseProperties.java
index 7331cfe88..b3deadd12 100644
--- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/config/DatabaseProperties.java
+++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/config/DatabaseProperties.java
@@ -1,7 +1,6 @@
 package eu.europa.ec.edelivery.smp.config;
 
 import eu.europa.ec.edelivery.smp.data.model.DBConfiguration;
-import eu.europa.ec.edelivery.smp.data.ui.enums.SMPPropertyEnum;
 import eu.europa.ec.edelivery.smp.logging.SMPLogger;
 import eu.europa.ec.edelivery.smp.logging.SMPLoggerFactory;
 import eu.europa.ec.edelivery.smp.utils.PropertyUtils;
@@ -10,7 +9,6 @@ import javax.persistence.EntityManager;
 import javax.persistence.TypedQuery;
 import java.time.OffsetDateTime;
 import java.util.List;
-import java.util.Optional;
 import java.util.Properties;
 
 import static org.apache.commons.lang3.StringUtils.trim;
diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/cron/SMPDynamicCronTrigger.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/cron/SMPDynamicCronTrigger.java
index 555ad498f..2dc72e758 100644
--- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/cron/SMPDynamicCronTrigger.java
+++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/cron/SMPDynamicCronTrigger.java
@@ -3,12 +3,12 @@ package eu.europa.ec.edelivery.smp.cron;
 import eu.europa.ec.edelivery.smp.data.ui.enums.SMPPropertyEnum;
 import eu.europa.ec.edelivery.smp.logging.SMPLogger;
 import eu.europa.ec.edelivery.smp.logging.SMPLoggerFactory;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.commons.lang3.time.DateFormatUtils;
 import org.springframework.scheduling.Trigger;
 import org.springframework.scheduling.TriggerContext;
+import org.springframework.scheduling.support.CronExpression;
 import org.springframework.scheduling.support.CronTrigger;
 
+import java.util.Calendar;
 import java.util.Date;
 
 /**
@@ -39,20 +39,22 @@ public class SMPDynamicCronTrigger implements Trigger {
         return nextExecutionDate;
     }
 
-    public void updateCronExpression(String expression) {
-        if (StringUtils.isBlank(expression)) {
-            LOG.debug("Disable cron trigger for property: [{}]. ", expression);
+    public String getExpression() {
+        return cronTrigger.getExpression();
+    }
+
+    public void updateCronExpression(CronExpression expression) {
+        if (expression == null) {
+            LOG.debug("Disable cron trigger for property: [{}]. ", cronExpressionProperty.getProperty());
             cronTrigger = null;
             nextExecutionDate = null;
             return;
         }
-        cronTrigger = new CronTrigger(expression);
+        cronTrigger = new CronTrigger(expression.toString());
         LOG.debug("Set new cron expression: [{}] for property: [{}]. ", expression,
                 cronExpressionProperty.getProperty());
-        if (nextExecutionDate != null) {
-            LOG.debug("The new cron expression will be used after the current planned execution [{}].",
-                    DateFormatUtils.ISO_8601_EXTENDED_DATETIME_FORMAT.format(nextExecutionDate));
-        }
+
+        nextExecutionDate = Calendar.getInstance().getTime();
     }
 
     /**
diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/dao/ConfigurationDao.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/dao/ConfigurationDao.java
index ca29a5326..19b20bd99 100644
--- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/dao/ConfigurationDao.java
+++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/dao/ConfigurationDao.java
@@ -286,7 +286,8 @@ public class ConfigurationDao extends BaseDao<DBConfiguration> {
                 LOG.error("cachedPropertyValues is FOR SOME REASON NULL ");
             }
             if (this.cachedPropertyValues != null && this.cachedPropertyValues.containsKey(prop.getProperty())) {
-                LOG.debug("Put property [{}] value [{}]", prop.getProperty(), this.cachedPropertyValues.get(prop.getProperty()));
+                LOG.debug("Put property [{}] value [{}]", prop.getProperty(),
+                        this.cachedPropertyValues.get(prop.getProperty()));
                 mapProp.put(prop, this.cachedPropertyValues.get(prop.getProperty()));
             } else {
                 LOG.debug("Property [{}] does not exist in cached map!", prop.getProperty());
diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/dao/UserDao.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/dao/UserDao.java
index dcfadf546..1e049b0ef 100644
--- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/dao/UserDao.java
+++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/dao/UserDao.java
@@ -15,8 +15,11 @@ package eu.europa.ec.edelivery.smp.data.dao;
 
 import eu.europa.ec.edelivery.smp.data.model.DBUser;
 import eu.europa.ec.edelivery.smp.data.model.DBUserDeleteValidation;
+import eu.europa.ec.edelivery.smp.data.ui.enums.CredentialTypeEnum;
 import eu.europa.ec.edelivery.smp.exceptions.ErrorCode;
 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 org.apache.commons.lang3.StringUtils;
 import org.springframework.stereotype.Repository;
 
@@ -36,11 +39,11 @@ import static eu.europa.ec.edelivery.smp.exceptions.ErrorCode.ILLEGAL_STATE_USER
  */
 @Repository
 public class UserDao extends BaseDao<DBUser> {
-
-    private static final String QUERY_PARAM_ALERT_CREDENTIAL_START_DATE="startAlertDate";
-    private static final String QUERY_PARAM_ALERT_CREDENTIAL_END_DATE="endAlertDate";
-    private static final String QUERY_PARAM_ALERT_CREDENTIAL_EXPIRE_DATE="expireDate";
-    private static final String QUERY_PARAM_ALERT_CREDENTIAL_LAST_ALERT_DATE="lastSendAlertDate";
+    private static final SMPLogger LOG = SMPLoggerFactory.getLogger(UserDao.class);
+    private static final String QUERY_PARAM_ALERT_CREDENTIAL_START_DATE = "startAlertDate";
+    private static final String QUERY_PARAM_ALERT_CREDENTIAL_END_DATE = "endAlertDate";
+    private static final String QUERY_PARAM_ALERT_CREDENTIAL_EXPIRE_DATE = "expireDate";
+    private static final String QUERY_PARAM_ALERT_CREDENTIAL_LAST_ALERT_DATE = "lastSendAlertDate";
 
 
     /**
@@ -266,4 +269,24 @@ public class UserDao extends BaseDao<DBUser> {
         query.setParameter("idList", userIds);
         return query.getResultList();
     }
+
+    @Transactional
+    public void updateAlertSentForUserCredentials(Long userId, CredentialTypeEnum credentialType, OffsetDateTime dateTime) {
+        DBUser user = find(userId);
+        switch (credentialType) {
+            case USERNAME_PASSWORD:
+                user.setPasswordExpireAlertOn(dateTime);
+                break;
+            case ACCESS_TOKEN:
+                user.setAccessTokenExpireAlertOn(dateTime);
+                break;
+            case CERTIFICATE:
+                if (user.getCertificate() == null) {
+                    LOG.warn("Can not set certificate alert sent date for user [{}] without certificate!", user.getUsername());
+                } else {
+                    user.getCertificate().setCertificateLastExpireAlertOn(dateTime);
+                }
+                break;
+        }
+    }
 }
\ No newline at end of file
diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/model/DBUser.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/model/DBUser.java
index a0d4b27f9..1a54b99c0 100644
--- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/model/DBUser.java
+++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/model/DBUser.java
@@ -62,7 +62,8 @@ import java.util.Objects;
                         " AND u.certificate.validTo IS NOT NULL " +
                         " AND u.certificate.validTo <= :startAlertDate " +
                         " AND u.certificate.validTo > :expireDate" +
-                        " AND (u.certificate.certificateLastExpireAlertOn IS NULL OR u.certificate.certificateLastExpireAlertOn < :lastSendAlertDate )"),
+                        " AND (u.certificate.certificateLastExpireAlertOn IS NULL " +
+                        "       OR u.certificate.certificateLastExpireAlertOn < :lastSendAlertDate )"),
         @NamedQuery(name = "DBUser.getUsersForCertificateExpiredAlerts",
                 query = "SELECT u FROM DBUser u WHERE u.certificate IS NOT NULL" +
                         " AND u.certificate.validTo IS NOT NULL " +
diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/AlertRO.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/AlertRO.java
index 5b4f6ca8f..3580004e4 100644
--- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/AlertRO.java
+++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/AlertRO.java
@@ -6,6 +6,8 @@ import eu.europa.ec.edelivery.smp.data.ui.enums.AlertStatusEnum;
 import eu.europa.ec.edelivery.smp.data.ui.enums.AlertTypeEnum;
 
 import java.time.OffsetDateTime;
+import java.util.HashMap;
+import java.util.Map;
 
 /**
  * @author Joze Rihtarsic
@@ -25,6 +27,7 @@ public class AlertRO extends BaseRO {
     private AlertStatusEnum alertStatus;
     private String alertStatusDesc;
     private AlertLevelEnum alertLevel;
+    private Map<String, String> alertDetails = new HashMap<>();
 
     public String getSid() {
         return sid;
@@ -97,4 +100,8 @@ public class AlertRO extends BaseRO {
     public void setAlertLevel(AlertLevelEnum alertLevel) {
         this.alertLevel = alertLevel;
     }
+
+    public Map<String, String> getAlertDetails() {
+        return alertDetails;
+    }
 }
diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/enums/SMPPropertyEnum.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/enums/SMPPropertyEnum.java
index 47a94ec6e..8e0b36294 100644
--- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/enums/SMPPropertyEnum.java
+++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/enums/SMPPropertyEnum.java
@@ -55,7 +55,7 @@ public enum SMPPropertyEnum {
     CERTIFICATE_ALLOWED_CERTIFICATEPOLICY_OIDS("smp.certificate.validation.allowedCertificatePolicyOIDs","","List of certificate policy OIDs separated by | where at least one must be in the CertifictePolicy extension", false, false,false, STRING),
     CERTIFICATE_SUBJECT_REGULAR_EXPRESSION("smp.certificate.validation.subjectRegex",".*","Regular expression to validate subject of the certificate", false, false,false, REGEXP),
 
-    SMP_PROPERTY_REFRESH_CRON("smp.property.refresh.cronJobExpression", "0 48 */1 * * *", "Property refresh cron expression (def 12 minutes to each hour). Property change is refreshed at restart!", false, false, true, CRON_EXPRESSION),
+    SMP_PROPERTY_REFRESH_CRON("smp.property.refresh.cronJobExpression", "0 48 */1 * * *", "Property refresh cron expression (def 12 minutes to each hour). Property change is refreshed at restart!", false, false, false, CRON_EXPRESSION),
     // UI COOKIE configuration
     UI_COOKIE_SESSION_SECURE("smp.ui.session.secure", "false", "Cookie is only sent to the server when a request is made with the https: scheme (except on localhost), and therefore is more resistant to man-in-the-middle attacks.", false, false, false, BOOLEAN),
     UI_COOKIE_SESSION_MAX_AGE("smp.ui.session.max-age", "", "Number of seconds until the cookie expires. A zero or negative number will expire the cookie immediately. Empty value will not set parameter", false, false, false, INTEGER),
diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/AlertService.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/AlertService.java
index 7daf52f5c..3059a3d15 100644
--- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/AlertService.java
+++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/AlertService.java
@@ -1,6 +1,8 @@
 package eu.europa.ec.edelivery.smp.services;
 
+import eu.europa.ec.edelivery.smp.cron.SMPDynamicCronTrigger;
 import eu.europa.ec.edelivery.smp.data.dao.AlertDao;
+import eu.europa.ec.edelivery.smp.data.dao.UserDao;
 import eu.europa.ec.edelivery.smp.data.model.DBAlert;
 import eu.europa.ec.edelivery.smp.data.model.DBUser;
 import eu.europa.ec.edelivery.smp.data.ui.enums.AlertLevelEnum;
@@ -17,10 +19,13 @@ import eu.europa.ec.edelivery.smp.services.mail.prop.CredentialsExpirationProper
 import eu.europa.ec.edelivery.smp.utils.HttpUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.exception.ExceptionUtils;
+import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.stereotype.Service;
 
 import java.time.OffsetDateTime;
+import java.util.Date;
 
+import static eu.europa.ec.edelivery.smp.cron.CronTriggerConfig.TRIGGER_BEAN_CREDENTIAL_ALERTS;
 import static java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME;
 
 /**
@@ -36,11 +41,19 @@ public class AlertService {
     final AlertDao alertDao;
     final MailService mailService;
     final ConfigurationService configurationService;
-
-    public AlertService(AlertDao alertDao, MailService mailService, ConfigurationService configurationService) {
+    final UserDao userDao;
+    final SMPDynamicCronTrigger alertCronTrigger;
+
+    public AlertService(AlertDao alertDao,
+                        MailService mailService,
+                        ConfigurationService configurationService,
+                        UserDao userDao,
+                        @Qualifier(TRIGGER_BEAN_CREDENTIAL_ALERTS) SMPDynamicCronTrigger alertCronTrigger) {
         this.alertDao = alertDao;
         this.mailService = mailService;
         this.configurationService = configurationService;
+        this.userDao = userDao;
+        this.alertCronTrigger = alertCronTrigger;
     }
 
     public void alertBeforeUsernamePasswordExpire(DBUser user) {
@@ -58,7 +71,7 @@ public class AlertService {
 
         DBAlert alert = createAlert(user.getUsername(), mailSubject, mailTo, alertLevel, alertType);
 
-        alertCredentialExpiration(alert, credentialType, credentialId, expiredOn);
+        alertCredentialExpiration(user, alert, credentialType, credentialId, expiredOn);
     }
 
     public void alertUsernamePasswordExpired(DBUser user) {
@@ -75,9 +88,9 @@ public class AlertService {
         AlertLevelEnum alertLevel = configurationService.getAlertExpiredPasswordLevel();
         AlertTypeEnum alertType = AlertTypeEnum.CREDENTIAL_EXPIRED;
 
-        DBAlert alert = createAlert(user.getUsername(),mailSubject, mailTo, alertLevel, alertType);
+        DBAlert alert = createAlert(user.getUsername(), mailSubject, mailTo, alertLevel, alertType);
 
-        alertCredentialExpiration(alert, credentialType, credentialId, expiredOn);
+        alertCredentialExpiration(user, alert, credentialType, credentialId, expiredOn);
     }
 
     public void alertBeforeAccessTokenExpire(DBUser user) {
@@ -96,8 +109,8 @@ public class AlertService {
         AlertLevelEnum alertLevel = configurationService.getAlertBeforeExpireAccessTokenLevel();
         AlertTypeEnum alertType = AlertTypeEnum.CREDENTIAL_IMMINENT_EXPIRATION;
 
-        DBAlert alert = createAlert(user.getUsername(),mailSubject, mailTo, alertLevel, alertType);
-        alertCredentialExpiration(alert, credentialType, credentialId, expiredOn);
+        DBAlert alert = createAlert(user.getUsername(), mailSubject, mailTo, alertLevel, alertType);
+        alertCredentialExpiration(user, alert, credentialType, credentialId, expiredOn);
     }
 
     public void alertAccessTokenExpired(DBUser user) {
@@ -116,8 +129,8 @@ public class AlertService {
         AlertLevelEnum alertLevel = configurationService.getAlertExpiredAccessTokenLevel();
         AlertTypeEnum alertType = AlertTypeEnum.CREDENTIAL_EXPIRED;
 
-        DBAlert alert = createAlert(user.getUsername(),mailSubject, mailTo, alertLevel, alertType);
-        alertCredentialExpiration(alert, credentialType, credentialId, expiredOn);
+        DBAlert alert = createAlert(user.getUsername(), mailSubject, mailTo, alertLevel, alertType);
+        alertCredentialExpiration(user, alert, credentialType, credentialId, expiredOn);
     }
 
 
@@ -136,8 +149,8 @@ public class AlertService {
         AlertLevelEnum alertLevel = configurationService.getAlertBeforeExpireCertificateLevel();
         AlertTypeEnum alertType = AlertTypeEnum.CREDENTIAL_IMMINENT_EXPIRATION;
 
-        DBAlert alert = createAlert(user.getUsername(),mailSubject, mailTo, alertLevel, alertType);
-        alertCredentialExpiration(alert, credentialType, credentialId, expiredOn);
+        DBAlert alert = createAlert(user.getUsername(), mailSubject, mailTo, alertLevel, alertType);
+        alertCredentialExpiration(user, alert, credentialType, credentialId, expiredOn);
     }
 
     public void alertCertificateExpired(DBUser user) {
@@ -155,8 +168,8 @@ public class AlertService {
         AlertLevelEnum alertLevel = configurationService.getAlertExpiredCertificateLevel();
         AlertTypeEnum alertType = AlertTypeEnum.CREDENTIAL_EXPIRED;
 
-        DBAlert alert = createAlert(user.getUsername(),mailSubject, mailTo, alertLevel, alertType);
-        alertCredentialExpiration(alert, credentialType, credentialId, expiredOn);
+        DBAlert alert = createAlert(user.getUsername(), mailSubject, mailTo, alertLevel, alertType);
+        alertCredentialExpiration(user, alert, credentialType, credentialId, expiredOn);
     }
 
     public void alertCredentialVerificationFailed(DBUser user, CredentialTypeEnum credentialType) {
@@ -175,11 +188,11 @@ public class AlertService {
         OffsetDateTime lastFailedLoginDate;
         String credentialId;
 
-        if(credentialType == CredentialTypeEnum.ACCESS_TOKEN) {
+        if (credentialType == CredentialTypeEnum.ACCESS_TOKEN) {
             failureCount = user.getSequentialTokenLoginFailureCount();
             lastFailedLoginDate = user.getLastTokenFailedLoginAttempt();
             credentialId = user.getAccessTokenIdentifier();
-        }else if(credentialType == CredentialTypeEnum.USERNAME_PASSWORD) {
+        } else if (credentialType == CredentialTypeEnum.USERNAME_PASSWORD) {
             failureCount = user.getSequentialLoginFailureCount();
             lastFailedLoginDate = user.getLastFailedLoginAttempt();
             credentialId = user.getUsername();
@@ -187,8 +200,8 @@ public class AlertService {
             LOG.error("Alert for suspended credentials type [{}] is not supported", credentialType);
             return;
         }
-        DBAlert alert = createAlert(user.getUsername(),mailSubject, mailTo, alertLevel, alertType);
-        alertCredentialVerificationFailed(alert,
+        DBAlert alert = createAlert(user.getUsername(), mailSubject, mailTo, alertLevel, alertType);
+        alertCredentialVerificationFailed(user, alert,
                 credentialType, credentialId,
                 failureCount, lastFailedLoginDate);
     }
@@ -209,12 +222,12 @@ public class AlertService {
         OffsetDateTime lastFailedLoginDate;
         OffsetDateTime suspendedUtil;
         String credentialId;
-        if(credentialType == CredentialTypeEnum.ACCESS_TOKEN) {
+        if (credentialType == CredentialTypeEnum.ACCESS_TOKEN) {
             failureCount = user.getSequentialTokenLoginFailureCount();
             lastFailedLoginDate = user.getLastTokenFailedLoginAttempt();
             suspendedUtil = lastFailedLoginDate.plusSeconds(configurationService.getAccessTokenLoginSuspensionTimeInSeconds());
             credentialId = user.getAccessTokenIdentifier();
-        }else if(credentialType == CredentialTypeEnum.USERNAME_PASSWORD) {
+        } else if (credentialType == CredentialTypeEnum.USERNAME_PASSWORD) {
             failureCount = user.getSequentialLoginFailureCount();
             lastFailedLoginDate = user.getLastFailedLoginAttempt();
             suspendedUtil = lastFailedLoginDate.plusSeconds(configurationService.getLoginSuspensionTimeInSeconds());
@@ -223,18 +236,19 @@ public class AlertService {
             LOG.error("Alert for suspended credentials type [{}] is not supported", credentialType);
             return;
         }
-        DBAlert alert = createAlert(user.getUsername(),mailSubject, mailTo, alertLevel, alertType);
+        DBAlert alert = createAlert(user.getUsername(), mailSubject, mailTo, alertLevel, alertType);
 
-        alertCredentialSuspended(alert,
+        alertCredentialSuspended(user, alert,
                 credentialType, credentialId,
                 failureCount, lastFailedLoginDate, suspendedUtil);
     }
 
-    public void alertCredentialExpiration(DBAlert alert,
+    public void alertCredentialExpiration(DBUser user,
+                                          DBAlert alert,
                                           CredentialTypeEnum credentialType,
                                           String credentialId,
                                           OffsetDateTime expirationDate
-                                          ) {
+    ) {
 
         String serverName = HttpUtils.getServerAddress();
         // add alert properties
@@ -247,14 +261,21 @@ public class AlertService {
         alertDao.persistFlushDetach(alert);
         // submit alerts
         submitAlertMail(alert);
+        // when alert about to expire - check if the next cron execution is expired
+        // and set date sent tp null to ensure alert submission in next cron execution
+        userDao.updateAlertSentForUserCredentials(user.getId(), credentialType,
+                alert.getAlertType() == AlertTypeEnum.CREDENTIAL_IMMINENT_EXPIRATION
+                        && isNextExecutionExpired(expirationDate) ?
+                        null : OffsetDateTime.now());
     }
 
-    public void alertCredentialVerificationFailed(DBAlert alert,
+    public void alertCredentialVerificationFailed(DBUser user,
+                                                  DBAlert alert,
                                                   CredentialTypeEnum credentialType,
                                                   String credentialId,
                                                   Integer failedLoginCount,
                                                   OffsetDateTime lastFailedLoginDate
-                                                  ) {
+    ) {
         String serverName = HttpUtils.getServerAddress();
         // add alert properties
         alert.addProperty(CredentialVerificationFailedProperties.CREDENTIAL_TYPE.name(), credentialType.name());
@@ -269,7 +290,8 @@ public class AlertService {
         submitAlertMail(alert);
     }
 
-    public void alertCredentialSuspended(DBAlert alert,
+    public void alertCredentialSuspended(DBUser user,
+                                         DBAlert alert,
                                          CredentialTypeEnum credentialType,
                                          String credentialId,
                                          Integer failedLoginCount,
@@ -292,7 +314,7 @@ public class AlertService {
     }
 
     /**
-     *  Create Alert DB entity
+     * Create Alert DB entity
      *
      * @param mailSubject
      * @param mailTo
@@ -301,9 +323,9 @@ public class AlertService {
      * @return
      */
     protected DBAlert createAlert(String username, String mailSubject,
-                                String mailTo,
-                                AlertLevelEnum level,
-                                AlertTypeEnum alertType) {
+                                  String mailTo,
+                                  AlertLevelEnum level,
+                                  AlertTypeEnum alertType) {
 
         DBAlert alert = new DBAlert();
         alert.setMailSubject(mailSubject);
@@ -318,13 +340,14 @@ public class AlertService {
 
     /**
      * Submit mail  for the alert
+     *
      * @param alert
      */
     public void submitAlertMail(DBAlert alert) {
         String mailTo = alert.getMailTo();
         if (StringUtils.isBlank(mailTo)) {
             LOG.warn("Can not send mail (empty mail) for alert [{}]!", alert);
-            updateAlertStatus(alert, AlertStatusEnum.FAILED, "Can not send mail (empty mail) for alert!");
+            updateAlertStatus(alert, AlertStatusEnum.SUCCESS, "Alert created but mail not send (empty mail) for alert!");
             return;
         }
 
@@ -341,16 +364,22 @@ public class AlertService {
 
     }
 
-    public void updateAlertStatus(DBAlert alert, AlertStatusEnum status, String statusDesc){
+    public void updateAlertStatus(DBAlert alert, AlertStatusEnum status, String statusDesc) {
         alert.setAlertStatus(status);
         alert.setAlertStatusDesc(statusDesc);
         if (status == AlertStatusEnum.SUCCESS
-                ||status == AlertStatusEnum.FAILED ){
+                || status == AlertStatusEnum.FAILED) {
             alert.setProcessedTime(OffsetDateTime.now());
         }
         alertDao.update(alert);
     }
 
-
+    public boolean isNextExecutionExpired(OffsetDateTime expireOn) {
+        Date nextExecutionDate = alertCronTrigger.getNextExecutionDate();
+        // get expire offset - presume that expired On was generated
+        // on server in the same zone
+        return nextExecutionDate == null || expireOn == null ||
+                expireOn.isBefore(nextExecutionDate.toInstant().atOffset(expireOn.getOffset()));
+    }
 
 }
diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIAlertService.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIAlertService.java
index 69d96a11f..9c42c52f7 100644
--- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIAlertService.java
+++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIAlertService.java
@@ -5,12 +5,20 @@ import eu.europa.ec.edelivery.smp.data.dao.BaseDao;
 import eu.europa.ec.edelivery.smp.data.model.DBAlert;
 import eu.europa.ec.edelivery.smp.data.ui.AlertRO;
 import eu.europa.ec.edelivery.smp.data.ui.ServiceResult;
+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 org.apache.commons.beanutils.BeanUtils;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
+import java.lang.reflect.InvocationTargetException;
+
+import static eu.europa.ec.edelivery.smp.exceptions.ErrorCode.INTERNAL_ERROR;
+
 @Service
 public class UIAlertService extends UIServiceBase<DBAlert, AlertRO> {
-
+    private static final SMPLogger LOG = SMPLoggerFactory.getLogger(UIAlertService.class);
     AlertDao alertDao;
 
     public UIAlertService(AlertDao alertDao) {
@@ -40,4 +48,21 @@ public class UIAlertService extends UIServiceBase<DBAlert, AlertRO> {
         return super.getTableList(page, pageSize, sortField, sortOrder, filter);
     }
 
+    @Override
+    public AlertRO convertToRo(DBAlert d) {
+        AlertRO alertRO = new AlertRO();
+        try {
+            BeanUtils.copyProperties(alertRO, d);
+            if (d.getProperties()!=null) {
+                d.getProperties().forEach((s, dbAlertProperty) ->
+                        alertRO.getAlertDetails().put(s, dbAlertProperty.getValue()));
+            }
+        } catch (InvocationTargetException | IllegalAccessException e) {
+            String msg = "Error occurred while converting  DBAlert to AlertRO";
+            LOG.error(msg, e);
+            throw new SMPRuntimeException(INTERNAL_ERROR, "DB to RO entity conversion.", msg);
+        }
+        return alertRO;
+    }
+
 }
diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIPropertyService.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIPropertyService.java
index f200b8798..d12d8ad1d 100644
--- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIPropertyService.java
+++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIPropertyService.java
@@ -15,10 +15,12 @@ import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
-import org.springframework.web.bind.annotation.RequestBody;
 
 import java.io.File;
-import java.util.*;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
 import java.util.function.Function;
 import java.util.stream.Collectors;
 
@@ -62,7 +64,7 @@ public class UIPropertyService {
         LOG.debug("Get properties for page [{}], pageSize [{}] and filter [{}]", page, pageSize, filterByProperty);
         List<SMPPropertyEnum> filteredProperties = Arrays.asList(SMPPropertyEnum.values()).stream()
                 .filter(prop -> StringUtils.isBlank(filterByProperty)
-                        || StringUtils.containsIgnoreCase(prop.getProperty(),filterByProperty))
+                        || StringUtils.containsIgnoreCase(prop.getProperty(), filterByProperty))
                 .collect(Collectors.toList());
         LOG.debug("Got filtered properties count [{}]", filteredProperties.size());
 
@@ -70,8 +72,8 @@ public class UIPropertyService {
                 .collect(Collectors.toMap(DBConfiguration::getProperty, Function.identity()));
 
         List<PropertyRO> properties = filteredProperties.stream()
-                .skip( page<0?0:page * (long)pageSize)
-                .limit(pageSize<0?SMPPropertyEnum.values().length:pageSize)
+                .skip(page < 0 ? 0 : page * (long) pageSize)
+                .limit(pageSize < 0 ? SMPPropertyEnum.values().length : pageSize)
                 .map(prop -> createProperty(prop, changedProps))
                 .collect(Collectors.toList());
 
diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/utils/PropertyUtils.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/utils/PropertyUtils.java
index f12a899ac..a79807b8e 100644
--- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/utils/PropertyUtils.java
+++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/utils/PropertyUtils.java
@@ -140,6 +140,13 @@ public class PropertyUtils {
                 }
             case STRING:
                 return value;
+            case CRON_EXPRESSION:
+                try {
+                    return CronExpression.parse(value);
+                } catch (IllegalArgumentException ex) {
+                    throw new SMPRuntimeException(ErrorCode.CONFIGURATION_ERROR, "cron expression:  ["
+                            + value + "]. Error:" + ExceptionUtils.getRootCauseMessage(ex), ex);
+                }
         }
         return null;
     }
diff --git a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/AbstractServiceIntegrationTest.java b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/AbstractServiceIntegrationTest.java
index 9bd050a3b..257fa28b1 100644
--- a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/AbstractServiceIntegrationTest.java
+++ b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/AbstractServiceIntegrationTest.java
@@ -5,6 +5,7 @@ import eu.europa.ec.edelivery.smp.config.ConversionTestConfig;
 import eu.europa.ec.edelivery.smp.config.H2JPATestConfig;
 import eu.europa.ec.edelivery.smp.config.ServicesBeansConfiguration;
 import eu.europa.ec.edelivery.smp.conversion.CaseSensitivityNormalizer;
+import eu.europa.ec.edelivery.smp.cron.CronTriggerConfig;
 import eu.europa.ec.edelivery.smp.data.dao.*;
 import eu.europa.ec.edelivery.smp.data.model.DBDomain;
 import eu.europa.ec.edelivery.smp.data.model.DBServiceGroup;
@@ -49,7 +50,8 @@ import static eu.europa.ec.edelivery.smp.testutil.TestConstants.*;
         CRLVerifierService.class,
         ConfigurationService.class,
         ServicesBeansConfiguration.class,
-        AlertService.class})
+        AlertService.class,
+        CronTriggerConfig.class})
 @Sql(scripts = {"classpath:cleanup-database.sql",
         "classpath:basic_conf_data-h2.sql"
 }, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD, config = @SqlConfig
diff --git a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/AlertServiceTest.java b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/AlertServiceTest.java
index b69ab3035..d13b8799c 100644
--- a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/AlertServiceTest.java
+++ b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/AlertServiceTest.java
@@ -1,6 +1,8 @@
 package eu.europa.ec.edelivery.smp.services;
 
+import eu.europa.ec.edelivery.smp.cron.SMPDynamicCronTrigger;
 import eu.europa.ec.edelivery.smp.data.dao.AlertDao;
+import eu.europa.ec.edelivery.smp.data.dao.UserDao;
 import eu.europa.ec.edelivery.smp.data.model.DBAlert;
 import eu.europa.ec.edelivery.smp.data.model.DBUser;
 import eu.europa.ec.edelivery.smp.data.ui.enums.AlertLevelEnum;
@@ -31,9 +33,11 @@ public class AlertServiceTest {
     AlertDao alertDao = Mockito.mock(AlertDao.class);
     MailService mailService = Mockito.mock(MailService.class);
     ConfigurationService configurationService = Mockito.mock(ConfigurationService.class);
+    UserDao userDao = Mockito.mock(UserDao.class);
+    SMPDynamicCronTrigger alertCronTrigger = Mockito.mock(SMPDynamicCronTrigger.class);
 
 
-    AlertService testInstance = new AlertService(alertDao, mailService, configurationService);
+    AlertService testInstance = new AlertService(alertDao, mailService, configurationService,userDao,alertCronTrigger);
 
     @Test
     public void testCreateAlert() {
diff --git a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/ui/UIPropertyServiceIntegrationTest.java b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/ui/UIPropertyServiceIntegrationTest.java
index bfabe011c..a5ee854f6 100644
--- a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/ui/UIPropertyServiceIntegrationTest.java
+++ b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/ui/UIPropertyServiceIntegrationTest.java
@@ -2,7 +2,6 @@ package eu.europa.ec.edelivery.smp.services.ui;
 
 import eu.europa.ec.edelivery.smp.cron.CronTriggerConfig;
 import eu.europa.ec.edelivery.smp.cron.SMPDynamicCronTrigger;
-import eu.europa.ec.edelivery.smp.data.dao.ConfigurationDao;
 import eu.europa.ec.edelivery.smp.data.model.DBConfiguration;
 import eu.europa.ec.edelivery.smp.data.ui.PropertyRO;
 import eu.europa.ec.edelivery.smp.data.ui.PropertyValidationRO;
@@ -25,17 +24,17 @@ import static eu.europa.ec.edelivery.smp.data.ui.enums.SMPPropertyEnum.SMP_CLUST
 import static eu.europa.ec.edelivery.smp.data.ui.enums.SMPPropertyEnum.SMP_PROPERTY_REFRESH_CRON;
 import static org.junit.Assert.*;
 
-@ContextConfiguration(classes = {UIPropertyService.class, CronTriggerConfig.class})
+@ContextConfiguration(classes = {UIPropertyService.class})
 public class UIPropertyServiceIntegrationTest extends AbstractServiceIntegrationTest {
 
 
     @Autowired
     protected UIPropertyService testInstance;
-    @Autowired @Qualifier(TRIGGER_BEAN_PROPERTY_REFRESH)
+    @Autowired
+    @Qualifier(TRIGGER_BEAN_PROPERTY_REFRESH)
     SMPDynamicCronTrigger refreshPropertiesTrigger;
 
 
-
     @Test
     public void getTableListAll() {
 
@@ -87,7 +86,7 @@ public class UIPropertyServiceIntegrationTest extends AbstractServiceIntegration
         assertEquals(propertyType.isEncrypted(), propertyRO.isEncrypted());
         assertEquals(propertyType.isMandatory(), propertyRO.isMandatory());
         assertEquals(propertyType.isRestartNeeded(), propertyRO.isRestartNeeded());
-}
+    }
 
     @Test
     public void updatePropertyList() {
@@ -103,12 +102,12 @@ public class UIPropertyServiceIntegrationTest extends AbstractServiceIntegration
 
         testInstance.updatePropertyList(Collections.singletonList(propertyRO));
         assertEquals(value, configurationDao.getCachedProperty(propertyType));
-
     }
+
     @Test
     public void validatePropertyNotExists() {
-        String propertyName="DoesNotExist";
-        String propertyValue="DoesNotExistValue";
+        String propertyName = "DoesNotExist";
+        String propertyValue = "DoesNotExistValue";
         PropertyRO property = new PropertyRO(propertyName, propertyValue);
 
         PropertyValidationRO result = testInstance.validateProperty(property);
@@ -116,13 +115,13 @@ public class UIPropertyServiceIntegrationTest extends AbstractServiceIntegration
         assertEquals(propertyName, result.getProperty());
         assertEquals(propertyValue, result.getValue());
         assertFalse(result.isPropertyValid());
-        MatcherAssert.assertThat(result.getErrorMessage(), CoreMatchers.containsString("Property ["+propertyName+"] is not SMP property!"));
+        MatcherAssert.assertThat(result.getErrorMessage(), CoreMatchers.containsString("Property [" + propertyName + "] is not SMP property!"));
     }
 
     @Test
     public void validatePropertyInvalidValue() {
-        String propertyName=SMPPropertyEnum.ACCESS_TOKEN_FAIL_DELAY.getProperty();
-        String propertyValue="NotANumber";
+        String propertyName = SMPPropertyEnum.ACCESS_TOKEN_FAIL_DELAY.getProperty();
+        String propertyValue = "NotANumber";
         PropertyRO property = new PropertyRO(propertyName, propertyValue);
 
         PropertyValidationRO result = testInstance.validateProperty(property);
@@ -130,13 +129,13 @@ public class UIPropertyServiceIntegrationTest extends AbstractServiceIntegration
         assertEquals(propertyName, result.getProperty());
         assertEquals(propertyValue, result.getValue());
         assertFalse(result.isPropertyValid());
-        MatcherAssert.assertThat(result.getErrorMessage(), CoreMatchers.containsString("Invalid integer: ["+propertyValue+"]. Error:NumberFormatException: For input string: \""+propertyValue+"\"!"));
+        MatcherAssert.assertThat(result.getErrorMessage(), CoreMatchers.containsString("Invalid integer: [" + propertyValue + "]. Error:NumberFormatException: For input string: \"" + propertyValue + "\"!"));
     }
 
     @Test
     public void validatePropertyOK() {
-        String propertyName=SMPPropertyEnum.ACCESS_TOKEN_FAIL_DELAY.getProperty();
-        String propertyValue="1223232";
+        String propertyName = SMPPropertyEnum.ACCESS_TOKEN_FAIL_DELAY.getProperty();
+        String propertyValue = "1223232";
         PropertyRO property = new PropertyRO(propertyName, propertyValue);
 
         PropertyValidationRO result = testInstance.validateProperty(property);
diff --git a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/utils/PropertyUtilsTest.java b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/utils/PropertyUtilsTest.java
index 9f689426e..71d024622 100644
--- a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/utils/PropertyUtilsTest.java
+++ b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/utils/PropertyUtilsTest.java
@@ -8,6 +8,7 @@ import junitparams.Parameters;
 import org.junit.Assert;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.springframework.scheduling.support.CronExpression;
 
 import java.io.File;
 import java.nio.file.Paths;
@@ -166,6 +167,9 @@ public class PropertyUtilsTest {
             case STRING:
                 Assert.assertEquals(String.class, value.getClass());
                 break;
+            case CRON_EXPRESSION:
+                Assert.assertEquals(CronExpression.class, value.getClass());
+                break;
             default:
                 fail("Unknown property type");
         }
diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/config/SMPTaskSchedulerConfig.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/config/SMPTaskSchedulerConfig.java
index a9e194844..380a9d740 100644
--- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/config/SMPTaskSchedulerConfig.java
+++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/config/SMPTaskSchedulerConfig.java
@@ -5,18 +5,17 @@ import eu.europa.ec.edelivery.smp.cron.SMPDynamicCronTrigger;
 import eu.europa.ec.edelivery.smp.data.dao.ConfigurationDao;
 import eu.europa.ec.edelivery.smp.logging.SMPLogger;
 import eu.europa.ec.edelivery.smp.logging.SMPLoggerFactory;
-import eu.europa.ec.edelivery.smp.services.AlertService;
 import eu.europa.ec.edelivery.smp.services.CredentialValidatorService;
-import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.scheduling.annotation.EnableScheduling;
 import org.springframework.scheduling.annotation.SchedulingConfigurer;
+import org.springframework.scheduling.config.CronTask;
 import org.springframework.scheduling.config.ScheduledTaskRegistrar;
 
-import java.net.InetAddress;
+import java.util.List;
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
 
@@ -33,6 +32,8 @@ public class SMPTaskSchedulerConfig implements SchedulingConfigurer {
     final SMPDynamicCronTrigger refreshPropertiesTrigger;
     final SMPDynamicCronTrigger credentialsAlertTrigger;
 
+    ScheduledTaskRegistrar taskRegistrar;
+
     @Autowired
     public SMPTaskSchedulerConfig(
             ConfigurationDao configurationDao,
@@ -53,10 +54,11 @@ public class SMPTaskSchedulerConfig implements SchedulingConfigurer {
 
     @Override
     public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
+        this.taskRegistrar = taskRegistrar;
         LOG.info("Configure cron tasks");
-        taskRegistrar.setScheduler(taskExecutor());
+        this.taskRegistrar.setScheduler(taskExecutor());
         LOG.debug("Configure cron task for property refresh");
-        taskRegistrar.addTriggerTask(
+        this.taskRegistrar.addTriggerTask(
                 () -> {
                     configurationDao.refreshProperties();
                 },
@@ -64,11 +66,19 @@ public class SMPTaskSchedulerConfig implements SchedulingConfigurer {
         );
 
         LOG.debug("Configure cron task for alerts: credentials validation");
-        taskRegistrar.addTriggerTask(
+        this.taskRegistrar.addTriggerTask(
                 () -> {
                     credentialValidatorService.validateCredentials();
                 },
                 credentialsAlertTrigger
         );
     }
+
+    public void updateCronTasks() { //call it when you want to change chron
+        synchronized (SMPTaskSchedulerConfig.class) {
+            List<CronTask> crons = this.taskRegistrar.getCronTaskList();
+            taskRegistrar.destroy(); //important, cleanups current scheduled tasks
+            taskRegistrar.afterPropertiesSet(); //rebuild
+        }
+    }
 }
diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/config/properties/SMPCronExpressionPropertyUpdateListener.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/config/properties/SMPCronExpressionPropertyUpdateListener.java
index 6d648c369..0767bb6f9 100644
--- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/config/properties/SMPCronExpressionPropertyUpdateListener.java
+++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/config/properties/SMPCronExpressionPropertyUpdateListener.java
@@ -1,16 +1,20 @@
 package eu.europa.ec.edelivery.smp.config.properties;
 
 import eu.europa.ec.edelivery.smp.config.PropertyUpdateListener;
+import eu.europa.ec.edelivery.smp.config.SMPTaskSchedulerConfig;
 import eu.europa.ec.edelivery.smp.cron.SMPDynamicCronTrigger;
 import eu.europa.ec.edelivery.smp.data.ui.enums.SMPPropertyEnum;
 import eu.europa.ec.edelivery.smp.logging.SMPLogger;
 import eu.europa.ec.edelivery.smp.logging.SMPLoggerFactory;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.scheduling.support.CronExpression;
 import org.springframework.stereotype.Component;
 
 import java.util.*;
 
 import static eu.europa.ec.edelivery.smp.data.ui.enums.SMPPropertyEnum.SMP_ALERT_CREDENTIALS_CRON;
 import static eu.europa.ec.edelivery.smp.data.ui.enums.SMPPropertyEnum.SMP_PROPERTY_REFRESH_CRON;
+
 /**
  * Property change listener for cron expression. Component updates crone version for the trigger with matching
  * Cron Expression Property
@@ -24,8 +28,13 @@ public class SMPCronExpressionPropertyUpdateListener implements PropertyUpdateLi
 
     final List<SMPDynamicCronTrigger> smpDynamicCronTriggerList;
 
-    public SMPCronExpressionPropertyUpdateListener(Optional<List<SMPDynamicCronTrigger>> cronTriggerList) {
+    SMPTaskSchedulerConfig taskSchedulerConfig;
+
+    public SMPCronExpressionPropertyUpdateListener(Optional<List<SMPDynamicCronTrigger>> cronTriggerList,
+                                                   SMPTaskSchedulerConfig taskSchedulerConfig
+    ) {
         this.smpDynamicCronTriggerList = cronTriggerList.orElse(Collections.emptyList());
+        this.taskSchedulerConfig = taskSchedulerConfig;
     }
 
 
@@ -36,13 +45,38 @@ public class SMPCronExpressionPropertyUpdateListener implements PropertyUpdateLi
             return;
         }
         // update cron expressions!
-        smpDynamicCronTriggerList.forEach(trigger -> {
-                    if (properties.containsKey(trigger.getCronExpressionProperty())) {
-                        trigger.updateCronExpression(
-                                (String) properties.get(trigger.getCronExpressionProperty()));
-                    }
-                }
-        );
+        boolean cronExpressionChanged = false;
+        for (SMPDynamicCronTrigger trigger : smpDynamicCronTriggerList) {
+            // check if updated properties contains value for the cron trigger
+            if (!properties.containsKey(trigger.getCronExpressionProperty())) {
+                LOG.debug("Update cron properties does not contain change for cron [{}]", trigger.getCronExpressionProperty());
+                continue;
+            }
+            // check if cron was changed
+            CronExpression newCronExpression = (CronExpression) properties.get(trigger.getCronExpressionProperty());
+            if (newCronExpression ==null) {
+                LOG.debug("New cron expression for property: [{}] is not set!, skip re-setting the cron!", trigger.getCronExpressionProperty());
+                continue;
+            }
+
+            if (StringUtils.equalsIgnoreCase(trigger.getExpression(), newCronExpression.toString())) {
+                LOG.debug("Cron expression did not changed for cron: [{}], skip re-setting the cron!", trigger.getCronExpressionProperty());
+                continue;
+            }
+            LOG.info("Change expression from [{}] to [{}] for property: [{}]!",
+                    trigger.getExpression(),
+                    newCronExpression.toString(),
+                    trigger.getCronExpressionProperty());
+
+            trigger.updateCronExpression((CronExpression)
+                    properties.get(trigger.getCronExpressionProperty()));
+            cronExpressionChanged = true;
+        }
+
+        if (cronExpressionChanged) {
+            LOG.debug("One of monitored cron expression changed! Reset the cron task configuration!");
+            taskSchedulerConfig.updateCronTasks();
+        }
     }
 
     @Override
-- 
GitLab