1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package gov.nist.secauto.oscal.lib.profile.resolver.policy;
7   
8   import gov.nist.secauto.metaschema.core.metapath.item.node.IModelNodeItem;
9   import gov.nist.secauto.metaschema.core.util.ObjectUtils;
10  import gov.nist.secauto.oscal.lib.profile.resolver.ProfileResolutionEvaluationException;
11  import gov.nist.secauto.oscal.lib.profile.resolver.support.IEntityItem;
12  
13  import org.apache.logging.log4j.LogManager;
14  import org.apache.logging.log4j.Logger;
15  
16  import java.util.List;
17  
18  import edu.umd.cs.findbugs.annotations.NonNull;
19  import edu.umd.cs.findbugs.annotations.Nullable;
20  
21  public abstract class AbstractCustomReferencePolicy<TYPE> implements ICustomReferencePolicy<TYPE> {
22    private static final Logger LOGGER = LogManager.getLogger(AbstractCustomReferencePolicy.class);
23  
24    @NonNull
25    private final IIdentifierParser identifierParser;
26  
27    protected AbstractCustomReferencePolicy(
28        @NonNull IIdentifierParser identifierParser) {
29      this.identifierParser = identifierParser;
30    }
31  
32    @Override
33    @NonNull
34    public IIdentifierParser getIdentifierParser() {
35      return identifierParser;
36    }
37  
38    /**
39     * Get the possible item types that can be searched in the order in which the
40     * identifier will be looked up.
41     * <p>
42     * The {@code reference} object is provided to allow for context sensitive item
43     * type tailoring.
44     *
45     * @param reference
46     *          the reference object
47     * @return a list of item types to search for
48     */
49    @NonNull
50    protected abstract List<IEntityItem.ItemType> getEntityItemTypes(@NonNull TYPE reference);
51  
52    /**
53     * Handle an index hit.
54     *
55     * @param contextItem
56     *          the node containing the identifier reference
57     * @param reference
58     *          the identifier reference object generating the hit
59     * @param item
60     *          the referenced item
61     * @param visitorContext
62     *          the reference visitor state, which can be used for further
63     *          processing
64     * @return {@code true} if the hit was handled or {@code false} otherwise
65     * @throws ProfileResolutionEvaluationException
66     *           if there was an error handing the index hit
67     */
68    protected boolean handleIndexHit(
69        @NonNull IModelNodeItem<?, ?> contextItem,
70        @NonNull TYPE reference,
71        @NonNull IEntityItem item,
72        @NonNull ReferenceCountingVisitor.Context visitorContext) {
73  
74      if (visitorContext.getIndexer().isSelected(item)) {
75        if (!visitorContext.isResolved(item)) {
76          // this referenced item will need to be resolved
77          ReferenceCountingVisitor.instance().resolveEntity(item, visitorContext);
78        }
79        item.incrementReferenceCount();
80  
81        if (item.isIdentifierReassigned()) {
82          String referenceText = ObjectUtils.notNull(getReferenceText(reference));
83          String newReferenceText = getIdentifierParser().update(referenceText, item.getIdentifier());
84          setReferenceText(reference, newReferenceText);
85          if (LOGGER.isDebugEnabled()) {
86            LOGGER.atDebug().log("Mapping {} reference '{}' to '{}'.", item.getItemType().name(), referenceText,
87                newReferenceText);
88          }
89        }
90        handleSelected(contextItem, reference, item, visitorContext);
91      } else {
92        handleUnselected(contextItem, reference, item, visitorContext);
93      }
94      return true;
95    }
96  
97    /**
98     * Handle an index hit against an item related to an unselected control.
99     * <p>
100    * Subclasses can override this method to perform extra processing.
101    *
102    * @param contextItem
103    *          the node containing the identifier reference
104    * @param reference
105    *          the identifier reference object generating the hit
106    * @param item
107    *          the referenced item
108    * @param visitorContext
109    *          the reference visitor, which can be used for further processing
110    * @throws ProfileResolutionEvaluationException
111    *           if there was an error handing the index hit
112    */
113   protected void handleUnselected( // NOPMD noop default
114       @NonNull IModelNodeItem<?, ?> contextItem,
115       @NonNull TYPE reference,
116       @NonNull IEntityItem item,
117       @NonNull ReferenceCountingVisitor.Context visitorContext) {
118     // do nothing by default
119   }
120 
121   /**
122    * Handle an index hit against an item related to an selected control.
123    * <p>
124    * Subclasses can override this method to perform extra processing.
125    *
126    * @param contextItem
127    *          the node containing the identifier reference
128    * @param reference
129    *          the identifier reference object generating the hit
130    * @param item
131    *          the referenced item
132    * @param visitorContext
133    *          the reference visitor state, which can be used for further
134    *          processing
135    * @throws ProfileResolutionEvaluationException
136    *           if there was an error handing the index hit
137    */
138   protected void handleSelected( // NOPMD noop default
139       @NonNull IModelNodeItem<?, ?> contextItem,
140       @NonNull TYPE reference,
141       @NonNull IEntityItem item,
142       @NonNull ReferenceCountingVisitor.Context visitorContext) {
143     // do nothing by default
144   }
145 
146   /**
147    * Handle an index miss for a reference. This occurs when the referenced item
148    * was not found in the index.
149    * <p>
150    * Subclasses can override this method to perform extra processing.
151    *
152    * @param contextItem
153    *          the node containing the identifier reference
154    * @param reference
155    *          the identifier reference object generating the hit
156    * @param itemTypes
157    *          the possible item types for this reference
158    * @param identifier
159    *          the parsed identifier
160    * @param visitorContext
161    *          the reference visitor state, which can be used for further
162    *          processing
163    * @return {@code true} if the reference is handled by this method or
164    *         {@code false} otherwise
165    * @throws ProfileResolutionEvaluationException
166    *           if there was an error handing the index miss
167    */
168   protected boolean handleIndexMiss(
169       @NonNull IModelNodeItem<?, ?> contextItem,
170       @NonNull TYPE reference,
171       @NonNull List<IEntityItem.ItemType> itemTypes,
172       @NonNull String identifier,
173       @NonNull ReferenceCountingVisitor.Context visitorContext) {
174     // provide no handler by default
175     return false;
176   }
177 
178   /**
179    * Handle the case where the identifier was not a syntax match for an expected
180    * identifier. This can occur when the reference is malformed, using an
181    * unrecognized syntax.
182    * <p>
183    * Subclasses can override this method to perform extra processing.
184    *
185    * @param contextItem
186    *          the node containing the identifier reference
187    * @param reference
188    *          the identifier reference object generating the hit
189    * @param visitorContext
190    *          the reference visitor state, which can be used for further
191    *          processing
192    * @return {@code true} if the reference is handled by this method or
193    *         {@code false} otherwise
194    * @throws ProfileResolutionEvaluationException
195    *           if there was an error handing the index miss due to a non match
196    */
197   protected boolean handleIdentifierNonMatch(
198       @NonNull IModelNodeItem<?, ?> contextItem,
199       @NonNull TYPE reference,
200       @NonNull ReferenceCountingVisitor.Context visitorContext) {
201     // provide no handler by default
202     return false;
203   }
204 
205   @Override
206   public boolean handleReference(
207       @NonNull IModelNodeItem<?, ?> contextItem,
208       @NonNull TYPE type,
209       @NonNull ReferenceCountingVisitor.Context visitorContext) {
210     String referenceText = getReferenceText(type);
211 
212     // if the reference text does not exist, ignore the reference; otherwise, handle
213     // it.
214     return referenceText == null
215         || handleIdentifier(contextItem, type, getIdentifierParser().parse(referenceText), visitorContext);
216   }
217 
218   /**
219    * Handle the provided {@code identifier} for a given {@code type} of reference.
220    *
221    * @param contextItem
222    *          the node containing the identifier reference
223    * @param type
224    *          the item type of the reference
225    * @param identifier
226    *          the identifier
227    * @param visitorContext
228    *          the reference visitor state, which can be used for further
229    *          processing
230    * @return {@code true} if the reference is handled by this method or
231    *         {@code false} otherwise
232    * @throws ProfileResolutionEvaluationException
233    *           if there was an error handing the reference
234    */
235   protected boolean handleIdentifier(
236       @NonNull IModelNodeItem<?, ?> contextItem,
237       @NonNull TYPE type,
238       @Nullable String identifier,
239       @NonNull ReferenceCountingVisitor.Context visitorContext) {
240     boolean retval;
241     if (identifier == null) {
242       retval = handleIdentifierNonMatch(contextItem, type, visitorContext);
243     } else {
244       List<IEntityItem.ItemType> itemTypes = getEntityItemTypes(type);
245       IEntityItem item = null;
246       for (IEntityItem.ItemType itemType : itemTypes) {
247         assert itemType != null;
248 
249         item = visitorContext.getEntity(itemType, identifier);
250         if (item != null) {
251           break;
252         }
253       }
254 
255       if (item == null) {
256         retval = handleIndexMiss(contextItem, type, itemTypes, identifier, visitorContext);
257       } else {
258         retval = handleIndexHit(contextItem, type, item, visitorContext);
259       }
260     }
261     return retval;
262   }
263 }