1 package io.apicurio.registry.maven;
2
3 import com.fasterxml.jackson.core.JsonProcessingException;
4 import io.apicurio.registry.content.ContentHandle;
5 import io.apicurio.registry.content.TypedContent;
6 import io.apicurio.registry.rest.client.RegistryClient;
7 import io.apicurio.registry.rest.client.models.ArtifactReference;
8 import io.apicurio.registry.rules.ParsedJsonSchema;
9 import io.apicurio.registry.rules.compatibility.jsonschema.JsonUtil;
10 import io.apicurio.registry.types.ContentTypes;
11 import org.everit.json.schema.ArraySchema;
12 import org.everit.json.schema.ObjectSchema;
13 import org.everit.json.schema.ReferenceSchema;
14 import org.slf4j.Logger;
15 import org.slf4j.LoggerFactory;
16
17 import java.io.File;
18 import java.io.FileNotFoundException;
19 import java.util.ArrayList;
20 import java.util.Arrays;
21 import java.util.Collections;
22 import java.util.HashMap;
23 import java.util.HashSet;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.Objects;
27 import java.util.Set;
28 import java.util.concurrent.ExecutionException;
29 import java.util.stream.Collectors;
30
31 public class JsonSchemaDirectoryParser extends AbstractDirectoryParser<ParsedJsonSchema> {
32
33 private static final String JSON_SCHEMA_EXTENSION = ".json";
34 private static final Logger log = LoggerFactory.getLogger(JsonSchemaDirectoryParser.class);
35
36 public JsonSchemaDirectoryParser(RegistryClient client) {
37 super(client);
38 }
39
40 @Override
41 public ParsedDirectoryWrapper<ParsedJsonSchema> parse(File rootSchemaFile) {
42 return parseDirectory(rootSchemaFile.getParentFile(), rootSchemaFile);
43 }
44
45 @Override
46 public List<ArtifactReference> handleSchemaReferences(RegisterArtifact rootArtifact,
47 ParsedJsonSchema someRootSchema, Map<String, TypedContent> fileContents)
48 throws FileNotFoundException, ExecutionException, InterruptedException {
49
50 if (someRootSchema.getJsonsKema() != null) {
51 log.warn("Reference handling for JSON schema version 2020-12 is not supported yet.");
52 return List.of();
53 }
54 var rootSchema = someRootSchema.getEverit();
55
56 if (rootSchema instanceof ObjectSchema) {
57
58 ObjectSchema objectSchema = (ObjectSchema) rootSchema;
59 Set<ArtifactReference> references = new HashSet<>();
60
61 Map<String, org.everit.json.schema.Schema> rootSchemaPropertySchemas = objectSchema
62 .getPropertySchemas();
63
64 for (String schemaKey : rootSchemaPropertySchemas.keySet()) {
65
66 List<ArtifactReference> nestedArtifactReferences = new ArrayList<>();
67
68 if (rootSchemaPropertySchemas.get(schemaKey) instanceof ReferenceSchema) {
69
70 ReferenceSchema nestedSchema = (ReferenceSchema) rootSchemaPropertySchemas.get(schemaKey);
71 RegisterArtifact nestedRegisterArtifact = buildFromRoot(rootArtifact,
72 nestedSchema.getSchemaLocation());
73
74 if (nestedSchema.getReferredSchema() instanceof ObjectSchema) {
75 ObjectSchema nestedObjectSchema = (ObjectSchema) nestedSchema.getReferredSchema();
76 nestedArtifactReferences = handleSchemaReferences(nestedRegisterArtifact,
77 new ParsedJsonSchema(nestedObjectSchema), fileContents);
78 }
79
80 references.add(registerNestedSchema(nestedSchema.getSchemaLocation(),
81 nestedArtifactReferences, nestedRegisterArtifact,
82 fileContents.get(nestedSchema.getSchemaLocation()).getContent().content()));
83
84 } else if (rootSchemaPropertySchemas.get(schemaKey) instanceof ArraySchema) {
85
86 final ArraySchema arraySchema = (ArraySchema) rootSchemaPropertySchemas.get(schemaKey);
87 if (arraySchema.getAllItemSchema() instanceof ReferenceSchema) {
88
89 ReferenceSchema arrayElementSchema = (ReferenceSchema) arraySchema.getAllItemSchema();
90 RegisterArtifact nestedRegisterArtifact = buildFromRoot(rootArtifact,
91 arrayElementSchema.getSchemaLocation());
92
93 if (arrayElementSchema.getReferredSchema() instanceof ObjectSchema) {
94
95 nestedArtifactReferences = handleSchemaReferences(nestedRegisterArtifact,
96 new ParsedJsonSchema(arrayElementSchema), fileContents);
97 }
98 references.add(registerNestedSchema(arrayElementSchema.getSchemaLocation(),
99 nestedArtifactReferences, nestedRegisterArtifact, fileContents
100 .get(arrayElementSchema.getSchemaLocation()).getContent().content()));
101 }
102 }
103 }
104 return new ArrayList<>(references);
105 } else {
106 return Collections.emptyList();
107 }
108 }
109
110 private JsonSchemaWrapper parseDirectory(File directory, File rootSchema) {
111 Set<File> typesToAdd = Arrays
112 .stream(Objects.requireNonNull(
113 directory.listFiles((dir, name) -> name.endsWith(JSON_SCHEMA_EXTENSION))))
114 .filter(file -> !file.getName().equals(rootSchema.getName())).collect(Collectors.toSet());
115
116 Map<String, ParsedJsonSchema> processed = new HashMap<>();
117 Map<String, TypedContent> schemaContents = new HashMap<>();
118
119 while (processed.size() != typesToAdd.size()) {
120 boolean fileParsed = false;
121 for (File typeToAdd : typesToAdd) {
122 if (typeToAdd.getName().equals(rootSchema.getName())) {
123 continue;
124 }
125 try {
126 final ContentHandle schemaContent = readSchemaContent(typeToAdd);
127 final TypedContent typedSchemaContent = TypedContent.create(schemaContent,
128 ContentTypes.APPLICATION_JSON);
129 final ParsedJsonSchema schema = JsonUtil.readSchema(schemaContent.content(), schemaContents, false);
130 processed.put(schema.getId(), schema);
131 schemaContents.put(schema.getId(), typedSchemaContent);
132 fileParsed = true;
133 } catch (JsonProcessingException ex) {
134 log.warn(
135 "Error processing json schema with name {}. This usually means that the references are not ready yet to parse it",
136 typeToAdd.getName());
137 }
138 }
139
140
141
142 if (!fileParsed) {
143 throw new IllegalStateException(
144 "Error found in the directory structure. Check that all required files are present.");
145 }
146 }
147
148 try {
149 return new JsonSchemaWrapper(
150 JsonUtil.readSchema(readSchemaContent(rootSchema).content(), schemaContents, false),
151 schemaContents);
152 } catch (JsonProcessingException e) {
153 throw new RuntimeException("Unable to parse main schema", e);
154 }
155 }
156
157 public static class JsonSchemaWrapper implements ParsedDirectoryWrapper<ParsedJsonSchema> {
158 final ParsedJsonSchema schema;
159 final Map<String, TypedContent> fileContents;
160
161 public JsonSchemaWrapper(ParsedJsonSchema schema, Map<String, TypedContent> fileContents) {
162 this.schema = schema;
163 this.fileContents = fileContents;
164 }
165
166 @Override
167 public ParsedJsonSchema getSchema() {
168 return schema;
169 }
170
171 @Override
172 public Map<String, TypedContent> getSchemaContents() {
173 return fileContents;
174 }
175 }
176
177 }