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