View Javadoc
1   /*
2    * Portions of this software was developed by employees of the National Institute
3    * of Standards and Technology (NIST), an agency of the Federal Government and is
4    * being made available as a public service. Pursuant to title 17 United States
5    * Code Section 105, works of NIST employees are not subject to copyright
6    * protection in the United States. This software may be subject to foreign
7    * copyright. Permission in the United States and in foreign countries, to the
8    * extent that NIST may hold copyright, to use, copy, modify, create derivative
9    * works, and distribute this software and its documentation without fee is hereby
10   * granted on a non-exclusive basis, provided that this notice and disclaimer
11   * of warranty appears in all copies.
12   *
13   * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER
14   * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY
15   * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF
16   * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM
17   * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE
18   * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE.  IN NO EVENT
19   * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT,
20   * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM,
21   * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY,
22   * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR
23   * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT
24   * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER.
25   */
26  
27  package gov.nist.secauto.metaschema.schemagen.xml; // NOPMD
28  
29  import com.ctc.wstx.stax.WstxOutputFactory;
30  
31  import gov.nist.secauto.metaschema.core.configuration.IConfiguration;
32  import gov.nist.secauto.metaschema.core.datatype.markup.MarkupMultiline;
33  import gov.nist.secauto.metaschema.core.model.IAssemblyDefinition;
34  import gov.nist.secauto.metaschema.core.model.IModule;
35  import gov.nist.secauto.metaschema.core.util.AutoCloser;
36  import gov.nist.secauto.metaschema.core.util.ObjectUtils;
37  import gov.nist.secauto.metaschema.schemagen.AbstractSchemaGenerator;
38  import gov.nist.secauto.metaschema.schemagen.SchemaGenerationException;
39  import gov.nist.secauto.metaschema.schemagen.SchemaGenerationFeature;
40  import gov.nist.secauto.metaschema.schemagen.xml.datatype.XmlDatatypeManager;
41  import gov.nist.secauto.metaschema.schemagen.xml.impl.XmlGenerationState;
42  import gov.nist.secauto.metaschema.schemagen.xml.schematype.IXmlType;
43  
44  import net.sf.saxon.s9api.Processor;
45  import net.sf.saxon.s9api.SaxonApiException;
46  import net.sf.saxon.s9api.Serializer;
47  import net.sf.saxon.s9api.Xslt30Transformer;
48  import net.sf.saxon.s9api.XsltCompiler;
49  import net.sf.saxon.s9api.XsltExecutable;
50  
51  import org.codehaus.stax2.XMLOutputFactory2;
52  import org.codehaus.stax2.XMLStreamWriter2;
53  
54  import java.io.IOException;
55  import java.io.InputStream;
56  import java.io.StringReader;
57  import java.io.StringWriter;
58  import java.io.Writer;
59  import java.util.HashMap;
60  import java.util.List;
61  import java.util.Map;
62  
63  import javax.xml.namespace.QName;
64  import javax.xml.stream.XMLOutputFactory;
65  import javax.xml.stream.XMLStreamException;
66  import javax.xml.transform.stream.StreamSource;
67  
68  import edu.umd.cs.findbugs.annotations.NonNull;
69  import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
70  
71  public class XmlSchemaGenerator
72      extends AbstractSchemaGenerator<
73          AutoCloser<XMLStreamWriter2, SchemaGenerationException>,
74          XmlDatatypeManager,
75          XmlGenerationState> {
76    // private static final Logger LOGGER =
77    // LogManager.getLogger(XmlSchemaGenerator.class);
78  
79    @NonNull
80    public static final String PREFIX_XML_SCHEMA = "xs";
81    @NonNull
82    public static final String NS_XML_SCHEMA = "http://www.w3.org/2001/XMLSchema";
83    @NonNull
84    private static final String PREFIX_XML_SCHEMA_VERSIONING = "vs";
85    @NonNull
86    private static final String NS_XML_SCHEMA_VERSIONING = "http://www.w3.org/2007/XMLSchema-versioning";
87    @NonNull
88    public static final String NS_XHTML = "http://www.w3.org/1999/xhtml";
89  
90    @NonNull
91    private final XMLOutputFactory2 xmlOutputFactory;
92  
93    @NonNull
94    private static XMLOutputFactory2 defaultXMLOutputFactory() {
95      XMLOutputFactory2 xmlOutputFactory = (XMLOutputFactory2) XMLOutputFactory.newInstance();
96      assert xmlOutputFactory instanceof WstxOutputFactory;
97      xmlOutputFactory.configureForSpeed();
98      xmlOutputFactory.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, true);
99      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 }