View Javadoc
1   /*
2    * Portions of this software was developed by employees of the National Institute
3    * of Standards and Technology (NIST), an agency of the Federal Government and is
4    * being made available as a public service. Pursuant to title 17 United States
5    * Code Section 105, works of NIST employees are not subject to copyright
6    * protection in the United States. This software may be subject to foreign
7    * copyright. Permission in the United States and in foreign countries, to the
8    * extent that NIST may hold copyright, to use, copy, modify, create derivative
9    * works, and distribute this software and its documentation without fee is hereby
10   * granted on a non-exclusive basis, provided that this notice and disclaimer
11   * of warranty appears in all copies.
12   *
13   * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER
14   * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY
15   * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF
16   * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM
17   * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE
18   * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE.  IN NO EVENT
19   * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT,
20   * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM,
21   * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY,
22   * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR
23   * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT
24   * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER.
25   */
26  
27  package gov.nist.secauto.metaschema.core.model.constraint;
28  
29  import gov.nist.secauto.metaschema.core.metapath.DynamicContext;
30  import gov.nist.secauto.metaschema.core.metapath.InvalidTypeMetapathException;
31  import gov.nist.secauto.metaschema.core.metapath.MetapathException;
32  import gov.nist.secauto.metaschema.core.metapath.MetapathExpression;
33  import gov.nist.secauto.metaschema.core.metapath.MetapathExpression.ResultType;
34  import gov.nist.secauto.metaschema.core.metapath.function.library.FnData;
35  import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItem;
36  import gov.nist.secauto.metaschema.core.util.CollectionUtil;
37  import gov.nist.secauto.metaschema.core.util.ObjectUtils;
38  
39  import java.util.ArrayList;
40  import java.util.List;
41  import java.util.regex.Matcher;
42  import java.util.regex.Pattern;
43  import java.util.stream.Collectors;
44  
45  import edu.umd.cs.findbugs.annotations.NonNull;
46  import edu.umd.cs.findbugs.annotations.Nullable;
47  
48  public interface IIndex {
49  
50    /**
51     * Construct a new index using the provided key field components to generate
52     * keys.
53     *
54     * @param keyFields
55     *          the key field components to use to generate keys by default
56     * @return the new index
57     */
58    @NonNull
59    static IIndex newInstance(@NonNull List<? extends IKeyField> keyFields) {
60      return new DefaultIndex(keyFields);
61    }
62  
63    /**
64     * Check if a key contains information other than {@code null} Strings.
65     *
66     * @param key
67     *          the key to check
68     * @return {@code true} if the series of key values contains only {@code null}
69     *         values, or {@code false} otherwise
70     */
71    static boolean isAllNulls(@NonNull Iterable<String> key) {
72      for (String value : key) {
73        if (value != null) {
74          return false; // NOPMD readability
75        }
76      }
77      return true;
78    }
79  
80    /**
81     * Retrieve the key field components used to generate a key for this index.
82     *
83     * @return the key field components
84     */
85    @NonNull
86    List<IKeyField> getKeyFields();
87  
88    /**
89     * Store the provided item in the index using the index's key field components
90     * to generate the key.
91     *
92     * @param item
93     *          the item to store in the index
94     * @param dynamicContext
95     *          the Metapath evaluation context
96     * @return the previous item stored in the index, or {@code null} otherwise
97     */
98    @Nullable
99    default INodeItem put(@NonNull INodeItem item, @NonNull DynamicContext dynamicContext) {
100     List<String> key = toKey(item, getKeyFields(), dynamicContext);
101     return put(item, key);
102   }
103 
104   /**
105    * Store the provided item using the provided key.
106    *
107    * @param item
108    *          the item to store
109    * @param key
110    *          the key to store the item with
111    * @return the previous item stored in the index using the key, or {@code null}
112    *         otherwise
113    */
114   @Nullable
115   INodeItem put(@NonNull INodeItem item, @NonNull List<String> key);
116 
117   /**
118    * Retrieve the item from the index that matches the key generated by evaluating
119    * the index's default key field components against the provided item.
120    *
121    * @param item
122    *          the item to store in the index
123    * @param dynamicContext
124    *          the Metapath evaluation context
125    * @return the previous item stored in the index, or {@code null} otherwise
126    */
127   @Nullable
128   default INodeItem get(@NonNull INodeItem item, @NonNull DynamicContext dynamicContext) {
129     List<String> key = toKey(item, getKeyFields(), dynamicContext);
130     return get(key);
131   }
132 
133   /**
134    * Retrieve the item from the index that matches the provided key.
135    *
136    * @param key
137    *          the key to use for lookup
138    * @return the item with the matching key or {@code null} if no matching item
139    *         was found
140    */
141   INodeItem get(List<String> key);
142 
143   /**
144    * Construct a key by evaluating the provided key field components against the
145    * provided item.
146    *
147    * @param item
148    *          the item to generate the key from
149    * @param keyFields
150    *          the key field components used to generate the key
151    * @param dynamicContext
152    *          the Metapath evaluation context
153    * @return a new key
154    */
155   @NonNull
156   static List<String> toKey(@NonNull INodeItem item, @NonNull List<? extends IKeyField> keyFields,
157       @NonNull DynamicContext dynamicContext) {
158     return CollectionUtil.unmodifiableList(
159         ObjectUtils.notNull(keyFields.stream()
160             .map(keyField -> {
161               assert keyField != null;
162               return buildKeyItem(item, keyField, dynamicContext);
163             })
164             .collect(Collectors.toCollection(ArrayList::new))));
165   }
166 
167   /**
168    * Evaluates the provided key field component against the item to generate a key
169    * value.
170    *
171    * @param item
172    *          the item to generate the key value from
173    * @param keyField
174    *          the key field component used to generate the key value
175    * @param dynamicContext
176    *          the Metapath evaluation context
177    * @return the key value or {@code null} if the evaluation resulted in no value
178    */
179   @Nullable
180   private static String buildKeyItem(
181       @NonNull INodeItem item,
182       @NonNull IKeyField keyField,
183       @NonNull DynamicContext dynamicContext) {
184     MetapathExpression keyPath = keyField.getTarget();
185 
186     INodeItem keyItem;
187     try {
188       keyItem = keyPath.evaluateAs(item, ResultType.NODE, dynamicContext);
189     } catch (InvalidTypeMetapathException ex) {
190       throw new MetapathException("Key path did not result in a single node", ex);
191     }
192 
193     String keyValue = null;
194     if (keyItem != null) {
195       keyValue = FnData.fnDataItem(keyItem).asString();
196       Pattern pattern = keyField.getPattern();
197       if (pattern != null) {
198         keyValue = applyPattern(keyItem, keyValue, pattern);
199       }
200     } // empty key
201     return keyValue;
202   }
203 
204   /**
205    * Apply the key value pattern, if configured, to generate the final key value.
206    *
207    * @param keyItem
208    *          the node item used to form the key field
209    * @param pattern
210    *          the key field pattern configuration from the constraint
211    * @param keyValue
212    *          the current key value
213    * @return the final key value
214    */
215   private static String applyPattern(@NonNull INodeItem keyItem, @NonNull String keyValue,
216       @NonNull Pattern pattern) {
217     Matcher matcher = pattern.matcher(keyValue);
218     if (!matcher.matches()) {
219       throw new MetapathException(
220           String.format("Key field declares the pattern '%s' which does not match the value '%s' of node '%s'",
221               pattern.pattern(), keyValue, keyItem.getMetapath()));
222     }
223 
224     if (matcher.groupCount() != 1) {
225       throw new MetapathException(String.format(
226           "The first group was not a match for value '%s' of node '%s' for key field pattern '%s'",
227           keyValue, keyItem.getMetapath(), pattern.pattern()));
228     }
229     return matcher.group(1);
230   }
231 }