001/*
002 * Portions of this software was developed by employees of the National Institute
003 * of Standards and Technology (NIST), an agency of the Federal Government and is
004 * being made available as a public service. Pursuant to title 17 United States
005 * Code Section 105, works of NIST employees are not subject to copyright
006 * protection in the United States. This software may be subject to foreign
007 * copyright. Permission in the United States and in foreign countries, to the
008 * extent that NIST may hold copyright, to use, copy, modify, create derivative
009 * works, and distribute this software and its documentation without fee is hereby
010 * granted on a non-exclusive basis, provided that this notice and disclaimer
011 * of warranty appears in all copies.
012 *
013 * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER
014 * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY
015 * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF
016 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM
017 * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE
018 * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE.  IN NO EVENT
019 * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT,
020 * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM,
021 * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY,
022 * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR
023 * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT
024 * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER.
025 */
026
027package gov.nist.secauto.metaschema.core.metapath;
028
029import gov.nist.secauto.metaschema.core.configuration.DefaultConfiguration;
030import gov.nist.secauto.metaschema.core.configuration.IConfiguration;
031import gov.nist.secauto.metaschema.core.configuration.IMutableConfiguration;
032import gov.nist.secauto.metaschema.core.metapath.function.DefaultFunction.CallingContext;
033import gov.nist.secauto.metaschema.core.metapath.item.node.IDocumentNodeItem;
034import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItem;
035import gov.nist.secauto.metaschema.core.model.IUriResolver;
036import gov.nist.secauto.metaschema.core.util.ObjectUtils;
037
038import java.io.IOException;
039import java.io.InputStream;
040import java.net.URI;
041import java.net.URISyntaxException;
042import java.net.URL;
043import java.nio.file.Path;
044import java.time.Clock;
045import java.time.ZoneId;
046import java.time.ZonedDateTime;
047import java.util.Collections;
048import java.util.HashMap;
049import java.util.Map;
050import java.util.concurrent.ConcurrentHashMap;
051
052import edu.umd.cs.findbugs.annotations.NonNull;
053
054public class DynamicContext { // NOPMD - intentional data class
055  @NonNull
056  private final StaticContext staticContext;
057  @NonNull
058  private final ZoneId implicitTimeZone;
059  @NonNull
060  private final ZonedDateTime currentDateTime;
061  @NonNull
062  private final Map<URI, IDocumentNodeItem> availableDocuments;
063  private final Map<CallingContext, ISequence<?>> functionResultCache;
064  private CachingLoader documentLoader;
065  @NonNull
066  private final IMutableConfiguration<MetapathEvaluationFeature<?>> configuration;
067  @NonNull
068  private final Map<String, ISequence<?>> letVariableMap;
069
070  @SuppressWarnings("null")
071  public DynamicContext(@NonNull StaticContext staticContext) {
072    this.staticContext = staticContext;
073
074    Clock clock = Clock.systemDefaultZone();
075
076    this.implicitTimeZone = clock.getZone();
077    this.currentDateTime = ZonedDateTime.now(clock);
078    this.availableDocuments = new HashMap<>();
079    this.functionResultCache = new HashMap<>();
080    this.configuration = new DefaultConfiguration<>();
081    this.configuration.enableFeature(MetapathEvaluationFeature.METAPATH_EVALUATE_PREDICATES);
082    this.letVariableMap = new ConcurrentHashMap<>();
083  }
084
085  @NonNull
086  public StaticContext getStaticContext() {
087    return staticContext;
088  }
089
090  @NonNull
091  public ZoneId getImplicitTimeZone() {
092    return implicitTimeZone;
093  }
094
095  @NonNull
096  public ZonedDateTime getCurrentDateTime() {
097    return currentDateTime;
098  }
099
100  @SuppressWarnings("null")
101  @NonNull
102  public Map<URI, INodeItem> getAvailableDocuments() {
103    return Collections.unmodifiableMap(availableDocuments);
104  }
105
106  public IDocumentLoader getDocumentLoader() {
107    return documentLoader;
108  }
109
110  public void setDocumentLoader(@NonNull IDocumentLoader documentLoader) {
111    this.documentLoader = new CachingLoader(documentLoader);
112  }
113
114  public ISequence<?> getCachedResult(@NonNull CallingContext callingContext) {
115    return functionResultCache.get(callingContext);
116  }
117
118  @NonNull
119  public DynamicContext disablePredicateEvaluation() {
120    this.configuration.disableFeature(MetapathEvaluationFeature.METAPATH_EVALUATE_PREDICATES);
121    return this;
122  }
123
124  @NonNull
125  public IConfiguration<MetapathEvaluationFeature<?>> getConfiguration() {
126    return configuration;
127  }
128
129  public void cacheResult(@NonNull CallingContext callingContext, @NonNull ISequence<?> result) {
130    ISequence<?> old = functionResultCache.put(callingContext, result);
131    assert old == null;
132  }
133
134  @NonNull
135  public ISequence<?> getVariableValue(String name) {
136    return ObjectUtils.requireNonNull(letVariableMap.get(name));
137  }
138
139  public void setVariableValue(String name, ISequence<?> boundValue) {
140    letVariableMap.put(name, boundValue);
141  }
142
143  public void clearVariableValue(String name) {
144    letVariableMap.remove(name);
145  }
146
147  private class CachingLoader implements IDocumentLoader {
148    @NonNull
149    private final IDocumentLoader proxy;
150
151    public CachingLoader(@NonNull IDocumentLoader proxy) {
152      this.proxy = proxy;
153    }
154
155    @Override
156    public IUriResolver getUriResolver() {
157      return new ContextUriResolver();
158    }
159
160    @Override
161    public void setUriResolver(@NonNull IUriResolver resolver) {
162      // we delegate to the document loader proxy, so the resolver should be set there
163      throw new UnsupportedOperationException("Set the resolver on the proxy");
164    }
165
166    @NonNull
167    protected IDocumentLoader getProxiedDocumentLoader() {
168      return proxy;
169    }
170
171    @Override
172    public IDocumentNodeItem loadAsNodeItem(Path path) throws IOException {
173      URI uri = path.toUri();
174      IDocumentNodeItem retval = availableDocuments.get(uri);
175      if (retval == null) {
176        retval = getProxiedDocumentLoader().loadAsNodeItem(path);
177        availableDocuments.put(uri, retval);
178      }
179      return retval;
180    }
181
182    @Override
183    public IDocumentNodeItem loadAsNodeItem(URL url) throws IOException, URISyntaxException {
184      URI uri = ObjectUtils.notNull(url.toURI());
185      IDocumentNodeItem retval = availableDocuments.get(uri);
186      if (retval == null) {
187        retval = getProxiedDocumentLoader().loadAsNodeItem(uri);
188        availableDocuments.put(uri, retval);
189      }
190      return retval;
191    }
192
193    @Override
194    public IDocumentNodeItem loadAsNodeItem(URI uri) throws IOException {
195      IDocumentNodeItem retval = availableDocuments.get(uri);
196      if (retval == null) {
197        retval = getProxiedDocumentLoader().loadAsNodeItem(uri);
198        availableDocuments.put(uri, retval);
199      }
200      return retval;
201    }
202
203    @Override
204    public @NonNull IDocumentNodeItem loadAsNodeItem(
205        @NonNull InputStream is,
206        @NonNull URI documentUri) throws IOException {
207      throw new UnsupportedOperationException();
208      // return getProxiedDocumentLoader().loadAsNodeItem(is, documentUri);
209    }
210
211    public class ContextUriResolver implements IUriResolver {
212
213      /**
214       * {@inheritDoc}
215       * <p>
216       * This method first resolves the provided URI against the static context's base
217       * URI.
218       */
219      @Override
220      public URI resolve(URI uri) {
221        URI baseUri = getStaticContext().getBaseUri();
222
223        URI resolvedUri;
224        if (baseUri == null) {
225          resolvedUri = uri;
226        } else {
227          resolvedUri = ObjectUtils.notNull(baseUri.resolve(uri));
228        }
229
230        IUriResolver resolver = getProxiedDocumentLoader().getUriResolver();
231        return resolver == null ? resolvedUri : resolver.resolve(resolvedUri);
232      }
233    }
234  }
235}