001/* 002 * Portions of this software was developed by employees of the National Institute 003 * of Standards and Technology (NIST), an agency of the Federal Government and is 004 * being made available as a public service. Pursuant to title 17 United States 005 * Code Section 105, works of NIST employees are not subject to copyright 006 * protection in the United States. This software may be subject to foreign 007 * copyright. Permission in the United States and in foreign countries, to the 008 * extent that NIST may hold copyright, to use, copy, modify, create derivative 009 * works, and distribute this software and its documentation without fee is hereby 010 * granted on a non-exclusive basis, provided that this notice and disclaimer 011 * of warranty appears in all copies. 012 * 013 * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER 014 * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY 015 * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF 016 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM 017 * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE 018 * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE. IN NO EVENT 019 * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT, 020 * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM, 021 * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY, 022 * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR 023 * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT 024 * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER. 025 */ 026 027package gov.nist.secauto.metaschema.databind.io.json; 028 029import com.fasterxml.jackson.core.JsonGenerator; 030 031import gov.nist.secauto.metaschema.databind.model.IAssemblyClassBinding; 032import gov.nist.secauto.metaschema.databind.model.IBoundFieldValueInstance; 033import gov.nist.secauto.metaschema.databind.model.IBoundFlagInstance; 034import gov.nist.secauto.metaschema.databind.model.IBoundNamedInstance; 035import gov.nist.secauto.metaschema.databind.model.IBoundNamedModelInstance; 036import gov.nist.secauto.metaschema.databind.model.IClassBinding; 037import gov.nist.secauto.metaschema.databind.model.IFieldClassBinding; 038import gov.nist.secauto.metaschema.databind.model.info.IDataTypeHandler; 039import gov.nist.secauto.metaschema.databind.model.info.IModelPropertyInfo; 040 041import org.apache.logging.log4j.LogManager; 042import org.apache.logging.log4j.Logger; 043 044import java.io.IOException; 045import java.util.Map; 046 047import edu.umd.cs.findbugs.annotations.NonNull; 048 049public class MetaschemaJsonWriter implements IJsonWritingContext { 050 private static final Logger LOGGER = LogManager.getLogger(MetaschemaJsonWriter.class); 051 052 @NonNull 053 private final JsonGenerator writer; 054 055 /** 056 * Construct a new Module-aware JSON writer. 057 * 058 * @param generator 059 * the JSON generator to write with 060 * @see DefaultJsonProblemHandler 061 */ 062 public MetaschemaJsonWriter( 063 @NonNull JsonGenerator generator) { 064 this.writer = generator; 065 } 066 067 @Override 068 public JsonGenerator getWriter() { 069 return writer; 070 } 071 072 /** 073 * Writes data in a bound object to JSON. This assembly must be a root assembly 074 * for which a call to {@link IAssemblyClassBinding#isRoot()} will return 075 * {@code true}. 076 * 077 * @param targetDefinition 078 * the definition describing the root element data to write 079 * @param targetObject 080 * the bound object 081 * @throws IOException 082 * if an error occurred while reading the JSON 083 */ 084 public void write( 085 @NonNull IAssemblyClassBinding targetDefinition, 086 @NonNull Object targetObject) throws IOException { 087 if (!targetDefinition.isRoot()) { 088 throw new UnsupportedOperationException( 089 String.format("The assembly '%s' is not a root assembly.", targetDefinition.getBoundClass().getName())); 090 } 091 // first write the initial START_OBJECT 092 writer.writeStartObject(); 093 094 writer.writeFieldName(targetDefinition.getRootJsonName()); 095 096 // Make a temporary data type handler for the root class 097 IDataTypeHandler dataTypeHandler = IDataTypeHandler.newDataTypeHandler(targetDefinition); 098 dataTypeHandler.writeItem(targetObject, this); 099 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}