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