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