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.getParentQName(); 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 parentQName) { 147 super(parentQName); 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 instance, 248 @NonNull IBoundObject parentItem, 249 @NonNull ObjectWriter<T> propertyWriter) throws IOException { 250 251 try { 252 QName wrapperQName = instance.getXmlQName(); 253 NamespaceContext nsContext = writer.getNamespaceContext(); 254 String prefix = nsContext.getPrefix(wrapperQName.getNamespaceURI()); 255 if (prefix == null) { 256 prefix = ""; 257 } 258 259 writer.writeStartElement(prefix, wrapperQName.getLocalPart(), wrapperQName.getNamespaceURI()); 260 261 propertyWriter.accept(parentItem, instance); 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 = instance.getJavaTypeAdapter().asString(item); 272 QName name = instance.getXmlQName(); 273 try { 274 if (name.getNamespaceURI().isEmpty()) { 275 writer.writeAttribute(name.getLocalPart(), itemString); 276 } else { 277 writer.writeAttribute(name.getNamespaceURI(), name.getLocalPart(), itemString); 278 } 279 } catch (XMLStreamException ex) { 280 throw new IOException(ex); 281 } 282 } 283 284 @Override 285 public void writeItemField(Object item, IBoundInstanceModelFieldScalar instance) throws IOException { 286 try { 287 if (instance.isEffectiveValueWrappedInXml()) { 288 QName wrapperQName = instance.getXmlQName(); 289 writer.writeStartElement(wrapperQName.getNamespaceURI(), wrapperQName.getLocalPart()); 290 instance.getJavaTypeAdapter().writeXmlValue(item, wrapperQName, writer); 291 writer.writeEndElement(); 292 } else { 293 instance.getJavaTypeAdapter().writeXmlValue(item, getParentQName(), writer); 294 } 295 } catch (XMLStreamException ex) { 296 throw new IOException(ex); 297 } 298 } 299 300 @Override 301 public void writeItemField(IBoundObject item, IBoundInstanceModelFieldComplex instance) throws IOException { 302 ItemWriter itemWriter = new ItemWriter(instance.getXmlQName()); 303 writeModelObject( 304 instance, 305 item, 306 ((ObjectWriter<IBoundInstanceModelFieldComplex>) this::writeFlags) 307 .andThen(itemWriter::writeFieldValue)); 308 } 309 310 @Override 311 public void writeItemField(IBoundObject item, IBoundInstanceModelGroupedField instance) throws IOException { 312 ItemWriter itemWriter = new ItemWriter(instance.getXmlQName()); 313 writeGroupedModelObject( 314 instance, 315 item, 316 ((ObjectWriter<IBoundInstanceModelGroupedField>) this::writeFlags) 317 .andThen(itemWriter::writeFieldValue)); 318 } 319 320 @Override 321 public void writeItemField(IBoundObject item, IBoundDefinitionModelFieldComplex definition) throws IOException { 322 ItemWriter itemWriter = new ItemWriter(definition.getXmlQName()); 323 writeDefinitionObject( 324 definition, 325 item, 326 ((ObjectWriter<IBoundDefinitionModelFieldComplex>) this::writeFlags) 327 .andThen(itemWriter::writeFieldValue)); 328 } 329 330 @Override 331 public void writeItemFieldValue(Object parentItem, IBoundFieldValue fieldValue) throws IOException { 332 Object item = fieldValue.getValue(parentItem); 333 if (item != null) { 334 try { 335 fieldValue.getJavaTypeAdapter().writeXmlValue(item, getParentQName(), writer); 336 } catch (XMLStreamException ex) { 337 throw new IOException(ex); 338 } 339 } 340 } 341 342 @Override 343 public void writeItemAssembly(IBoundObject item, IBoundInstanceModelAssembly instance) throws IOException { 344 ItemWriter itemWriter = new ItemWriter(instance.getXmlQName()); 345 writeModelObject( 346 instance, 347 item, 348 ((ObjectWriter<IBoundInstanceModelAssembly>) this::writeFlags) 349 .andThen(itemWriter::writeAssemblyModel)); 350 } 351 352 @Override 353 public void writeItemAssembly(IBoundObject item, IBoundInstanceModelGroupedAssembly instance) throws IOException { 354 ItemWriter itemWriter = new ItemWriter(instance.getXmlQName()); 355 writeGroupedModelObject( 356 instance, 357 item, 358 ((ObjectWriter<IBoundInstanceModelGroupedAssembly>) this::writeFlags) 359 .andThen(itemWriter::writeAssemblyModel)); 360 } 361 362 @Override 363 public void writeItemAssembly(IBoundObject item, IBoundDefinitionModelAssembly definition) throws IOException { 364 ItemWriter itemWriter = new ItemWriter(definition.getXmlQName()); 365 writeDefinitionObject( 366 definition, 367 item, 368 ((ObjectWriter<IBoundDefinitionModelAssembly>) this::writeFlags) 369 .andThen(itemWriter::writeAssemblyModel)); 370 } 371 372 @Override 373 public void writeChoiceGroupItem(IBoundObject item, IBoundInstanceModelChoiceGroup instance) throws IOException { 374 IBoundInstanceModelGroupedNamed actualInstance = instance.getItemInstance(item); 375 assert actualInstance != null; 376 actualInstance.writeItem(item, this); 377 } 378 } 379 380 private abstract static class AbstractItemWriter implements IItemWriteHandler { 381 @NonNull 382 private final QName parentQName; 383 384 protected AbstractItemWriter(@NonNull QName parentQName) { 385 this.parentQName = parentQName; 386 } 387 388 /** 389 * Get the qualified name of the item's parent. 390 * 391 * @return the qualified name 392 */ 393 @NonNull 394 protected QName getParentQName() { 395 return parentQName; 396 } 397 } 398}