View Javadoc
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             // If no schema has been processed during this iteration, that means there is an error in the
141             // configuration, throw exception.
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 }