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.pure.bound;
18  
19  import javax.annotation.Nonnull;
20  import javax.annotation.Nullable;
21  import javax.annotation.concurrent.Immutable;
22  import javax.xml.xpath.XPathFunctionResolver;
23  import javax.xml.xpath.XPathVariableResolver;
24  
25  import org.slf4j.Logger;
26  import org.slf4j.LoggerFactory;
27  import org.xml.sax.EntityResolver;
28  
29  import com.helger.commons.ValueEnforcer;
30  import com.helger.commons.annotation.OverrideOnDemand;
31  import com.helger.commons.equals.EqualsHelper;
32  import com.helger.commons.hashcode.HashCodeGenerator;
33  import com.helger.commons.hashcode.IHashCodeGenerator;
34  import com.helger.commons.io.resource.IReadableResource;
35  import com.helger.commons.string.ToStringGenerator;
36  import com.helger.schematron.SchematronDebug;
37  import com.helger.schematron.SchematronException;
38  import com.helger.schematron.pure.binding.IPSQueryBinding;
39  import com.helger.schematron.pure.binding.PSQueryBindingRegistry;
40  import com.helger.schematron.pure.errorhandler.IPSErrorHandler;
41  import com.helger.schematron.pure.exchange.PSReader;
42  import com.helger.schematron.pure.model.PSSchema;
43  import com.helger.schematron.pure.preprocess.PSPreprocessor;
44  import com.helger.schematron.pure.preprocess.SchematronPreprocessException;
45  import com.helger.xml.microdom.serialize.MicroWriter;
46  
47  /**
48   * This class represents keys for the {@link PSBoundSchemaCache}. It is a
49   * combination of a resource and a phase. It is the responsible class for
50   * reading and binding a Schematron resource.
51   *
52   * @author Philip Helger
53   */
54  @Immutable
55  public class PSBoundSchemaCacheKey
56  {
57    private static final Logger s_aLogger = LoggerFactory.getLogger (PSBoundSchemaCacheKey.class);
58  
59    private final IReadableResource m_aResource;
60    private final String m_sPhase;
61    private final IPSErrorHandler m_aErrorHandler;
62    private final XPathVariableResolver m_aVariableResolver;
63    private final XPathFunctionResolver m_aFunctionResolver;
64    private final EntityResolver m_aEntityResolver;
65    // Status vars
66    private transient int m_nHashCode = IHashCodeGenerator.ILLEGAL_HASHCODE;
67  
68    public PSBoundSchemaCacheKey (@Nonnull final IReadableResource aResource,
69                                  @Nullable final String sPhase,
70                                  @Nullable final IPSErrorHandler aErrorHandler,
71                                  @Nullable final XPathVariableResolver aVariableResolver,
72                                  @Nullable final XPathFunctionResolver aFunctionResolver,
73                                  @Nullable final EntityResolver aEntityResolver)
74    {
75      ValueEnforcer.notNull (aResource, "Resource");
76  
77      m_aResource = aResource;
78      m_sPhase = sPhase;
79      m_aErrorHandler = aErrorHandler;
80      m_aVariableResolver = aVariableResolver;
81      m_aFunctionResolver = aFunctionResolver;
82      m_aEntityResolver = aEntityResolver;
83    }
84  
85    /**
86     * @return The resource passed in the constructor. Never <code>null</code>.
87     */
88    @Nonnull
89    public final IReadableResource getResource ()
90    {
91      return m_aResource;
92    }
93  
94    /**
95     * @return The phase selected in the constructor. May be <code>null</code>.
96     */
97    @Nullable
98    public final String getPhase ()
99    {
100     return m_sPhase;
101   }
102 
103   /**
104    * @return The error handler passed in the constructor. May be
105    *         <code>null</code>.
106    */
107   @Nullable
108   public final IPSErrorHandler getErrorHandler ()
109   {
110     return m_aErrorHandler;
111   }
112 
113   /**
114    * @return The variable resolver to be used. May be <code>null</code>.
115    */
116   @Nullable
117   public final XPathVariableResolver getVariableResolver ()
118   {
119     return m_aVariableResolver;
120   }
121 
122   /**
123    * @return The function resolver to be used. May be <code>null</code>.
124    */
125   @Nullable
126   public final XPathFunctionResolver getFunctionResolver ()
127   {
128     return m_aFunctionResolver;
129   }
130 
131   /**
132    * @return The XML entity resolver to be used. May be <code>null</code>.
133    */
134   @Nullable
135   public final EntityResolver getEntityResolver ()
136   {
137     return m_aEntityResolver;
138   }
139 
140   /**
141    * Read the specified schema from the passed resource.
142    *
143    * @param aResource
144    *        The resource to read from. Never <code>null</code>.
145    * @param aErrorHandler
146    *        The error handler to use. May be <code>null</code>.
147    * @param aEntityResolver
148    *        The XML entity resolver to be used. May be <code>null</code>.
149    * @return The read schema. May not be <code>null</code>.
150    * @throws SchematronException
151    *         In case there is an error reading.
152    */
153   @Nonnull
154   @OverrideOnDemand
155   public PSSchema readSchema (@Nonnull final IReadableResource aResource,
156                               @Nullable final IPSErrorHandler aErrorHandler,
157                               @Nullable final EntityResolver aEntityResolver) throws SchematronException
158   {
159     return new PSReader (aResource, aErrorHandler, aEntityResolver).readSchema ();
160   }
161 
162   /**
163    * Determine the query binding for the read schema.
164    *
165    * @param aSchema
166    *        The read schema. Never <code>null</code>.
167    * @return The query binding to use. Never <code>null</code>.
168    * @throws SchematronException
169    *         In case the determination fails.
170    */
171   @Nonnull
172   @OverrideOnDemand
173   public IPSQueryBinding getQueryBinding (@Nonnull final PSSchema aSchema) throws SchematronException
174   {
175     return PSQueryBindingRegistry.getQueryBindingOfNameOrThrow (aSchema.getQueryBinding ());
176   }
177 
178   /**
179    * Create the pre-processor to be used for
180    * {@link #createPreprocessedSchema(PSSchema, IPSQueryBinding)}.
181    *
182    * @param aQueryBinding
183    *        The query binding to be determined from the read schema. Never
184    *        <code>null</code>.
185    * @return The pre-processor to be used.
186    */
187   @Nonnull
188   @OverrideOnDemand
189   public PSPreprocessor createPreprocessor (@Nonnull final IPSQueryBinding aQueryBinding)
190   {
191     final PSPreprocessor aPreprocessor = PSPreprocessor.createPreprocessorWithoutInformationLoss (aQueryBinding);
192     return aPreprocessor;
193   }
194 
195   /**
196    * Pre-process the read schema, using the determined query binding.
197    *
198    * @param aSchema
199    *        The read schema. Never <code>null</code>.
200    * @param aQueryBinding
201    *        The determined query binding. Never <code>null</code>.
202    * @return The pre-processed schema and never <code>null</code>.
203    * @throws SchematronException
204    *         In case pre-processing fails
205    */
206   @Nonnull
207   @OverrideOnDemand
208   public PSSchema createPreprocessedSchema (@Nonnull final PSSchema aSchema,
209                                             @Nonnull final IPSQueryBinding aQueryBinding) throws SchematronException
210   {
211     final PSPreprocessor aPreprocessor = createPreprocessor (aQueryBinding);
212     final PSSchema aPreprocessedSchema = aPreprocessor.getAsPreprocessedSchema (aSchema);
213     if (aPreprocessedSchema == null)
214       throw new SchematronPreprocessException ("Failed to preprocess schema " +
215                                                aSchema +
216                                                " with query binding " +
217                                                aQueryBinding);
218     if (SchematronDebug.isShowPreprocessedSchematron ())
219       s_aLogger.info ("Preprocessed Schematron:\n" +
220                       MicroWriter.getNodeAsString (aPreprocessedSchema.getAsMicroElement ()));
221     return aPreprocessedSchema;
222   }
223 
224   /**
225    * The main routine to create a bound schema from the passed resource and
226    * phase. The usual routine is to
227    * <ol>
228    * <li>read the schema from the resource - see
229    * {@link #readSchema(IReadableResource, IPSErrorHandler, EntityResolver)}</li>
230    * <li>resolve the query binding - see {@link #getQueryBinding(PSSchema)}</li>
231    * <li>pre-process the schema -
232    * {@link #createPreprocessedSchema(PSSchema, IPSQueryBinding)}</li>
233    * <li>and finally bind it -
234    * {@link IPSQueryBinding#bind(PSSchema, String, IPSErrorHandler, javax.xml.xpath.XPathVariableResolver, javax.xml.xpath.XPathFunctionResolver)}
235    * </li>
236    * </ol>
237    *
238    * @return The bound schema. Never <code>null</code>.
239    * @throws SchematronException
240    *         In case reading or binding fails.
241    */
242   @Nonnull
243   public IPSBoundSchema createBoundSchema () throws SchematronException
244   {
245     // Read schema from resource
246     final PSSchema aSchema = readSchema (getResource (), getErrorHandler (), getEntityResolver ());
247 
248     // Resolve the query binding to be used
249     final IPSQueryBinding aQueryBinding = getQueryBinding (aSchema);
250 
251     // Pre-process schema
252     final PSSchema aPreprocessedSchema = createPreprocessedSchema (aSchema, aQueryBinding);
253 
254     // And finally bind the pre-processed schema
255     return aQueryBinding.bind (aPreprocessedSchema,
256                                getPhase (),
257                                getErrorHandler (),
258                                getVariableResolver (),
259                                getFunctionResolver ());
260   }
261 
262   @Override
263   public boolean equals (final Object o)
264   {
265     if (o == this)
266       return true;
267     if (o == null || !getClass ().equals (o.getClass ()))
268       return false;
269     final PSBoundSchemaCacheKey rhs = (PSBoundSchemaCacheKey) o;
270     return m_aResource.equals (rhs.m_aResource) &&
271            EqualsHelper.equals (m_sPhase, rhs.m_sPhase) &&
272            EqualsHelper.equals (m_aVariableResolver, rhs.m_aVariableResolver) &&
273            EqualsHelper.equals (m_aFunctionResolver, rhs.m_aFunctionResolver);
274   }
275 
276   @Override
277   public int hashCode ()
278   {
279     int ret = m_nHashCode;
280     if (ret == IHashCodeGenerator.ILLEGAL_HASHCODE)
281       ret = m_nHashCode = new HashCodeGenerator (this).append (m_aResource)
282                                                       .append (m_sPhase)
283                                                       .append (m_aVariableResolver)
284                                                       .append (m_aFunctionResolver)
285                                                       .getHashCode ();
286     return ret;
287   }
288 
289   @Override
290   public String toString ()
291   {
292     return new ToStringGenerator (this).append ("resource", m_aResource)
293                                        .append ("phase", m_sPhase)
294                                        .appendIfNotNull ("errorHandler", m_aErrorHandler)
295                                        .appendIfNotNull ("variableResolver", m_aVariableResolver)
296                                        .appendIfNotNull ("functionResolver", m_aFunctionResolver)
297                                        .getToString ();
298   }
299 }