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 }