-
Notifications
You must be signed in to change notification settings - Fork 188
/
CombinatorialEvaluation.java
326 lines (290 loc) · 13.7 KB
/
CombinatorialEvaluation.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
/*
* Copyright (c) 2020 Evolveum and contributors
*
* This work is dual-licensed under the Apache License 2.0
* and European Union Public License. See LICENSE file for details.
*/
package com.evolveum.midpoint.model.common.expression.evaluator.transformation;
import com.evolveum.midpoint.prism.*;
import com.evolveum.midpoint.prism.delta.ItemDelta;
import com.evolveum.midpoint.prism.delta.PlusMinusZero;
import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple;
import com.evolveum.midpoint.repo.common.expression.*;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.util.MiscUtil;
import com.evolveum.midpoint.util.exception.*;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import com.evolveum.midpoint.xml.ns._public.common.common_3.TransformExpressionEvaluatorType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ValueTransformationEvaluationModeType;
import com.evolveum.prism.xml.ns._public.types_3.DeltaSetTripleType;
import org.jetbrains.annotations.NotNull;
import java.util.*;
/**
* Application of expression evaluator in "combinatorial" ("relative") mode.
*
* Each combinations of values from sources is evaluated separately and the resulting values
* are sorted out into plus-minus-zero sets.
*/
class CombinatorialEvaluation<V extends PrismValue, D extends ItemDefinition, E extends TransformExpressionEvaluatorType> extends TransformationalEvaluation<V, D, E> {
private static final Trace LOGGER = TraceManager.getTrace(CombinatorialEvaluation.class);
/**
* Configuration of the evaluator.
*/
@NotNull private final E evaluatorBean;
/**
* Plus-Minus-Zero sets of input values for individual sources (with some "fake nulls" magic).
* Ordered according to the order of sources.
*/
@NotNull final List<SourceTriple<?,?>> sourceTripleList;
/**
* Which triples do have which sets? (Looking for plus and zero sets.)
* Special value of null in the set means that the respective source is empty.
*/
@NotNull private final List<Set<PlusMinusZero>> setsOccupiedPlusZero = new ArrayList<>();
/**
* Which triples do have which sets? (Looking for minus and zero sets.)
* Special value of null in the set means that the respective source is empty.
*/
@NotNull private final List<Set<PlusMinusZero>> setsOccupiedMinusZero = new ArrayList<>();
/**
* Pre-compiled condition expression, to be evaluated for individual value combinations.
*/
final Expression<PrismPropertyValue<Boolean>, PrismPropertyDefinition<Boolean>> conditionExpression;
/**
* Resulting output triple (plus-minus-zero).
*/
@NotNull final PrismValueDeltaSetTriple<V> outputTriple;
CombinatorialEvaluation(ExpressionEvaluationContext context, OperationResult parentResult,
AbstractValueTransformationExpressionEvaluator<V, D, E> evaluator) throws SecurityViolationException, ObjectNotFoundException, SchemaException {
super(context, parentResult, evaluator);
this.evaluatorBean = evaluator.getExpressionEvaluatorBean();
this.sourceTripleList = createSourceTriplesList();
this.conditionExpression = createConditionExpression();
this.outputTriple = prismContext.deltaFactory().createPrismValueDeltaSetTriple();
computeSetsOccupied();
}
PrismValueDeltaSetTriple<V> evaluate() throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException,
CommunicationException, ConfigurationException, SecurityViolationException {
recordEvaluationStart();
try {
transformValues();
} catch (TunnelException e) {
unwrapTunnelException(e);
}
cleanUpOutputTriple();
recordEvaluationEnd(outputTriple);
return outputTriple;
}
/**
* Going through combinations of all non-negative and non-positive values and transform each tuple found.
* It is done in two steps:
*
* 1) Specific plus-minus-zero sets are selected for each source, combining non-negative to PLUS (except for all zeros
* that go into ZERO), and then combining non-positive to MINUS (except for all zeros that are skipped because they are
* already computed).
*
* 2) All values from selected sets are combined together.
*/
private void transformValues() {
// The skipEvaluationPlus is evaluated within.
transformToPlusAndZeroSets();
// But for minus we can prune this branch even here.
if (!context.isSkipEvaluationMinus()) {
transformToMinusSets();
}
}
private void transformToPlusAndZeroSets() {
MiscUtil.carthesian(setsOccupiedPlusZero, sets -> {
if (isAllZeros(sets)) {
transform(sets, PlusMinusZero.ZERO);
} else {
if (!context.isSkipEvaluationPlus()) {
transform(sets, PlusMinusZero.PLUS);
}
}
});
}
private void transformToMinusSets() {
MiscUtil.carthesian(setsOccupiedMinusZero, sets -> {
if (isAllZeros(sets)) {
// already done
} else {
transform(sets, PlusMinusZero.MINUS);
}
});
}
private boolean isAllZeros(List<PlusMinusZero> sets) {
return sets.stream().allMatch(set -> set == null || set == PlusMinusZero.ZERO);
}
private void transform(List<PlusMinusZero> sets, PlusMinusZero outputSet) {
List<Collection<PrismValue>> domains = createDomainsForSets(sets);
logDomainsForSets(domains, sets, outputSet);
MiscUtil.carthesian(domains, valuesTuple -> {
try (ValueTupleTransformation<V> valueTupleTransformation =
new ValueTupleTransformation<>(sets, valuesTuple, outputSet, this, parentResult)) {
valueTupleTransformation.evaluate();
}
});
}
private void logDomainsForSets(List<Collection<PrismValue>> domains, List<PlusMinusZero> sets, PlusMinusZero outputSet) {
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Domains for sets, targeting {}:", outputSet);
Iterator<PlusMinusZero> setsIterator = sets.iterator();
for (Collection<PrismValue> domain : domains) {
LOGGER.trace(" - set: {}, domain: {}", setsIterator.next(), domain);
}
}
}
private List<Collection<PrismValue>> createDomainsForSets(List<PlusMinusZero> sets) {
List<Collection<PrismValue>> domains = new ArrayList<>();
Iterator<PlusMinusZero> setsIterator = sets.iterator();
for (SourceTriple<?, ?> sourceTriple : sourceTripleList) {
PlusMinusZero set = setsIterator.next();
Collection<PrismValue> domain;
if (set != null) {
//noinspection unchecked
domain = (Collection<PrismValue>) sourceTriple.getSet(set);
} else {
// This is a special value ensuring that the tuple computation will run at least once
// even for empty sources.
domain = Collections.singleton(null);
}
domains.add(domain);
}
return domains;
}
private void computeSetsOccupied() {
for (SourceTriple<?, ?> sourceTriple : sourceTripleList) {
Set<PlusMinusZero> setOccupiedPlusZero = new HashSet<>();
Set<PlusMinusZero> setOccupiedMinusZero = new HashSet<>();
if (sourceTriple.isEmpty()) {
setOccupiedPlusZero.add(null);
setOccupiedMinusZero.add(null);
} else {
if (sourceTriple.hasPlusSet()) {
setOccupiedPlusZero.add(PlusMinusZero.PLUS);
}
if (sourceTriple.hasMinusSet()) {
setOccupiedMinusZero.add(PlusMinusZero.MINUS);
}
if (sourceTriple.hasZeroSet()) {
setOccupiedPlusZero.add(PlusMinusZero.ZERO);
setOccupiedMinusZero.add(PlusMinusZero.ZERO);
}
}
setsOccupiedPlusZero.add(setOccupiedPlusZero);
setsOccupiedMinusZero.add(setOccupiedMinusZero);
LOGGER.trace("Adding setsOccupiedPlusZero: {}, setOccupiedMinusZero: {} for source {}",
setOccupiedPlusZero, setOccupiedMinusZero, sourceTriple.getName());
}
}
private void recordEvaluationStart() throws SchemaException {
if (trace != null) {
super.recordEvaluationStart(ValueTransformationEvaluationModeType.COMBINATORIAL);
int i = 0;
for (SourceTriple<?, ?> sourceTriple : sourceTripleList) {
trace.getSource().get(i)
.setDeltaSetTriple(
DeltaSetTripleType.fromDeltaSetTriple(sourceTriple, prismContext));
i++;
}
}
}
private Expression<PrismPropertyValue<Boolean>, PrismPropertyDefinition<Boolean>> createConditionExpression()
throws SchemaException, ObjectNotFoundException, SecurityViolationException {
if (evaluatorBean.getCondition() != null) {
return ExpressionUtil.createCondition(evaluatorBean.getCondition(), context.getExpressionProfile(),
context.getExpressionFactory(), "condition in " + context.getContextDescription(), context.getTask(),
parentResult);
} else {
return null;
}
}
private List<SourceTriple<?,?>> createSourceTriplesList() throws SchemaException {
Collection<Source<?, ?>> sources = context.getSources();
List<SourceTriple<?,?>> sourceTriples = new ArrayList<>(sources.size());
for (Source<?,?> source: sources) {
//noinspection unchecked
sourceTriples.add(
createSourceTriple((Source) source));
}
return sourceTriples;
}
private SourceTriple<V, D> createSourceTriple(Source<V, D> source) throws SchemaException {
SourceTriple<V, D> sourceTriple = new SourceTriple<>(source, prismContext);
ItemDelta<V, D> delta = source.getDelta();
if (delta != null) {
sourceTriple.merge(delta.toDeltaSetTriple(source.getItemOld()));
} else {
if (source.getItemOld() != null) {
sourceTriple.addAllToZeroSet(source.getItemOld().getValues());
}
}
if (evaluator.isIncludeNullInputs()) {
addFakeNullValues(sourceTriple, source);
}
LOGGER.trace("Processed source {} triple\n{}", source.getName().getLocalPart(), sourceTriple.debugDumpLazily(1));
return sourceTriple;
}
/**
* We have to ensure that no source would block computation of neither "non-positive"
* nor "non-negative" values by not providing any inputs in respective sets.
*
* TODO Verify the correctness of this algorithm.
*/
private void addFakeNullValues(SourceTriple<V, D> sourceTriple, Source<V, D> source) {
if (sourceTriple.hasZeroSet()) {
// This is good. Because we have zero set, it will be applied both for non-positive
// and non-negative computations. Nothing has to be done.
return;
}
boolean hasPlus = sourceTriple.hasPlusSet();
boolean hasMinus = sourceTriple.hasMinusSet();
if (!hasPlus && !hasMinus) {
// We have nothing in plus nor minus sets. Both non-positive/non-negative cases can
// be treated by including null into zero set.
sourceTriple.addToZeroSet(null);
} else if (!hasPlus) {
// Only plus set is empty. So let's treat that one.
sourceTriple.addToPlusSet(null);
} else if (!hasMinus) {
// Only minus set is empty. Do the same.
sourceTriple.addToMinusSet(null);
}
}
private void cleanUpOutputTriple() {
Collection<V> minusSet = outputTriple.getMinusSet();
Collection<V> plusSet = outputTriple.getPlusSet();
Iterator<V> plusIter = plusSet.iterator();
while (plusIter.hasNext()) {
V plusVal = plusIter.next();
if (minusSet.contains(plusVal)) {
plusIter.remove();
minusSet.remove(plusVal);
outputTriple.addToZeroSet(plusVal);
}
}
}
private void unwrapTunnelException(TunnelException e) throws ExpressionEvaluationException, ObjectNotFoundException,
SchemaException, CommunicationException, ConfigurationException, SecurityViolationException {
Throwable originalException = e.getCause();
if (originalException instanceof ExpressionEvaluationException) {
throw (ExpressionEvaluationException)originalException;
} else if (originalException instanceof ObjectNotFoundException) {
throw (ObjectNotFoundException)originalException;
} else if (originalException instanceof SchemaException) {
throw (SchemaException)originalException;
} else if (originalException instanceof CommunicationException) {
throw (CommunicationException)originalException;
} else if (originalException instanceof ConfigurationException) {
throw (ConfigurationException)originalException;
} else if (originalException instanceof SecurityViolationException) {
throw (SecurityViolationException)originalException;
} else if (originalException instanceof RuntimeException) {
throw (RuntimeException)originalException;
} else {
throw new IllegalStateException("Unexpected exception: "+e+": "+e.getMessage(),e);
}
}
}