View Javadoc
1   /**
2    * Copyright (C) 2014-2018 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;
18  
19  import java.io.InputStream;
20  
21  import javax.annotation.Nonnull;
22  import javax.annotation.Nullable;
23  import javax.annotation.concurrent.NotThreadSafe;
24  import javax.xml.transform.Source;
25  import javax.xml.transform.stream.StreamSource;
26  
27  import org.oclc.purl.dsdl.svrl.SchematronOutputType;
28  import org.slf4j.Logger;
29  import org.slf4j.LoggerFactory;
30  import org.w3c.dom.Document;
31  import org.w3c.dom.Node;
32  import org.xml.sax.EntityResolver;
33  
34  import com.helger.commons.ValueEnforcer;
35  import com.helger.commons.annotation.ReturnsMutableCopy;
36  import com.helger.commons.io.IHasInputStream;
37  import com.helger.commons.io.resource.IReadableResource;
38  import com.helger.commons.state.EValidity;
39  import com.helger.commons.string.ToStringGenerator;
40  import com.helger.xml.EXMLParserFeature;
41  import com.helger.xml.sax.DefaultEntityResolver;
42  import com.helger.xml.serialize.read.DOMReader;
43  import com.helger.xml.serialize.read.DOMReaderSettings;
44  import com.helger.xml.transform.TransformSourceFactory;
45  
46  /**
47   * Abstract implementation of the {@link ISchematronResource} interface handling
48   * the underlying resource and wrapping one method.
49   *
50   * @author Philip Helger
51   */
52  @NotThreadSafe
53  public abstract class AbstractSchematronResource implements ISchematronResource
54  {
55    private static final Logger LOGGER = LoggerFactory.getLogger (AbstractSchematronResource.class);
56  
57    private final IReadableResource m_aResource;
58    private final String m_sResourceID;
59    private boolean m_bUseCache = true;
60    private EntityResolver m_aEntityResolver;
61  
62    /**
63     * Constructor
64     *
65     * @param aResource
66     *        The Schematron resource. May not be <code>null</code>.
67     */
68    public AbstractSchematronResource (@Nonnull final IReadableResource aResource)
69    {
70      m_aResource = ValueEnforcer.notNull (aResource, "Resource");
71      m_sResourceID = aResource.getResourceID ();
72      // Set a default entity resolver
73      m_aEntityResolver = DefaultEntityResolver.createOnDemand (aResource);
74    }
75  
76    @Nonnull
77    public final String getID ()
78    {
79      return m_sResourceID;
80    }
81  
82    @Nonnull
83    public final IReadableResource getResource ()
84    {
85      return m_aResource;
86    }
87  
88    public boolean isUseCache ()
89    {
90      return m_bUseCache;
91    }
92  
93    public void setUseCache (final boolean bUseCache)
94    {
95      m_bUseCache = bUseCache;
96    }
97  
98    @Nullable
99    public EntityResolver getEntityResolver ()
100   {
101     return m_aEntityResolver;
102   }
103 
104   /**
105    * Set the XML entity resolver to be used when reading the Schematron or the
106    * XML to be validated. This can only be set before the Schematron is bound.
107    * If it is already bound an exception is thrown to indicate the unnecessity
108    * of the call.
109    *
110    * @param aEntityResolver
111    *        The entity resolver to set. May be <code>null</code>.
112    * @since 4.2.3
113    */
114   protected final void internalSetEntityResolver (@Nullable final EntityResolver aEntityResolver)
115   {
116     m_aEntityResolver = aEntityResolver;
117   }
118 
119   /**
120    * @return The {@link DOMReaderSettings} to be used for reading the XML files
121    *         to be validated. This includes the {@link EntityResolver} to be
122    *         used.
123    * @see #getEntityResolver()
124    */
125   @Nonnull
126   @ReturnsMutableCopy
127   protected DOMReaderSettings internalCreateDOMReaderSettings ()
128   {
129     final DOMReaderSettings aDRS = new DOMReaderSettings ();
130     if (m_aEntityResolver != null)
131       aDRS.setEntityResolver (m_aEntityResolver);
132     if (false)
133     {
134       final boolean m_bLoadExternalSchemas = false;
135       aDRS.setFeatureValue (EXMLParserFeature.EXTERNAL_GENERAL_ENTITIES, m_bLoadExternalSchemas);
136       aDRS.setFeatureValue (EXMLParserFeature.EXTERNAL_PARAMETER_ENTITIES, m_bLoadExternalSchemas);
137       aDRS.setFeatureValue (EXMLParserFeature.LOAD_EXTERNAL_DTD, m_bLoadExternalSchemas);
138       aDRS.setFeatureValue (EXMLParserFeature.VALIDATION, true);
139       aDRS.setFeatureValue (EXMLParserFeature.NAMESPACES, true);
140     }
141     return aDRS;
142   }
143 
144   protected static final class NodeAndBaseURI
145   {
146     private final Document m_aDoc;
147     private final String m_sBaseURI;
148 
149     public NodeAndBaseURI (@Nonnull final Document aDoc, @Nullable final String sBaseURI)
150     {
151       m_aDoc = aDoc;
152       m_sBaseURI = sBaseURI;
153     }
154   }
155 
156   @Nullable
157   protected NodeAndBaseURI getAsNode (@Nonnull final IHasInputStream aXMLResource) throws Exception
158   {
159     final StreamSource aStreamSrc = TransformSourceFactory.create (aXMLResource);
160     InputStream aIS = null;
161     try
162     {
163       aIS = aStreamSrc.getInputStream ();
164     }
165     catch (final IllegalStateException ex)
166     {
167       // Fall through
168       // Happens e.g. for ResourceStreamSource with non-existing resources
169     }
170     if (aIS == null)
171     {
172       // Resource not found
173       LOGGER.warn ("XML resource " + aXMLResource + " does not exist!");
174       return null;
175     }
176     final Document aDoc = DOMReader.readXMLDOM (aIS, internalCreateDOMReaderSettings ());
177     if (aDoc == null)
178       throw new IllegalArgumentException ("Failed to read resource " + aXMLResource + " as XML");
179 
180     LOGGER.info ("Read XML resource " + aXMLResource);
181     return new NodeAndBaseURI (aDoc, aStreamSrc.getSystemId ());
182   }
183 
184   @Nullable
185   protected Node getAsNode (@Nonnull final Source aXMLSource) throws Exception
186   {
187     // Convert to Node
188     final Node aNode = SchematronResourceHelper.getNodeOfSource (aXMLSource, internalCreateDOMReaderSettings ());
189     if (aNode == null)
190       return null;
191     return aNode;
192   }
193 
194   @Nonnull
195   public EValidity getSchematronValidity (@Nonnull final IHasInputStream aXMLResource) throws Exception
196   {
197     if (!isValidSchematron ())
198       return EValidity.INVALID;
199 
200     final NodeAndBaseURI aXMLNode = getAsNode (aXMLResource);
201     if (aXMLNode == null)
202       return EValidity.INVALID;
203 
204     return getSchematronValidity (aXMLNode.m_aDoc, aXMLNode.m_sBaseURI);
205   }
206 
207   @Nonnull
208   public EValidity getSchematronValidity (@Nonnull final Source aXMLSource) throws Exception
209   {
210     if (!isValidSchematron ())
211       return EValidity.INVALID;
212 
213     final Node aXMLNode = getAsNode (aXMLSource);
214     if (aXMLNode == null)
215       return EValidity.INVALID;
216 
217     return getSchematronValidity (aXMLNode, aXMLSource.getSystemId ());
218   }
219 
220   @Nullable
221   public Document applySchematronValidation (@Nonnull final IHasInputStream aXMLResource) throws Exception
222   {
223     if (!isValidSchematron ())
224       return null;
225 
226     final NodeAndBaseURI aXMLNode = getAsNode (aXMLResource);
227     if (aXMLNode == null)
228       return null;
229 
230     return applySchematronValidation (aXMLNode.m_aDoc, aXMLNode.m_sBaseURI);
231   }
232 
233   @Nullable
234   public Document applySchematronValidation (@Nonnull final Source aXMLSource) throws Exception
235   {
236     if (!isValidSchematron ())
237       return null;
238 
239     final Node aXMLNode = getAsNode (aXMLSource);
240     if (aXMLNode == null)
241       return null;
242 
243     return applySchematronValidation (aXMLNode, aXMLSource.getSystemId ());
244   }
245 
246   @Nullable
247   public SchematronOutputType applySchematronValidationToSVRL (@Nonnull final IHasInputStream aXMLResource) throws Exception
248   {
249     if (!isValidSchematron ())
250       return null;
251 
252     final NodeAndBaseURI aXMLNode = getAsNode (aXMLResource);
253     if (aXMLNode == null)
254       return null;
255 
256     return applySchematronValidationToSVRL (aXMLNode.m_aDoc, aXMLNode.m_sBaseURI);
257   }
258 
259   @Nullable
260   public SchematronOutputType applySchematronValidationToSVRL (@Nonnull final Source aXMLSource) throws Exception
261   {
262     if (!isValidSchematron ())
263       return null;
264 
265     final Node aXMLNode = getAsNode (aXMLSource);
266     if (aXMLNode == null)
267       return null;
268 
269     return applySchematronValidationToSVRL (aXMLNode, aXMLSource.getSystemId ());
270   }
271 
272   @Override
273   public String toString ()
274   {
275     return new ToStringGenerator (this).append ("Resource", m_aResource)
276                                        .append ("UseCache", m_bUseCache)
277                                        .appendIfNotNull ("EntityResolver", m_aEntityResolver)
278                                        .getToString ();
279   }
280 }