View Javadoc
1   /**
2    * Copyright (C) 2014-2017 Philip Helger (www.helger.com)
3    * philip[at]helger[dot]com
4    *
5    * Licensed under the Apache License, Version 2.0 (the "License");
6    * you may not use this file except in compliance with the License.
7    * You may obtain a copy of the License at
8    *
9    *         http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package com.helger.schematron.xslt;
18  
19  import java.util.Locale;
20  import java.util.Map;
21  
22  import javax.annotation.Nonnull;
23  import javax.annotation.Nullable;
24  import javax.annotation.concurrent.NotThreadSafe;
25  import javax.xml.transform.ErrorListener;
26  import javax.xml.transform.Transformer;
27  import javax.xml.transform.TransformerException;
28  import javax.xml.transform.URIResolver;
29  import javax.xml.transform.dom.DOMResult;
30  import javax.xml.transform.dom.DOMSource;
31  
32  import org.oclc.purl.dsdl.svrl.SchematronOutputType;
33  import org.slf4j.Logger;
34  import org.slf4j.LoggerFactory;
35  import org.w3c.dom.Document;
36  import org.w3c.dom.Node;
37  import org.xml.sax.EntityResolver;
38  
39  import com.helger.commons.ValueEnforcer;
40  import com.helger.commons.annotation.ReturnsMutableCopy;
41  import com.helger.commons.collection.ext.CommonsLinkedHashMap;
42  import com.helger.commons.collection.ext.ICommonsOrderedMap;
43  import com.helger.commons.io.resource.IReadableResource;
44  import com.helger.commons.state.EValidity;
45  import com.helger.commons.string.ToStringGenerator;
46  import com.helger.commons.traits.IGenericImplTrait;
47  import com.helger.schematron.AbstractSchematronResource;
48  import com.helger.schematron.SchematronDebug;
49  import com.helger.schematron.svrl.SVRLReader;
50  import com.helger.schematron.xslt.validator.ISchematronXSLTValidator;
51  import com.helger.schematron.xslt.validator.SchematronXSLTValidatorDefault;
52  import com.helger.xml.XMLFactory;
53  import com.helger.xml.serialize.write.XMLWriter;
54  import com.helger.xml.transform.DefaultTransformURIResolver;
55  import com.helger.xml.transform.LoggingTransformErrorListener;
56  
57  import net.sf.saxon.jaxp.TransformerImpl;
58  import net.sf.saxon.lib.StandardLogger;
59  import net.sf.saxon.s9api.XsltTransformer;
60  import net.sf.saxon.trace.TraceEventMulticaster;
61  import net.sf.saxon.trace.XSLTTraceListener;
62  
63  /**
64   * Abstract implementation of a Schematron resource that is based on XSLT
65   * transformations.
66   *
67   * @author Philip Helger
68   * @param <IMPLTYPE>
69   *        Implementation type
70   */
71  @NotThreadSafe
72  public abstract class AbstractSchematronXSLTBasedResource <IMPLTYPE extends AbstractSchematronXSLTBasedResource <IMPLTYPE>>
73                                                            extends
74                                                            AbstractSchematronResource implements
75                                                            IGenericImplTrait <IMPLTYPE>
76  {
77    private static final Logger s_aLogger = LoggerFactory.getLogger (AbstractSchematronXSLTBasedResource.class);
78  
79    protected ErrorListener m_aCustomErrorListener;
80    protected URIResolver m_aCustomURIResolver = new DefaultTransformURIResolver ();
81    protected ICommonsOrderedMap <String, ?> m_aCustomParameters;
82    private ISchematronXSLTValidator m_aXSLTValidator = new SchematronXSLTValidatorDefault ();
83  
84    public AbstractSchematronXSLTBasedResource (@Nonnull final IReadableResource aSCHResource)
85    {
86      super (aSCHResource);
87    }
88  
89    @Nullable
90    public ErrorListener getErrorListener ()
91    {
92      return m_aCustomErrorListener;
93    }
94  
95    @Nonnull
96    public IMPLTYPE setErrorListener (@Nullable final ErrorListener aCustomErrorListener)
97    {
98      m_aCustomErrorListener = aCustomErrorListener;
99      return thisAsT ();
100   }
101 
102   /**
103    * @return The {@link URIResolver} to be used for reading the Schematron. May
104    *         be <code>null</code>.
105    */
106   @Nullable
107   public URIResolver getURIResolver ()
108   {
109     return m_aCustomURIResolver;
110   }
111 
112   /**
113    * Set the {@link URIResolver} to be used for reading Schematron.
114    *
115    * @param aCustomURIResolver
116    *        The {@link URIResolver} to use. May be <code>null</code>,
117    * @return this for chaining
118    */
119   @Nonnull
120   public IMPLTYPE setURIResolver (@Nullable final URIResolver aCustomURIResolver)
121   {
122     m_aCustomURIResolver = aCustomURIResolver;
123     return thisAsT ();
124   }
125 
126   public boolean hasParameters ()
127   {
128     return m_aCustomParameters != null && m_aCustomParameters.isNotEmpty ();
129   }
130 
131   @Nonnull
132   @ReturnsMutableCopy
133   public ICommonsOrderedMap <String, ?> getParameters ()
134   {
135     return new CommonsLinkedHashMap <> (m_aCustomParameters);
136   }
137 
138   @Nonnull
139   public IMPLTYPE setParameters (@Nullable final Map <String, ?> aCustomParameters)
140   {
141     m_aCustomParameters = new CommonsLinkedHashMap <> (aCustomParameters);
142     return thisAsT ();
143   }
144 
145   /**
146    * Set the XML entity resolver to be used when reading the XML to be
147    * validated.
148    *
149    * @param aEntityResolver
150    *        The entity resolver to set. May be <code>null</code>.
151    * @return this
152    * @since 4.2.3
153    */
154   @Nonnull
155   public IMPLTYPE setEntityResolver (@Nullable final EntityResolver aEntityResolver)
156   {
157     internalSetEntityResolver (aEntityResolver);
158     return thisAsT ();
159   }
160 
161   /**
162    * @return The XSLT provider passed in the constructor. May be
163    *         <code>null</code>.
164    */
165   @Nullable
166   public abstract ISchematronXSLTBasedProvider getXSLTProvider ();
167 
168   /**
169    * @return The XSLT validator to be used. Never <code>null</code>.
170    */
171   @Nonnull
172   public ISchematronXSLTValidator getXSLTValidator ()
173   {
174     return m_aXSLTValidator;
175   }
176 
177   @Nonnull
178   public IMPLTYPE setXSLTValidator (@Nonnull final ISchematronXSLTValidator aXSLTValidator)
179   {
180     ValueEnforcer.notNull (aXSLTValidator, "XSLTValidator");
181     m_aXSLTValidator = aXSLTValidator;
182     return thisAsT ();
183   }
184 
185   public final boolean isValidSchematron ()
186   {
187     final ISchematronXSLTBasedProvider aXSLTProvider = getXSLTProvider ();
188     return aXSLTProvider != null && aXSLTProvider.isValidSchematron ();
189   }
190 
191   @Nonnull
192   public EValidity getSchematronValidity (@Nonnull final Node aXMLNode) throws Exception
193   {
194     ValueEnforcer.notNull (aXMLNode, "XMLNode");
195 
196     // We don't have a short circuit here - apply the full validation
197     final SchematronOutputType aSO = applySchematronValidationToSVRL (aXMLNode);
198     if (aSO == null)
199       return EValidity.INVALID;
200 
201     // And now filter all elements that make the passed source invalid
202     return m_aXSLTValidator.getSchematronValidity (aSO);
203   }
204 
205   @Nullable
206   public final Document applySchematronValidation (@Nonnull final Node aXMLNode) throws TransformerException
207   {
208     ValueEnforcer.notNull (aXMLNode, "XMLNode");
209 
210     final ISchematronXSLTBasedProvider aXSLTProvider = getXSLTProvider ();
211     if (aXSLTProvider == null || !aXSLTProvider.isValidSchematron ())
212     {
213       // We cannot progress because of invalid Schematron
214       return null;
215     }
216 
217     // Debug print the created XSLT document
218     if (SchematronDebug.isShowCreatedXSLT ())
219       s_aLogger.info ("Created XSLT document: " + XMLWriter.getNodeAsString (aXSLTProvider.getXSLTDocument ()));
220 
221     // Create result document
222     final Document ret = XMLFactory.newDocument ();
223 
224     // Create the transformer object from the templates specified in the
225     // constructor
226     final Transformer aTransformer = aXSLTProvider.getXSLTTransformer ();
227 
228     // Apply customizations
229     // Ensure an error listener is present
230     if (m_aCustomErrorListener != null)
231       aTransformer.setErrorListener (m_aCustomErrorListener);
232     else
233       aTransformer.setErrorListener (new LoggingTransformErrorListener (Locale.US));
234 
235     // Set the optional URI Resolver
236     if (m_aCustomURIResolver != null)
237       aTransformer.setURIResolver (m_aCustomURIResolver);
238 
239     // Set all custom parameters
240     if (m_aCustomParameters != null)
241       for (final Map.Entry <String, ?> aEntry : m_aCustomParameters.entrySet ())
242         aTransformer.setParameter (aEntry.getKey (), aEntry.getValue ());
243 
244     if (s_aLogger.isDebugEnabled ())
245       s_aLogger.debug ("Applying Schematron XSLT on XML [start]");
246 
247     // Enable this for hardcore Saxon debugging only
248     if (false)
249       if (aTransformer.getClass ().getName ().equals ("net.sf.saxon.jaxp.TransformerImpl"))
250       {
251         final XsltTransformer aXT = ((TransformerImpl) aTransformer).getUnderlyingXsltTransformer ();
252 
253         aXT.setMessageListener ( (a, b, c) -> s_aLogger.info ("MessageListener: " + a + ", " + b + ", " + c));
254         aXT.setTraceFunctionDestination (new StandardLogger (System.err));
255         if (false)
256           aXT.getUnderlyingController ().setTraceListener (new XSLTTraceListener ());
257         if (false)
258         {
259           final XSLTTraceListener aTL = new XSLTTraceListener ();
260           aTL.setOutputDestination (new StandardLogger (System.err));
261           aXT.getUnderlyingController ().setTraceListener (TraceEventMulticaster.add (aTL, null));
262         }
263 
264         if (false)
265           System.out.println ("mode=" + aXT.getInitialMode ());
266         if (false)
267           System.out.println ("temp=" + aXT.getInitialTemplate ());
268         if (false)
269           System.out.println (aTransformer.getOutputProperties ());
270       }
271 
272     // Do the main transformation
273     aTransformer.transform (new DOMSource (aXMLNode), new DOMResult (ret));
274 
275     if (s_aLogger.isDebugEnabled ())
276       s_aLogger.debug ("Applying Schematron XSLT on XML [end]");
277 
278     // Debug print the created SVRL document
279     if (SchematronDebug.isShowCreatedSVRL ())
280       s_aLogger.info ("Created SVRL:\n" + XMLWriter.getNodeAsString (ret));
281 
282     return ret;
283   }
284 
285   @Nullable
286   public SchematronOutputType applySchematronValidationToSVRL (@Nonnull final Node aXMLSource) throws Exception
287   {
288     final Document aDoc = applySchematronValidation (aXMLSource);
289     if (aDoc == null)
290       return null;
291 
292     // Avoid NPE later on
293     if (aDoc.getDocumentElement () == null)
294       throw new IllegalStateException ("Internal error: created SVRL DOM Document has no document node!");
295     return SVRLReader.readXML (aDoc);
296   }
297 
298   @Override
299   public String toString ()
300   {
301     return ToStringGenerator.getDerived (super.toString ()).append ("XSLTValidator", m_aXSLTValidator).getToString ();
302   }
303 }