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