1
2
3
4
5
6 package gov.nist.secauto.metaschema.core.metapath;
7
8 import com.github.benmanes.caffeine.cache.Caffeine;
9
10 import gov.nist.secauto.metaschema.core.configuration.DefaultConfiguration;
11 import gov.nist.secauto.metaschema.core.configuration.IConfiguration;
12 import gov.nist.secauto.metaschema.core.configuration.IMutableConfiguration;
13 import gov.nist.secauto.metaschema.core.metapath.function.CalledContext;
14 import gov.nist.secauto.metaschema.core.metapath.function.DateTimeFunctionException;
15 import gov.nist.secauto.metaschema.core.metapath.function.IFunction;
16 import gov.nist.secauto.metaschema.core.metapath.function.IFunction.FunctionProperty;
17 import gov.nist.secauto.metaschema.core.metapath.item.ISequence;
18 import gov.nist.secauto.metaschema.core.metapath.item.atomic.IDayTimeDurationItem;
19 import gov.nist.secauto.metaschema.core.metapath.item.node.IDocumentNodeItem;
20 import gov.nist.secauto.metaschema.core.model.IUriResolver;
21 import gov.nist.secauto.metaschema.core.qname.IEnhancedQName;
22 import gov.nist.secauto.metaschema.core.util.ObjectUtils;
23
24 import java.io.IOException;
25 import java.net.URI;
26 import java.time.Clock;
27 import java.time.Duration;
28 import java.time.LocalDateTime;
29 import java.time.ZoneId;
30 import java.time.ZoneOffset;
31 import java.time.ZonedDateTime;
32 import java.util.ArrayDeque;
33 import java.util.Collections;
34 import java.util.Deque;
35 import java.util.HashMap;
36 import java.util.Map;
37 import java.util.concurrent.ConcurrentHashMap;
38 import java.util.concurrent.TimeUnit;
39 import java.util.stream.Collectors;
40
41 import edu.umd.cs.findbugs.annotations.NonNull;
42 import edu.umd.cs.findbugs.annotations.Nullable;
43
44
45
46
47
48
49 public class DynamicContext {
50
51 @NonNull
52 private final Map<Integer, ISequence<?>> letVariableMap;
53 @NonNull
54 private final SharedState sharedState;
55
56
57
58
59 public DynamicContext() {
60 this(StaticContext.instance());
61 }
62
63
64
65
66
67
68
69 public DynamicContext(@NonNull StaticContext staticContext) {
70 this.letVariableMap = new ConcurrentHashMap<>();
71 this.sharedState = new SharedState(staticContext);
72 }
73
74 private DynamicContext(@NonNull DynamicContext context) {
75 this.letVariableMap = new ConcurrentHashMap<>(context.letVariableMap);
76 this.sharedState = context.sharedState;
77 }
78
79 private static class SharedState {
80 @NonNull
81 private final StaticContext staticContext;
82 @NonNull
83 private final ZonedDateTime currentDateTime;
84 @NonNull
85 private final Map<URI, IDocumentNodeItem> availableDocuments;
86 @NonNull
87 private final Map<CalledContext, ISequence<?>> functionResultCache;
88 @Nullable
89 private CachingLoader documentLoader;
90 @NonNull
91 private final IMutableConfiguration<MetapathEvaluationFeature<?>> configuration;
92 @NonNull
93 private final Deque<IExpression> executionStack = new ArrayDeque<>();
94 @NonNull
95 private ZoneId implicitTimeZone;
96
97 public SharedState(@NonNull StaticContext staticContext) {
98 this.staticContext = staticContext;
99
100 Clock clock = Clock.systemDefaultZone();
101
102 this.implicitTimeZone = ObjectUtils.notNull(clock.getZone());
103
104 this.currentDateTime = ObjectUtils.notNull(ZonedDateTime.now(clock));
105 this.availableDocuments = new HashMap<>();
106 this.functionResultCache = ObjectUtils.notNull(Caffeine.newBuilder()
107 .maximumSize(5000)
108 .expireAfterAccess(10, TimeUnit.MINUTES)
109 .<CalledContext, ISequence<?>>build().asMap());
110 this.configuration = new DefaultConfiguration<>();
111 this.configuration.enableFeature(MetapathEvaluationFeature.METAPATH_EVALUATE_PREDICATES);
112 }
113 }
114
115
116
117
118
119
120
121
122
123
124
125 @NonNull
126 public DynamicContext subContext() {
127 return new DynamicContext(this);
128 }
129
130
131
132
133
134
135 @NonNull
136 public StaticContext getStaticContext() {
137 return sharedState.staticContext;
138 }
139
140
141
142
143
144
145 @NonNull
146 public ZoneId getImplicitTimeZone() {
147 return sharedState.implicitTimeZone;
148 }
149
150
151
152
153
154
155 @NonNull
156 public IDayTimeDurationItem getImplicitTimeZoneAsDayTimeDuration() {
157 LocalDateTime referenceDateTime = MetapathConstants.REFERENCE_DATE_TIME.asLocalDateTime();
158 ZonedDateTime reference = referenceDateTime.atZone(getImplicitTimeZone());
159 ZonedDateTime referenceZ = referenceDateTime.atZone(ZoneOffset.UTC);
160
161 return IDayTimeDurationItem.valueOf(ObjectUtils.notNull(
162 Duration.between(
163 reference,
164 referenceZ)));
165 }
166
167
168
169
170
171
172
173
174
175
176 public void setImplicitTimeZone(@NonNull ZoneId timezone) {
177 sharedState.implicitTimeZone = timezone;
178 }
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193 public void setImplicitTimeZone(@NonNull IDayTimeDurationItem offset) {
194 setImplicitTimeZone(offset.asZoneOffset());
195 }
196
197
198
199
200
201
202 @NonNull
203 public ZonedDateTime getCurrentDateTime() {
204 return sharedState.currentDateTime;
205 }
206
207
208
209
210
211
212
213 @SuppressWarnings("null")
214 @NonNull
215 public Map<URI, IDocumentNodeItem> getAvailableDocuments() {
216 return Collections.unmodifiableMap(sharedState.availableDocuments);
217 }
218
219
220
221
222
223
224
225
226 @NonNull
227 public IDocumentLoader getDocumentLoader() {
228 IDocumentLoader retval = sharedState.documentLoader;
229 if (retval == null) {
230 throw new UnsupportedOperationException(
231 "No document loader configured for the dynamic context. Use setDocumentLoader(loader) to confgure one.");
232 }
233 return retval;
234 }
235
236
237
238
239
240
241
242 public void setDocumentLoader(@NonNull IDocumentLoader documentLoader) {
243 this.sharedState.documentLoader = new CachingLoader(documentLoader);
244 }
245
246
247
248
249
250
251
252
253
254
255 @Nullable
256 public ISequence<?> getCachedResult(@NonNull CalledContext callingContext) {
257 return sharedState.functionResultCache.get(callingContext);
258 }
259
260
261
262
263
264
265
266
267
268
269
270 public void cacheResult(@NonNull CalledContext callingContext, @NonNull ISequence<?> result) {
271 ISequence<?> old = sharedState.functionResultCache.put(callingContext, result);
272 assert old == null;
273 }
274
275
276
277
278
279
280
281
282
283
284
285 @NonNull
286 public DynamicContext disablePredicateEvaluation() {
287 this.sharedState.configuration.disableFeature(MetapathEvaluationFeature.METAPATH_EVALUATE_PREDICATES);
288 return this;
289 }
290
291
292
293
294
295
296
297
298
299 @NonNull
300 public DynamicContext enablePredicateEvaluation() {
301 this.sharedState.configuration.enableFeature(MetapathEvaluationFeature.METAPATH_EVALUATE_PREDICATES);
302 return this;
303 }
304
305
306
307
308
309
310 @NonNull
311 public IConfiguration<MetapathEvaluationFeature<?>> getConfiguration() {
312 return sharedState.configuration;
313 }
314
315
316
317
318
319
320
321
322
323
324
325
326 @NonNull
327 public ISequence<?> getVariableValue(@NonNull IEnhancedQName name) {
328 ISequence<?> retval = letVariableMap.get(name.getIndexPosition());
329 if (retval == null) {
330 throw new StaticMetapathException(
331 StaticMetapathException.NOT_DEFINED,
332 String.format("Variable '%s' not defined in the dynamic context.", name))
333 .registerEvaluationContext(this);
334 }
335 return retval;
336 }
337
338
339
340
341
342
343
344
345
346
347
348
349
350 @NonNull
351 public IFunction lookupFunction(@NonNull IEnhancedQName name, int arity) {
352 return getStaticContext().lookupFunction(name, arity);
353 }
354
355
356
357
358
359
360
361
362
363
364 @NonNull
365 public DynamicContext bindVariableValue(@NonNull IEnhancedQName name, @NonNull ISequence<?> boundValue) {
366 letVariableMap.put(name.getIndexPosition(), boundValue);
367 return this;
368 }
369
370
371
372
373
374
375
376 public void pushExecutionStack(@NonNull IExpression expression) {
377 this.sharedState.executionStack.push(expression);
378 }
379
380
381
382
383
384
385
386 public void popExecutionStack(@NonNull IExpression expression) {
387 IExpression popped = this.sharedState.executionStack.pop();
388 if (!expression.equals(popped)) {
389 throw new IllegalStateException("Popped expression does not match expected expression");
390 }
391 }
392
393
394
395
396
397
398 @NonNull
399 public Deque<IExpression> getExecutionStack() {
400 return new ArrayDeque<>(this.sharedState.executionStack);
401 }
402
403
404
405
406
407
408 @NonNull
409 public String formatExecutionStackTrace() {
410 return ObjectUtils.notNull(getExecutionStack().stream()
411 .map(IExpression::toCSTString)
412 .collect(Collectors.joining("\n-> ")));
413 }
414
415 private class CachingLoader implements IDocumentLoader {
416 @NonNull
417 private final IDocumentLoader proxy;
418
419 public CachingLoader(@NonNull IDocumentLoader proxy) {
420 this.proxy = proxy;
421 }
422
423 @Override
424 public IUriResolver getUriResolver() {
425 return new ContextUriResolver();
426 }
427
428 @Override
429 public void setUriResolver(@NonNull IUriResolver resolver) {
430
431 throw new UnsupportedOperationException("Set the resolver on the proxy");
432 }
433
434 @NonNull
435 protected IDocumentLoader getProxiedDocumentLoader() {
436 return proxy;
437 }
438
439 @Override
440 public IDocumentNodeItem loadAsNodeItem(URI uri) throws IOException {
441 IDocumentNodeItem retval = sharedState.availableDocuments.get(uri);
442 if (retval == null) {
443 retval = getProxiedDocumentLoader().loadAsNodeItem(uri);
444 sharedState.availableDocuments.put(uri, retval);
445 }
446 return retval;
447 }
448
449 public class ContextUriResolver implements IUriResolver {
450
451
452
453
454
455
456
457 @Override
458 public URI resolve(URI uri) {
459 URI baseUri = getStaticContext().getBaseUri();
460
461 URI resolvedUri;
462 if (baseUri == null) {
463 resolvedUri = uri;
464 } else {
465 resolvedUri = ObjectUtils.notNull(baseUri.resolve(uri));
466 }
467
468 IUriResolver resolver = getProxiedDocumentLoader().getUriResolver();
469 return resolver == null ? resolvedUri : resolver.resolve(resolvedUri);
470 }
471 }
472 }
473 }