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