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