View Javadoc
1   /*
2    * Portions of this software was developed by employees of the National Institute
3    * of Standards and Technology (NIST), an agency of the Federal Government and is
4    * being made available as a public service. Pursuant to title 17 United States
5    * Code Section 105, works of NIST employees are not subject to copyright
6    * protection in the United States. This software may be subject to foreign
7    * copyright. Permission in the United States and in foreign countries, to the
8    * extent that NIST may hold copyright, to use, copy, modify, create derivative
9    * works, and distribute this software and its documentation without fee is hereby
10   * granted on a non-exclusive basis, provided that this notice and disclaimer
11   * of warranty appears in all copies.
12   *
13   * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER
14   * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY
15   * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF
16   * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM
17   * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE
18   * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE.  IN NO EVENT
19   * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT,
20   * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM,
21   * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY,
22   * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR
23   * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT
24   * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER.
25   */
26  
27  package gov.nist.secauto.metaschema.databind.io.json;
28  
29  import com.fasterxml.jackson.core.JsonGenerator;
30  
31  import gov.nist.secauto.metaschema.databind.model.IAssemblyClassBinding;
32  import gov.nist.secauto.metaschema.databind.model.IBoundFieldValueInstance;
33  import gov.nist.secauto.metaschema.databind.model.IBoundFlagInstance;
34  import gov.nist.secauto.metaschema.databind.model.IBoundNamedInstance;
35  import gov.nist.secauto.metaschema.databind.model.IBoundNamedModelInstance;
36  import gov.nist.secauto.metaschema.databind.model.IClassBinding;
37  import gov.nist.secauto.metaschema.databind.model.IFieldClassBinding;
38  import gov.nist.secauto.metaschema.databind.model.info.IDataTypeHandler;
39  import gov.nist.secauto.metaschema.databind.model.info.IModelPropertyInfo;
40  
41  import org.apache.logging.log4j.LogManager;
42  import org.apache.logging.log4j.Logger;
43  
44  import java.io.IOException;
45  import java.util.Map;
46  
47  import edu.umd.cs.findbugs.annotations.NonNull;
48  
49  public class MetaschemaJsonWriter implements IJsonWritingContext {
50    private static final Logger LOGGER = LogManager.getLogger(MetaschemaJsonWriter.class);
51  
52    @NonNull
53    private final JsonGenerator writer;
54  
55    /**
56     * Construct a new Module-aware JSON writer.
57     *
58     * @param generator
59     *          the JSON generator to write with
60     * @see DefaultJsonProblemHandler
61     */
62    public MetaschemaJsonWriter(
63        @NonNull JsonGenerator generator) {
64      this.writer = generator;
65    }
66  
67    @Override
68    public JsonGenerator getWriter() {
69      return writer;
70    }
71  
72    /**
73     * Writes data in a bound object to JSON. This assembly must be a root assembly
74     * for which a call to {@link IAssemblyClassBinding#isRoot()} will return
75     * {@code true}.
76     *
77     * @param targetDefinition
78     *          the definition describing the root element data to write
79     * @param targetObject
80     *          the bound object
81     * @throws IOException
82     *           if an error occurred while reading the JSON
83     */
84    public void write(
85        @NonNull IAssemblyClassBinding targetDefinition,
86        @NonNull Object targetObject) throws IOException {
87      if (!targetDefinition.isRoot()) {
88        throw new UnsupportedOperationException(
89            String.format("The assembly '%s' is not a root assembly.", targetDefinition.getBoundClass().getName()));
90      }
91      // first write the initial START_OBJECT
92      writer.writeStartObject();
93  
94      writer.writeFieldName(targetDefinition.getRootJsonName());
95  
96      // Make a temporary data type handler for the root class
97      IDataTypeHandler dataTypeHandler = IDataTypeHandler.newDataTypeHandler(targetDefinition);
98      dataTypeHandler.writeItem(targetObject, this);
99  
100     // end of root object
101     writer.writeEndObject();
102   }
103 
104   @Override
105   public void writeDefinitionValue(
106       IClassBinding targetDefinition,
107       Object targetObject,
108       Map<String, ? extends IBoundNamedInstance> instances) throws IOException {
109     for (IBoundNamedInstance property : instances.values()) {
110       assert property != null;
111       writeInstance(property, targetObject);
112     }
113 
114     if (targetDefinition instanceof IFieldClassBinding) {
115       IFieldClassBinding fieldDefinition = (IFieldClassBinding) targetDefinition;
116       IBoundFieldValueInstance fieldValueInstance = fieldDefinition.getFieldValueInstance();
117       Object fieldValue = fieldValueInstance.getValue(targetObject);
118       if (fieldValue != null) {
119         String valueKeyName;
120         IBoundFlagInstance jsonValueKey = fieldDefinition.getJsonValueKeyFlagInstance();
121         if (jsonValueKey != null) {
122           valueKeyName = jsonValueKey.getValueAsString(jsonValueKey.getValue(targetObject));
123         } else {
124           valueKeyName = fieldValueInstance.getJsonValueKeyName();
125         }
126         writer.writeFieldName(valueKeyName);
127         fieldValueInstance.getJavaTypeAdapter().writeJsonValue(fieldValue, writer);
128       }
129     }
130   }
131 
132   /**
133    * Write the instance data contained in the {@code parentObject} based on the
134    * structure described by the {@code targetInstance}.
135    *
136    * @param targetInstance
137    *          the instance to write data for
138    * @param parentObject
139    *          the Java object containing the instance data to write
140    * @throws IOException
141    *           if an error occurred while writing the data
142    */
143   protected void writeInstance(
144       @NonNull IBoundNamedInstance targetInstance,
145       @NonNull Object parentObject)
146       throws IOException {
147     if (targetInstance instanceof IBoundFlagInstance) {
148       writeFlagInstanceValue((IBoundFlagInstance) targetInstance, parentObject);
149     } else if (targetInstance instanceof IBoundNamedModelInstance) {
150       writeModelInstanceValues((IBoundNamedModelInstance) targetInstance, parentObject);
151     } else if (targetInstance instanceof IBoundFieldValueInstance) {
152       writeFieldValueInstanceValue((IBoundFieldValueInstance) targetInstance, parentObject);
153     } else {
154       throw new UnsupportedOperationException(
155           String.format("Unsupported class binding type: %s", targetInstance.getClass().getName()));
156     }
157   }
158 
159   /**
160    * Write the instance data contained in the {@code parentObject} based on the
161    * structure described by the {@code targetInstance}.
162    *
163    * @param targetInstance
164    *          the instance to write data for
165    * @param parentObject
166    *          the Java object containing the instance data to write
167    * @throws IOException
168    *           if an error occurred while writing the data
169    */
170   protected void writeFlagInstanceValue(
171       @NonNull IBoundFlagInstance targetInstance,
172       @NonNull Object parentObject) throws IOException {
173     Object value = targetInstance.getValue(parentObject);
174     if (value != null) {
175       // write the field name
176       writer.writeFieldName(targetInstance.getJsonName());
177 
178       // write the value
179       targetInstance.getDefinition().getJavaTypeAdapter().writeJsonValue(value, writer);
180     }
181   }
182 
183   /**
184    * Write the instance data contained in the {@code parentObject} based on the
185    * structure described by the {@code targetInstance}.
186    *
187    * @param targetInstance
188    *          the instance to write data for
189    * @param parentObject
190    *          the Java object containing the instance data to write
191    * @throws IOException
192    *           if an error occurred while writing the data
193    */
194   protected void writeModelInstanceValues(
195       @NonNull IBoundNamedModelInstance targetInstance,
196       @NonNull Object parentObject) throws IOException {
197     IModelPropertyInfo propertyInfo = targetInstance.getPropertyInfo();
198     if (propertyInfo.isValueSet(parentObject)) {
199       // write the field name
200       writer.writeFieldName(targetInstance.getJsonName());
201 
202       // dispatch to the property info implementation to address cardinality
203       propertyInfo.writeValues(parentObject, this);
204     }
205   }
206 
207   /**
208    * Write the instance data contained in the {@code parentObject} based on the
209    * structure described by the {@code targetInstance}.
210    *
211    * @param targetInstance
212    *          the instance to write data for
213    * @param parentObject
214    *          the Java object containing the instance data to write
215    * @throws IOException
216    *           if an error occurred while writing the data
217    */
218   protected void writeFieldValueInstanceValue(
219       @NonNull IBoundFieldValueInstance targetInstance,
220       @NonNull Object parentObject) throws IOException {
221     Object value = targetInstance.getValue(parentObject);
222     if (value != null) {
223       // There are two modes:
224       // 1) use of a JSON value key, or
225       // 2) a simple value named "value"
226       IBoundFlagInstance jsonValueKey = targetInstance.getParentClassBinding().getJsonValueKeyFlagInstance();
227 
228       String valueKeyName;
229       if (jsonValueKey != null) {
230         // this is the JSON value key case
231         valueKeyName = jsonValueKey.getValue(parentObject).toString();
232       } else {
233         valueKeyName = targetInstance.getJsonValueKeyName();
234       }
235       writer.writeFieldName(valueKeyName);
236       LOGGER.info("FIELD: {}", valueKeyName);
237       targetInstance.getJavaTypeAdapter().writeJsonValue(value, writer);
238     }
239   }
240 }