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