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