001/*
002 * SPDX-FileCopyrightText: none
003 * SPDX-License-Identifier: CC0-1.0
004 */
005
006package gov.nist.secauto.oscal.lib.profile.resolver.policy;
007
008import gov.nist.secauto.metaschema.core.metapath.item.node.IModelNodeItem;
009import gov.nist.secauto.metaschema.core.util.ObjectUtils;
010import gov.nist.secauto.oscal.lib.profile.resolver.ProfileResolutionEvaluationException;
011import gov.nist.secauto.oscal.lib.profile.resolver.support.IEntityItem;
012
013import org.apache.logging.log4j.LogManager;
014import org.apache.logging.log4j.Logger;
015
016import java.util.List;
017
018import edu.umd.cs.findbugs.annotations.NonNull;
019import edu.umd.cs.findbugs.annotations.Nullable;
020
021public abstract class AbstractCustomReferencePolicy<TYPE> implements ICustomReferencePolicy<TYPE> {
022  private static final Logger LOGGER = LogManager.getLogger(AbstractCustomReferencePolicy.class);
023
024  @NonNull
025  private final IIdentifierParser identifierParser;
026
027  protected AbstractCustomReferencePolicy(
028      @NonNull IIdentifierParser identifierParser) {
029    this.identifierParser = identifierParser;
030  }
031
032  @Override
033  @NonNull
034  public IIdentifierParser getIdentifierParser() {
035    return identifierParser;
036  }
037
038  /**
039   * Get the possible item types that can be searched in the order in which the
040   * identifier will be looked up.
041   * <p>
042   * The {@code reference} object is provided to allow for context sensitive item
043   * type tailoring.
044   *
045   * @param reference
046   *          the reference object
047   * @return a list of item types to search for
048   */
049  @NonNull
050  protected abstract List<IEntityItem.ItemType> getEntityItemTypes(@NonNull TYPE reference);
051
052  /**
053   * Handle an index hit.
054   *
055   * @param contextItem
056   *          the node containing the identifier reference
057   * @param reference
058   *          the identifier reference object generating the hit
059   * @param item
060   *          the referenced item
061   * @param visitorContext
062   *          the reference visitor state, which can be used for further
063   *          processing
064   * @return {@code true} if the hit was handled or {@code false} otherwise
065   * @throws ProfileResolutionEvaluationException
066   *           if there was an error handing the index hit
067   */
068  protected boolean handleIndexHit(
069      @NonNull IModelNodeItem<?, ?> contextItem,
070      @NonNull TYPE reference,
071      @NonNull IEntityItem item,
072      @NonNull ReferenceCountingVisitor.Context visitorContext) {
073
074    if (visitorContext.getIndexer().isSelected(item)) {
075      if (!visitorContext.isResolved(item)) {
076        // this referenced item will need to be resolved
077        ReferenceCountingVisitor.instance().resolveEntity(item, visitorContext);
078      }
079      item.incrementReferenceCount();
080
081      if (item.isIdentifierReassigned()) {
082        String referenceText = ObjectUtils.notNull(getReferenceText(reference));
083        String newReferenceText = getIdentifierParser().update(referenceText, item.getIdentifier());
084        setReferenceText(reference, newReferenceText);
085        if (LOGGER.isDebugEnabled()) {
086          LOGGER.atDebug().log("Mapping {} reference '{}' to '{}'.", item.getItemType().name(), referenceText,
087              newReferenceText);
088        }
089      }
090      handleSelected(contextItem, reference, item, visitorContext);
091    } else {
092      handleUnselected(contextItem, reference, item, visitorContext);
093    }
094    return true;
095  }
096
097  /**
098   * Handle an index hit against an item related to an unselected control.
099   * <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}