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.model.constraint; 028 029import gov.nist.secauto.metaschema.core.metapath.DynamicContext; 030import gov.nist.secauto.metaschema.core.metapath.InvalidTypeMetapathException; 031import gov.nist.secauto.metaschema.core.metapath.MetapathException; 032import gov.nist.secauto.metaschema.core.metapath.MetapathExpression; 033import gov.nist.secauto.metaschema.core.metapath.MetapathExpression.ResultType; 034import gov.nist.secauto.metaschema.core.metapath.function.library.FnData; 035import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItem; 036import gov.nist.secauto.metaschema.core.util.CollectionUtil; 037import gov.nist.secauto.metaschema.core.util.ObjectUtils; 038 039import java.util.ArrayList; 040import java.util.List; 041import java.util.regex.Matcher; 042import java.util.regex.Pattern; 043import java.util.stream.Collectors; 044 045import edu.umd.cs.findbugs.annotations.NonNull; 046import edu.umd.cs.findbugs.annotations.Nullable; 047 048public interface IIndex { 049 050 /** 051 * Construct a new index using the provided key field components to generate 052 * keys. 053 * 054 * @param keyFields 055 * the key field components to use to generate keys by default 056 * @return the new index 057 */ 058 @NonNull 059 static IIndex newInstance(@NonNull List<? extends IKeyField> keyFields) { 060 return new DefaultIndex(keyFields); 061 } 062 063 /** 064 * Check if a key contains information other than {@code null} Strings. 065 * 066 * @param key 067 * the key to check 068 * @return {@code true} if the series of key values contains only {@code null} 069 * values, or {@code false} otherwise 070 */ 071 static boolean isAllNulls(@NonNull Iterable<String> key) { 072 for (String value : key) { 073 if (value != null) { 074 return false; // NOPMD readability 075 } 076 } 077 return true; 078 } 079 080 /** 081 * Retrieve the key field components used to generate a key for this index. 082 * 083 * @return the key field components 084 */ 085 @NonNull 086 List<IKeyField> getKeyFields(); 087 088 /** 089 * Store the provided item in the index using the index's key field components 090 * to generate the key. 091 * 092 * @param item 093 * the item to store in the index 094 * @param dynamicContext 095 * the Metapath evaluation context 096 * @return the previous item stored in the index, or {@code null} otherwise 097 */ 098 @Nullable 099 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}