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 com.vladsch.flexmark.ast.InlineLinkNode;
009import com.vladsch.flexmark.util.sequence.BasedSequence;
010import com.vladsch.flexmark.util.sequence.CharSubSequence;
011
012import gov.nist.secauto.metaschema.core.metapath.format.IPathFormatter;
013import gov.nist.secauto.metaschema.core.metapath.item.node.IModelNodeItem;
014import gov.nist.secauto.metaschema.core.util.CustomCollectors;
015import gov.nist.secauto.metaschema.core.util.ObjectUtils;
016import gov.nist.secauto.oscal.lib.profile.resolver.support.IEntityItem;
017
018import org.apache.logging.log4j.LogManager;
019import org.apache.logging.log4j.Logger;
020
021import java.net.URI;
022import java.util.List;
023import java.util.Locale;
024
025import edu.umd.cs.findbugs.annotations.NonNull;
026
027public class AnchorReferencePolicy
028    extends AbstractCustomReferencePolicy<InlineLinkNode> {
029  private static final Logger LOGGER = LogManager.getLogger(AnchorReferencePolicy.class);
030
031  public AnchorReferencePolicy() {
032    super(IIdentifierParser.FRAGMENT_PARSER);
033  }
034
035  @SuppressWarnings("null")
036  @Override
037  protected List<IEntityItem.ItemType> getEntityItemTypes(@NonNull InlineLinkNode link) {
038    return List.of(
039        IEntityItem.ItemType.RESOURCE,
040        IEntityItem.ItemType.CONTROL,
041        IEntityItem.ItemType.GROUP,
042        IEntityItem.ItemType.PART);
043  }
044
045  @Override
046  public String getReferenceText(@NonNull InlineLinkNode link) {
047    return link.getUrl().toString();
048  }
049
050  @Override
051  public void setReferenceText(@NonNull InlineLinkNode link, @NonNull String newValue) {
052    link.setUrl(BasedSequence.of(newValue));
053  }
054
055  @Override
056  protected void handleUnselected(
057      @NonNull IModelNodeItem<?, ?> contextItem,
058      @NonNull InlineLinkNode link,
059      @NonNull IEntityItem item,
060      @NonNull ReferenceCountingVisitor.Context visitorContext) {
061    URI linkHref = ObjectUtils.notNull(URI.create(link.getUrl().toString()));
062    URI sourceUri = item.getSource();
063
064    URI resolved = visitorContext.getUriResolver().resolve(linkHref, sourceUri);
065    if (LOGGER.isTraceEnabled()) {
066      LOGGER.atTrace().log("At path '{}', remapping orphaned URI '{}' to '{}'",
067          contextItem.toPath(IPathFormatter.METAPATH_PATH_FORMATER),
068          linkHref.toString(),
069          resolved.toString());
070    }
071    link.setUrl(CharSubSequence.of(resolved.toString()));
072  }
073
074  @Override
075  protected boolean handleIndexMiss(
076      @NonNull IModelNodeItem<?, ?> contextItem,
077      @NonNull InlineLinkNode reference,
078      @NonNull List<IEntityItem.ItemType> itemTypes,
079      @NonNull String identifier,
080      @NonNull ReferenceCountingVisitor.Context visitorContext) {
081    if (LOGGER.isWarnEnabled()) {
082      LOGGER.atWarn().log(
083          "The anchor at '{}' should reference a {} identified by '{}', but the identifier was not found in the index.",
084          contextItem.toPath(IPathFormatter.METAPATH_PATH_FORMATER),
085          itemTypes.stream()
086              .map(en -> en.name().toLowerCase(Locale.ROOT))
087              .collect(CustomCollectors.joiningWithOxfordComma("or")),
088          identifier);
089    }
090    return true;
091  }
092
093  @Override
094  protected boolean handleIdentifierNonMatch(
095      @NonNull IModelNodeItem<?, ?> contextItem,
096      @NonNull InlineLinkNode reference,
097      @NonNull ReferenceCountingVisitor.Context visitorContext) {
098    if (LOGGER.isDebugEnabled()) {
099      LOGGER.atDebug().log("Ignoring URI '{}' at '{}'",
100          reference.getUrl().toStringOrNull(),
101          contextItem.toPath(IPathFormatter.METAPATH_PATH_FORMATER));
102    }
103
104    return true;
105  }
106}