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.schemagen.xml; // NOPMD 028 029import com.ctc.wstx.stax.WstxOutputFactory; 030 031import gov.nist.secauto.metaschema.core.configuration.IConfiguration; 032import gov.nist.secauto.metaschema.core.datatype.markup.MarkupMultiline; 033import gov.nist.secauto.metaschema.core.model.IAssemblyDefinition; 034import gov.nist.secauto.metaschema.core.model.IModule; 035import gov.nist.secauto.metaschema.core.util.AutoCloser; 036import gov.nist.secauto.metaschema.core.util.ObjectUtils; 037import gov.nist.secauto.metaschema.schemagen.AbstractSchemaGenerator; 038import gov.nist.secauto.metaschema.schemagen.SchemaGenerationException; 039import gov.nist.secauto.metaschema.schemagen.SchemaGenerationFeature; 040import gov.nist.secauto.metaschema.schemagen.xml.datatype.XmlDatatypeManager; 041import gov.nist.secauto.metaschema.schemagen.xml.impl.XmlGenerationState; 042import gov.nist.secauto.metaschema.schemagen.xml.schematype.IXmlType; 043 044import net.sf.saxon.s9api.Processor; 045import net.sf.saxon.s9api.SaxonApiException; 046import net.sf.saxon.s9api.Serializer; 047import net.sf.saxon.s9api.Xslt30Transformer; 048import net.sf.saxon.s9api.XsltCompiler; 049import net.sf.saxon.s9api.XsltExecutable; 050 051import org.codehaus.stax2.XMLOutputFactory2; 052import org.codehaus.stax2.XMLStreamWriter2; 053 054import java.io.IOException; 055import java.io.InputStream; 056import java.io.StringReader; 057import java.io.StringWriter; 058import java.io.Writer; 059import java.util.HashMap; 060import java.util.List; 061import java.util.Map; 062 063import javax.xml.namespace.QName; 064import javax.xml.stream.XMLOutputFactory; 065import javax.xml.stream.XMLStreamException; 066import javax.xml.transform.stream.StreamSource; 067 068import edu.umd.cs.findbugs.annotations.NonNull; 069import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 070 071public class XmlSchemaGenerator 072 extends AbstractSchemaGenerator< 073 AutoCloser<XMLStreamWriter2, SchemaGenerationException>, 074 XmlDatatypeManager, 075 XmlGenerationState> { 076 // private static final Logger LOGGER = 077 // LogManager.getLogger(XmlSchemaGenerator.class); 078 079 @NonNull 080 public static final String PREFIX_XML_SCHEMA = "xs"; 081 @NonNull 082 public static final String NS_XML_SCHEMA = "http://www.w3.org/2001/XMLSchema"; 083 @NonNull 084 private static final String PREFIX_XML_SCHEMA_VERSIONING = "vs"; 085 @NonNull 086 private static final String NS_XML_SCHEMA_VERSIONING = "http://www.w3.org/2007/XMLSchema-versioning"; 087 @NonNull 088 public static final String NS_XHTML = "http://www.w3.org/1999/xhtml"; 089 090 @NonNull 091 private final XMLOutputFactory2 xmlOutputFactory; 092 093 @NonNull 094 private static XMLOutputFactory2 defaultXMLOutputFactory() { 095 XMLOutputFactory2 xmlOutputFactory = (XMLOutputFactory2) XMLOutputFactory.newInstance(); 096 assert xmlOutputFactory instanceof WstxOutputFactory; 097 xmlOutputFactory.configureForSpeed(); 098 xmlOutputFactory.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, true); 099 return xmlOutputFactory; 100 } 101 102 public XmlSchemaGenerator() { 103 this(defaultXMLOutputFactory()); 104 } 105 106 @SuppressFBWarnings("EI_EXPOSE_REP2") 107 public XmlSchemaGenerator(@NonNull XMLOutputFactory2 xmlOutputFactory) { 108 this.xmlOutputFactory = xmlOutputFactory; 109 } 110 111 protected XMLOutputFactory2 getXmlOutputFactory() { 112 return xmlOutputFactory; 113 } 114 115 @Override 116 protected AutoCloser<XMLStreamWriter2, SchemaGenerationException> newWriter( 117 Writer out) { 118 XMLStreamWriter2 writer; 119 try { 120 writer = ObjectUtils.notNull((XMLStreamWriter2) getXmlOutputFactory().createXMLStreamWriter(out)); 121 } catch (XMLStreamException ex) { 122 throw new SchemaGenerationException(ex); 123 } 124 return AutoCloser.autoClose(writer, t -> { 125 try { 126 t.close(); 127 } catch (XMLStreamException ex) { 128 throw new SchemaGenerationException(ex); 129 } 130 }); 131 } 132 133 @Override 134 protected XmlGenerationState newGenerationState( 135 IModule module, 136 AutoCloser<XMLStreamWriter2, SchemaGenerationException> schemaWriter, 137 IConfiguration<SchemaGenerationFeature<?>> configuration) { 138 return new XmlGenerationState(module, schemaWriter, configuration); 139 } 140 141 @Override 142 public void generateFromModule( 143 @NonNull IModule module, 144 @NonNull Writer out, 145 @NonNull IConfiguration<SchemaGenerationFeature<?>> configuration) { 146 try (StringWriter stringWriter = new StringWriter()) { 147 super.generateFromModule(module, stringWriter, configuration); 148 stringWriter.flush(); 149 150 try (StringReader stringReader = new StringReader(stringWriter.toString())) { 151 Processor processor = new Processor(false); 152 XsltCompiler compiler = processor.newXsltCompiler(); 153 try (InputStream is = getClass().getResourceAsStream("/identity.xsl")) { 154 XsltExecutable stylesheet = compiler.compile(new StreamSource(is)); 155 Xslt30Transformer transformer = stylesheet.load30(); 156 Serializer serializer = processor.newSerializer(out); 157 StreamSource source = new StreamSource(stringReader); 158 transformer.transform(source, serializer); 159 } 160 } 161 } catch (IOException | SaxonApiException ex) { 162 throw new SchemaGenerationException(ex); 163 } 164 } 165 166 @Override 167 protected void generateSchema(XmlGenerationState state) { 168 169 try { 170 String targetNS = state.getDefaultNS(); 171 172 // analyze all definitions 173 Map<String, String> prefixToNamespaceMap = new HashMap<>(); // NOPMD concurrency not needed 174 final List<IAssemblyDefinition> rootAssemblyDefinitions = analyzeDefinitions( 175 state, 176 (entry, definition) -> { 177 assert entry != null; 178 assert definition != null; 179 IXmlType type = state.getTypeForDefinition(definition); 180 if (!entry.isInline()) { 181 QName qname = type.getQName(); 182 String namespace = qname.getNamespaceURI(); 183 if (!targetNS.equals(namespace)) { 184 // collect namespaces and prefixes for definitions with a different namespace 185 prefixToNamespaceMap.computeIfAbsent(qname.getPrefix(), x -> namespace); 186 } 187 } 188 }); 189 190 // write some root elements 191 XMLStreamWriter2 writer = state.getXMLStreamWriter(); 192 writer.writeStartDocument("UTF-8", "1.0"); 193 writer.writeStartElement(PREFIX_XML_SCHEMA, "schema", NS_XML_SCHEMA); 194 writer.writeDefaultNamespace(targetNS); 195 writer.writeNamespace(PREFIX_XML_SCHEMA_VERSIONING, NS_XML_SCHEMA_VERSIONING); 196 197 // write namespaces for all indexed definitions 198 for (Map.Entry<String, String> entry : prefixToNamespaceMap.entrySet()) { 199 state.writeNamespace(entry.getKey(), entry.getValue()); 200 } 201 202 IModule module = state.getModule(); 203 204 // write remaining root attributes 205 writer.writeAttribute("targetNamespace", targetNS); 206 writer.writeAttribute("elementFormDefault", "qualified"); 207 writer.writeAttribute(NS_XML_SCHEMA_VERSIONING, "minVersion", "1.0"); 208 writer.writeAttribute(NS_XML_SCHEMA_VERSIONING, "maxVersion", "1.1"); 209 writer.writeAttribute("version", module.getVersion()); 210 211 generateSchemaMetadata(module, state); 212 213 for (IAssemblyDefinition definition : rootAssemblyDefinitions) { 214 QName xmlQName = definition.getRootXmlQName(); 215 if (xmlQName != null 216 && (xmlQName.getNamespaceURI() == null || state.getDefaultNS().equals(xmlQName.getNamespaceURI()))) { 217 generateRootElement(definition, state); 218 } 219 } 220 221 state.generateXmlTypes(); 222 223 writer.writeEndElement(); // xs:schema 224 writer.writeEndDocument(); 225 writer.flush(); 226 } catch (XMLStreamException ex) { 227 throw new SchemaGenerationException(ex); 228 } 229 } 230 231 protected static void generateSchemaMetadata( 232 @NonNull IModule module, 233 @NonNull XmlGenerationState state) 234 throws XMLStreamException { 235 String targetNS = ObjectUtils.notNull(module.getXmlNamespace().toASCIIString()); 236 state.writeStartElement(PREFIX_XML_SCHEMA, "annotation", NS_XML_SCHEMA); 237 state.writeStartElement(PREFIX_XML_SCHEMA, "appinfo", NS_XML_SCHEMA); 238 239 state.writeStartElement(targetNS, "schema-name"); 240 241 module.getName().writeXHtml(targetNS, state.getXMLStreamWriter()); 242 243 state.writeEndElement(); 244 245 state.writeStartElement(targetNS, "schema-version"); 246 state.writeCharacters(module.getVersion()); 247 state.writeEndElement(); 248 249 state.writeStartElement(targetNS, "short-name"); 250 state.writeCharacters(module.getShortName()); 251 state.writeEndElement(); 252 253 state.writeEndElement(); 254 255 MarkupMultiline remarks = module.getRemarks(); 256 if (remarks != null) { 257 state.writeStartElement(PREFIX_XML_SCHEMA, "documentation", NS_XML_SCHEMA); 258 259 remarks.writeXHtml(targetNS, state.getXMLStreamWriter()); 260 state.writeEndElement(); 261 } 262 263 state.writeEndElement(); 264 } 265 266 private static void generateRootElement(@NonNull IAssemblyDefinition definition, @NonNull XmlGenerationState state) 267 throws XMLStreamException { 268 assert definition.isRoot(); 269 270 XMLStreamWriter2 writer = state.getXMLStreamWriter(); 271 QName xmlQName = definition.getRootXmlQName(); 272 273 writer.writeStartElement(PREFIX_XML_SCHEMA, "element", NS_XML_SCHEMA); 274 writer.writeAttribute("name", xmlQName.getLocalPart()); 275 writer.writeAttribute("type", state.getTypeForDefinition(definition).getTypeReference()); 276 277 writer.writeEndElement(); 278 } 279}