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.xml; 028 029import gov.nist.secauto.metaschema.databind.io.json.DefaultJsonProblemHandler; 030import gov.nist.secauto.metaschema.databind.model.IAssemblyClassBinding; 031import gov.nist.secauto.metaschema.databind.model.IBoundAssemblyInstance; 032import gov.nist.secauto.metaschema.databind.model.IBoundFieldInstance; 033import gov.nist.secauto.metaschema.databind.model.IBoundFieldValueInstance; 034import gov.nist.secauto.metaschema.databind.model.IBoundFlagInstance; 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.codehaus.stax2.XMLStreamWriter2; 042 043import java.io.IOException; 044 045import javax.xml.namespace.NamespaceContext; 046import javax.xml.namespace.QName; 047import javax.xml.stream.XMLStreamException; 048 049import edu.umd.cs.findbugs.annotations.NonNull; 050 051public class MetaschemaXmlWriter implements IXmlWritingContext { 052 @NonNull 053 private final XMLStreamWriter2 writer; 054 055 /** 056 * Construct a new Module-aware JSON writer. 057 * 058 * @param writer 059 * the XML stream writer to write with 060 * @see DefaultJsonProblemHandler 061 */ 062 public MetaschemaXmlWriter( 063 @NonNull XMLStreamWriter2 writer) { 064 this.writer = writer; 065 } 066 067 @Override 068 public XMLStreamWriter2 getWriter() { 069 return writer; 070 } 071 072 /** 073 * Writes data in a bound object to XML. 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 XMLStreamException 082 * if an error occurred while parsing into XML 083 * @throws IOException 084 * if an error occurred while writing the output 085 */ 086 public void write( 087 @NonNull IAssemblyClassBinding targetDefinition, 088 @NonNull Object targetObject) throws XMLStreamException, IOException { 089 writer.writeStartDocument("UTF-8", "1.0"); 090 091 QName rootQName = targetDefinition.getRootXmlQName(); 092 093 NamespaceContext nsContext = writer.getNamespaceContext(); 094 String prefix = nsContext.getPrefix(rootQName.getNamespaceURI()); 095 if (prefix == null) { 096 prefix = ""; 097 } 098 099 writer.writeStartElement(prefix, rootQName.getLocalPart(), rootQName.getNamespaceURI()); 100 101 writeDefinitionValue(targetDefinition, targetObject, rootQName); 102 103 writer.writeEndElement(); 104 } 105 106 @Override 107 public void writeDefinitionValue( 108 @NonNull IClassBinding targetDefinition, 109 @NonNull Object targetObject, 110 @NonNull QName parentName) throws IOException { 111 // write flags 112 for (IBoundFlagInstance flag : targetDefinition.getFlagInstances()) { 113 assert flag != null; 114 writeFlagInstance(flag, targetObject); 115 } 116 117 if (targetDefinition instanceof IAssemblyClassBinding) { 118 for (IBoundNamedModelInstance modelProperty : ((IAssemblyClassBinding) targetDefinition).getModelInstances()) { 119 assert modelProperty != null; 120 writeModelInstanceValues(modelProperty, targetObject, parentName); 121 } 122 } else if (targetDefinition instanceof IFieldClassBinding) { 123 IBoundFieldValueInstance fieldValueInstance = ((IFieldClassBinding) targetDefinition).getFieldValueInstance(); 124 125 Object value = fieldValueInstance.getValue(targetObject); 126 if (value != null) { 127 try { 128 fieldValueInstance.getJavaTypeAdapter().writeXmlValue(value, parentName, writer); 129 } catch (XMLStreamException ex) { 130 throw new IOException(ex); 131 } 132 } 133 } else { 134 throw new UnsupportedOperationException( 135 String.format("Unsupported class binding type: %s", targetDefinition.getClass().getName())); 136 } 137 } 138 139 /** 140 * Write the data described by the provided {@code targetInstance} as an XML 141 * attribute. 142 * 143 * @param targetInstance 144 * the model instance that describes the syntax of the data to write 145 * @param parentObject 146 * the Java object that data written by this method is stored in 147 * @throws IOException 148 * if an error occurred while writing the XML 149 */ 150 protected void writeFlagInstance( 151 @NonNull IBoundFlagInstance targetInstance, 152 @NonNull Object parentObject) 153 throws IOException { 154 Object objectValue = targetInstance.getValue(parentObject); 155 String value 156 = objectValue == null ? null : targetInstance.getDefinition().getJavaTypeAdapter().asString(objectValue); 157 158 if (value != null) { 159 QName name = targetInstance.getXmlQName(); 160 try { 161 if (name.getNamespaceURI().isEmpty()) { 162 writer.writeAttribute(name.getLocalPart(), value); 163 } else { 164 writer.writeAttribute(name.getNamespaceURI(), name.getLocalPart(), value); 165 } 166 } catch (XMLStreamException ex) { 167 throw new IOException(ex); 168 } 169 } 170 } 171 172 /** 173 * Write the data described by the provided {@code targetInstance} as an XML 174 * element. 175 * 176 * @param targetInstance 177 * the model instance that describes the syntax of the data to write 178 * @param parentObject 179 * the Java object that data written by this method is stored in 180 * @param parentName 181 * the qualified name of the XML data's parent element 182 * @return {@code true} id the value was written or {@code false} otherwise 183 * @throws IOException 184 * if an error occurred while writing the XML 185 */ 186 protected boolean writeModelInstanceValues( 187 @NonNull IBoundNamedModelInstance targetInstance, 188 @NonNull Object parentObject, 189 @NonNull QName parentName) 190 throws IOException { 191 Object value = targetInstance.getValue(parentObject); 192 if (value == null) { 193 return false; // NOPMD - intentional 194 } 195 196 IModelPropertyInfo propertyInfo = targetInstance.getPropertyInfo(); 197 if (targetInstance.getMinOccurs() > 0 || propertyInfo.getItemCount(value) > 0) { 198 // only write a property if the wrapper is required or if it has contents 199 QName currentStart = parentName; 200 201 try { 202 QName groupQName = targetInstance.getXmlGroupAsQName(); 203 if (groupQName != null) { 204 // write the grouping element 205 writer.writeStartElement(groupQName.getNamespaceURI(), groupQName.getLocalPart()); 206 currentStart = groupQName; 207 } 208 209 // There are one or more named values based on cardinality 210 propertyInfo.writeValues(value, currentStart, this); 211 212 if (groupQName != null) { 213 writer.writeEndElement(); 214 } 215 } catch (XMLStreamException ex) { 216 throw new IOException(ex); 217 } 218 } 219 return true; 220 } 221 222 @Override 223 public void writeInstanceValue( 224 @NonNull IBoundNamedModelInstance targetInstance, 225 @NonNull Object targetObject, 226 @NonNull QName parentName) throws IOException { 227 // figure out how to parse the item 228 IDataTypeHandler handler = targetInstance.getDataTypeHandler(); 229 230 // figure out if we need to write the wrapper or not 231 boolean writeWrapper = targetInstance instanceof IBoundAssemblyInstance 232 || ((IBoundFieldInstance) targetInstance).isInXmlWrapped() 233 || !handler.isUnwrappedValueAllowedInXml(); 234 235 try { 236 QName currentParentName; 237 if (writeWrapper) { 238 currentParentName = targetInstance.getXmlQName(); 239 writer.writeStartElement(currentParentName.getNamespaceURI(), currentParentName.getLocalPart()); 240 } else { 241 currentParentName = parentName; 242 } 243 244 // write the value 245 handler.writeItem(targetObject, currentParentName, this); 246 247 if (writeWrapper) { 248 writer.writeEndElement(); 249 } 250 } catch (XMLStreamException ex) { 251 throw new IOException(ex); 252 } 253 } 254}