001/** 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.camel.processor.validation; 018 019import java.io.File; 020import java.io.IOException; 021import java.io.InputStream; 022import java.net.URL; 023import java.util.Collections; 024 025import javax.xml.XMLConstants; 026import javax.xml.parsers.ParserConfigurationException; 027import javax.xml.transform.Result; 028import javax.xml.transform.Source; 029import javax.xml.transform.dom.DOMResult; 030import javax.xml.transform.dom.DOMSource; 031import javax.xml.transform.sax.SAXResult; 032import javax.xml.transform.sax.SAXSource; 033import javax.xml.transform.stax.StAXSource; 034import javax.xml.transform.stream.StreamSource; 035import javax.xml.validation.Schema; 036import javax.xml.validation.SchemaFactory; 037import javax.xml.validation.Validator; 038 039import org.w3c.dom.Node; 040import org.w3c.dom.ls.LSResourceResolver; 041 042import org.xml.sax.SAXException; 043import org.xml.sax.SAXParseException; 044 045import org.apache.camel.AsyncCallback; 046import org.apache.camel.AsyncProcessor; 047import org.apache.camel.Exchange; 048import org.apache.camel.ExpectedBodyTypeException; 049import org.apache.camel.RuntimeTransformException; 050import org.apache.camel.TypeConverter; 051import org.apache.camel.converter.jaxp.XmlConverter; 052import org.apache.camel.util.AsyncProcessorHelper; 053import org.apache.camel.util.IOHelper; 054import org.slf4j.Logger; 055import org.slf4j.LoggerFactory; 056 057import static org.apache.camel.processor.validation.SchemaReader.ACCESS_EXTERNAL_DTD; 058 059/** 060 * A processor which validates the XML version of the inbound message body 061 * against some schema either in XSD or RelaxNG 062 */ 063public class ValidatingProcessor implements AsyncProcessor { 064 private static final Logger LOG = LoggerFactory.getLogger(ValidatingProcessor.class); 065 private final SchemaReader schemaReader; 066 private ValidatorErrorHandler errorHandler = new DefaultValidationErrorHandler(); 067 private final XmlConverter converter = new XmlConverter(); 068 private boolean useDom; 069 private boolean useSharedSchema = true; 070 private boolean failOnNullBody = true; 071 private boolean failOnNullHeader = true; 072 private String headerName; 073 074 public ValidatingProcessor() { 075 schemaReader = new SchemaReader(); 076 } 077 078 public ValidatingProcessor(SchemaReader schemaReader) { 079 // schema reader can be a singelton per schema, therefore make reuse, see ValidatorEndpoint and ValidatorProducer 080 this.schemaReader = schemaReader; 081 } 082 083 public void process(Exchange exchange) throws Exception { 084 AsyncProcessorHelper.process(this, exchange); 085 } 086 087 public boolean process(Exchange exchange, AsyncCallback callback) { 088 try { 089 doProcess(exchange); 090 } catch (Exception e) { 091 exchange.setException(e); 092 } 093 callback.done(true); 094 return true; 095 } 096 097 protected void doProcess(Exchange exchange) throws Exception { 098 Schema schema; 099 if (isUseSharedSchema()) { 100 schema = getSchema(); 101 } else { 102 schema = createSchema(); 103 } 104 105 Validator validator = schema.newValidator(); 106 // turn off access to external schema by default 107 if (!Boolean.parseBoolean(exchange.getContext().getGlobalOptions().get(ACCESS_EXTERNAL_DTD))) { 108 try { 109 LOG.debug("Configuring Validator to not allow access to external DTD/Schema"); 110 validator.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); 111 validator.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); 112 } catch (SAXException e) { 113 LOG.warn(e.getMessage(), e); 114 } 115 } 116 117 // the underlying input stream, which we need to close to avoid locking files or other resources 118 Source source = null; 119 InputStream is = null; 120 try { 121 Result result = null; 122 // only convert to input stream if really needed 123 if (isInputStreamNeeded(exchange)) { 124 is = getContentToValidate(exchange, InputStream.class); 125 if (is != null) { 126 source = getSource(exchange, is); 127 } 128 } else { 129 Object content = getContentToValidate(exchange); 130 if (content != null) { 131 source = getSource(exchange, content); 132 } 133 } 134 135 if (shouldUseHeader()) { 136 if (source == null && isFailOnNullHeader()) { 137 throw new NoXmlHeaderValidationException(exchange, headerName); 138 } 139 } else { 140 if (source == null && isFailOnNullBody()) { 141 throw new NoXmlBodyValidationException(exchange); 142 } 143 } 144 145 //CAMEL-7036 We don't need to set the result if the source is an instance of StreamSource 146 if (source instanceof DOMSource) { 147 result = new DOMResult(); 148 } else if (source instanceof SAXSource) { 149 result = new SAXResult(); 150 } else if (source instanceof StAXSource || source instanceof StreamSource) { 151 result = null; 152 } 153 154 if (source != null) { 155 // create a new errorHandler and set it on the validator 156 // must be a local instance to avoid problems with concurrency (to be 157 // thread safe) 158 ValidatorErrorHandler handler = errorHandler.getClass().newInstance(); 159 validator.setErrorHandler(handler); 160 161 try { 162 LOG.trace("Validating {}", source); 163 validator.validate(source, result); 164 handler.handleErrors(exchange, schema, result); 165 } catch (SAXParseException e) { 166 // can be thrown for non well formed XML 167 throw new SchemaValidationException(exchange, schema, Collections.singletonList(e), 168 Collections.<SAXParseException>emptyList(), 169 Collections.<SAXParseException>emptyList()); 170 } 171 } 172 } finally { 173 IOHelper.close(is); 174 } 175 } 176 177 private Object getContentToValidate(Exchange exchange) { 178 if (shouldUseHeader()) { 179 return exchange.getIn().getHeader(headerName); 180 } else { 181 return exchange.getIn().getBody(); 182 } 183 } 184 185 private <T> T getContentToValidate(Exchange exchange, Class<T> clazz) { 186 if (shouldUseHeader()) { 187 return exchange.getIn().getHeader(headerName, clazz); 188 } else { 189 return exchange.getIn().getBody(clazz); 190 } 191 } 192 193 private boolean shouldUseHeader() { 194 return headerName != null; 195 } 196 197 public void loadSchema() throws Exception { 198 schemaReader.loadSchema(); 199 } 200 201 // Properties 202 // ----------------------------------------------------------------------- 203 204 public Schema getSchema() throws IOException, SAXException { 205 return schemaReader.getSchema(); 206 } 207 208 public void setSchema(Schema schema) { 209 schemaReader.setSchema(schema); 210 } 211 212 public String getSchemaLanguage() { 213 return schemaReader.getSchemaLanguage(); 214 } 215 216 public void setSchemaLanguage(String schemaLanguage) { 217 schemaReader.setSchemaLanguage(schemaLanguage); 218 } 219 220 public Source getSchemaSource() throws IOException { 221 return schemaReader.getSchemaSource(); 222 } 223 224 public void setSchemaSource(Source schemaSource) { 225 schemaReader.setSchemaSource(schemaSource); 226 } 227 228 public URL getSchemaUrl() { 229 return schemaReader.getSchemaUrl(); 230 } 231 232 public void setSchemaUrl(URL schemaUrl) { 233 schemaReader.setSchemaUrl(schemaUrl); 234 } 235 236 public File getSchemaFile() { 237 return schemaReader.getSchemaFile(); 238 } 239 240 public void setSchemaFile(File schemaFile) { 241 schemaReader.setSchemaFile(schemaFile); 242 } 243 244 public byte[] getSchemaAsByteArray() { 245 return schemaReader.getSchemaAsByteArray(); 246 } 247 248 public void setSchemaAsByteArray(byte[] schemaAsByteArray) { 249 schemaReader.setSchemaAsByteArray(schemaAsByteArray); 250 } 251 252 public SchemaFactory getSchemaFactory() { 253 return schemaReader.getSchemaFactory(); 254 } 255 256 public void setSchemaFactory(SchemaFactory schemaFactory) { 257 schemaReader.setSchemaFactory(schemaFactory); 258 } 259 260 public ValidatorErrorHandler getErrorHandler() { 261 return errorHandler; 262 } 263 264 public void setErrorHandler(ValidatorErrorHandler errorHandler) { 265 this.errorHandler = errorHandler; 266 } 267 268 @Deprecated 269 public boolean isUseDom() { 270 return useDom; 271 } 272 273 /** 274 * Sets whether DOMSource and DOMResult should be used. 275 * 276 * @param useDom true to use DOM otherwise 277 */ 278 @Deprecated 279 public void setUseDom(boolean useDom) { 280 this.useDom = useDom; 281 } 282 283 public boolean isUseSharedSchema() { 284 return useSharedSchema; 285 } 286 287 public void setUseSharedSchema(boolean useSharedSchema) { 288 this.useSharedSchema = useSharedSchema; 289 } 290 291 public LSResourceResolver getResourceResolver() { 292 return schemaReader.getResourceResolver(); 293 } 294 295 public void setResourceResolver(LSResourceResolver resourceResolver) { 296 schemaReader.setResourceResolver(resourceResolver); 297 } 298 299 public boolean isFailOnNullBody() { 300 return failOnNullBody; 301 } 302 303 public void setFailOnNullBody(boolean failOnNullBody) { 304 this.failOnNullBody = failOnNullBody; 305 } 306 307 public boolean isFailOnNullHeader() { 308 return failOnNullHeader; 309 } 310 311 public void setFailOnNullHeader(boolean failOnNullHeader) { 312 this.failOnNullHeader = failOnNullHeader; 313 } 314 315 public String getHeaderName() { 316 return headerName; 317 } 318 319 public void setHeaderName(String headerName) { 320 this.headerName = headerName; 321 } 322 323 // Implementation methods 324 // ----------------------------------------------------------------------- 325 326 protected SchemaFactory createSchemaFactory() { 327 return schemaReader.createSchemaFactory(); 328 } 329 330 protected Source createSchemaSource() throws IOException { 331 return schemaReader.createSchemaSource(); 332 } 333 334 protected Schema createSchema() throws SAXException, IOException { 335 return schemaReader.createSchema(); 336 } 337 338 /** 339 * Checks whether we need an {@link InputStream} to access the message body or header. 340 * <p/> 341 * Depending on the content in the message body or header, we may not need to convert 342 * to {@link InputStream}. 343 * 344 * @param exchange the current exchange 345 * @return <tt>true</tt> to convert to {@link InputStream} beforehand converting to {@link Source} afterwards. 346 */ 347 protected boolean isInputStreamNeeded(Exchange exchange) { 348 Object content = getContentToValidate(exchange); 349 if (content == null) { 350 return false; 351 } 352 353 if (content instanceof InputStream) { 354 return true; 355 } else if (content instanceof Source) { 356 return false; 357 } else if (content instanceof String) { 358 return false; 359 } else if (content instanceof byte[]) { 360 return false; 361 } else if (content instanceof Node) { 362 return false; 363 } else if (exchange.getContext().getTypeConverterRegistry().lookup(Source.class, content.getClass()) != null) { 364 //there is a direct and hopefully optimized converter to Source 365 return false; 366 } 367 // yes an input stream is needed 368 return true; 369 } 370 371 /** 372 * Converts the inbound body or header to a {@link Source}, if it is <b>not</b> already a {@link Source}. 373 * <p/> 374 * This implementation will prefer to source in the following order: 375 * <ul> 376 * <li>DOM - DOM if explicit configured to use DOM</li> 377 * <li>SAX - SAX as 2nd choice</li> 378 * <li>Stream - Stream as 3rd choice</li> 379 * <li>DOM - DOM as 4th choice</li> 380 * </ul> 381 */ 382 protected Source getSource(Exchange exchange, Object content) { 383 if (isUseDom()) { 384 // force DOM 385 return exchange.getContext().getTypeConverter().tryConvertTo(DOMSource.class, exchange, content); 386 } 387 388 // body or header may already be a source 389 if (content instanceof Source) { 390 return (Source) content; 391 } 392 Source source = null; 393 if (content instanceof InputStream) { 394 return new StreamSource((InputStream) content); 395 } 396 if (content != null) { 397 TypeConverter tc = exchange.getContext().getTypeConverterRegistry().lookup(Source.class, content.getClass()); 398 if (tc != null) { 399 source = tc.convertTo(Source.class, exchange, content); 400 } 401 } 402 403 if (source == null) { 404 // then try SAX 405 source = exchange.getContext().getTypeConverter().tryConvertTo(SAXSource.class, exchange, content); 406 } 407 if (source == null) { 408 // then try stream 409 source = exchange.getContext().getTypeConverter().tryConvertTo(StreamSource.class, exchange, content); 410 } 411 if (source == null) { 412 // and fallback to DOM 413 source = exchange.getContext().getTypeConverter().tryConvertTo(DOMSource.class, exchange, content); 414 } 415 if (source == null) { 416 if (isFailOnNullBody()) { 417 throw new ExpectedBodyTypeException(exchange, Source.class); 418 } else { 419 try { 420 source = converter.toDOMSource(converter.createDocument()); 421 } catch (ParserConfigurationException e) { 422 throw new RuntimeTransformException(e); 423 } 424 } 425 } 426 return source; 427 } 428 429}