001/*
002 * Portions of this software was developed by employees of the National Institute
003 * of Standards and Technology (NIST), an agency of the Federal Government and is
004 * being made available as a public service. Pursuant to title 17 United States
005 * Code Section 105, works of NIST employees are not subject to copyright
006 * protection in the United States. This software may be subject to foreign
007 * copyright. Permission in the United States and in foreign countries, to the
008 * extent that NIST may hold copyright, to use, copy, modify, create derivative
009 * works, and distribute this software and its documentation without fee is hereby
010 * granted on a non-exclusive basis, provided that this notice and disclaimer
011 * of warranty appears in all copies.
012 *
013 * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER
014 * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY
015 * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF
016 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM
017 * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE
018 * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE.  IN NO EVENT
019 * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT,
020 * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM,
021 * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY,
022 * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR
023 * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT
024 * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER.
025 */
026
027package gov.nist.secauto.oscal.lib.profile.resolver.policy;
028
029import gov.nist.secauto.metaschema.model.common.metapath.item.IRequiredValueModelNodeItem;
030import gov.nist.secauto.metaschema.model.common.util.ObjectUtils;
031import gov.nist.secauto.oscal.lib.profile.resolver.ProfileResolutionEvaluationException;
032import gov.nist.secauto.oscal.lib.profile.resolver.support.IEntityItem;
033
034import org.apache.logging.log4j.LogManager;
035import org.apache.logging.log4j.Logger;
036
037import java.util.List;
038
039import edu.umd.cs.findbugs.annotations.NonNull;
040import edu.umd.cs.findbugs.annotations.Nullable;
041
042public abstract class AbstractCustomReferencePolicy<TYPE> implements ICustomReferencePolicy<TYPE> {
043  private static final Logger LOGGER = LogManager.getLogger(AbstractCustomReferencePolicy.class);
044
045  @NonNull
046  private final IIdentifierParser identifierParser;
047
048  protected AbstractCustomReferencePolicy(
049      @NonNull IIdentifierParser identifierParser) {
050    this.identifierParser = identifierParser;
051  }
052
053  @Override
054  @NonNull
055  public IIdentifierParser getIdentifierParser() {
056    return identifierParser;
057  }
058
059  /**
060   * Get the possible item types that can be searched in the order in which the
061   * identifier will be looked up.
062   * <p>
063   * The {@code reference} object is provided to allow for context sensitive item
064   * type tailoring.
065   *
066   * @param reference
067   *          the reference object
068   * @return a list of item types to search for
069   */
070  @NonNull
071  protected abstract List<IEntityItem.ItemType> getEntityItemTypes(@NonNull TYPE reference);
072
073  /**
074   * Handle an index hit.
075   *
076   * @param contextItem
077   *          the node containing the identifier reference
078   * @param reference
079   *          the identifier reference object generating the hit
080   * @param item
081   *          the referenced item
082   * @param visitorContext
083   *          the reference visitor state, which can be used for further
084   *          processing
085   * @return {@code true} if the hit was handled or {@code false} otherwise
086   * @throws ProfileResolutionEvaluationException
087   *           if there was an error handing the index hit
088   */
089  protected boolean handleIndexHit(
090      @NonNull IRequiredValueModelNodeItem contextItem,
091      @NonNull TYPE reference,
092      @NonNull IEntityItem item,
093      @NonNull ReferenceCountingVisitor.Context visitorContext) {
094
095    if (visitorContext.getIndexer().isSelected(item)) {
096      if (!visitorContext.isResolved(item)) {
097        // this referenced item will need to be resolved
098        ReferenceCountingVisitor.instance().resolveEntity(item, visitorContext);
099      }
100      item.incrementReferenceCount();
101
102      if (item.isIdentifierReassigned()) {
103        String referenceText = ObjectUtils.notNull(getReferenceText(reference));
104        String newReferenceText = getIdentifierParser().update(referenceText, item.getIdentifier());
105        setReferenceText(reference, newReferenceText);
106        if (LOGGER.isDebugEnabled()) {
107          LOGGER.atDebug().log("Mapping {} reference '{}' to '{}'.", item.getItemType().name(), referenceText,
108              newReferenceText);
109        }
110      }
111      handleSelected(contextItem, reference, item, visitorContext);
112    } else {
113      handleUnselected(contextItem, reference, item, visitorContext);
114    }
115    return true;
116  }
117
118  /**
119   * Handle an index hit against an item related to an unselected control.
120   * <p>
121   * Subclasses can override this method to perform extra processing.
122   *
123   * @param contextItem
124   *          the node containing the identifier reference
125   * @param reference
126   *          the identifier reference object generating the hit
127   * @param item
128   *          the referenced item
129   * @param visitorContext
130   *          the reference visitor, which can be used for further processing
131   * @throws ProfileResolutionEvaluationException
132   *           if there was an error handing the index hit
133   */
134  protected void handleUnselected( // NOPMD noop default
135      @NonNull IRequiredValueModelNodeItem contextItem,
136      @NonNull TYPE reference,
137      @NonNull IEntityItem item,
138      @NonNull ReferenceCountingVisitor.Context visitorContext) {
139    // do nothing by default
140  }
141
142  /**
143   * Handle an index hit against an item related to an selected control.
144   * <p>
145   * Subclasses can override this method to perform extra processing.
146   *
147   * @param contextItem
148   *          the node containing the identifier reference
149   * @param reference
150   *          the identifier reference object generating the hit
151   * @param item
152   *          the referenced item
153   * @param visitorContext
154   *          the reference visitor state, which can be used for further
155   *          processing
156   * @throws ProfileResolutionEvaluationException
157   *           if there was an error handing the index hit
158   */
159  protected void handleSelected( // NOPMD noop default
160      @NonNull IRequiredValueModelNodeItem contextItem,
161      @NonNull TYPE reference,
162      @NonNull IEntityItem item,
163      @NonNull ReferenceCountingVisitor.Context visitorContext) {
164    // do nothing by default
165  }
166
167  /**
168   * Handle an index miss for a reference. This occurs when the referenced item
169   * was not found in the index.
170   * <p>
171   * Subclasses can override this method to perform extra processing.
172   *
173   * @param contextItem
174   *          the node containing the identifier reference
175   * @param reference
176   *          the identifier reference object generating the hit
177   * @param itemTypes
178   *          the possible item types for this reference
179   * @param identifier
180   *          the parsed identifier
181   * @param visitorContext
182   *          the reference visitor state, which can be used for further
183   *          processing
184   * @return {@code true} if the reference is handled by this method or
185   *         {@code false} otherwise
186   * @throws ProfileResolutionEvaluationException
187   *           if there was an error handing the index miss
188   */
189  protected boolean handleIndexMiss(
190      @NonNull IRequiredValueModelNodeItem contextItem,
191      @NonNull TYPE reference,
192      @NonNull List<IEntityItem.ItemType> itemTypes,
193      @NonNull String identifier,
194      @NonNull ReferenceCountingVisitor.Context visitorContext) {
195    // provide no handler by default
196    return false;
197  }
198
199  /**
200   * Handle the case where the identifier was not a syntax match for an expected
201   * identifier. This can occur when the reference is malformed, using an
202   * unrecognized syntax.
203   * <p>
204   * Subclasses can override this method to perform extra processing.
205   *
206   * @param contextItem
207   *          the node containing the identifier reference
208   * @param reference
209   *          the identifier reference object generating the hit
210   * @param visitorContext
211   *          the reference visitor state, which can be used for further
212   *          processing
213   * @return {@code true} if the reference is handled by this method or
214   *         {@code false} otherwise
215   * @throws ProfileResolutionEvaluationException
216   *           if there was an error handing the index miss due to a non match
217   */
218  protected boolean handleIdentifierNonMatch(
219      @NonNull IRequiredValueModelNodeItem contextItem,
220      @NonNull TYPE reference,
221      @NonNull ReferenceCountingVisitor.Context visitorContext) {
222    // provide no handler by default
223    return false;
224  }
225
226  @Override
227  public boolean handleReference(
228      @NonNull IRequiredValueModelNodeItem contextItem,
229      @NonNull TYPE type,
230      @NonNull ReferenceCountingVisitor.Context visitorContext) {
231    String referenceText = getReferenceText(type);
232
233    // if the reference text does not exist, ignore the reference; otherwise, handle
234    // it.
235    return referenceText == null
236        || handleIdentifier(contextItem, type, getIdentifierParser().parse(referenceText), visitorContext);
237  }
238
239  /**
240   * Handle the provided {@code identifier} for a given {@code type} of reference.
241   *
242   * @param contextItem
243   *          the node containing the identifier reference
244   * @param type
245   *          the item type of the reference
246   * @param identifier
247   *          the identifier
248   * @param visitorContext
249   *          the reference visitor state, which can be used for further
250   *          processing
251   * @return {@code true} if the reference is handled by this method or
252   *         {@code false} otherwise
253   * @throws ProfileResolutionEvaluationException
254   *           if there was an error handing the reference
255   */
256  protected boolean handleIdentifier(
257      @NonNull IRequiredValueModelNodeItem contextItem,
258      @NonNull TYPE type,
259      @Nullable String identifier,
260      @NonNull ReferenceCountingVisitor.Context visitorContext) {
261    boolean retval;
262    if (identifier == null) {
263      retval = handleIdentifierNonMatch(contextItem, type, visitorContext);
264    } else {
265      List<IEntityItem.ItemType> itemTypes = getEntityItemTypes(type);
266      IEntityItem item = null;
267      for (IEntityItem.ItemType itemType : itemTypes) {
268        assert itemType != null;
269
270        item = visitorContext.getEntity(itemType, identifier);
271        if (item != null) {
272          break;
273        }
274      }
275
276      if (item == null) {
277        retval = handleIndexMiss(contextItem, type, itemTypes, identifier, visitorContext);
278      } else {
279        retval = handleIndexHit(contextItem, type, item, visitorContext);
280      }
281    }
282    return retval;
283  }
284}