001/* 002 * SPDX-FileCopyrightText: none 003 * SPDX-License-Identifier: CC0-1.0 004 */ 005 006package gov.nist.secauto.metaschema.databind.io.xml; 007 008import gov.nist.secauto.metaschema.core.model.IBoundObject; 009import gov.nist.secauto.metaschema.core.qname.IEnhancedQName; 010import gov.nist.secauto.metaschema.databind.io.json.DefaultJsonProblemHandler; 011import gov.nist.secauto.metaschema.databind.model.IBoundDefinitionModel; 012import gov.nist.secauto.metaschema.databind.model.IBoundDefinitionModelAssembly; 013import gov.nist.secauto.metaschema.databind.model.IBoundDefinitionModelComplex; 014import gov.nist.secauto.metaschema.databind.model.IBoundDefinitionModelFieldComplex; 015import gov.nist.secauto.metaschema.databind.model.IBoundFieldValue; 016import gov.nist.secauto.metaschema.databind.model.IBoundInstanceFlag; 017import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModel; 018import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelAssembly; 019import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelChoiceGroup; 020import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelFieldComplex; 021import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelFieldScalar; 022import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelGroupedAssembly; 023import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelGroupedField; 024import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelGroupedNamed; 025import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelNamed; 026import gov.nist.secauto.metaschema.databind.model.info.AbstractModelInstanceWriteHandler; 027import gov.nist.secauto.metaschema.databind.model.info.IFeatureComplexItemValueHandler; 028import gov.nist.secauto.metaschema.databind.model.info.IItemWriteHandler; 029import gov.nist.secauto.metaschema.databind.model.info.IModelInstanceCollectionInfo; 030 031import org.codehaus.stax2.XMLStreamWriter2; 032 033import java.io.IOException; 034 035import javax.xml.namespace.NamespaceContext; 036import javax.xml.stream.XMLStreamException; 037 038import edu.umd.cs.findbugs.annotations.NonNull; 039 040public class MetaschemaXmlWriter implements IXmlWritingContext { 041 @NonNull 042 private final XMLStreamWriter2 writer; 043 044 /** 045 * Construct a new Module-aware JSON writer. 046 * 047 * @param writer 048 * the XML stream writer to write with 049 * @see DefaultJsonProblemHandler 050 */ 051 public MetaschemaXmlWriter( 052 @NonNull XMLStreamWriter2 writer) { 053 this.writer = writer; 054 } 055 056 @Override 057 public XMLStreamWriter2 getWriter() { 058 return writer; 059 } 060 061 // ===================================== 062 // Entry point for top-level-definitions 063 // ===================================== 064 065 @Override 066 public void write( 067 @NonNull IBoundDefinitionModelComplex definition, 068 @NonNull IBoundObject item) throws IOException { 069 070 IEnhancedQName qname = definition.getQName(); 071 072 definition.writeItem(item, new ItemWriter(qname)); 073 } 074 075 @Override 076 public void writeRoot( 077 @NonNull IBoundDefinitionModelAssembly definition, 078 @NonNull IBoundObject item) throws IOException { 079 IEnhancedQName rootEQName = definition.getRootQName(); 080 if (rootEQName == null) { 081 throw new IllegalArgumentException( 082 String.format("The assembly definition '%s' does not have a root QName.", 083 definition.getQName())); 084 } 085 086 definition.writeItem(item, new ItemWriter(rootEQName)); 087 } 088 089 // ================ 090 // Instance writers 091 // ================ 092 093 private <T> void writeModelInstance( 094 @NonNull IBoundInstanceModel<T> instance, 095 @NonNull Object parentItem, 096 @NonNull ItemWriter itemWriter) throws IOException { 097 Object value = instance.getValue(parentItem); 098 if (value == null) { 099 return; 100 } 101 102 // this if is not strictly needed, since isEmpty will return false on a null 103 // value 104 // checking null here potentially avoids the expensive operation of 105 // instantiating 106 IModelInstanceCollectionInfo<T> collectionInfo = instance.getCollectionInfo(); 107 if (!collectionInfo.isEmpty(value)) { 108 IEnhancedQName currentQName = itemWriter.getObjectQName(); 109 IEnhancedQName groupAsEQName = instance.getEffectiveXmlGroupAsQName(); 110 try { 111 if (groupAsEQName != null) { 112 // write the grouping element 113 writer.writeStartElement(groupAsEQName.getNamespace(), groupAsEQName.getLocalName()); 114 currentQName = groupAsEQName; 115 } 116 117 collectionInfo.writeItems( 118 new ModelInstanceWriteHandler<>(instance, new ItemWriter(currentQName)), 119 value); 120 121 if (groupAsEQName != null) { 122 writer.writeEndElement(); 123 } 124 } catch (XMLStreamException ex) { 125 throw new IOException(ex); 126 } 127 } 128 } 129 130 private static class ModelInstanceWriteHandler<ITEM> 131 extends AbstractModelInstanceWriteHandler<ITEM> { 132 @NonNull 133 private final ItemWriter itemWriter; 134 135 public ModelInstanceWriteHandler( 136 @NonNull IBoundInstanceModel<ITEM> instance, 137 @NonNull ItemWriter itemWriter) { 138 super(instance); 139 this.itemWriter = itemWriter; 140 } 141 142 @Override 143 public void writeItem(ITEM item) throws IOException { 144 IBoundInstanceModel<ITEM> instance = getInstance(); 145 instance.writeItem(item, itemWriter); 146 } 147 } 148 149 private class ItemWriter 150 extends AbstractItemWriter { 151 152 public ItemWriter(@NonNull IEnhancedQName qname) { 153 super(qname); 154 } 155 156 private <T extends IBoundInstanceModelNamed<IBoundObject> & IFeatureComplexItemValueHandler> void writeFlags( 157 @NonNull IBoundObject parentItem, 158 @NonNull T instance) throws IOException { 159 writeFlags(parentItem, instance.getDefinition()); 160 } 161 162 private <T extends IBoundInstanceModelGroupedNamed & IFeatureComplexItemValueHandler> void writeFlags( 163 @NonNull IBoundObject parentItem, 164 @NonNull T instance) throws IOException { 165 writeFlags(parentItem, instance.getDefinition()); 166 } 167 168 private void writeFlags( 169 @NonNull IBoundObject parentItem, 170 @NonNull IBoundDefinitionModel<?> definition) throws IOException { 171 for (IBoundInstanceFlag flag : definition.getFlagInstances()) { 172 assert flag != null; 173 174 Object value = flag.getValue(parentItem); 175 if (value != null) { 176 writeItemFlag(value, flag); 177 } 178 } 179 } 180 181 private <T extends IBoundInstanceModelAssembly & IFeatureComplexItemValueHandler> void writeAssemblyModel( 182 @NonNull IBoundObject parentItem, 183 @NonNull T instance) throws IOException { 184 writeAssemblyModel(parentItem, instance.getDefinition()); 185 } 186 187 private <T extends IBoundInstanceModelGroupedAssembly & IFeatureComplexItemValueHandler> void writeAssemblyModel( 188 @NonNull IBoundObject parentItem, 189 @NonNull T instance) throws IOException { 190 writeAssemblyModel(parentItem, instance.getDefinition()); 191 } 192 193 private void writeAssemblyModel( 194 @NonNull IBoundObject parentItem, 195 @NonNull IBoundDefinitionModelAssembly definition) throws IOException { 196 for (IBoundInstanceModel<?> modelInstance : definition.getModelInstances()) { 197 assert modelInstance != null; 198 writeModelInstance(modelInstance, parentItem, this); 199 } 200 } 201 202 private void writeFieldValue( 203 @NonNull IBoundObject parentItem, 204 @NonNull IBoundInstanceModelFieldComplex instance) throws IOException { 205 writeFieldValue(parentItem, instance.getDefinition()); 206 } 207 208 private void writeFieldValue( 209 @NonNull IBoundObject parentItem, 210 @NonNull IBoundInstanceModelGroupedField instance) throws IOException { 211 writeFieldValue(parentItem, instance.getDefinition()); 212 } 213 214 private void writeFieldValue( 215 @NonNull IBoundObject parentItem, 216 @NonNull IBoundDefinitionModelFieldComplex definition) throws IOException { 217 definition.getFieldValue().writeItem(parentItem, this); 218 } 219 220 private <T extends IFeatureComplexItemValueHandler & IBoundInstanceModelNamed<IBoundObject>> void writeModelObject( 221 @NonNull T instance, 222 @NonNull IBoundObject parentItem, 223 @NonNull ObjectWriter<T> propertyWriter) throws IOException { 224 try { 225 IEnhancedQName wrapperQName = instance.getQName(); 226 writer.writeStartElement(wrapperQName.getNamespace(), wrapperQName.getLocalName()); 227 228 propertyWriter.accept(parentItem, instance); 229 230 writer.writeEndElement(); 231 } catch (XMLStreamException ex) { 232 throw new IOException(ex); 233 } 234 } 235 236 private <T extends IFeatureComplexItemValueHandler & IBoundInstanceModelGroupedNamed> void writeGroupedModelObject( 237 @NonNull T instance, 238 @NonNull IBoundObject parentItem, 239 @NonNull ObjectWriter<T> propertyWriter) throws IOException { 240 try { 241 IEnhancedQName wrapperQName = instance.getQName(); 242 writer.writeStartElement(wrapperQName.getNamespace(), wrapperQName.getLocalName()); 243 244 propertyWriter.accept(parentItem, instance); 245 246 writer.writeEndElement(); 247 } catch (XMLStreamException ex) { 248 throw new IOException(ex); 249 } 250 } 251 252 private <T extends IFeatureComplexItemValueHandler & IBoundDefinitionModelComplex> void writeDefinitionObject( 253 @NonNull T definition, 254 @NonNull IBoundObject parentItem, 255 @NonNull ObjectWriter<T> propertyWriter) throws IOException { 256 257 try { 258 IEnhancedQName qname = getObjectQName(); 259 NamespaceContext nsContext = writer.getNamespaceContext(); 260 String prefix = nsContext.getPrefix(qname.getNamespace()); 261 if (prefix == null) { 262 prefix = ""; 263 } 264 265 writer.writeStartElement(prefix, qname.getLocalName(), qname.getNamespace()); 266 267 propertyWriter.accept(parentItem, definition); 268 269 writer.writeEndElement(); 270 } catch (XMLStreamException ex) { 271 throw new IOException(ex); 272 } 273 } 274 275 @Override 276 public void writeItemFlag(Object item, IBoundInstanceFlag instance) throws IOException { 277 String itemString; 278 try { 279 itemString = instance.getJavaTypeAdapter().asString(item); 280 } catch (IllegalArgumentException ex) { 281 throw new IOException(ex); 282 } 283 IEnhancedQName name = instance.getQName(); 284 try { 285 if (name.getNamespace().isEmpty()) { 286 writer.writeAttribute(name.getLocalName(), itemString); 287 } else { 288 writer.writeAttribute(name.getNamespace(), name.getLocalName(), itemString); 289 } 290 } catch (XMLStreamException ex) { 291 throw new IOException(ex); 292 } 293 } 294 295 @Override 296 public void writeItemField(Object item, IBoundInstanceModelFieldScalar instance) throws IOException { 297 try { 298 if (instance.isEffectiveValueWrappedInXml()) { 299 IEnhancedQName wrapperQName = instance.getQName(); 300 writer.writeStartElement(wrapperQName.getNamespace(), wrapperQName.getLocalName()); 301 instance.getJavaTypeAdapter().writeXmlValue(item, wrapperQName, writer); 302 writer.writeEndElement(); 303 } else { 304 instance.getJavaTypeAdapter().writeXmlValue(item, getObjectQName(), writer); 305 } 306 } catch (XMLStreamException ex) { 307 throw new IOException(ex); 308 } 309 } 310 311 @Override 312 public void writeItemField(IBoundObject item, IBoundInstanceModelFieldComplex instance) throws IOException { 313 ItemWriter itemWriter = new ItemWriter(instance.getQName()); 314 writeModelObject( 315 instance, 316 item, 317 ((ObjectWriter<IBoundInstanceModelFieldComplex>) this::writeFlags) 318 .andThen(itemWriter::writeFieldValue)); 319 } 320 321 @Override 322 public void writeItemField(IBoundObject item, IBoundInstanceModelGroupedField instance) throws IOException { 323 ItemWriter itemWriter = new ItemWriter(instance.getQName()); 324 writeGroupedModelObject( 325 instance, 326 item, 327 ((ObjectWriter<IBoundInstanceModelGroupedField>) this::writeFlags) 328 .andThen(itemWriter::writeFieldValue)); 329 } 330 331 @Override 332 public void writeItemField(IBoundObject item, IBoundDefinitionModelFieldComplex definition) throws IOException { 333 ItemWriter itemWriter = new ItemWriter(definition.getQName()); 334 writeDefinitionObject( 335 definition, 336 item, 337 ((ObjectWriter<IBoundDefinitionModelFieldComplex>) this::writeFlags) 338 .andThen(itemWriter::writeFieldValue)); 339 } 340 341 @Override 342 public void writeItemFieldValue(Object parentItem, IBoundFieldValue fieldValue) throws IOException { 343 Object item = fieldValue.getValue(parentItem); 344 if (item != null) { 345 fieldValue.getJavaTypeAdapter().writeXmlValue(item, getObjectQName(), writer); 346 } 347 } 348 349 @Override 350 public void writeItemAssembly(IBoundObject item, IBoundInstanceModelAssembly instance) throws IOException { 351 ItemWriter itemWriter = new ItemWriter(instance.getQName()); 352 writeModelObject( 353 instance, 354 item, 355 ((ObjectWriter<IBoundInstanceModelAssembly>) this::writeFlags) 356 .andThen(itemWriter::writeAssemblyModel)); 357 } 358 359 @Override 360 public void writeItemAssembly(IBoundObject item, IBoundInstanceModelGroupedAssembly instance) throws IOException { 361 ItemWriter itemWriter = new ItemWriter(instance.getQName()); 362 writeGroupedModelObject( 363 instance, 364 item, 365 ((ObjectWriter<IBoundInstanceModelGroupedAssembly>) this::writeFlags) 366 .andThen(itemWriter::writeAssemblyModel)); 367 } 368 369 @Override 370 public void writeItemAssembly(IBoundObject item, IBoundDefinitionModelAssembly definition) throws IOException { 371 // this is a special case where we are writing a top-level, potentially root, 372 // element. Need to take the object qname passed in 373 writeDefinitionObject( 374 definition, 375 item, 376 ((ObjectWriter<IBoundDefinitionModelAssembly>) this::writeFlags) 377 .andThen(this::writeAssemblyModel)); 378 } 379 380 @Override 381 public void writeChoiceGroupItem(IBoundObject item, IBoundInstanceModelChoiceGroup instance) throws IOException { 382 IBoundInstanceModelGroupedNamed actualInstance = instance.getItemInstance(item); 383 assert actualInstance != null; 384 actualInstance.writeItem(item, this); 385 } 386 } 387 388 private abstract static class AbstractItemWriter implements IItemWriteHandler { 389 @NonNull 390 private final IEnhancedQName objectQName; 391 392 protected AbstractItemWriter(@NonNull IEnhancedQName qname) { 393 this.objectQName = qname; 394 } 395 396 /** 397 * Get the qualified name of the item's parent. 398 * 399 * @return the qualified name 400 */ 401 @NonNull 402 protected IEnhancedQName getObjectQName() { 403 return objectQName; 404 } 405 } 406}