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.databind.node.ArrayNode;
9   import com.fasterxml.jackson.databind.node.JsonNodeFactory;
10  import com.fasterxml.jackson.databind.node.ObjectNode;
11  
12  import gov.nist.secauto.metaschema.core.model.IFieldDefinition;
13  import gov.nist.secauto.metaschema.core.model.IFlagInstance;
14  import gov.nist.secauto.metaschema.core.util.ObjectUtils;
15  import gov.nist.secauto.metaschema.schemagen.FlagInstanceFilter;
16  import gov.nist.secauto.metaschema.schemagen.json.IDataTypeJsonSchema;
17  import gov.nist.secauto.metaschema.schemagen.json.IDefinitionJsonSchema;
18  import gov.nist.secauto.metaschema.schemagen.json.IJsonGenerationState;
19  import gov.nist.secauto.metaschema.schemagen.json.impl.IJsonProperty.PropertyCollection;
20  
21  import java.io.IOException;
22  import java.util.Collection;
23  import java.util.Map;
24  
25  import edu.umd.cs.findbugs.annotations.NonNull;
26  import edu.umd.cs.findbugs.annotations.Nullable;
27  
28  public class FieldDefinitionJsonSchema
29      extends AbstractModelDefinitionJsonSchema<IFieldDefinition> {
30  
31    public FieldDefinitionJsonSchema(
32        @NonNull IFieldDefinition definition,
33        @Nullable String jsonKeyFlagName,
34        @Nullable String discriminatorProperty,
35        @Nullable String discriminatorValue,
36        @NonNull IJsonGenerationState state) {
37      super(definition, jsonKeyFlagName, discriminatorProperty, discriminatorValue);
38      // register the flag data type
39      state.getDataTypeSchemaForDefinition(definition);
40    }
41  
42    @SuppressWarnings("PMD.CognitiveComplexity")
43    @Override
44    protected void generateBody(
45        IJsonGenerationState state,
46        ObjectNode obj) throws IOException {
47      IFieldDefinition definition = getDefinition();
48  
49      Collection<? extends IFlagInstance> flags = definition.getFlagInstances();
50      String discriminatorProperty = getDiscriminatorProperty();
51      IFlagInstance jsonKeyFlag = definition.getJsonKey();
52      if (discriminatorProperty == null
53          && (flags.isEmpty() || jsonKeyFlag != null && flags.size() == 1)) { // NOPMD readability
54        // field is a simple data type value if there are no flags or if the only flag
55        // is a JSON key
56        IDataTypeJsonSchema schema = state.getDataTypeSchemaForDefinition(definition);
57        schema.generateSchemaOrRef(obj, state);
58      } else {
59        obj.put("type", "object");
60  
61        // determine the flag instances to generate
62        IFlagInstance jsonValueKeyFlag = definition.getJsonValueKeyFlagInstance();
63        flags = FlagInstanceFilter.filterFlags(flags, jsonKeyFlag, jsonValueKeyFlag);
64  
65        PropertyCollection properties = new PropertyCollection();
66  
67        // handle possible discriminator
68        if (discriminatorProperty != null) {
69          ObjectNode discriminatorObj = state.getJsonNodeFactory().objectNode();
70          discriminatorObj.put("const", getDiscriminatorValue());
71          properties.addProperty(discriminatorProperty, discriminatorObj);
72        }
73  
74        // generate flag properties
75        for (IFlagInstance flag : flags) {
76          assert flag != null;
77          new FlagInstanceJsonProperty(flag)
78              .generateProperty(properties, state); // NOPMD unavoidable instantiation
79        }
80  
81        // generate value property
82        if (jsonValueKeyFlag == null) {
83          generateSimpleFieldValueInstance(properties, state);
84        }
85  
86        properties.generate(obj);
87  
88        if (jsonValueKeyFlag == null) {
89          obj.put("additionalProperties", false);
90        } else {
91          ObjectNode additionalPropertiesTypeNode;
92  
93          additionalPropertiesTypeNode = ObjectUtils.notNull(JsonNodeFactory.instance.objectNode());
94          // the type of the additional properties must be the datatype of the field value
95          IDataTypeJsonSchema schema = state.getDataTypeSchemaForDefinition(definition);
96          schema.generateSchemaOrRef(additionalPropertiesTypeNode, state);
97  
98          ObjectNode additionalPropertiesNode = ObjectUtils.notNull(JsonNodeFactory.instance.objectNode());
99          ArrayNode allOf = additionalPropertiesNode.putArray("allOf");
100         allOf.add(additionalPropertiesTypeNode);
101         allOf.addObject()
102             .put("minProperties", properties.getRequired().size() + 1)
103             .put("maxProperties", properties.getProperties().size() + 1);
104 
105         obj.set("additionalProperties", additionalPropertiesNode);
106       }
107     }
108   }
109 
110   public void generateSimpleFieldValueInstance(
111       @NonNull PropertyCollection properties,
112       @NonNull IJsonGenerationState state) {
113 
114     IFieldDefinition definition = getDefinition();
115 
116     String propertyName = definition.getEffectiveJsonValueKeyName();
117 
118     ObjectNode propertyObject = ObjectUtils.notNull(JsonNodeFactory.instance.objectNode());
119     IDataTypeJsonSchema schema = state.getDataTypeSchemaForDefinition(definition);
120     schema.generateSchemaOrRef(propertyObject, state);
121 
122     properties.addProperty(propertyName, propertyObject);
123     properties.addRequired(propertyName);
124   }
125 
126   @Override
127   public void gatherDefinitions(
128       @NonNull Map<IKey, IDefinitionJsonSchema<?>> gatheredDefinitions,
129       @NonNull IJsonGenerationState state) {
130     super.gatherDefinitions(gatheredDefinitions, state);
131 
132     IFieldDefinition definition = getDefinition();
133     IDataTypeJsonSchema schema = state.getDataTypeSchemaForDefinition(definition);
134     if (schema instanceof IDefinitionJsonSchema) {
135       ((IDefinitionJsonSchema<?>) schema).gatherDefinitions(gatheredDefinitions, state);
136     }
137   }
138 
139 }