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        flags.forEach(flag -> {
76          assert flag != null;
77          new FlagInstanceJsonProperty(flag).generateProperty(properties, state);
78        });
79  
80        // generate value property
81        if (jsonValueKeyFlag == null) {
82          generateSimpleFieldValueInstance(properties, state);
83        }
84  
85        properties.generate(obj);
86  
87        if (jsonValueKeyFlag == null) {
88          obj.put("additionalProperties", false);
89        } else {
90          ObjectNode additionalPropertiesTypeNode;
91  
92          additionalPropertiesTypeNode = ObjectUtils.notNull(JsonNodeFactory.instance.objectNode());
93          // the type of the additional properties must be the datatype of the field value
94          IDataTypeJsonSchema schema = state.getDataTypeSchemaForDefinition(definition);
95          schema.generateSchemaOrRef(additionalPropertiesTypeNode, state);
96  
97          ObjectNode additionalPropertiesNode = ObjectUtils.notNull(JsonNodeFactory.instance.objectNode());
98          ArrayNode allOf = additionalPropertiesNode.putArray("allOf");
99          allOf.add(additionalPropertiesTypeNode);
100         allOf.addObject()
101             .put("minProperties", properties.getRequired().size() + 1)
102             .put("maxProperties", properties.getProperties().size() + 1);
103 
104         obj.set("additionalProperties", additionalPropertiesNode);
105       }
106     }
107   }
108 
109   public void generateSimpleFieldValueInstance(
110       @NonNull PropertyCollection properties,
111       @NonNull IJsonGenerationState state) {
112 
113     IFieldDefinition definition = getDefinition();
114 
115     String propertyName = definition.getEffectiveJsonValueKeyName();
116 
117     ObjectNode propertyObject = ObjectUtils.notNull(JsonNodeFactory.instance.objectNode());
118     IDataTypeJsonSchema schema = state.getDataTypeSchemaForDefinition(definition);
119     schema.generateSchemaOrRef(propertyObject, state);
120 
121     properties.addProperty(propertyName, propertyObject);
122     properties.addRequired(propertyName);
123   }
124 
125   @Override
126   public void gatherDefinitions(
127       @NonNull Map<IKey, IDefinitionJsonSchema<?>> gatheredDefinitions,
128       @NonNull IJsonGenerationState state) {
129     super.gatherDefinitions(gatheredDefinitions, state);
130 
131     IFieldDefinition definition = getDefinition();
132     IDataTypeJsonSchema schema = state.getDataTypeSchemaForDefinition(definition);
133     if (schema instanceof IDefinitionJsonSchema) {
134       ((IDefinitionJsonSchema<?>) schema).gatherDefinitions(gatheredDefinitions, state);
135     }
136   }
137 
138 }