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