1 /*
2 * SPDX-FileCopyrightText: none
3 * SPDX-License-Identifier: CC0-1.0
4 */
5
6 package dev.metaschema.oscal.lib.profile.resolver.policy;
7
8 import org.apache.logging.log4j.LogManager;
9 import org.apache.logging.log4j.Logger;
10
11 import java.util.List;
12
13 import dev.metaschema.core.metapath.item.node.IModelNodeItem;
14 import dev.metaschema.core.util.ObjectUtils;
15 import dev.metaschema.oscal.lib.profile.resolver.ProfileResolutionEvaluationException;
16 import dev.metaschema.oscal.lib.profile.resolver.support.IEntityItem;
17 import edu.umd.cs.findbugs.annotations.NonNull;
18 import edu.umd.cs.findbugs.annotations.Nullable;
19
20 public abstract class AbstractCustomReferencePolicy<TYPE> implements ICustomReferencePolicy<TYPE> {
21 private static final Logger LOGGER = LogManager.getLogger(AbstractCustomReferencePolicy.class);
22
23 @NonNull
24 private final IIdentifierParser identifierParser;
25
26 protected AbstractCustomReferencePolicy(
27 @NonNull IIdentifierParser identifierParser) {
28 this.identifierParser = identifierParser;
29 }
30
31 @Override
32 @NonNull
33 public IIdentifierParser getIdentifierParser() {
34 return identifierParser;
35 }
36
37 /**
38 * Get the possible item types that can be searched in the order in which the
39 * identifier will be looked up.
40 * <p>
41 * The {@code reference} object is provided to allow for context sensitive item
42 * type tailoring.
43 *
44 * @param reference
45 * the reference object
46 * @return a list of item types to search for
47 */
48 @NonNull
49 protected abstract List<IEntityItem.ItemType> getEntityItemTypes(@NonNull TYPE reference);
50
51 /**
52 * Handle an index hit.
53 *
54 * @param contextItem
55 * the node containing the identifier reference
56 * @param reference
57 * the identifier reference object generating the hit
58 * @param item
59 * the referenced item
60 * @param visitorContext
61 * the reference visitor state, which can be used for further
62 * processing
63 * @return {@code true} if the hit was handled or {@code false} otherwise
64 * @throws ProfileResolutionEvaluationException
65 * if there was an error handing the index hit
66 */
67 protected boolean handleIndexHit(
68 @NonNull IModelNodeItem<?, ?> contextItem,
69 @NonNull TYPE reference,
70 @NonNull IEntityItem item,
71 @NonNull ReferenceCountingVisitor.Context visitorContext) {
72
73 if (visitorContext.getIndexer().isSelected(item)) {
74 if (!visitorContext.isResolved(item)) {
75 // this referenced item will need to be resolved
76 ReferenceCountingVisitor.instance().resolveEntity(item, visitorContext);
77 }
78 item.incrementReferenceCount();
79
80 if (item.isIdentifierReassigned()) {
81 String referenceText = ObjectUtils.notNull(getReferenceText(reference));
82 String newReferenceText = getIdentifierParser().update(referenceText, item.getIdentifier());
83 setReferenceText(reference, newReferenceText);
84 if (LOGGER.isDebugEnabled()) {
85 LOGGER.atDebug().log("Mapping {} reference '{}' to '{}'.", item.getItemType().name(), referenceText,
86 newReferenceText);
87 }
88 }
89 handleSelected(contextItem, reference, item, visitorContext);
90 } else {
91 handleUnselected(contextItem, reference, item, visitorContext);
92 }
93 return true;
94 }
95
96 /**
97 * Handle an index hit against an item related to an unselected control.
98 * <p>
99 * 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 }