diff --git a/smp-angular/src/app/alert/alert-controller.ts b/smp-angular/src/app/alert/alert-controller.ts index b1a20b74410a7c650acd717f02cb7f5db7f2e78d..6009413b48b295ff2db84889fcc5e0cd2c90d63e 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 aa85012192b432b56297316b67e1632c553eafea..4f83ab2fc4a67e2301d39ac920926b470e028a4a 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 c751cd65bd1c2dba074e969637a54071c00e2215..b5a77433cd4bdc9c6fab92a4ee834dbbad756b5b 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 3afced83ca812977c29e72eeaa41049f70606f60..797401c5c18afd868c21c93bd355a050b1f055d3 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 79ebfde18ba907c3ac78266329ca3c6cfef3f0cf..b99c529394891292428fe07d03f22e313005df02 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 0000000000000000000000000000000000000000..5e50dc67f4b2471a98467e74fa1e4d596015b9a2 --- /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 0000000000000000000000000000000000000000..15511a6d54ec46a267f226066429a66cd7d4843d --- /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 0000000000000000000000000000000000000000..5709f7bcbf780ab6f33628e5a8b10a91bb43e2ac --- /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 0098e1cef1f1933ad1df2d784c54c5e9694f4d28..c28cd854a92d4d4c94501c20010678a0c997c14a 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 c2befd1b159e1ba56a1a36f6b758e4e1644bd98c..535e2cf453d616529f33e501d47e887965cf0254 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 7331cfe880c0a35afd21a5798ae1fcfde1613419..b3deadd120462e7ad8edcfe6420907acf04fccee 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 555ad498f7262788b4743e563699d339996d5fc2..2dc72e7587448c15a8da19118a1490a6ed0a9238 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 ca29a53265313adf8cbd4639032ff273d9ba21b8..19b20bd99e97b9c3976ef8c4f21029e4d5fdf27e 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 dcfadf546b045cc1a3f2dd6f3d597baa9b77efe8..1e049b0ef19c03890141173c1efedf8abd5810ee 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 a0d4b27f991c59364dd430015d04d5bac9fe02e0..1a54b99c0da95822c52d91b2d0c2ea2f050b7096 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 5b4f6ca8fe487509550351e74efe777229ce5d74..3580004e4e670c28dc13381d5d2c136305f4ddb6 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 47a94ec6e2617e9af54e6aa0cdf4d95de4a4c71b..8e0b362940e7585bdc82ce3e5db744e580539db6 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 7daf52f5cd547361b7fed14a33a158e31ee61846..3059a3d15bff7fd1c653412aa56a2bf489953a77 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 69d96a11f09cd05e37ae6135b918f7668fc4a633..9c42c52f70f05216dae1ee5139e2504b577696d2 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 f200b8798882aba05701fbfac5cde706e65b2bf2..d12d8ad1dd4c51269c996fa8934d25eb6b896c2f 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 f12a899acee8abdb4c411fae4a60607a225d2357..a79807b8e4a0d4ae9c85f936103255379a2fe302 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 9bd050a3b8ec55f4ef04d1912c7ca3a3eb9ed2ab..257fa28b1ff41bfff33937a9a6c8e1dba6833b2c 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 b69ab3035f43dbc5a6e0493ae5cd66997fa76524..d13b8799c6c1b9664929658d24fddc59472a18d8 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 bfabe011c619250ece09af20f505b42dcb8bf86d..a5ee854f6b130c1656543dfaf4a6b32b2b0f76b7 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 9f689426eff1206df46a02b3987f85c956dfc4a4..71d0246229078ec2673e97dbb09d6306e6d68c03 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 a9e1948440ec10209c17b9d1fe5cf692c6174899..380a9d740e4d9463cec8b5de3a30591e3ca09495 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 6d648c369857918cb60e279c32903a052a91d134..0767bb6f90165bb28c5e963ed52e71407e1b86be 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