1
+ /*
2
+ * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
3
+ *
4
+ * This program and the accompanying materials are made available under the
5
+ * terms of the Apache License, Version 2.0 which is available at
6
+ * https://www.apache.org/licenses/LICENSE-2.0
7
+ *
8
+ * SPDX-License-Identifier: Apache-2.0
9
+ *
10
+ * Contributors:
11
+ * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation
12
+ * Dataport AöR for the POSSIBLE project - Custom reimplementation for negotiation scope
13
+ *
14
+ */
15
+
16
+ package org .eclipse .edc .extension .possiblepolicy ;
17
+
18
+ import org .eclipse .edc .connector .core .policy .ContractExpiryCheckFunction ;
19
+ import org .eclipse .edc .policy .engine .spi .AtomicConstraintFunction ;
20
+ import org .eclipse .edc .policy .engine .spi .PolicyContext ;
21
+ import org .eclipse .edc .policy .model .Operator ;
22
+ import org .eclipse .edc .policy .model .Rule ;
23
+ import org .eclipse .edc .spi .monitor .Monitor ;
24
+
25
+ import java .time .Instant ;
26
+ import java .time .format .DateTimeParseException ;
27
+
28
+ import static java .lang .String .format ;
29
+
30
+ /**
31
+ * Custom reimplementation of the {@link ContractExpiryCheckFunction} from the Connector core to allow it to be used in the negotiation scope.
32
+ * <p>
33
+ * Constraint function that evaluates a time-based constraint. That is a constraint that either uses the "inForceDate" operand
34
+ * with a fixed time (ISO-8061 UTC) as:
35
+ * <pre>
36
+ * {
37
+ * "leftOperand": "edc:inForceDate",
38
+ * "operator": "GEQ",
39
+ * "rightOperand": "2024-01-01T00:00:01Z"
40
+ * }
41
+ * </pre>
42
+ * Alternatively, it is possible to use a duration expression:
43
+ * <pre>
44
+ * {
45
+ * "leftOperand": "edc:inForceDate",
46
+ * "operator": "GEQ",
47
+ * "rightOperand": "contractAgreement+365d"
48
+ * }
49
+ * </pre>
50
+ * following the following schema: {@code <offset> + <numeric value>s|m|h|d} where {@code offset} must be equal to {@code "contractAgreement"}
51
+ * (not case-sensitive) and refers to the signing date of the contract in Epoch seconds. Omitting the {@code offset} is not permitted.
52
+ * The numeric value can have negative values.
53
+ * Thus, the following examples would be valid:
54
+ * <ul>
55
+ * <li>contractAgreement+15s</li>
56
+ * <li>contractAgreement+7d</li>
57
+ * <li>contractAgreement+1h</li>
58
+ * <li>contractAgreement+-5m (means "5 minutes before the signing of the contract")</li>
59
+ * </ul>
60
+ * Please note that all {@link Operator}s except {@link Operator#IN} are supported.
61
+ */
62
+ public class NegotiationContractExpiryCheckFunction <R extends Rule > implements AtomicConstraintFunction <R > {
63
+
64
+ private final Monitor monitor ;
65
+ private static final String EXPRESSION_REGEX = "(contract[A,a]greement)\\ +(-?[0-9]+)(s|m|h|d)" ;
66
+
67
+ public NegotiationContractExpiryCheckFunction (Monitor monitor ) {
68
+
69
+ this .monitor = monitor ;
70
+ }
71
+
72
+ @ Override
73
+ public boolean evaluate (Operator operator , Object rightValue , R rule , PolicyContext context ) {
74
+ if (!(rightValue instanceof String )) {
75
+ context .reportProblem ("Right-value expected to be String but was " + rightValue .getClass ());
76
+ return false ;
77
+ }
78
+
79
+ try {
80
+ Instant now = Instant .now (); // "now" is not in context during negotiation, use current time
81
+
82
+ var rightValueStr = (String ) rightValue ;
83
+ var bound = asInstant (rightValueStr );
84
+ if (bound != null ) {
85
+ monitor .info (format ("Validating time constraint %s %s %s" , now , operator , bound ));
86
+ return checkFixedPeriod (now , operator , bound );
87
+ }
88
+
89
+ if (rightValueStr .matches (EXPRESSION_REGEX )) {
90
+ monitor .info (format ("Offset constraints are ignored during negotiation. %s %s %s" ,
91
+ ContractExpiryCheckFunction .CONTRACT_EXPIRY_EVALUATION_KEY , operator , rightValueStr ));
92
+ return true ;
93
+ }
94
+
95
+ context .reportProblem (format ("Unsupported right-value, expected either an ISO-8061 String or a expression matching '%s', but got '%s'" ,
96
+ ContractExpiryCheckFunction .CONTRACT_EXPIRY_EVALUATION_KEY , rightValueStr ));
97
+ } catch (NullPointerException | DateTimeParseException ex ) {
98
+ monitor .warning (format ("Exception during time policy evaluation: %s %s" , ex .getClass ().getCanonicalName (), ex .getMessage ()));
99
+ context .reportProblem (ex .getMessage ());
100
+ }
101
+ return false ;
102
+ }
103
+
104
+ private boolean checkFixedPeriod (Instant now , Operator operator , Instant bound ) {
105
+ var comparison = now .compareTo (bound );
106
+
107
+ return switch (operator ) {
108
+ case EQ -> comparison == 0 ;
109
+ case NEQ -> comparison != 0 ;
110
+ case GT -> comparison > 0 ;
111
+ case GEQ -> comparison >= 0 ;
112
+ case LT -> comparison < 0 ;
113
+ case LEQ -> comparison <= 0 ;
114
+ default -> throw new IllegalStateException ("Unexpected value: " + operator );
115
+ };
116
+ }
117
+
118
+ private Instant asInstant (String isoString ) {
119
+ try {
120
+ return Instant .parse (isoString );
121
+ } catch (DateTimeParseException e ) {
122
+ return null ;
123
+ }
124
+ }
125
+ }
0 commit comments