001/* 002 * SPDX-FileCopyrightText: none 003 * SPDX-License-Identifier: CC0-1.0 004 */ 005 006package dev.metaschema.oscal.lib.profile.resolver.policy; 007 008import org.apache.logging.log4j.LogManager; 009import org.apache.logging.log4j.Logger; 010 011import java.util.List; 012 013import dev.metaschema.core.metapath.item.node.IModelNodeItem; 014import dev.metaschema.core.util.ObjectUtils; 015import dev.metaschema.oscal.lib.profile.resolver.ProfileResolutionEvaluationException; 016import dev.metaschema.oscal.lib.profile.resolver.support.IEntityItem; 017import edu.umd.cs.findbugs.annotations.NonNull; 018import edu.umd.cs.findbugs.annotations.Nullable; 019 020public abstract class AbstractCustomReferencePolicy<TYPE> implements ICustomReferencePolicy<TYPE> { 021 private static final Logger LOGGER = LogManager.getLogger(AbstractCustomReferencePolicy.class); 022 023 @NonNull 024 private final IIdentifierParser identifierParser; 025 026 protected AbstractCustomReferencePolicy( 027 @NonNull IIdentifierParser identifierParser) { 028 this.identifierParser = identifierParser; 029 } 030 031 @Override 032 @NonNull 033 public IIdentifierParser getIdentifierParser() { 034 return identifierParser; 035 } 036 037 /** 038 * Get the possible item types that can be searched in the order in which the 039 * identifier will be looked up. 040 * <p> 041 * The {@code reference} object is provided to allow for context sensitive item 042 * type tailoring. 043 * 044 * @param reference 045 * the reference object 046 * @return a list of item types to search for 047 */ 048 @NonNull 049 protected abstract List<IEntityItem.ItemType> getEntityItemTypes(@NonNull TYPE reference); 050 051 /** 052 * Handle an index hit. 053 * 054 * @param contextItem 055 * the node containing the identifier reference 056 * @param reference 057 * the identifier reference object generating the hit 058 * @param item 059 * the referenced item 060 * @param visitorContext 061 * the reference visitor state, which can be used for further 062 * processing 063 * @return {@code true} if the hit was handled or {@code false} otherwise 064 * @throws ProfileResolutionEvaluationException 065 * if there was an error handing the index hit 066 */ 067 protected boolean handleIndexHit( 068 @NonNull IModelNodeItem<?, ?> contextItem, 069 @NonNull TYPE reference, 070 @NonNull IEntityItem item, 071 @NonNull ReferenceCountingVisitor.Context visitorContext) { 072 073 if (visitorContext.getIndexer().isSelected(item)) { 074 if (!visitorContext.isResolved(item)) { 075 // this referenced item will need to be resolved 076 ReferenceCountingVisitor.instance().resolveEntity(item, visitorContext); 077 } 078 item.incrementReferenceCount(); 079 080 if (item.isIdentifierReassigned()) { 081 String referenceText = ObjectUtils.notNull(getReferenceText(reference)); 082 String newReferenceText = getIdentifierParser().update(referenceText, item.getIdentifier()); 083 setReferenceText(reference, newReferenceText); 084 if (LOGGER.isDebugEnabled()) { 085 LOGGER.atDebug().log("Mapping {} reference '{}' to '{}'.", item.getItemType().name(), referenceText, 086 newReferenceText); 087 } 088 } 089 handleSelected(contextItem, reference, item, visitorContext); 090 } else { 091 handleUnselected(contextItem, reference, item, visitorContext); 092 } 093 return true; 094 } 095 096 /** 097 * Handle an index hit against an item related to an unselected control. 098 * <p> 099 * Subclasses can override this method to perform extra processing. 100 * 101 * @param contextItem 102 * the node containing the identifier reference 103 * @param reference 104 * the identifier reference object generating the hit 105 * @param item 106 * the referenced item 107 * @param visitorContext 108 * the reference visitor, which can be used for further processing 109 * @throws ProfileResolutionEvaluationException 110 * if there was an error handing the index hit 111 */ 112 protected void handleUnselected( // NOPMD noop default 113 @NonNull IModelNodeItem<?, ?> contextItem, 114 @NonNull TYPE reference, 115 @NonNull IEntityItem item, 116 @NonNull ReferenceCountingVisitor.Context visitorContext) { 117 // do nothing by default 118 } 119 120 /** 121 * Handle an index hit against an item related to an selected control. 122 * <p> 123 * Subclasses can override this method to perform extra processing. 124 * 125 * @param contextItem 126 * the node containing the identifier reference 127 * @param reference 128 * the identifier reference object generating the hit 129 * @param item 130 * the referenced item 131 * @param visitorContext 132 * the reference visitor state, which can be used for further 133 * processing 134 * @throws ProfileResolutionEvaluationException 135 * if there was an error handing the index hit 136 */ 137 protected void handleSelected( // NOPMD noop default 138 @NonNull IModelNodeItem<?, ?> contextItem, 139 @NonNull TYPE reference, 140 @NonNull IEntityItem item, 141 @NonNull ReferenceCountingVisitor.Context visitorContext) { 142 // do nothing by default 143 } 144 145 /** 146 * Handle an index miss for a reference. This occurs when the referenced item 147 * was not found in the index. 148 * <p> 149 * Subclasses can override this method to perform extra processing. 150 * 151 * @param contextItem 152 * the node containing the identifier reference 153 * @param reference 154 * the identifier reference object generating the hit 155 * @param itemTypes 156 * the possible item types for this reference 157 * @param identifier 158 * the parsed identifier 159 * @param visitorContext 160 * the reference visitor state, which can be used for further 161 * processing 162 * @return {@code true} if the reference is handled by this method or 163 * {@code false} otherwise 164 * @throws ProfileResolutionEvaluationException 165 * if there was an error handing the index miss 166 */ 167 protected boolean handleIndexMiss( 168 @NonNull IModelNodeItem<?, ?> contextItem, 169 @NonNull TYPE reference, 170 @NonNull List<IEntityItem.ItemType> itemTypes, 171 @NonNull String identifier, 172 @NonNull ReferenceCountingVisitor.Context visitorContext) { 173 // provide no handler by default 174 return false; 175 } 176 177 /** 178 * Handle the case where the identifier was not a syntax match for an expected 179 * identifier. This can occur when the reference is malformed, using an 180 * unrecognized syntax. 181 * <p> 182 * Subclasses can override this method to perform extra processing. 183 * 184 * @param contextItem 185 * the node containing the identifier reference 186 * @param reference 187 * the identifier reference object generating the hit 188 * @param visitorContext 189 * the reference visitor state, which can be used for further 190 * processing 191 * @return {@code true} if the reference is handled by this method or 192 * {@code false} otherwise 193 * @throws ProfileResolutionEvaluationException 194 * if there was an error handing the index miss due to a non match 195 */ 196 protected boolean handleIdentifierNonMatch( 197 @NonNull IModelNodeItem<?, ?> contextItem, 198 @NonNull TYPE reference, 199 @NonNull ReferenceCountingVisitor.Context visitorContext) { 200 // provide no handler by default 201 return false; 202 } 203 204 @Override 205 public boolean handleReference( 206 @NonNull IModelNodeItem<?, ?> contextItem, 207 @NonNull TYPE type, 208 @NonNull ReferenceCountingVisitor.Context visitorContext) { 209 String referenceText = getReferenceText(type); 210 211 // if the reference text does not exist, ignore the reference; otherwise, handle 212 // it. 213 return referenceText == null 214 || handleIdentifier(contextItem, type, getIdentifierParser().parse(referenceText), visitorContext); 215 } 216 217 /** 218 * Handle the provided {@code identifier} for a given {@code type} of reference. 219 * 220 * @param contextItem 221 * the node containing the identifier reference 222 * @param type 223 * the item type of the reference 224 * @param identifier 225 * the identifier 226 * @param visitorContext 227 * the reference visitor state, which can be used for further 228 * processing 229 * @return {@code true} if the reference is handled by this method or 230 * {@code false} otherwise 231 * @throws ProfileResolutionEvaluationException 232 * if there was an error handing the reference 233 */ 234 protected boolean handleIdentifier( 235 @NonNull IModelNodeItem<?, ?> contextItem, 236 @NonNull TYPE type, 237 @Nullable String identifier, 238 @NonNull ReferenceCountingVisitor.Context visitorContext) { 239 boolean retval; 240 if (identifier == null) { 241 retval = handleIdentifierNonMatch(contextItem, type, visitorContext); 242 } else { 243 List<IEntityItem.ItemType> itemTypes = getEntityItemTypes(type); 244 IEntityItem item = null; 245 for (IEntityItem.ItemType itemType : itemTypes) { 246 assert itemType != null; 247 248 item = visitorContext.getEntity(itemType, identifier); 249 if (item != null) { 250 break; 251 } 252 } 253 254 if (item == null) { 255 retval = handleIndexMiss(contextItem, type, itemTypes, identifier, visitorContext); 256 } else { 257 retval = handleIndexHit(contextItem, type, item, visitorContext); 258 } 259 } 260 return retval; 261 } 262}