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}