1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package gov.nist.secauto.metaschema.schemagen.json.impl;
7   
8   import com.fasterxml.jackson.core.JsonGenerator;
9   import com.fasterxml.jackson.databind.node.JsonNodeFactory;
10  import com.fasterxml.jackson.databind.node.ObjectNode;
11  
12  import gov.nist.secauto.metaschema.core.configuration.IConfiguration;
13  import gov.nist.secauto.metaschema.core.datatype.IDataTypeAdapter;
14  import gov.nist.secauto.metaschema.core.model.IAssemblyDefinition;
15  import gov.nist.secauto.metaschema.core.model.IDefinition;
16  import gov.nist.secauto.metaschema.core.model.IFieldDefinition;
17  import gov.nist.secauto.metaschema.core.model.IFlagDefinition;
18  import gov.nist.secauto.metaschema.core.model.IModule;
19  import gov.nist.secauto.metaschema.core.model.IValuedDefinition;
20  import gov.nist.secauto.metaschema.core.model.constraint.IAllowedValue;
21  import gov.nist.secauto.metaschema.core.util.ObjectUtils;
22  import gov.nist.secauto.metaschema.schemagen.AbstractGenerationState;
23  import gov.nist.secauto.metaschema.schemagen.ModuleIndex.DefinitionEntry;
24  import gov.nist.secauto.metaschema.schemagen.SchemaGenerationFeature;
25  import gov.nist.secauto.metaschema.schemagen.json.IDataTypeJsonSchema;
26  import gov.nist.secauto.metaschema.schemagen.json.IDefineableJsonSchema.IKey;
27  import gov.nist.secauto.metaschema.schemagen.json.IDefinitionJsonSchema;
28  import gov.nist.secauto.metaschema.schemagen.json.IJsonGenerationState;
29  
30  import java.io.IOException;
31  import java.util.Comparator;
32  import java.util.HashMap;
33  import java.util.List;
34  import java.util.Map;
35  import java.util.concurrent.ConcurrentHashMap;
36  
37  import edu.umd.cs.findbugs.annotations.NonNull;
38  import edu.umd.cs.findbugs.annotations.Nullable;
39  
40  public class JsonGenerationState
41      extends AbstractGenerationState<JsonGenerator, JsonDatatypeManager>
42      implements IJsonGenerationState {
43  
44    @NonNull
45    private final JsonNodeFactory jsonNodeFactory = new JsonNodeFactory(true);
46    @NonNull
47    private final Map<IKey, IDefinitionJsonSchema<?>> schemaDefinitions = new HashMap<>();
48    @NonNull
49    private final Map<IValuedDefinition, IDataTypeJsonSchema> definitionValueToDataTypeSchemaMap
50        = new ConcurrentHashMap<>();
51    @NonNull
52    private final Map<IDataTypeAdapter<?>, IDataTypeJsonSchema> dataTypeToSchemaMap = new ConcurrentHashMap<>();
53  
54    public JsonGenerationState(
55        @NonNull IModule module,
56        @NonNull JsonGenerator writer,
57        @NonNull IConfiguration<SchemaGenerationFeature<?>> configuration) {
58      super(module, writer, configuration, new JsonDatatypeManager());
59  
60      // // seed definition schema mapping
61      // this.schemaDefinitions =
62      // ObjectUtils.notNull(getMetaschemaIndex().getDefinitions().stream()
63      // .filter(entry -> !isInline(entry.getDefinition()) &&
64      // entry.isUsedWithoutJsonKey()
65      // && !entry.isChoiceGroupMember())
66      // .map(entry -> newJsonSchema(entry.getDefinition(), null, null, null, this))
67      // .collect(Collectors.toMap(
68      // schema -> schema.getKey(),
69      // Function.identity(),
70      // (v1, v2) -> v2,
71      // ConcurrentHashMap::new)));
72    }
73  
74    @Override
75    @NonNull
76    public <DEF extends IDefinition> IDefinitionJsonSchema<DEF> getSchema(@NonNull IKey key) {
77      IDefinitionJsonSchema<?> retval = getDefinitionSchema(key, this);
78      return ObjectUtils.asType(ObjectUtils.requireNonNull(retval));
79    }
80  
81    @Override
82    @NonNull
83    public IDataTypeJsonSchema getSchema(@NonNull IDataTypeAdapter<?> datatype) {
84      IDataTypeJsonSchema retval = dataTypeToSchemaMap.get(datatype);
85      if (retval == null) {
86        retval = new DataTypeJsonSchema(
87            getDatatypeManager().getTypeNameForDatatype(datatype),
88            datatype);
89        dataTypeToSchemaMap.put(datatype, retval);
90      }
91      return retval;
92    }
93  
94    /**
95     * Get the JSON schema info for the provided definition.
96     *
97     * @param key
98     *          the key to use to lookup the definition schema info
99     * @return the definition's schema info
100    */
101   private IDefinitionJsonSchema<?> getDefinitionSchema(
102       @NonNull IKey key,
103       @NonNull IJsonGenerationState state) {
104     synchronized (schemaDefinitions) {
105       return schemaDefinitions.computeIfAbsent(key, k -> {
106         IDefinitionJsonSchema<?> retval = newJsonSchema(
107             k.getDefinition(),
108             k.getJsonKeyFlagName(),
109             k.getDiscriminatorProperty(),
110             k.getDiscriminatorValue(),
111             state);
112         assert key.equals(retval.getKey());
113         return retval;
114       });
115     }
116   }
117 
118   @Override
119   public boolean isDefinitionRegistered(IDefinitionJsonSchema<?> schema) {
120     return schemaDefinitions.containsKey(schema.getKey());
121   }
122 
123   @Override
124   public void registerDefinitionSchema(IDefinitionJsonSchema<?> schema) {
125     IDefinitionJsonSchema<?> old = schemaDefinitions.put(schema.getKey(), schema);
126     assert old == null;
127   }
128 
129   /**
130    * Get the JSON schema info for the provided definition.
131    *
132    * @param definition
133    *          the definition to get the schema info for
134    * @param jsonKeyFlagName
135    *          the name of the flag to use as the JSON key, or @{code null} if no
136    *          flag is used as the JSON key
137    * @param discriminatorProperty
138    *          the property name to use as the choice group discriminator,
139    *          or @{code null} if no choice group discriminator is used
140    * @param discriminatorValue
141    *          the property value to use as the choice group discriminator,
142    *          or @{code null} if no choice group discriminator is used
143    * @return the definition's schema info
144    */
145   @NonNull
146   private static IDefinitionJsonSchema<?> newJsonSchema(
147       @NonNull IDefinition definition,
148       @Nullable String jsonKeyFlagName,
149       @Nullable String discriminatorProperty,
150       @Nullable String discriminatorValue,
151       @NonNull IJsonGenerationState state) {
152     IDefinitionJsonSchema<?> retval;
153     if (definition instanceof IFlagDefinition) {
154       retval = new FlagDefinitionJsonSchema((IFlagDefinition) definition, state);
155     } else if (definition instanceof IAssemblyDefinition) {
156       retval = new AssemblyDefinitionJsonSchema(
157           (IAssemblyDefinition) definition,
158           jsonKeyFlagName,
159           discriminatorProperty,
160           discriminatorValue,
161           state);
162     } else if (definition instanceof IFieldDefinition) {
163       retval = new FieldDefinitionJsonSchema(
164           (IFieldDefinition) definition,
165           jsonKeyFlagName,
166           discriminatorProperty,
167           discriminatorValue,
168           state);
169     } else {
170       throw new IllegalArgumentException("Unsupported definition type" + definition.getClass().getName());
171     }
172     return retval;
173   }
174 
175   public ObjectNode generateDefinitions() {
176     @NonNull
177     Map<IKey, IDefinitionJsonSchema<?>> gatheredDefinitions = new HashMap<>();
178 
179     getMetaschemaIndex().getDefinitions().stream()
180         .filter(DefinitionEntry::isRoot)
181         .map(DefinitionEntry::getDefinition)
182         .forEachOrdered(def -> {
183           IDefinitionJsonSchema<?> definitionSchema = getSchema(IKey.of(def));
184           assert definitionSchema != null;
185           definitionSchema.gatherDefinitions(gatheredDefinitions, this);
186         });
187 
188     ObjectNode definitionsObject = ObjectUtils.notNull(JsonNodeFactory.instance.objectNode());
189 
190     gatheredDefinitions.values().stream()
191         .filter(schema -> !isInline(schema.getDefinition()))
192         .sorted(Comparator.comparing(schema -> schema.getDefinitionName(this)))
193         .forEachOrdered(schema -> {
194           schema.generateDefinition(this, definitionsObject);
195         });
196 
197     getDatatypeManager().generateDatatypes(definitionsObject);
198 
199     return definitionsObject;
200   }
201 
202   @Override
203   public JsonNodeFactory getJsonNodeFactory() {
204     return jsonNodeFactory;
205   }
206 
207   @Override
208   @NonNull
209   public IDataTypeJsonSchema getDataTypeSchemaForDefinition(@NonNull IValuedDefinition definition) {
210     IDataTypeJsonSchema retval = definitionValueToDataTypeSchemaMap.get(definition);
211     if (retval == null) {
212       AllowedValueCollection allowedValuesCollection = getContextIndependentEnumeratedValues(definition);
213       List<IAllowedValue> allowedValues = allowedValuesCollection.getValues();
214 
215       IDataTypeAdapter<?> dataTypeAdapter = definition.getJavaTypeAdapter();
216 
217       // register data type use
218       retval = getSchema(dataTypeAdapter);
219       if (!allowedValues.isEmpty()) {
220         // create restriction
221         retval = new DataTypeRestrictionDefinitionJsonSchema(definition, allowedValuesCollection);
222       }
223       definitionValueToDataTypeSchemaMap.put(definition, retval);
224     }
225     return retval;
226   }
227 
228   @SuppressWarnings("resource")
229   public void writeStartObject() throws IOException {
230     getWriter().writeStartObject();
231   }
232 
233   @SuppressWarnings("resource")
234   public void writeEndObject() throws IOException {
235     getWriter().writeEndObject();
236   }
237 
238   @SuppressWarnings("resource")
239   public void writeField(String fieldName, String value) throws IOException {
240     getWriter().writeStringField(fieldName, value);
241 
242   }
243 
244   @SuppressWarnings("resource")
245   public void writeField(String fieldName, ObjectNode obj) throws IOException {
246     JsonGenerator writer = getWriter(); // NOPMD not closable here
247 
248     writer.writeFieldName(fieldName);
249     writer.writeTree(obj);
250   }
251 
252   @SuppressWarnings("resource")
253   @Override
254   public void flushWriter() throws IOException {
255     getWriter().flush();
256   }
257 
258 }