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}