001    package com.oxygenxml.validate.isoschematron;
002    
003    import com.thaiopensource.util.PropertyMap;
004    import com.thaiopensource.util.PropertyMapBuilder;
005    import com.thaiopensource.util.Localizer;
006    import com.thaiopensource.util.PropertyId;
007    import com.thaiopensource.validate.IncorrectSchemaException;
008    import com.thaiopensource.validate.Schema;
009    import com.thaiopensource.validate.SchemaReader;
010    import com.thaiopensource.validate.ValidateProperty;
011    import com.thaiopensource.validate.Validator;
012    import com.thaiopensource.validate.Option;
013    import com.thaiopensource.validate.rng.CompactSchemaReader;
014    import com.thaiopensource.validate.rng.RngProperty;
015    import com.thaiopensource.xml.sax.CountingErrorHandler;
016    import com.thaiopensource.xml.sax.DelegatingContentHandler;
017    import com.thaiopensource.xml.sax.DraconianErrorHandler;
018    import com.thaiopensource.xml.sax.ForkContentHandler;
019    import com.thaiopensource.xml.sax.XMLReaderCreator;
020    import org.xml.sax.Attributes;
021    import org.xml.sax.ContentHandler;
022    import org.xml.sax.ErrorHandler;
023    import org.xml.sax.InputSource;
024    import org.xml.sax.Locator;
025    import org.xml.sax.SAXException;
026    import org.xml.sax.SAXParseException;
027    import org.xml.sax.XMLReader;
028    
029    import javax.xml.transform.ErrorListener;
030    import javax.xml.transform.SourceLocator;
031    import javax.xml.transform.Templates;
032    import javax.xml.transform.Transformer;
033    import javax.xml.transform.TransformerConfigurationException;
034    import javax.xml.transform.TransformerException;
035    import javax.xml.transform.TransformerFactory;
036    import javax.xml.transform.sax.SAXResult;
037    import javax.xml.transform.sax.SAXSource;
038    import javax.xml.transform.stream.StreamSource;
039    import java.io.IOException;
040    import java.io.InputStream;
041    
042    class SchemaReaderImpl implements SchemaReader {
043      static final String ISO_SCHEMATRON_URI = "http://purl.oclc.org/dsdl/schematron";
044      private static final String LOCATION_URI = "http://www.thaiopensource.com/ns/location";
045      private static final String ERROR_URI = "http://www.thaiopensource.com/ns/error";
046      private final Localizer localizer = new Localizer(SchemaReaderImpl.class);
047    
048      private final Class transformerFactoryClass;
049      private final Templates schematron;
050      private final Schema schematronSchema;
051      private static final String SCHEMATRON_SCHEMA = "iso-schematron.rnc";
052      private static final String SCHEMATRON_STYLESHEET = "iso_schematron_skeleton.xsl";
053      private static final PropertyId[] supportedPropertyIds = {
054        ValidateProperty.ERROR_HANDLER,
055        ValidateProperty.XML_READER_CREATOR,
056        SchematronProperty.DIAGNOSE,
057        SchematronProperty.PHASE,
058      };
059    
060      SchemaReaderImpl(TransformerFactory transformerFactory) throws TransformerConfigurationException, IncorrectSchemaException {
061        this.transformerFactoryClass = transformerFactory.getClass();
062        String resourceName = fullResourceName(SCHEMATRON_STYLESHEET);
063        StreamSource source = new StreamSource(getResourceAsStream(resourceName));
064        initTransformerFactory(transformerFactory);
065        schematron = transformerFactory.newTemplates(source);
066        InputSource schemaSource = new InputSource(getResourceAsStream(fullResourceName(SCHEMATRON_SCHEMA)));
067        PropertyMapBuilder builder = new PropertyMapBuilder();
068        ValidateProperty.ERROR_HANDLER.put(builder, new DraconianErrorHandler());
069        RngProperty.CHECK_ID_IDREF.add(builder);
070        try {
071          schematronSchema = CompactSchemaReader.getInstance().createSchema(schemaSource, builder.toPropertyMap());
072        }
073        catch (SAXException e) {
074          throw new IncorrectSchemaException();
075        }
076        catch (IOException e) {
077          throw new IncorrectSchemaException();
078        }
079      }
080    
081      public Option getOption(String uri) {
082        return SchematronProperty.getOption(uri);
083      }
084    
085      private void initTransformerFactory(TransformerFactory factory) {
086        String name = factory.getClass().getName();
087        try {
088          if (name.equals("com.icl.saxon.TransformerFactoryImpl"))
089            factory.setAttribute("http://icl.com/saxon/feature/linenumbering",
090                                 Boolean.TRUE);
091          else if (name.equals("org.apache.xalan.processor.TransformerFactoryImpl")) {
092            // Try both the documented URI and the URI that the code expects.
093            try {
094              // This is the URI that the code expects.
095              factory.setAttribute("http://xml.apache.org/xalan/properties/source-location",
096                                   Boolean.TRUE);
097            }
098            catch (IllegalArgumentException e) {
099              // This is the URI that's documented.
100              factory.setAttribute("http://apache.org/xalan/features/source_location",
101                                   Boolean.TRUE);
102            }
103          }
104        }
105        catch (IllegalArgumentException e) {
106        }
107      }
108    
109      static class ValidateStage extends XMLReaderImpl {
110        private final ContentHandler validator;
111        private ContentHandler contentHandler;
112        private final XMLReader reader;
113        private final CountingErrorHandler ceh;
114    
115        ValidateStage(XMLReader reader, Validator validator, CountingErrorHandler ceh) {
116          this.reader = reader;
117          this.validator = validator.getContentHandler();
118          this.ceh = ceh;
119        }
120    
121        public void parse(InputSource input)
122                throws SAXException, IOException {
123          reader.parse(input);
124          if (ceh.getHadErrorOrFatalError())
125            throw new SAXException(new IncorrectSchemaException());
126        }
127    
128        public void setContentHandler(ContentHandler handler) {
129          this.contentHandler = handler;
130          reader.setContentHandler(new ForkContentHandler(validator, contentHandler));
131        }
132    
133        public ContentHandler getContentHandler() {
134          return contentHandler;
135        }
136      }
137    
138      static class UserException extends Exception {
139        private final SAXException exception;
140    
141        UserException(SAXException exception) {
142          this.exception = exception;
143        }
144    
145        SAXException getException() {
146          return exception;
147        }
148      }
149    
150      static class UserWrapErrorHandler extends CountingErrorHandler {
151        UserWrapErrorHandler(ErrorHandler errorHandler) {
152          super(errorHandler);
153        }
154    
155        public void warning(SAXParseException exception)
156                throws SAXException {
157          try {
158            super.warning(exception);
159          }
160          catch (SAXException e) {
161            throw new SAXException(new UserException(e));
162          }
163        }
164    
165        public void error(SAXParseException exception)
166                throws SAXException {
167          try {
168            super.error(exception);
169          }
170          catch (SAXException e) {
171            throw new SAXException(new UserException(e));
172          }
173        }
174    
175        public void fatalError(SAXParseException exception)
176                throws SAXException {
177          try {
178            super.fatalError(exception);
179          }
180          catch (SAXException e) {
181            throw new SAXException(new UserException(e));
182          }
183        }
184      }
185    
186      static class ErrorFilter extends DelegatingContentHandler {
187        private final ErrorHandler eh;
188        private final Localizer localizer;
189        private Locator locator;
190    
191        ErrorFilter(ContentHandler delegate, ErrorHandler eh, Localizer localizer) {
192          super(delegate);
193          this.eh = eh;
194          this.localizer = localizer;
195        }
196    
197        public void setDocumentLocator(Locator locator) {
198          this.locator = locator;
199          super.setDocumentLocator(locator);
200        }
201    
202        public void startElement(String namespaceURI, String localName,
203                                 String qName, Attributes atts)
204                throws SAXException {
205          if (namespaceURI.equals(ERROR_URI) && localName.equals("error"))
206            eh.error(new SAXParseException(localizer.message(atts.getValue("", "message"),
207                                                             atts.getValue("", "arg")),
208                                           locator));
209          super.startElement(namespaceURI, localName, qName, atts);
210        }
211      }
212    
213      static class LocationFilter extends DelegatingContentHandler implements Locator {
214        private final String systemId;
215        private int lineNumber = -1;
216        private SAXException exception = null;
217    
218        LocationFilter(ContentHandler delegate, String systemId) {
219          super(delegate);
220          this.systemId = systemId;
221        }
222    
223        SAXException getException() {
224          return exception;
225        }
226    
227        public void setDocumentLocator(Locator locator) {
228        }
229    
230        public void startDocument()
231                throws SAXException {
232          getDelegate().setDocumentLocator(this);
233          super.startDocument();
234        }
235    
236        public void startElement(String namespaceURI, String localName,
237                                 String qName, Attributes atts)
238                throws SAXException {
239          String value = atts.getValue(LOCATION_URI, "line-number");
240          if (value != null) {
241            try {
242              lineNumber = Integer.parseInt(value);
243            }
244            catch (NumberFormatException e) {
245              lineNumber = -1;
246            }
247          }
248          else
249            lineNumber = -1;
250          try {
251            super.startElement(namespaceURI, localName, qName, atts);
252          }
253          catch (SAXException e) {
254            this.exception = e;
255            setDelegate(null);
256          }
257          lineNumber = -1;
258        }
259    
260        public String getPublicId() {
261          return null;
262        }
263    
264        public String getSystemId() {
265          return systemId;
266        }
267    
268        public int getLineNumber() {
269          return lineNumber;
270        }
271    
272        public int getColumnNumber() {
273          return -1;
274        }
275      }
276    
277      static class TransformStage extends XMLReaderImpl {
278        private ContentHandler contentHandler;
279        private final Transformer transformer;
280        private final SAXSource transformSource;
281        private final String systemId;
282        private final CountingErrorHandler ceh;
283        private final Localizer localizer;
284    
285        TransformStage(Transformer transformer, SAXSource transformSource, String systemId,
286                       CountingErrorHandler ceh, Localizer localizer) {
287          this.transformer = transformer;
288          this.transformSource = transformSource;
289          this.systemId = systemId;
290          this.ceh = ceh;
291          this.localizer = localizer;
292        }
293    
294        public void parse(InputSource input)
295                throws IOException, SAXException {
296          try {
297            LocationFilter handler = new LocationFilter(new ErrorFilter(contentHandler, ceh, localizer),
298                                                                        systemId);
299            transformer.transform(transformSource, new SAXResult(handler));
300            SAXException exception = handler.getException();
301            if (exception != null)
302              throw exception;
303          }
304          catch (TransformerException e) {
305            if (e.getException() instanceof IOException)
306              throw (IOException)e.getException();
307            throw ValidatorImpl.toSAXException(e);
308          }
309          if (ceh.getHadErrorOrFatalError())
310            throw new SAXException(new IncorrectSchemaException());
311        }
312    
313        public ContentHandler getContentHandler() {
314          return contentHandler;
315        }
316    
317        public void setContentHandler(ContentHandler contentHandler) {
318          this.contentHandler = contentHandler;
319        }
320      }
321    
322      static class SAXErrorListener implements ErrorListener {
323         private final ErrorHandler eh;
324         private final String systemId;
325         private boolean hadError = false;
326         SAXErrorListener(ErrorHandler eh, String systemId) {
327           this.eh = eh;
328           this.systemId = systemId;
329         }
330    
331         boolean getHadError() {
332           return hadError;
333         }
334    
335         public void warning(TransformerException exception)
336                 throws TransformerException {
337           SAXParseException spe = transform(exception);
338           try {
339             eh.warning(spe);
340           }
341           catch (SAXException e) {
342             throw new TransformerException(new UserException(e));
343           }
344         }
345    
346         public void error(TransformerException exception)
347                 throws TransformerException {
348           hadError = true;
349           SAXParseException spe = transform(exception);
350           try {
351             eh.error(spe);
352           }
353           catch (SAXException e) {
354             throw new TransformerException(new UserException(e));
355           }
356         }
357    
358         public void fatalError(TransformerException exception)
359                 throws TransformerException {
360           hadError = true;
361           SAXParseException spe = transform(exception);
362           try {
363             eh.fatalError(spe);
364           }
365           catch (SAXException e) {
366             throw new TransformerException(new UserException(e));
367           }
368         }
369    
370         SAXParseException transform(TransformerException exception) throws TransformerException {
371           Throwable cause = exception.getException();
372           // Xalan takes it upon itself to catch exceptions and pass them to the ErrorListener.
373           if (cause instanceof RuntimeException)
374             throw (RuntimeException)cause;
375           if (cause instanceof SAXException
376               || cause instanceof IncorrectSchemaException
377               || cause instanceof IOException)
378             throw exception;
379           SourceLocator locator = exception.getLocator();
380           if (locator == null)
381             return new SAXParseException(exception.getMessage(), null);
382           // Xalan sometimes loses the systemId; work around this.
383           String s = locator.getSystemId();
384           if (s == null)
385            s = systemId;
386           return new SAXParseException(exception.getMessage(),
387                                        null,
388                                        s,
389                                        locator.getLineNumber(),
390                                        -1);
391         }
392       }
393    
394      public Schema createSchema(InputSource in, PropertyMap properties)
395              throws IOException, SAXException, IncorrectSchemaException {
396        ErrorHandler eh = ValidateProperty.ERROR_HANDLER.get(properties);
397        SAXErrorListener errorListener = new SAXErrorListener(eh, in.getSystemId());
398        UserWrapErrorHandler ueh1 = new UserWrapErrorHandler(eh);
399        UserWrapErrorHandler ueh2 = new UserWrapErrorHandler(eh);
400        try {
401          PropertyMapBuilder builder = new PropertyMapBuilder(properties);
402          ValidateProperty.ERROR_HANDLER.put(builder, ueh1);
403          SAXSource source = createValidatingSource(in, builder.toPropertyMap(), ueh1);
404          source = createTransformingSource(source,
405                                            SchematronProperty.PHASE.get(properties),
406                                            properties.contains(SchematronProperty.DIAGNOSE),
407                                            in.getSystemId(),
408                                            ueh2);
409          TransformerFactory transformerFactory = (TransformerFactory)transformerFactoryClass.newInstance();
410          initTransformerFactory(transformerFactory);
411          transformerFactory.setErrorListener(errorListener);
412          Templates templates = transformerFactory.newTemplates(source);
413          return new SchemaImpl(templates, properties, supportedPropertyIds);
414        }
415        catch (TransformerConfigurationException e) {
416          throw toSAXException(e, errorListener.getHadError()
417                                  || ueh1.getHadErrorOrFatalError()
418                                  || ueh2.getHadErrorOrFatalError());
419        }
420        catch (InstantiationException e) {
421          throw new SAXException(e);
422        }
423        catch (IllegalAccessException e) {
424          throw new SAXException(e);
425        }
426      }
427    
428      private SAXSource createValidatingSource(InputSource in, PropertyMap properties, CountingErrorHandler ceh) throws SAXException {
429        Validator validator = schematronSchema.createValidator(properties);
430        XMLReaderCreator xrc = ValidateProperty.XML_READER_CREATOR.get(properties);
431        XMLReader xr = xrc.createXMLReader();
432        xr.setErrorHandler(ceh);
433        return new SAXSource(new ValidateStage(xr, validator, ceh), in);
434      }
435    
436      private SAXSource createTransformingSource(SAXSource in, String phase, boolean diagnose,
437                                                 String systemId, CountingErrorHandler ceh) throws SAXException {
438        try {
439          Transformer transformer = schematron.newTransformer();
440          transformer.setErrorListener(new DraconianErrorListener());
441          if (phase != null)
442            transformer.setParameter("phase", phase);
443          if (diagnose)
444            transformer.setParameter("diagnose", Boolean.TRUE);
445          return new SAXSource(new TransformStage(transformer, in, systemId, ceh, localizer),
446                               new InputSource(systemId));
447        }
448        catch (TransformerConfigurationException e) {
449          throw new SAXException(e);
450        }
451      }
452    
453      private SAXException toSAXException(TransformerException e, boolean hadError) throws IOException, IncorrectSchemaException {
454          return causeToSAXException(e.getException(), hadError);
455        }
456    
457      private SAXException causeToSAXException(Throwable cause, boolean hadError) throws IOException, IncorrectSchemaException {
458          if (cause instanceof RuntimeException)
459            throw (RuntimeException)cause;
460          if (cause instanceof IOException)
461            throw (IOException)cause;
462          if (cause instanceof IncorrectSchemaException)
463            throw (IncorrectSchemaException)cause;
464          if (cause instanceof SAXException)
465            return causeToSAXException(((SAXException)cause).getException(), hadError);
466          if (cause instanceof TransformerException)
467            return toSAXException((TransformerException)cause, hadError);
468          if (cause instanceof UserException)
469            return toSAXException((UserException)cause);
470          if (hadError)
471            throw new IncorrectSchemaException();
472          return new SAXException(localizer.message("unexpected_schema_creation_error"),
473                                  cause instanceof Exception ? (Exception)cause : null);
474        }
475    
476      private static SAXException toSAXException(UserException e) throws IOException, IncorrectSchemaException {
477          SAXException se = e.getException();
478          Exception cause = se.getException();
479          if (cause instanceof IncorrectSchemaException)
480            throw (IncorrectSchemaException)cause;
481          if (cause instanceof IOException)
482            throw (IOException)cause;
483          return se;
484        }
485    
486      private static String fullResourceName(String name) {
487        String className = SchemaReaderImpl.class.getName();
488        return className.substring(0, className.lastIndexOf('.')).replace('.', '/') + "/resources/" + name;
489      }
490    
491      private static InputStream getResourceAsStream(String resourceName) {
492        ClassLoader cl = SchemaReaderImpl.class.getClassLoader();
493        // XXX see if we should borrow 1.2 code from Service
494        if (cl == null)
495          return ClassLoader.getSystemResourceAsStream(resourceName);
496        else
497          return cl.getResourceAsStream(resourceName);
498      }
499    }