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.DefaultFunction.CallingContext;
14 import gov.nist.secauto.metaschema.core.metapath.function.IFunction.FunctionProperty;
15 import gov.nist.secauto.metaschema.core.metapath.item.node.IDocumentNodeItem;
16 import gov.nist.secauto.metaschema.core.model.IUriResolver;
17 import gov.nist.secauto.metaschema.core.qname.IEnhancedQName;
18 import gov.nist.secauto.metaschema.core.util.ObjectUtils;
19
20 import java.io.IOException;
21 import java.net.URI;
22 import java.time.Clock;
23 import java.time.ZoneId;
24 import java.time.ZonedDateTime;
25 import java.util.Collections;
26 import java.util.HashMap;
27 import java.util.Map;
28 import java.util.concurrent.ConcurrentHashMap;
29 import java.util.concurrent.TimeUnit;
30
31 import edu.umd.cs.findbugs.annotations.NonNull;
32 import edu.umd.cs.findbugs.annotations.Nullable;
33
34
35
36
37
38
39 public class DynamicContext {
40 @NonNull
41 private final Map<Integer, ISequence<?>> letVariableMap;
42 @NonNull
43 private final SharedState sharedState;
44
45
46
47
48 public DynamicContext() {
49 this(StaticContext.instance());
50 }
51
52
53
54
55
56
57
58 public DynamicContext(@NonNull StaticContext staticContext) {
59 this.letVariableMap = new ConcurrentHashMap<>();
60 this.sharedState = new SharedState(staticContext);
61 }
62
63 private DynamicContext(@NonNull DynamicContext context) {
64 this.letVariableMap = new ConcurrentHashMap<>(context.letVariableMap);
65 this.sharedState = context.sharedState;
66 }
67
68 private static class SharedState {
69 @NonNull
70 private final StaticContext staticContext;
71 @NonNull
72 private final ZoneId implicitTimeZone;
73 @NonNull
74 private final ZonedDateTime currentDateTime;
75 @NonNull
76 private final Map<URI, IDocumentNodeItem> availableDocuments;
77 @NonNull
78 private final Map<CallingContext, ISequence<?>> functionResultCache;
79 @Nullable
80 private CachingLoader documentLoader;
81 @NonNull
82 private final IMutableConfiguration<MetapathEvaluationFeature<?>> configuration;
83
84 public SharedState(@NonNull StaticContext staticContext) {
85 this.staticContext = staticContext;
86
87 Clock clock = Clock.systemDefaultZone();
88
89 this.implicitTimeZone = ObjectUtils.notNull(clock.getZone());
90 this.currentDateTime = ObjectUtils.notNull(ZonedDateTime.now(clock));
91 this.availableDocuments = new HashMap<>();
92 this.functionResultCache = ObjectUtils.notNull(Caffeine.newBuilder()
93 .maximumSize(5000)
94 .expireAfterAccess(10, TimeUnit.MINUTES)
95 .<CallingContext, ISequence<?>>build().asMap());
96 this.configuration = new DefaultConfiguration<>();
97 this.configuration.enableFeature(MetapathEvaluationFeature.METAPATH_EVALUATE_PREDICATES);
98 }
99 }
100
101
102
103
104
105
106
107
108
109
110
111 @NonNull
112 public DynamicContext subContext() {
113 return new DynamicContext(this);
114 }
115
116
117
118
119
120
121 @NonNull
122 public StaticContext getStaticContext() {
123 return sharedState.staticContext;
124 }
125
126
127
128
129
130
131 @NonNull
132 public ZoneId getImplicitTimeZone() {
133 return sharedState.implicitTimeZone;
134 }
135
136
137
138
139
140
141 @NonNull
142 public ZonedDateTime getCurrentDateTime() {
143 return sharedState.currentDateTime;
144 }
145
146
147
148
149
150
151
152 @SuppressWarnings("null")
153 @NonNull
154 public Map<URI, IDocumentNodeItem> getAvailableDocuments() {
155 return Collections.unmodifiableMap(sharedState.availableDocuments);
156 }
157
158
159
160
161
162
163
164
165
166
167 @NonNull
168 public IDocumentLoader getDocumentLoader() {
169 IDocumentLoader retval = sharedState.documentLoader;
170 if (retval == null) {
171 throw new DynamicMetapathException(DynamicMetapathException.DYNAMIC_CONTEXT_ABSENT,
172 "No document loader configured for the dynamic context.");
173 }
174 return retval;
175 }
176
177
178
179
180
181
182
183 public void setDocumentLoader(@NonNull IDocumentLoader documentLoader) {
184 this.sharedState.documentLoader = new CachingLoader(documentLoader);
185 }
186
187
188
189
190
191
192
193
194
195
196 @Nullable
197 public ISequence<?> getCachedResult(@NonNull CallingContext callingContext) {
198 return sharedState.functionResultCache.get(callingContext);
199 }
200
201
202
203
204
205
206
207
208
209
210
211 public void cacheResult(@NonNull CallingContext callingContext, @NonNull ISequence<?> result) {
212 ISequence<?> old = sharedState.functionResultCache.put(callingContext, result);
213 assert old == null;
214 }
215
216
217
218
219
220
221
222
223
224
225
226 @NonNull
227 public DynamicContext disablePredicateEvaluation() {
228 this.sharedState.configuration.disableFeature(MetapathEvaluationFeature.METAPATH_EVALUATE_PREDICATES);
229 return this;
230 }
231
232
233
234
235
236
237
238
239
240 @NonNull
241 public DynamicContext enablePredicateEvaluation() {
242 this.sharedState.configuration.enableFeature(MetapathEvaluationFeature.METAPATH_EVALUATE_PREDICATES);
243 return this;
244 }
245
246
247
248
249
250
251 @NonNull
252 public IConfiguration<MetapathEvaluationFeature<?>> getConfiguration() {
253 return sharedState.configuration;
254 }
255
256
257
258
259
260
261
262
263
264
265
266
267 @NonNull
268 public ISequence<?> getVariableValue(@NonNull IEnhancedQName name) {
269 ISequence<?> retval = letVariableMap.get(name.getIndexPosition());
270 if (retval == null) {
271 if (letVariableMap.containsKey(name.getIndexPosition())) {
272 throw new MetapathException(String.format("Variable '%s' has null contents.", name));
273 }
274 throw new StaticMetapathException(
275 StaticMetapathException.NOT_DEFINED,
276 String.format("Variable '%s' not defined in the dynamic context.", name));
277 }
278 return retval;
279 }
280
281
282
283
284
285
286
287
288
289
290 @NonNull
291 public DynamicContext bindVariableValue(@NonNull IEnhancedQName name, @NonNull ISequence<?> boundValue) {
292 letVariableMap.put(name.getIndexPosition(), boundValue);
293 return this;
294 }
295
296 private class CachingLoader implements IDocumentLoader {
297 @NonNull
298 private final IDocumentLoader proxy;
299
300 public CachingLoader(@NonNull IDocumentLoader proxy) {
301 this.proxy = proxy;
302 }
303
304 @Override
305 public IUriResolver getUriResolver() {
306 return new ContextUriResolver();
307 }
308
309 @Override
310 public void setUriResolver(@NonNull IUriResolver resolver) {
311
312 throw new UnsupportedOperationException("Set the resolver on the proxy");
313 }
314
315 @NonNull
316 protected IDocumentLoader getProxiedDocumentLoader() {
317 return proxy;
318 }
319
320 @Override
321 public IDocumentNodeItem loadAsNodeItem(URI uri) throws IOException {
322 IDocumentNodeItem retval = sharedState.availableDocuments.get(uri);
323 if (retval == null) {
324 retval = getProxiedDocumentLoader().loadAsNodeItem(uri);
325 sharedState.availableDocuments.put(uri, retval);
326 }
327 return retval;
328 }
329
330 public class ContextUriResolver implements IUriResolver {
331
332
333
334
335
336
337
338 @Override
339 public URI resolve(URI uri) {
340 URI baseUri = getStaticContext().getBaseUri();
341
342 URI resolvedUri;
343 if (baseUri == null) {
344 resolvedUri = uri;
345 } else {
346 resolvedUri = ObjectUtils.notNull(baseUri.resolve(uri));
347 }
348
349 IUriResolver resolver = getProxiedDocumentLoader().getUriResolver();
350 return resolver == null ? resolvedUri : resolver.resolve(resolvedUri);
351 }
352 }
353 }
354
355 }