1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package gov.nist.secauto.metaschema.core.metapath;
7   
8   import gov.nist.secauto.metaschema.core.metapath.EQNameUtils.IEQNamePrefixResolver;
9   import gov.nist.secauto.metaschema.core.util.CollectionUtil;
10  import gov.nist.secauto.metaschema.core.util.ObjectUtils;
11  
12  import java.net.URI;
13  import java.util.Map;
14  import java.util.concurrent.ConcurrentHashMap;
15  import java.util.stream.Collectors;
16  
17  import javax.xml.XMLConstants;
18  
19  import edu.umd.cs.findbugs.annotations.NonNull;
20  import edu.umd.cs.findbugs.annotations.Nullable;
21  import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
22  
23  // add support for default namespace
24  /**
25   * The implementation of a Metapath
26   * <a href="https://www.w3.org/TR/xpath-31/#static_context">static context</a>.
27   */
28  public final class StaticContext {
29    @NonNull
30    private static final Map<String, URI> WELL_KNOWN_NAMESPACES;
31    @NonNull
32    private static final Map<String, String> WELL_KNOWN_URI_TO_PREFIX;
33  
34    static {
35      Map<String, URI> knownNamespaces = new ConcurrentHashMap<>();
36      knownNamespaces.put(
37          MetapathConstants.PREFIX_METAPATH,
38          MetapathConstants.NS_METAPATH);
39      knownNamespaces.put(
40          MetapathConstants.PREFIX_XML_SCHEMA,
41          MetapathConstants.NS_XML_SCHEMA);
42      knownNamespaces.put(
43          MetapathConstants.PREFIX_XPATH_FUNCTIONS,
44          MetapathConstants.NS_METAPATH_FUNCTIONS);
45      knownNamespaces.put(
46          MetapathConstants.PREFIX_XPATH_FUNCTIONS_MATH,
47          MetapathConstants.NS_METAPATH_FUNCTIONS_MATH);
48      knownNamespaces.put(
49          MetapathConstants.PREFIX_XPATH_FUNCTIONS_ARRAY,
50          MetapathConstants.NS_METAPATH_FUNCTIONS_ARRAY);
51      knownNamespaces.put(
52          MetapathConstants.PREFIX_XPATH_FUNCTIONS_MAP,
53          MetapathConstants.NS_METAPATH_FUNCTIONS_MAP);
54      WELL_KNOWN_NAMESPACES = CollectionUtil.unmodifiableMap(knownNamespaces);
55  
56      WELL_KNOWN_URI_TO_PREFIX = ObjectUtils.notNull(WELL_KNOWN_NAMESPACES.entrySet().stream()
57          .collect(Collectors.toUnmodifiableMap(
58              entry -> entry.getValue().toASCIIString(),
59              Map.Entry::getKey,
60              (v1, v2) -> v2)));
61    }
62  
63    @Nullable
64    private final URI baseUri;
65    @NonNull
66    private final Map<String, URI> knownNamespaces;
67    @Nullable
68    private final URI defaultModelNamespace;
69    @Nullable
70    private final URI defaultFunctionNamespace;
71    private final boolean useWildcardWhenNamespaceNotDefaulted;
72  
73    /**
74     * Get the mapping of prefix to namespace URI for all well-known namespaces
75     * provided by default to the static context.
76     * <p>
77     * These namespaces can be overridden using the
78     * {@link Builder#namespace(String, URI)} method.
79     *
80     * @return the mapping of prefix to namespace URI for all well-known namespaces
81     */
82    @SuppressFBWarnings("MS_EXPOSE_REP")
83    public static Map<String, URI> getWellKnownNamespacesMap() {
84      return WELL_KNOWN_NAMESPACES;
85    }
86  
87    /**
88     * Get the mapping of namespace URIs to prefixes for all well-known namespaces
89     * provided by default to the static context.
90     *
91     * @return the mapping of namespace URI to prefix for all well-known namespaces
92     */
93    @SuppressFBWarnings("MS_EXPOSE_REP")
94    public static Map<String, String> getWellKnownURIToPrefixMap() {
95      return WELL_KNOWN_URI_TO_PREFIX;
96    }
97  
98    /**
99     * Get the namespace prefix associated with the provided URI, if the URI is
100    * well-known.
101    *
102    * @param uri
103    *          the URI to get the prefix for
104    * @return the prefix or {@code null} if the provided URI is not well-known
105    */
106   @Nullable
107   public static String getWellKnownPrefixForUri(@NonNull String uri) {
108     return WELL_KNOWN_URI_TO_PREFIX.get(uri);
109   }
110 
111   /**
112    * Create a new static context instance using default values.
113    *
114    * @return a new static context instance
115    */
116   @NonNull
117   public static StaticContext instance() {
118     return builder().build();
119   }
120 
121   private StaticContext(Builder builder) {
122     this.baseUri = builder.baseUri;
123     this.knownNamespaces = CollectionUtil.unmodifiableMap(ObjectUtils.notNull(Map.copyOf(builder.namespaces)));
124     this.defaultModelNamespace = builder.defaultModelNamespace;
125     this.defaultFunctionNamespace = builder.defaultFunctionNamespace;
126     this.useWildcardWhenNamespaceNotDefaulted = builder.useWildcardWhenNamespaceNotDefaulted;
127   }
128 
129   /**
130    * Get the static base URI to use in resolving URIs handled by the Metapath
131    * processor. This URI, if provided, will be used when a document base URI is
132    * not available.
133    *
134    * @return the base URI or {@code null} if not defined
135    */
136   @Nullable
137   public URI getBaseUri() {
138     return baseUri;
139   }
140 
141   /**
142    * Get the namespace URI associated with the provided {@code prefix}, if any is
143    * bound.
144    * <p>
145    * This method uses the namespaces set by the
146    * {@link Builder#namespace(String, URI)} method, falling back to the well-known
147    * namespace bindings when a prefix match is not found.
148    * <p>
149    * The well-known namespace bindings can be retrieved using the
150    * {@link StaticContext#getWellKnownNamespacesMap()} method.
151    *
152    * @param prefix
153    *          the namespace prefix
154    * @return the namespace URI bound to the prefix, or {@code null} if no
155    *         namespace is bound to the prefix
156    * @see Builder#namespace(String, URI)
157    * @see #getWellKnownNamespacesMap()
158    */
159   @Nullable
160   public URI lookupNamespaceURIForPrefix(@NonNull String prefix) {
161     URI retval = knownNamespaces.get(prefix);
162     if (retval == null) {
163       // fall back to well-known namespaces
164       retval = WELL_KNOWN_NAMESPACES.get(prefix);
165     }
166     return retval;
167   }
168 
169   /**
170    * Get the namespace associated with the provided {@code prefix} as a string, if
171    * any is bound.
172    *
173    * @param prefix
174    *          the namespace prefix
175    * @return the namespace string bound to the prefix, or {@code null} if no
176    *         namespace is bound to the prefix
177    */
178   @Nullable
179   public String lookupNamespaceForPrefix(@NonNull String prefix) {
180     URI result = lookupNamespaceURIForPrefix(prefix);
181     return result == null ? null : result.toASCIIString();
182   }
183 
184   /**
185    * Get the default namespace for assembly, field, or flag references that have
186    * no namespace prefix.
187    *
188    * @return the namespace if defined or {@code null} otherwise
189    */
190   @Nullable
191   public URI getDefaultModelNamespace() {
192     return defaultModelNamespace;
193   }
194 
195   /**
196    * Get the default namespace for function references that have no namespace
197    * prefix.
198    *
199    * @return the namespace if defined or {@code null} otherwise
200    */
201   @Nullable
202   public URI getDefaultFunctionNamespace() {
203     return defaultFunctionNamespace;
204   }
205 
206   /**
207    * Get a prefix resolver for use with Metapath function names that will attempt
208    * to identify the namespace corresponding to a given prefix.
209    * <p>
210    * This will use the following lookup order, advancing to the next when a
211    * {@code null} value is returned:
212    * <ol>
213    * <li>Lookup the prefix using
214    * {@link StaticContext#lookupNamespaceForPrefix(String)}</li>
215    * <li>Return the result of
216    * {@link StaticContext#getDefaultFunctionNamespace()}</li>
217    * <li>Return {@link XMLConstants#NULL_NS_URI}</li>
218    * </ol>
219    *
220    * @return the resolver
221    */
222   @NonNull
223   public IEQNamePrefixResolver getFunctionPrefixResolver() {
224     return this::resolveFunctionPrefix;
225   }
226 
227   @NonNull
228   private String resolveFunctionPrefix(@NonNull String prefix) {
229     String ns = lookupNamespaceForPrefix(prefix);
230     if (ns == null) {
231       URI uri = getDefaultFunctionNamespace();
232       if (uri != null) {
233         ns = uri.toASCIIString();
234       }
235     }
236     return ns == null ? XMLConstants.NULL_NS_URI : ns;
237   }
238 
239   /**
240    * Get a prefix resolver for use with Metapath flag node names that will attempt
241    * to identify the namespace corresponding to a given prefix.
242    * <p>
243    * This will use the following lookup order, advancing to the next when a
244    * {@code null} value is returned:
245    * <ol>
246    * <li>Lookup the prefix using
247    * {@link StaticContext#lookupNamespaceForPrefix(String)}</li>
248    * <li>Return {@link XMLConstants#NULL_NS_URI}</li>
249    * </ol>
250    *
251    * @return the resolver
252    */
253   @NonNull
254   public IEQNamePrefixResolver getFlagPrefixResolver() {
255     return this::resolveFlagReferencePrefix;
256   }
257 
258   @NonNull
259   private String resolveFlagReferencePrefix(@NonNull String prefix) {
260     String ns = lookupNamespaceForPrefix(prefix);
261     return ns == null ? XMLConstants.NULL_NS_URI : ns;
262   }
263 
264   /**
265    * Get a prefix resolver for use with Metapath model node names that will
266    * attempt to identify the namespace corresponding to a given prefix.
267    * <p>
268    * This will use the following lookup order, advancing to the next when a
269    * {@code null} value is returned:
270    * <ol>
271    * <li>Lookup the prefix using
272    * {@link StaticContext#lookupNamespaceForPrefix(String)}</li>
273    * <li>Return the result of
274    * {@link StaticContext#getDefaultModelNamespace()}</li>
275    * <li>Return {@link XMLConstants#NULL_NS_URI}</li>
276    * </ol>
277    *
278    * @return the resolver
279    */
280   @NonNull
281   public IEQNamePrefixResolver getModelPrefixResolver() {
282     return this::resolveModelReferencePrefix;
283   }
284 
285   @NonNull
286   private String resolveModelReferencePrefix(@NonNull String prefix) {
287     String ns = lookupNamespaceForPrefix(prefix);
288     if (ns == null) {
289       URI uri = getDefaultModelNamespace();
290       if (uri != null) {
291         ns = uri.toASCIIString();
292       }
293     }
294     return ns == null ? XMLConstants.NULL_NS_URI : ns;
295   }
296 
297   /**
298    * Get a prefix resolver for use with Metapath variable names that will attempt
299    * to identify the namespace corresponding to a given prefix.
300    * <p>
301    * This will use the following lookup order, advancing to the next when a
302    * {@code null} value is returned:
303    * <ol>
304    * <li>Lookup the prefix using
305    * {@link StaticContext#lookupNamespaceForPrefix(String)}</li>
306    * <li>Return {@link XMLConstants#NULL_NS_URI}</li>
307    * </ol>
308    *
309    * @return the resolver
310    */
311   @NonNull
312   public IEQNamePrefixResolver getVariablePrefixResolver() {
313     return this::resolveVariablePrefix;
314   }
315 
316   @NonNull
317   private String resolveVariablePrefix(@NonNull String prefix) {
318     String ns = lookupNamespaceForPrefix(prefix);
319     return ns == null ? XMLConstants.NULL_NS_URI : ns;
320   }
321 
322   /**
323    * Get a new static context builder that is pre-populated with the setting of
324    * this static context.
325    *
326    * @return a new builder
327    */
328   @NonNull
329   public Builder buildFrom() {
330     Builder builder = builder();
331     builder.baseUri = this.baseUri;
332     builder.namespaces.putAll(this.knownNamespaces);
333     builder.defaultModelNamespace = this.defaultModelNamespace;
334     builder.defaultFunctionNamespace = this.defaultFunctionNamespace;
335     return builder;
336   }
337 
338   /**
339    * Indicates if a name match should use a wildcard for the namespace is the
340    * namespace does not have a value and the {@link #getDefaultModelNamespace()}
341    * is {@code null}.
342    *
343    * @return {@code true} if a wildcard match on the name space should be used or
344    *         {@code false} otherwise
345    */
346   public boolean isUseWildcardWhenNamespaceNotDefaulted() {
347     return useWildcardWhenNamespaceNotDefaulted && getDefaultModelNamespace() == null;
348   }
349 
350   /**
351    * Create a new static context builder that allows for fine-grained adjustments
352    * when creating a new static context.
353    *
354    * @return a new builder
355    */
356   @NonNull
357   public static Builder builder() {
358     return new Builder();
359   }
360 
361   /**
362    * A builder used to generate the static context.
363    */
364   public static final class Builder {
365     public boolean useWildcardWhenNamespaceNotDefaulted; // false
366     @Nullable
367     private URI baseUri;
368     @NonNull
369     private final Map<String, URI> namespaces = new ConcurrentHashMap<>();
370     @Nullable
371     private URI defaultModelNamespace;
372     @Nullable
373     private URI defaultFunctionNamespace = MetapathConstants.NS_METAPATH_FUNCTIONS;
374 
375     private Builder() {
376       namespaces.put(
377           MetapathConstants.PREFIX_METAPATH,
378           MetapathConstants.NS_METAPATH);
379       namespaces.put(
380           MetapathConstants.PREFIX_XML_SCHEMA,
381           MetapathConstants.NS_XML_SCHEMA);
382       namespaces.put(
383           MetapathConstants.PREFIX_XPATH_FUNCTIONS,
384           MetapathConstants.NS_METAPATH_FUNCTIONS);
385       namespaces.put(
386           MetapathConstants.PREFIX_XPATH_FUNCTIONS_MATH,
387           MetapathConstants.NS_METAPATH_FUNCTIONS_MATH);
388     }
389 
390     /**
391      * Sets the static base URI to use in resolving URIs handled by the Metapath
392      * processor, when a document base URI is not available. There is only a single
393      * base URI. Subsequent calls to this method will change the base URI.
394      *
395      * @param uri
396      *          the base URI to use
397      * @return this builder
398      */
399     @NonNull
400     public Builder baseUri(@NonNull URI uri) {
401       this.baseUri = uri;
402       return this;
403     }
404 
405     /**
406      * Adds a new prefix to namespace URI binding to the mapping of
407      * <a href="https://www.w3.org/TR/xpath-31/#dt-static-namespaces">statically
408      * known namespaces</a>.
409      * <p>
410      * A namespace set by this method can be resolved using the
411      * {@link StaticContext#lookupNamespaceForPrefix(String)} method.
412      * <p>
413      * Well-known namespace bindings are used by default, which can be retrieved
414      * using the {@link StaticContext#getWellKnownNamespacesMap()} method.
415      *
416      * @param prefix
417      *          the prefix to associate with the namespace, which may be
418      * @param uri
419      *          the namespace URI
420      * @return this builder
421      * @see StaticContext#lookupNamespaceForPrefix(String)
422      * @see StaticContext#lookupNamespaceURIForPrefix(String)
423      * @see StaticContext#getWellKnownNamespacesMap()
424      */
425     @NonNull
426     public Builder namespace(@NonNull String prefix, @NonNull URI uri) {
427       this.namespaces.put(prefix, uri);
428       return this;
429     }
430 
431     /**
432      * A convenience method for {@link #namespace(String, URI)}.
433      *
434      * @param prefix
435      *          the prefix to associate with the namespace, which may be
436      * @param uri
437      *          the namespace URI
438      * @return this builder
439      * @throws IllegalArgumentException
440      *           if the provided URI is invalid
441      * @see StaticContext#lookupNamespaceForPrefix(String)
442      * @see StaticContext#lookupNamespaceURIForPrefix(String)
443      * @see StaticContext#getWellKnownNamespacesMap()
444      */
445     @NonNull
446     public Builder namespace(@NonNull String prefix, @NonNull String uri) {
447       return namespace(prefix, ObjectUtils.notNull(URI.create(uri)));
448     }
449 
450     /**
451      * Defines the default namespace to use for assembly, field, or flag references
452      * that have no namespace prefix.
453      *
454      * @param uri
455      *          the namespace URI
456      * @return this builder
457      * @see StaticContext#getDefaultModelNamespace()
458      */
459     @NonNull
460     public Builder defaultModelNamespace(@NonNull URI uri) {
461       this.defaultModelNamespace = uri;
462       return this;
463     }
464 
465     /**
466      * A convenience method for {@link #defaultModelNamespace(URI)}.
467      *
468      * @param uri
469      *          the namespace URI
470      * @return this builder
471      * @throws IllegalArgumentException
472      *           if the provided URI is invalid
473      * @see StaticContext#getDefaultModelNamespace()
474      */
475     @NonNull
476     public Builder defaultModelNamespace(@NonNull String uri) {
477       return defaultModelNamespace(ObjectUtils.notNull(URI.create(uri)));
478     }
479 
480     /**
481      * Defines the default namespace to use for assembly, field, or flag references
482      * that have no namespace prefix.
483      *
484      * @param uri
485      *          the namespace URI
486      * @return this builder
487      * @see StaticContext#getDefaultFunctionNamespace()
488      */
489     @NonNull
490     public Builder defaultFunctionNamespace(@NonNull URI uri) {
491       this.defaultFunctionNamespace = uri;
492       return this;
493     }
494 
495     /**
496      * A convenience method for {@link #defaultFunctionNamespace(URI)}.
497      *
498      * @param uri
499      *          the namespace URI
500      * @return this builder
501      * @throws IllegalArgumentException
502      *           if the provided URI is invalid
503      * @see StaticContext#getDefaultFunctionNamespace()
504      */
505     @NonNull
506     public Builder defaultFunctionNamespace(@NonNull String uri) {
507       return defaultFunctionNamespace(ObjectUtils.notNull(URI.create(uri)));
508     }
509 
510     /**
511      * Set the name matching behavior for when a model node has no namespace.
512      *
513      * @param value
514      *          {@code true} if on or {@code false} otherwise
515      * @return this builder
516      */
517     public Builder useWildcardWhenNamespaceNotDefaulted(boolean value) {
518       this.useWildcardWhenNamespaceNotDefaulted = value;
519       return this;
520     }
521 
522     /**
523      * Construct a new static context using the information provided to the builder.
524      *
525      * @return the new static context
526      */
527     @NonNull
528     public StaticContext build() {
529       return new StaticContext(this);
530     }
531   }
532 }