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.component.validator;
018
019import javax.xml.XMLConstants;
020import javax.xml.validation.SchemaFactory;
021
022import org.w3c.dom.ls.LSResourceResolver;
023
024import org.apache.camel.Component;
025import org.apache.camel.Consumer;
026import org.apache.camel.Processor;
027import org.apache.camel.Producer;
028import org.apache.camel.api.management.ManagedOperation;
029import org.apache.camel.api.management.ManagedResource;
030import org.apache.camel.impl.DefaultEndpoint;
031import org.apache.camel.processor.validation.DefaultValidationErrorHandler;
032import org.apache.camel.processor.validation.SchemaReader;
033import org.apache.camel.processor.validation.ValidatingProcessor;
034import org.apache.camel.processor.validation.ValidatorErrorHandler;
035import org.apache.camel.spi.Metadata;
036import org.apache.camel.spi.UriEndpoint;
037import org.apache.camel.spi.UriParam;
038import org.apache.camel.spi.UriPath;
039
040
041/**
042 * Validates the payload of a message using XML Schema and JAXP Validation.
043 */
044@ManagedResource(description = "Managed ValidatorEndpoint")
045@UriEndpoint(firstVersion = "1.1.0", scheme = "validator", title = "Validator", syntax = "validator:resourceUri", producerOnly = true, label = "core,validation")
046public class ValidatorEndpoint extends DefaultEndpoint {
047
048    @UriPath(description = "URL to a local resource on the classpath, or a reference to lookup a bean in the Registry,"
049            + " or a full URL to a remote resource or resource on the file system which contains the XSD to validate against.")
050    @Metadata(required = "true")
051    private String resourceUri;
052    @UriParam(defaultValue = XMLConstants.W3C_XML_SCHEMA_NS_URI, label = "advanced",
053            description = "Configures the W3C XML Schema Namespace URI.")
054    private String schemaLanguage = XMLConstants.W3C_XML_SCHEMA_NS_URI;
055    @UriParam(label = "advanced", description = "To use a custom javax.xml.validation.SchemaFactory")
056    private SchemaFactory schemaFactory;
057    @UriParam(label = "advanced", description = "To use a custom org.apache.camel.processor.validation.ValidatorErrorHandler. The default error handler captures the errors and throws an exception.")
058    private ValidatorErrorHandler errorHandler = new DefaultValidationErrorHandler();
059    @UriParam(label = "advanced", description = "Whether DOMSource/DOMResult or SaxSource/SaxResult should be used by the validator.")
060    private boolean useDom;
061    @UriParam(defaultValue = "true", label = "advanced",
062            description = "Whether the Schema instance should be shared or not. This option is introduced to work around a JDK 1.6.x bug. Xerces should not have this issue.")
063    private boolean useSharedSchema = true;
064    @UriParam(label = "advanced", description = "To use a custom LSResourceResolver.  Do not use together with resourceResolverFactory")
065    private LSResourceResolver resourceResolver;
066    @UriParam(label = "advanced", description = "To use a custom LSResourceResolver which depends on a dynamic endpoint resource URI. " + //
067    "The default resource resolver factory resturns a resource resolver which can read files from the class path and file system. Do not use together with resourceResolver.")
068    private ValidatorResourceResolverFactory resourceResolverFactory;
069    @UriParam(defaultValue = "true", description = "Whether to fail if no body exists.")
070    private boolean failOnNullBody = true;
071    @UriParam(defaultValue = "true", description = "Whether to fail if no header exists when validating against a header.")
072    private boolean failOnNullHeader = true;
073    @UriParam(description = "To validate against a header instead of the message body.")
074    private String headerName;
075
076    /**
077     * We need a one-to-one relation between endpoint and schema reader in order
078     * to be able to clear the cached schema in the schema reader. See method
079     * {@link #clearCachedSchema}.
080     */
081    private final SchemaReader schemaReader;
082    private volatile boolean schemaReaderConfigured;
083
084    public ValidatorEndpoint() {
085        schemaReader = new SchemaReader();
086    }
087
088    public ValidatorEndpoint(String endpointUri, Component component, String resourceUri) {
089        super(endpointUri, component);
090        this.resourceUri = resourceUri;
091        schemaReader = new SchemaReader(getCamelContext(), resourceUri);
092    }
093
094    @ManagedOperation(description = "Clears the cached schema, forcing to re-load the schema on next request")
095    public void clearCachedSchema() {
096        
097        schemaReader.setSchema(null); // will cause to reload the schema
098    }
099
100    @Override
101    public Producer createProducer() throws Exception {
102        if (!schemaReaderConfigured) {
103            if (resourceResolver != null) {
104                schemaReader.setResourceResolver(resourceResolver);
105            } else if (resourceResolverFactory != null) {
106                resourceResolver = resourceResolverFactory.createResourceResolver(getCamelContext(), resourceUri);
107                // set the created resource resolver to the resourceResolver variable, so that it can 
108                // be accessed by the endpoint
109                schemaReader.setResourceResolver(resourceResolver);
110            } else {
111                schemaReader.setResourceResolver(new DefaultValidatorResourceResolverFactory().createResourceResolver(getCamelContext(), resourceUri));
112            }
113            schemaReader.setSchemaLanguage(getSchemaLanguage());
114            schemaReader.setSchemaFactory(getSchemaFactory());
115            
116            // force loading of schema at create time otherwise concurrent
117            // processing could cause thread safe issues for the
118            // javax.xml.validation.SchemaFactory
119            schemaReader.loadSchema();
120
121            // configure only once
122            schemaReaderConfigured = true;
123        }
124
125        ValidatingProcessor validator = new ValidatingProcessor(schemaReader);
126        configureValidator(validator);
127
128        return new ValidatorProducer(this, validator);
129    }
130
131    @Override
132    public Consumer createConsumer(Processor processor) throws Exception {
133        throw new UnsupportedOperationException("Cannot consume from validator");
134    }
135
136    @Override
137    public boolean isSingleton() {
138        return true;
139    }
140
141    protected void configureValidator(ValidatingProcessor validator) throws Exception {
142        validator.setErrorHandler(getErrorHandler());
143        validator.setUseDom(isUseDom());
144        validator.setUseSharedSchema(isUseSharedSchema());
145        validator.setFailOnNullBody(isFailOnNullBody());
146        validator.setFailOnNullHeader(isFailOnNullHeader());
147        validator.setHeaderName(getHeaderName());
148    }
149
150    public String getResourceUri() {
151        return resourceUri;
152    }
153
154    /**
155     * URL to a local resource on the classpath,or a reference to lookup a bean in the Registry,
156     * or a full URL to a remote resource or resource on the file system which contains the XSD to validate against.
157     */
158    public void setResourceUri(String resourceUri) {
159        this.resourceUri = resourceUri;
160    }
161
162    public String getSchemaLanguage() {
163        return schemaLanguage;
164    }
165
166    /**
167     * Configures the W3C XML Schema Namespace URI.
168     */
169    public void setSchemaLanguage(String schemaLanguage) {
170        this.schemaLanguage = schemaLanguage;
171    }
172
173    public SchemaFactory getSchemaFactory() {
174        return schemaFactory;
175    }
176
177    /**
178     * To use a custom javax.xml.validation.SchemaFactory
179     */
180    public void setSchemaFactory(SchemaFactory schemaFactory) {
181        this.schemaFactory = schemaFactory;
182    }
183
184    public ValidatorErrorHandler getErrorHandler() {
185        return errorHandler;
186    }
187
188    /**
189     * To use a custom org.apache.camel.processor.validation.ValidatorErrorHandler.
190     * <p/>
191     * The default error handler captures the errors and throws an exception.
192     */
193    public void setErrorHandler(ValidatorErrorHandler errorHandler) {
194        this.errorHandler = errorHandler;
195    }
196
197    public boolean isUseDom() {
198        return useDom;
199    }
200
201    /**
202     * Whether DOMSource/DOMResult or SaxSource/SaxResult should be used by the validator.
203     */
204    public void setUseDom(boolean useDom) {
205        this.useDom = useDom;
206    }
207
208    public boolean isUseSharedSchema() {
209        return useSharedSchema;
210    }
211
212    /**
213     * Whether the Schema instance should be shared or not. This option is introduced to work around a JDK 1.6.x bug. Xerces should not have this issue.
214     */
215    public void setUseSharedSchema(boolean useSharedSchema) {
216        this.useSharedSchema = useSharedSchema;
217    }
218
219    public LSResourceResolver getResourceResolver() {
220        return resourceResolver;
221    }
222
223    /**
224     * To use a custom LSResourceResolver. See also {@link #setResourceResolverFactory(ValidatorResourceResolverFactory)}
225     */
226    public void setResourceResolver(LSResourceResolver resourceResolver) {
227        this.resourceResolver = resourceResolver;
228    }
229
230    public ValidatorResourceResolverFactory getResourceResolverFactory() {
231        return resourceResolverFactory;
232    }
233
234    /** For creating a resource resolver which depends on the endpoint resource URI. 
235     * Must not be used in combination with method {@link #setResourceResolver(LSResourceResolver)}. 
236     * If not set then {@link DefaultValidatorResourceResolverFactory} is used 
237     */
238    public void setResourceResolverFactory(ValidatorResourceResolverFactory resourceResolverFactory) {
239        this.resourceResolverFactory = resourceResolverFactory;
240    }
241
242    public boolean isFailOnNullBody() {
243        return failOnNullBody;
244    }
245
246    /**
247     * Whether to fail if no body exists.
248     */
249    public void setFailOnNullBody(boolean failOnNullBody) {
250        this.failOnNullBody = failOnNullBody;
251    }
252
253    public boolean isFailOnNullHeader() {
254        return failOnNullHeader;
255    }
256
257    /**
258     * Whether to fail if no header exists when validating against a header.
259     */
260    public void setFailOnNullHeader(boolean failOnNullHeader) {
261        this.failOnNullHeader = failOnNullHeader;
262    }
263
264    public String getHeaderName() {
265        return headerName;
266    }
267
268    /**
269     * To validate against a header instead of the message body.
270     */
271    public void setHeaderName(String headerName) {
272        this.headerName = headerName;
273    }
274}