1
package com.thaiopensource.validate.schematron;
3
import com.thaiopensource.util.Localizer;
4
import com.thaiopensource.util.PropertyId;
5
import com.thaiopensource.util.PropertyMap;
6
import com.thaiopensource.util.PropertyMapBuilder;
7
import com.thaiopensource.validate.AbstractSchemaReader;
8
import com.thaiopensource.validate.IncorrectSchemaException;
9
import com.thaiopensource.validate.Option;
10
import com.thaiopensource.validate.ResolverFactory;
11
import com.thaiopensource.validate.Schema;
12
import com.thaiopensource.validate.ValidateProperty;
13
import com.thaiopensource.validate.Validator;
14
import com.thaiopensource.validate.prop.rng.RngProperty;
15
import com.thaiopensource.validate.prop.schematron.SchematronProperty;
16
import com.thaiopensource.validate.rng.CompactSchemaReader;
17
import com.thaiopensource.xml.sax.CountingErrorHandler;
18
import com.thaiopensource.xml.sax.DelegatingContentHandler;
19
import com.thaiopensource.xml.sax.DraconianErrorHandler;
20
import org.xml.sax.Attributes;
21
import org.xml.sax.ContentHandler;
22
import org.xml.sax.ErrorHandler;
23
import org.xml.sax.InputSource;
24
import org.xml.sax.Locator;
25
import org.xml.sax.SAXException;
26
import org.xml.sax.SAXParseException;
27
import org.xml.sax.XMLReader;
29
import javax.xml.transform.ErrorListener;
30
import javax.xml.transform.SourceLocator;
31
import javax.xml.transform.Templates;
32
import javax.xml.transform.Transformer;
33
import javax.xml.transform.TransformerConfigurationException;
34
import javax.xml.transform.TransformerException;
35
import javax.xml.transform.TransformerFactory;
36
import javax.xml.transform.sax.SAXResult;
37
import javax.xml.transform.sax.SAXSource;
38
import javax.xml.transform.sax.SAXTransformerFactory;
39
import javax.xml.transform.sax.TemplatesHandler;
40
import javax.xml.transform.sax.TransformerHandler;
41
import javax.xml.transform.stream.StreamSource;
42
import java.io.IOException;
43
import java.io.InputStream;
45
class ISOSchemaReaderImpl extends AbstractSchemaReader {
46
static final String SCHEMATRON_URI = "http://purl.oclc.org/dsdl/schematron";
47
private static final String LOCATION_URI = "http://www.thaiopensource.com/ns/location";
48
private static final String ERROR_URI = "http://www.thaiopensource.com/ns/error";
49
private final Localizer localizer = new Localizer(ISOSchemaReaderImpl.class);
51
private final Class<? extends SAXTransformerFactory> transformerFactoryClass;
52
private final TransformerFactoryInitializer transformerFactoryInitializer;
53
private final Templates schematron;
54
private final Schema schematronSchema;
55
private static final String SCHEMATRON_SCHEMA = "iso-schematron.rnc";
56
private static final String SCHEMATRON_STYLESHEET = "iso-schematron.xsl";
57
// XSLTC has some problems with extension functions and function-available, so
58
// we need a separate stylesheet. See
59
// https://issues.apache.org/jira/browse/XALANJ-2464
60
// https://issues.apache.org/jira/browse/XALANJ-2465
61
private static final String SCHEMATRON_XSLTC_STYLESHEET = "iso-schematron-xsltc.xsl";
62
private static final PropertyId<?>[] supportedPropertyIds = {
63
ValidateProperty.ERROR_HANDLER,
64
ValidateProperty.XML_READER_CREATOR,
65
ValidateProperty.ENTITY_RESOLVER,
66
ValidateProperty.URI_RESOLVER,
67
ValidateProperty.RESOLVER,
68
SchematronProperty.DIAGNOSE,
69
SchematronProperty.PHASE,
72
ISOSchemaReaderImpl(SAXTransformerFactory transformerFactory, TransformerFactoryInitializer transformerFactoryInitializer)
73
throws TransformerConfigurationException, IncorrectSchemaException {
74
this.transformerFactoryClass = transformerFactory.getClass();
75
this.transformerFactoryInitializer = transformerFactoryInitializer;
76
final boolean isXsltc = isXsltc(transformerFactoryClass);
77
final String stylesheet = isXsltc ? SCHEMATRON_XSLTC_STYLESHEET : SCHEMATRON_STYLESHEET;
78
final String resourceName = fullResourceName(stylesheet);
79
final StreamSource source = new StreamSource(getResourceAsStream(resourceName));
80
initTransformerFactory(transformerFactory);
81
schematron = transformerFactory.newTemplates(source);
82
InputSource schemaSource = new InputSource(getResourceAsStream(fullResourceName(SCHEMATRON_SCHEMA)));
83
PropertyMapBuilder builder = new PropertyMapBuilder();
84
builder.put(ValidateProperty.ERROR_HANDLER, new DraconianErrorHandler());
85
RngProperty.CHECK_ID_IDREF.add(builder);
87
schematronSchema = CompactSchemaReader.getInstance().createSchema(schemaSource, builder.toPropertyMap());
89
catch (SAXException e) {
90
throw new IncorrectSchemaException();
92
catch (IOException e) {
93
throw new IncorrectSchemaException();
97
static boolean isXsltc(Class<? extends SAXTransformerFactory> cls) {
98
return cls.getName().indexOf(".xsltc.") >= 0;
101
public Option getOption(String uri) {
102
return SchematronProperty.getOption(uri);
105
private void initTransformerFactory(TransformerFactory factory) {
106
transformerFactoryInitializer.initTransformerFactory(factory);
109
static class UserException extends Exception {
110
private final SAXException exception;
112
UserException(SAXException exception) {
113
this.exception = exception;
116
SAXException getException() {
121
static class UserWrapErrorHandler extends CountingErrorHandler {
122
UserWrapErrorHandler(ErrorHandler errorHandler) {
126
public void warning(SAXParseException exception)
127
throws SAXException {
129
super.warning(exception);
131
catch (SAXException e) {
132
throw new SAXException(new UserException(e));
136
public void error(SAXParseException exception)
137
throws SAXException {
139
super.error(exception);
141
catch (SAXException e) {
142
throw new SAXException(new UserException(e));
146
public void fatalError(SAXParseException exception)
147
throws SAXException {
149
super.fatalError(exception);
151
catch (SAXException e) {
152
throw new SAXException(new UserException(e));
157
static class ErrorFilter extends DelegatingContentHandler {
158
private final ErrorHandler eh;
159
private final Localizer localizer;
160
private Locator locator;
162
ErrorFilter(ContentHandler delegate, ErrorHandler eh, Localizer localizer) {
165
this.localizer = localizer;
168
public void setDocumentLocator(Locator locator) {
169
this.locator = locator;
170
super.setDocumentLocator(locator);
173
public void startElement(String namespaceURI, String localName,
174
String qName, Attributes atts)
175
throws SAXException {
176
if (namespaceURI.equals(ERROR_URI) && localName.equals("error"))
177
eh.error(new SAXParseException(localizer.message(atts.getValue("", "message"),
178
atts.getValue("", "arg")),
180
super.startElement(namespaceURI, localName, qName, atts);
184
static class LocationFilter extends DelegatingContentHandler implements Locator {
185
private final String mainSystemId;
186
private String systemId = null;
187
private int lineNumber = -1;
188
private int columnNumber = -1;
189
private SAXException exception = null;
191
LocationFilter(ContentHandler delegate, String systemId) {
193
this.mainSystemId = systemId;
196
SAXException getException() {
200
public void setDocumentLocator(Locator locator) {
203
public void startDocument()
204
throws SAXException {
205
getDelegate().setDocumentLocator(this);
206
super.startDocument();
209
public void startElement(String namespaceURI, String localName,
210
String qName, Attributes atts)
211
throws SAXException {
212
systemId = getLocationAttribute(atts, "system-id");
213
lineNumber = toInteger(getLocationAttribute(atts, "line-number"));
214
columnNumber = toInteger(getLocationAttribute(atts, "column-number"));
216
super.startElement(namespaceURI, localName, qName, atts);
218
catch (SAXException e) {
227
private static String getLocationAttribute(Attributes atts, String name) {
228
return atts.getValue(LOCATION_URI, name);
231
private static int toInteger(String value) {
235
return Integer.parseInt(value);
237
catch (NumberFormatException e) {
242
public String getPublicId() {
246
public String getSystemId() {
247
if (systemId != null && !systemId.equals(""))
252
public int getLineNumber() {
256
public int getColumnNumber() {
261
static class SAXErrorListener implements ErrorListener {
262
private final ErrorHandler eh;
263
private final String systemId;
264
private boolean hadError = false;
265
SAXErrorListener(ErrorHandler eh, String systemId) {
267
this.systemId = systemId;
270
boolean getHadError() {
274
public void warning(TransformerException exception)
275
throws TransformerException {
276
SAXParseException spe = transform(exception);
280
catch (SAXException e) {
281
throw new TransformerException(new UserException(e));
285
public void error(TransformerException exception)
286
throws TransformerException {
288
SAXParseException spe = transform(exception);
292
catch (SAXException e) {
293
throw new TransformerException(new UserException(e));
297
public void fatalError(TransformerException exception)
298
throws TransformerException {
300
SAXParseException spe = transform(exception);
304
catch (SAXException e) {
305
throw new TransformerException(new UserException(e));
309
SAXParseException transform(TransformerException exception) throws TransformerException {
310
Throwable cause = exception.getException();
311
// Xalan takes it upon itself to catch exceptions and pass them to the ErrorListener.
312
if (cause instanceof RuntimeException)
313
throw (RuntimeException)cause;
314
if (cause instanceof SAXException
315
|| cause instanceof IncorrectSchemaException
316
|| cause instanceof IOException)
318
SourceLocator locator = exception.getLocator();
320
return new SAXParseException(exception.getMessage(), null);
321
// Xalan sometimes loses the mainSystemId; work around this.
322
String s = locator.getSystemId();
325
return new SAXParseException(exception.getMessage(),
326
locator.getPublicId(),
328
locator.getLineNumber(),
329
locator.getColumnNumber());
334
// Minor problem is that
335
// Saxon 6.5.2 prints to System.err in TemplatesHandlerImpl.getTemplates().
337
public Schema createSchema(SAXSource source, PropertyMap properties)
338
throws IOException, SAXException, IncorrectSchemaException {
339
ErrorHandler eh = properties.get(ValidateProperty.ERROR_HANDLER);
340
CountingErrorHandler ceh = new CountingErrorHandler(eh);
341
InputSource in = source.getInputSource();
342
String systemId = in.getSystemId();
343
IfValidHandler ifValidHandler = new IfValidHandler();
344
ifValidHandler.setErrorHandler(ceh);
346
SAXTransformerFactory factory = (SAXTransformerFactory)transformerFactoryClass.newInstance();
347
initTransformerFactory(factory);
348
TransformerHandler transformerHandler = factory.newTransformerHandler(schematron);
349
ifValidHandler.setDelegate(transformerHandler);
350
Transformer transformer = transformerHandler.getTransformer();
351
String phase = properties.get(SchematronProperty.PHASE);
353
transformer.setParameter("phase", phase);
354
boolean diagnose = properties.contains(SchematronProperty.DIAGNOSE);
356
transformer.setParameter("diagnose", Boolean.TRUE);
357
PropertyMapBuilder builder = new PropertyMapBuilder(properties);
358
builder.put(ValidateProperty.ERROR_HANDLER, ifValidHandler);
359
Validator validator = schematronSchema.createValidator(builder.toPropertyMap());
360
ifValidHandler.setValidator(validator.getContentHandler());
361
XMLReader xr = source.getXMLReader();
363
xr = ResolverFactory.createResolver(properties).createXMLReader();
364
xr.setContentHandler(ifValidHandler);
365
xr.setDTDHandler(validator.getDTDHandler()); // not strictly necessary
366
factory.setErrorListener(new SAXErrorListener(ceh, systemId));
367
TemplatesHandler templatesHandler = factory.newTemplatesHandler();
368
templatesHandler.setSystemId(systemId);
369
LocationFilter stage2 = new LocationFilter(new ErrorFilter(templatesHandler, ceh, localizer), systemId);
370
transformerHandler.setResult(new SAXResult(stage2));
371
xr.setErrorHandler(ceh);
373
SAXException exception = stage2.getException();
374
if (exception != null)
376
if (ceh.getHadErrorOrFatalError())
377
throw new IncorrectSchemaException();
378
// Getting the templates can cause errors to be generated.
379
Templates templates = templatesHandler.getTemplates();
380
if (ceh.getHadErrorOrFatalError())
381
throw new IncorrectSchemaException();
382
return new SchemaImpl(templates,
383
transformerFactoryClass,
385
supportedPropertyIds);
387
catch (SAXException e) {
388
throw cleanupSAXException(e);
390
catch (TransformerConfigurationException e) {
391
throw new SAXException(localizer.message("unexpected_schema_creation_error"));
393
catch (InstantiationException e) {
394
throw new SAXException(e);
396
catch (IllegalAccessException e) {
397
throw new SAXException(e);
401
private static String fullResourceName(String name) {
402
String className = ISOSchemaReaderImpl.class.getName();
403
return className.substring(0, className.lastIndexOf('.')).replace('.', '/') + "/resources/" + name;
406
private static InputStream getResourceAsStream(String resourceName) {
407
ClassLoader cl = ISOSchemaReaderImpl.class.getClassLoader();
408
// XXX see if we should borrow 1.2 code from Service
410
return ClassLoader.getSystemResourceAsStream(resourceName);
412
return cl.getResourceAsStream(resourceName);
415
private static SAXException cleanupSAXException(SAXException saxException) {
416
if (exceptionHasLocation(saxException))
418
Exception exception = saxException.getException();
419
if (exception instanceof SAXException && exception.getMessage() == null)
420
return cleanupSAXException((SAXException)exception);
421
if (exception instanceof TransformerException)
422
return cleanupTransformerException((TransformerException)exception);
426
private static SAXException cleanupTransformerException(TransformerException e) {
427
String message = e.getMessage();
428
Throwable cause = e.getException();
429
SourceLocator transformLoc = e.getLocator();
430
// a TransformerException created with just a Throwable t argument
431
// gets a message of t.toString()
432
if (message != null && cause != null && message.equals(cause.toString()))
434
if (message == null && cause instanceof SAXException && transformLoc == null)
435
return cleanupSAXException((SAXException)cause);
436
if (cause instanceof TransformerException && transformLoc == null)
437
return cleanupTransformerException((TransformerException)cause);
438
Exception exception = null;
439
if (cause instanceof Exception)
440
exception = (Exception)cause;
441
String publicId = null;
442
String systemId = null;
444
int columnNumber = -1;
445
if (transformLoc != null) {
446
publicId = transformLoc.getPublicId();
447
systemId = transformLoc.getSystemId();
448
lineNumber = transformLoc.getLineNumber();
449
columnNumber = transformLoc.getColumnNumber();
451
if (publicId != null || systemId != null || lineNumber >= 0 || columnNumber >= 0)
452
return new SAXParseException(message, publicId, systemId, lineNumber, columnNumber, exception);
453
return new SAXException(message, exception);
456
private static boolean exceptionHasLocation(SAXException saxException) {
457
if (!(saxException instanceof SAXParseException))
459
SAXParseException pe = (SAXParseException)saxException;
460
return (pe.getPublicId() != null || pe.getSystemId() != null
461
|| pe.getLineNumber() >= 0 || pe.getColumnNumber() >= 0);