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 }