1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package gov.nist.secauto.oscal.lib.profile.resolver.policy;
7   
8   import com.vladsch.flexmark.ast.InlineLinkNode;
9   import com.vladsch.flexmark.util.sequence.BasedSequence;
10  import com.vladsch.flexmark.util.sequence.CharSubSequence;
11  
12  import gov.nist.secauto.metaschema.core.metapath.format.IPathFormatter;
13  import gov.nist.secauto.metaschema.core.metapath.item.node.IModelNodeItem;
14  import gov.nist.secauto.metaschema.core.util.CustomCollectors;
15  import gov.nist.secauto.oscal.lib.profile.resolver.support.IEntityItem;
16  
17  import org.apache.logging.log4j.LogManager;
18  import org.apache.logging.log4j.Logger;
19  
20  import java.net.URI;
21  import java.util.List;
22  import java.util.Locale;
23  
24  import edu.umd.cs.findbugs.annotations.NonNull;
25  
26  public class AnchorReferencePolicy
27      extends AbstractCustomReferencePolicy<InlineLinkNode> {
28    private static final Logger LOGGER = LogManager.getLogger(AnchorReferencePolicy.class);
29  
30    public AnchorReferencePolicy() {
31      super(IIdentifierParser.FRAGMENT_PARSER);
32    }
33  
34    @SuppressWarnings("null")
35    @Override
36    protected List<IEntityItem.ItemType> getEntityItemTypes(@NonNull InlineLinkNode link) {
37      return List.of(
38          IEntityItem.ItemType.RESOURCE,
39          IEntityItem.ItemType.CONTROL,
40          IEntityItem.ItemType.GROUP,
41          IEntityItem.ItemType.PART);
42    }
43  
44    @Override
45    public String getReferenceText(@NonNull InlineLinkNode link) {
46      return link.getUrl().toString();
47    }
48  
49    @Override
50    public void setReferenceText(@NonNull InlineLinkNode link, @NonNull String newValue) {
51      link.setUrl(BasedSequence.of(newValue));
52    }
53  
54    @Override
55    protected void handleUnselected(
56        @NonNull IModelNodeItem<?, ?> contextItem,
57        @NonNull InlineLinkNode link,
58        @NonNull IEntityItem item,
59        @NonNull ReferenceCountingVisitor.Context visitorContext) {
60      URI linkHref = URI.create(link.getUrl().toString());
61      URI sourceUri = item.getSource();
62  
63      URI resolved = sourceUri.resolve(linkHref);
64      if (LOGGER.isTraceEnabled()) {
65        LOGGER.atTrace().log("At path '{}', remapping orphaned URI '{}' to '{}'",
66            contextItem.toPath(IPathFormatter.METAPATH_PATH_FORMATER),
67            linkHref.toString(),
68            resolved.toString());
69      }
70      link.setUrl(CharSubSequence.of(resolved.toString()));
71    }
72  
73    @Override
74    protected boolean handleIndexMiss(
75        @NonNull IModelNodeItem<?, ?> contextItem,
76        @NonNull InlineLinkNode reference,
77        @NonNull List<IEntityItem.ItemType> itemTypes,
78        @NonNull String identifier,
79        @NonNull ReferenceCountingVisitor.Context visitorContext) {
80      if (LOGGER.isErrorEnabled()) {
81        LOGGER.atError().log(
82            "The anchor at '{}' should reference a {} identified by '{}', but the identifier was not found in the index.",
83            contextItem.toPath(IPathFormatter.METAPATH_PATH_FORMATER),
84            itemTypes.stream()
85                .map(en -> en.name().toLowerCase(Locale.ROOT))
86                .collect(CustomCollectors.joiningWithOxfordComma("or")),
87            identifier);
88      }
89      return true;
90    }
91  
92    @Override
93    protected boolean handleIdentifierNonMatch(
94        @NonNull IModelNodeItem<?, ?> contextItem,
95        @NonNull InlineLinkNode reference,
96        @NonNull ReferenceCountingVisitor.Context visitorContext) {
97      if (LOGGER.isDebugEnabled()) {
98        LOGGER.atDebug().log("Ignoring URI '{}' at '{}'",
99            reference.getUrl().toStringOrNull(),
100           contextItem.toPath(IPathFormatter.METAPATH_PATH_FORMATER));
101     }
102 
103     return true;
104   }
105 }