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