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.compatibility.jsonschema.JsonUtil;
9   import io.apicurio.registry.types.ContentTypes;
10  import org.everit.json.schema.ArraySchema;
11  import org.everit.json.schema.ObjectSchema;
12  import org.everit.json.schema.ReferenceSchema;
13  import org.everit.json.schema.Schema;
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<Schema> {
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<Schema> parse(File rootSchemaFile) {
42          return parseDirectory(rootSchemaFile.getParentFile(), rootSchemaFile);
43      }
44  
45      @Override
46      public List<ArtifactReference> handleSchemaReferences(RegisterArtifact rootArtifact,
47              org.everit.json.schema.Schema rootSchema, Map<String, TypedContent> fileContents)
48              throws FileNotFoundException, ExecutionException, InterruptedException {
49  
50          if (rootSchema instanceof ObjectSchema) {
51  
52              ObjectSchema objectSchema = (ObjectSchema) rootSchema;
53              Set<ArtifactReference> references = new HashSet<>();
54  
55              Map<String, org.everit.json.schema.Schema> rootSchemaPropertySchemas = objectSchema
56                      .getPropertySchemas();
57  
58              for (String schemaKey : rootSchemaPropertySchemas.keySet()) {
59  
60                  List<ArtifactReference> nestedArtifactReferences = new ArrayList<>();
61  
62                  if (rootSchemaPropertySchemas.get(schemaKey) instanceof ReferenceSchema) {
63  
64                      ReferenceSchema nestedSchema = (ReferenceSchema) rootSchemaPropertySchemas.get(schemaKey);
65                      RegisterArtifact nestedRegisterArtifact = buildFromRoot(rootArtifact,
66                              nestedSchema.getSchemaLocation());
67  
68                      if (nestedSchema.getReferredSchema() instanceof ObjectSchema) {
69                          ObjectSchema nestedObjectSchema = (ObjectSchema) nestedSchema.getReferredSchema();
70                          nestedArtifactReferences = handleSchemaReferences(nestedRegisterArtifact,
71                                  nestedObjectSchema, fileContents);
72                      }
73  
74                      references.add(registerNestedSchema(nestedSchema.getSchemaLocation(),
75                              nestedArtifactReferences, nestedRegisterArtifact,
76                              fileContents.get(nestedSchema.getSchemaLocation()).getContent().content()));
77  
78                  } else if (rootSchemaPropertySchemas.get(schemaKey) instanceof ArraySchema) {
79  
80                      final ArraySchema arraySchema = (ArraySchema) rootSchemaPropertySchemas.get(schemaKey);
81                      if (arraySchema.getAllItemSchema() instanceof ReferenceSchema) {
82  
83                          ReferenceSchema arrayElementSchema = (ReferenceSchema) arraySchema.getAllItemSchema();
84                          RegisterArtifact nestedRegisterArtifact = buildFromRoot(rootArtifact,
85                                  arrayElementSchema.getSchemaLocation());
86  
87                          if (arrayElementSchema.getReferredSchema() instanceof ObjectSchema) {
88  
89                              nestedArtifactReferences = handleSchemaReferences(nestedRegisterArtifact,
90                                      arrayElementSchema, fileContents);
91                          }
92                          references.add(registerNestedSchema(arrayElementSchema.getSchemaLocation(),
93                                  nestedArtifactReferences, nestedRegisterArtifact, fileContents
94                                          .get(arrayElementSchema.getSchemaLocation()).getContent().content()));
95                      }
96                  }
97              }
98              return new ArrayList<>(references);
99          } else {
100             return Collections.emptyList();
101         }
102     }
103 
104     private JsonSchemaWrapper parseDirectory(File directory, File rootSchema) {
105         Set<File> typesToAdd = Arrays
106                 .stream(Objects.requireNonNull(
107                         directory.listFiles((dir, name) -> name.endsWith(JSON_SCHEMA_EXTENSION))))
108                 .filter(file -> !file.getName().equals(rootSchema.getName())).collect(Collectors.toSet());
109 
110         Map<String, Schema> processed = new HashMap<>();
111         Map<String, TypedContent> schemaContents = new HashMap<>();
112 
113         while (processed.size() != typesToAdd.size()) {
114             boolean fileParsed = false;
115             for (File typeToAdd : typesToAdd) {
116                 if (typeToAdd.getName().equals(rootSchema.getName())) {
117                     continue;
118                 }
119                 try {
120                     final ContentHandle schemaContent = readSchemaContent(typeToAdd);
121                     final TypedContent typedSchemaContent = TypedContent.create(schemaContent,
122                             ContentTypes.APPLICATION_JSON);
123                     final Schema schema = JsonUtil.readSchema(schemaContent.content(), schemaContents, false);
124                     processed.put(schema.getId(), schema);
125                     schemaContents.put(schema.getId(), typedSchemaContent);
126                     fileParsed = true;
127                 } catch (JsonProcessingException ex) {
128                     log.warn(
129                             "Error processing json schema with name {}. This usually means that the references are not ready yet to parse it",
130                             typeToAdd.getName());
131                 }
132             }
133 
134             // If no schema has been processed during this iteration, that means there is an error in the
135             // configuration, throw exception.
136             if (!fileParsed) {
137                 throw new IllegalStateException(
138                         "Error found in the directory structure. Check that all required files are present.");
139             }
140         }
141 
142         try {
143             return new JsonSchemaWrapper(
144                     JsonUtil.readSchema(readSchemaContent(rootSchema).content(), schemaContents, false),
145                     schemaContents);
146         } catch (JsonProcessingException e) {
147             throw new RuntimeException("Unable to parse main schema", e);
148         }
149     }
150 
151     public static class JsonSchemaWrapper implements ParsedDirectoryWrapper<Schema> {
152         final Schema schema;
153         final Map<String, TypedContent> fileContents;
154 
155         public JsonSchemaWrapper(Schema schema, Map<String, TypedContent> fileContents) {
156             this.schema = schema;
157             this.fileContents = fileContents;
158         }
159 
160         @Override
161         public Schema getSchema() {
162             return schema;
163         }
164 
165         @Override
166         public Map<String, TypedContent> getSchemaContents() {
167             return fileContents;
168         }
169     }
170 
171 }