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.datatype.XmlDatatypeManager;
020import gov.nist.secauto.metaschema.schemagen.xml.impl.XmlGenerationState;
021import gov.nist.secauto.metaschema.schemagen.xml.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        QName xmlQName = definition.getRootXmlQName();
203        if (xmlQName != null
204            && (xmlQName.getNamespaceURI() == null || state.getDefaultNS().equals(xmlQName.getNamespaceURI()))) {
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    QName xmlQName = definition.getRootXmlQName();
260
261    writer.writeStartElement(PREFIX_XML_SCHEMA, "element", NS_XML_SCHEMA);
262    writer.writeAttribute("name", xmlQName.getLocalPart());
263    writer.writeAttribute("type", state.getXmlForDefinition(definition).getTypeReference());
264
265    writer.writeEndElement();
266  }
267}