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 }