View Javadoc
1   package io.apicurio.registry.maven.refs;
2   
3   import com.fasterxml.jackson.databind.JsonNode;
4   import com.fasterxml.jackson.databind.ObjectMapper;
5   import io.apicurio.datamodels.Library;
6   import io.apicurio.datamodels.models.Document;
7   import io.apicurio.datamodels.util.ModelTypeUtil;
8   import io.apicurio.registry.content.ContentHandle;
9   import io.apicurio.registry.rest.client.models.VersionMetaData;
10  import io.apicurio.registry.types.ArtifactType;
11  import io.apicurio.registry.utils.protobuf.schema.ProtobufFile;
12  
13  import java.nio.file.Path;
14  import java.util.HashSet;
15  import java.util.Set;
16  
17  /**
18   * An index of the files available when discovering references in an artifact. This index is typically
19   * populated by getting a list of all files in a directory, or zip file. The index maps a resource name (this
20   * will vary depending on the artifact type) to the content of the resource. For example, Avro schemas will
21   * have resource names based on the qualified name of the type they define. JSON Schemas will have resources
22   * names based on the name of the file. The intent of this index is to resolve an external reference found in
23   * an artifact to an actual piece of content (e.g. file) in the index. If it cannot be resolved, that would
24   * typically mean that there is a broken reference in the schema/design.
25   */
26  public class ReferenceIndex {
27  
28      private static final ObjectMapper mapper = new ObjectMapper();
29  
30      private Set<IndexedResource> index = new HashSet<>();
31      private Set<Path> schemaPaths = new HashSet<>();
32  
33      /**
34       * Constructor.
35       */
36      public ReferenceIndex() {
37      }
38  
39      /**
40       * Constructor.
41       * 
42       * @param schemaPath
43       */
44      public ReferenceIndex(Path schemaPath) {
45          this.schemaPaths.add(schemaPath);
46      }
47  
48      /**
49       * @param path
50       */
51      public void addSchemaPath(Path path) {
52          this.schemaPaths.add(path);
53      }
54  
55      /**
56       * Look up a resource in the index. Returns <code>null</code> if no resource with that name is found.
57       * 
58       * @param resourceName
59       * @param relativeToFile
60       */
61      public IndexedResource lookup(String resourceName, Path relativeToFile) {
62          return index.stream().filter(resource -> resource.matches(resourceName, relativeToFile, schemaPaths))
63                  .findFirst().orElse(null);
64      }
65  
66      /**
67       * Index an existing (remote) reference using a resource name and remote artifact metadata.
68       * 
69       * @param resourceName
70       * @param vmd
71       */
72      public void index(String resourceName, VersionMetaData vmd) {
73          IndexedResource res = new IndexedResource(null, null, resourceName, null);
74          res.setRegistration(vmd);
75          this.index.add(res);
76      }
77  
78      /**
79       * Index the given content. Indexing will parse the content and figure out its resource name and type.
80       * 
81       * @param path
82       * @param content
83       */
84      public void index(Path path, ContentHandle content) {
85          try {
86              JsonNode tree = mapper.readTree(content.content());
87  
88              // OpenAPI
89              if (tree.has("openapi") || tree.has("swagger") || tree.has("asyncapi")) {
90                  indexDataModels(path, content);
91              }
92              // JSON Schema
93              if (tree.has("$schema") && !tree.get("$schema").isNull()) {
94                  indexJsonSchema(tree, path, content);
95              }
96              // Avro
97              indexAvro(path, content, tree);
98          } catch (Exception e) {
99              // Must not be JSON...
100         }
101 
102         try {
103             indexProto(path, content);
104             return;
105         } catch (Exception e) {
106             // I guess it's not Protobuf.
107         }
108     }
109 
110     private void indexAvro(Path path, ContentHandle content, JsonNode parsed) {
111         // TODO: is namespace required for an Avro schema?
112         String ns = parsed.get("namespace").asText();
113         String name = parsed.get("name").asText();
114         String resourceName = ns != null ? ns + "." + name : name;
115         IndexedResource resource = new IndexedResource(path, ArtifactType.AVRO, resourceName, content);
116         this.index.add(resource);
117     }
118 
119     private void indexProto(Path path, ContentHandle content) {
120         ProtobufFile.toProtoFileElement(content.content());
121 
122         IndexedResource resource = new IndexedResource(path, ArtifactType.PROTOBUF, null, content);
123         this.index.add(resource);
124     }
125 
126     private void indexJsonSchema(JsonNode schema, Path path, ContentHandle content) {
127         String resourceName = null;
128         if (schema.has("$id")) {
129             resourceName = schema.get("$id").asText(null);
130         }
131         IndexedResource resource = new IndexedResource(path, ArtifactType.JSON, resourceName, content);
132         this.index.add(resource);
133     }
134 
135     private void indexDataModels(Path path, ContentHandle content) {
136         Document doc = Library.readDocumentFromJSONString(content.content());
137         if (doc == null) {
138             throw new UnsupportedOperationException("Content is not OpenAPI or AsyncAPI.");
139         }
140 
141         String type = ArtifactType.OPENAPI;
142         if (ModelTypeUtil.isAsyncApiModel(doc)) {
143             type = ArtifactType.ASYNCAPI;
144         }
145 
146         IndexedResource resource = new IndexedResource(path, type, null, content);
147         this.index.add(resource);
148     }
149 
150 }