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(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
103        if (!schemaReaderConfigured) {
104            if (resourceResolver != null) {
105                schemaReader.setResourceResolver(resourceResolver);
106            } else if (resourceResolverFactory != null) {
107                resourceResolver = resourceResolverFactory.createResourceResolver(getCamelContext(), resourceUri);
108                // set the created resource resolver to the resourceResolver variable, so that it can 
109                // be accessed by the endpoint
110                schemaReader.setResourceResolver(resourceResolver);
111            } else {
112                schemaReader.setResourceResolver(new DefaultValidatorResourceResolverFactory().createResourceResolver(getCamelContext(), resourceUri));
113            }
114            schemaReader.setSchemaLanguage(getSchemaLanguage());
115            schemaReader.setSchemaFactory(getSchemaFactory());
116            
117            // force loading of schema at create time otherwise concurrent
118            // processing could cause thread safe issues for the
119            // javax.xml.validation.SchemaFactory
120            schemaReader.loadSchema();
121
122            // configure only once
123            schemaReaderConfigured = true;
124        }
125
126        ValidatingProcessor validator = new ValidatingProcessor(schemaReader);
127        configureValidator(validator);
128
129        return new ValidatorProducer(this, validator);
130    }
131
132
133
134    @Override
135    public Consumer createConsumer(Processor processor) throws Exception {
136        throw new UnsupportedOperationException("Cannot consume from validator");
137    }
138
139    @Override
140    public boolean isSingleton() {
141        return true;
142    }
143
144    protected void configureValidator(ValidatingProcessor validator) throws Exception {
145        validator.setErrorHandler(getErrorHandler());
146        validator.setUseDom(isUseDom());
147        validator.setUseSharedSchema(isUseSharedSchema());
148        validator.setFailOnNullBody(isFailOnNullBody());
149        validator.setFailOnNullHeader(isFailOnNullHeader());
150        validator.setHeaderName(getHeaderName());
151    }
152
153    public String getResourceUri() {
154        return resourceUri;
155    }
156
157    /**
158     * URL to a local resource on the classpath,or a reference to lookup a bean in the Registry,
159     * or a full URL to a remote resource or resource on the file system which contains the XSD to validate against.
160     */
161    public void setResourceUri(String resourceUri) {
162        this.resourceUri = resourceUri;
163    }
164
165    public String getSchemaLanguage() {
166        return schemaLanguage;
167    }
168
169    /**
170     * Configures the W3C XML Schema Namespace URI.
171     */
172    public void setSchemaLanguage(String schemaLanguage) {
173        this.schemaLanguage = schemaLanguage;
174    }
175
176    public SchemaFactory getSchemaFactory() {
177        return schemaFactory;
178    }
179
180    /**
181     * To use a custom javax.xml.validation.SchemaFactory
182     */
183    public void setSchemaFactory(SchemaFactory schemaFactory) {
184        this.schemaFactory = schemaFactory;
185    }
186
187    public ValidatorErrorHandler getErrorHandler() {
188        return errorHandler;
189    }
190
191    /**
192     * To use a custom org.apache.camel.processor.validation.ValidatorErrorHandler.
193     * <p/>
194     * The default error handler captures the errors and throws an exception.
195     */
196    public void setErrorHandler(ValidatorErrorHandler errorHandler) {
197        this.errorHandler = errorHandler;
198    }
199
200    public boolean isUseDom() {
201        return useDom;
202    }
203
204    /**
205     * Whether DOMSource/DOMResult or SaxSource/SaxResult should be used by the validator.
206     */
207    public void setUseDom(boolean useDom) {
208        this.useDom = useDom;
209    }
210
211    public boolean isUseSharedSchema() {
212        return useSharedSchema;
213    }
214
215    /**
216     * 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.
217     */
218    public void setUseSharedSchema(boolean useSharedSchema) {
219        this.useSharedSchema = useSharedSchema;
220    }
221
222    public LSResourceResolver getResourceResolver() {
223        return resourceResolver;
224    }
225
226    /**
227     * To use a custom LSResourceResolver. See also {@link #setResourceResolverFactory(ValidatorResourceResolverFactory)}
228     */
229    public void setResourceResolver(LSResourceResolver resourceResolver) {
230        this.resourceResolver = resourceResolver;
231    }
232
233    public ValidatorResourceResolverFactory getResourceResolverFactory() {
234        return resourceResolverFactory;
235    }
236
237    /** For creating a resource resolver which depends on the endpoint resource URI. 
238     * Must not be used in combination with method {@link #setResourceResolver(LSResourceResolver). 
239     * If not set then {@link DefaultValidatorResourceResolverFactory} is used 
240     */
241    public void setResourceResolverFactory(ValidatorResourceResolverFactory resourceResolverFactory) {
242        this.resourceResolverFactory = resourceResolverFactory;
243    }
244
245    public boolean isFailOnNullBody() {
246        return failOnNullBody;
247    }
248
249    /**
250     * Whether to fail if no body exists.
251     */
252    public void setFailOnNullBody(boolean failOnNullBody) {
253        this.failOnNullBody = failOnNullBody;
254    }
255
256    public boolean isFailOnNullHeader() {
257        return failOnNullHeader;
258    }
259
260    /**
261     * Whether to fail if no header exists when validating against a header.
262     */
263    public void setFailOnNullHeader(boolean failOnNullHeader) {
264        this.failOnNullHeader = failOnNullHeader;
265    }
266
267    public String getHeaderName() {
268        return headerName;
269    }
270
271    /**
272     * To validate against a header instead of the message body.
273     */
274    public void setHeaderName(String headerName) {
275        this.headerName = headerName;
276    }
277}