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.servicemix.validation;
018    
019    import java.io.IOException;
020    
021    import javax.jbi.JBIException;
022    import javax.jbi.messaging.Fault;
023    import javax.jbi.messaging.MessageExchange;
024    import javax.jbi.messaging.MessagingException;
025    import javax.jbi.messaging.NormalizedMessage;
026    import javax.xml.parsers.ParserConfigurationException;
027    import javax.xml.transform.Source;
028    import javax.xml.transform.TransformerException;
029    import javax.xml.transform.dom.DOMResult;
030    import javax.xml.transform.dom.DOMSource;
031    import javax.xml.transform.stream.StreamSource;
032    import javax.xml.validation.Schema;
033    import javax.xml.validation.SchemaFactory;
034    import javax.xml.validation.Validator;
035    
036    import org.apache.servicemix.common.endpoints.ProviderEndpoint;
037    import org.apache.servicemix.common.util.MessageUtil;
038    import org.apache.servicemix.jbi.exception.FaultException;
039    import org.apache.servicemix.jbi.jaxp.SourceTransformer;
040    import org.apache.servicemix.jbi.jaxp.StringSource;
041    import org.apache.servicemix.validation.handler.CountingErrorHandlerFactory;
042    import org.apache.servicemix.validation.handler.MessageAwareErrorHandler;
043    import org.apache.servicemix.validation.handler.MessageAwareErrorHandlerFactory;
044    import org.springframework.core.io.Resource;
045    
046    import org.xml.sax.SAXException;
047    
048    /**
049     * @org.apache.xbean.XBean element="endpoint"
050     * @author lhein
051     */
052    public class ValidationEndpoint extends ProviderEndpoint implements
053            ValidationEndpointType {
054    
055        public static final String FAULT_FLOW = "FAULT_FLOW";
056    
057        public static final String FAULT_JBI = "FAULT_JBI";
058    
059        public static final String TAG_RESULT_START = "<result>";
060    
061        public static final String TAG_RESULT_END = "</result>";
062    
063        public static final String TAG_WARNING_START = "<warning>";
064    
065        public static final String TAG_WARNING_END = "</warning>";
066    
067        public static final String TAG_ERROR_START = "<error>";
068    
069        public static final String TAG_ERROR_END = "</error>";
070    
071        public static final String TAG_FATAL_START = "<fatalError>";
072    
073        public static final String TAG_FATAL_END = "</fatalError>";
074    
075        private String handlingErrorMethod = "FAULT_JBI";
076    
077        private Schema schema;
078    
079        private String schemaLanguage = "http://www.w3.org/2001/XMLSchema";
080    
081        private Source schemaSource;
082    
083        private Resource schemaResource;
084    
085        private MessageAwareErrorHandlerFactory errorHandlerFactory = new CountingErrorHandlerFactory();
086    
087        private SourceTransformer sourceTransformer = new SourceTransformer();
088    
089        /*
090         * (non-Javadoc)
091         * 
092         * @see org.apache.servicemix.common.endpoints.ProviderEndpoint#start()
093         */
094        @Override
095        public void start() throws Exception {
096            super.start();
097    
098            try {
099                if (schema == null) {
100                    SchemaFactory factory = SchemaFactory
101                            .newInstance(schemaLanguage);
102    
103                    if (schemaSource == null) {
104                        if (schemaResource == null) {
105                            throw new JBIException(
106                                    "You must specify a schema, schemaSource or schemaResource property");
107                        }
108                        if (schemaResource.getURL() == null) {
109                            schemaSource = new StreamSource(schemaResource
110                                    .getInputStream());
111                        } else {
112                            schemaSource = new StreamSource(schemaResource
113                                    .getInputStream(), schemaResource.getURL()
114                                        .toExternalForm());
115                        }
116                    }
117                    schema = factory.newSchema(schemaSource);
118                }
119            } catch (IOException e) {
120                throw new JBIException("Failed to load schema: " + e, e);
121            } catch (SAXException e) {
122                throw new JBIException("Failed to load schema: " + e, e);
123            }
124        }
125    
126        /*
127         * (non-Javadoc)
128         * 
129         * @see org.apache.servicemix.common.endpoints.ProviderEndpoint#stop()
130         */
131        @Override
132        public void stop() throws Exception {
133            super.stop();
134        }
135    
136        /*
137         * (non-Javadoc)
138         * 
139         * @see org.apache.servicemix.common.endpoints.ProviderEndpoint#processInOnly(javax.jbi.messaging.MessageExchange,
140         *      javax.jbi.messaging.NormalizedMessage)
141         */
142        @Override
143        protected void processInOnly(MessageExchange exchange, NormalizedMessage in)
144            throws Exception {
145            NormalizedMessage out = exchange.createMessage();
146            Fault fault = exchange.createFault();
147            this.startValidation(exchange, in, out, fault);
148    
149            if (fault.getContent() != null) {
150                throw new RuntimeException(sourceTransformer.contentToString(fault));
151            }
152        }
153    
154        /*
155         * (non-Javadoc)
156         * 
157         * @see org.apache.servicemix.common.endpoints.ProviderEndpoint#processInOut(javax.jbi.messaging.MessageExchange,
158         *      javax.jbi.messaging.NormalizedMessage,
159         *      javax.jbi.messaging.NormalizedMessage)
160         */
161        @Override
162        protected void processInOut(MessageExchange exchange, NormalizedMessage in,
163                NormalizedMessage out) throws Exception {
164            Fault fault = exchange.createFault();
165            this.startValidation(exchange, in, out, fault);
166            if (fault.getContent() != null) {
167                exchange.setFault(fault);
168            }
169        }
170    
171        /**
172         * does the validation
173         * 
174         * @param exchange
175         * @param in
176         * @param out
177         * @param fault
178         * @throws Exception
179         */
180        public void startValidation(MessageExchange exchange, NormalizedMessage in,
181                NormalizedMessage out, Fault fault) throws Exception {
182            Validator validator = schema.newValidator();
183    
184            // create a new errorHandler and set it on the validator
185            MessageAwareErrorHandler errorHandler = errorHandlerFactory
186                    .createMessageAwareErrorHandler();
187            validator.setErrorHandler(errorHandler);
188            DOMResult result = new DOMResult();
189    
190            fault.setContent(null);
191    
192            try {
193                // Only DOMSource and SAXSource are allowed for validating
194                // See
195                // http://java.sun.com/j2se/1.5.0/docs/api/javax/xml/validation/
196                // Validator.html#validate(javax.xml.transform.Source,%20javax.xml.transform.Result)
197                // As we expect a DOMResult as output, we must ensure that the input
198                // is a DOMSource
199                DOMSource src = sourceTransformer.toDOMSource(in.getContent());
200    
201                // call the validation method
202                doValidation(validator, src, result);
203    
204                // check if there were errors while validating
205                if (errorHandler.hasErrors()) {
206                    /*
207                     * check if this error handler supports the capturing of error
208                     * messages.
209                     */
210                    if (errorHandler.capturesMessages()) {
211                        /*
212                         * In descending order of preference select a format to use.
213                         * If neither DOMSource, StringSource or String are
214                         * supported throw a messaging exception.
215                         */
216                        if (errorHandler.supportsMessageFormat(DOMSource.class)) {
217                            fault.setContent((DOMSource) errorHandler
218                                    .getMessagesAs(DOMSource.class));
219                        } else if (errorHandler
220                                .supportsMessageFormat(StringSource.class)) {
221                            fault.setContent(sourceTransformer
222                                    .toDOMSource((StringSource) errorHandler
223                                            .getMessagesAs(StringSource.class)));
224                        } else if (errorHandler.supportsMessageFormat(String.class)) {
225                            fault.setContent(sourceTransformer
226                                    .toDOMSource(new StringSource(
227                                            (String) errorHandler
228                                                    .getMessagesAs(String.class))));
229                        } else {
230                            throw new MessagingException(
231                                    "MessageAwareErrorHandler implementation "
232                                            + errorHandler.getClass().getName()
233                                            + " does not support a compatible error message format.");
234                        }
235                    } else {
236                        /*
237                         * we can't do much here if the ErrorHandler implementation
238                         * does not support capturing messages
239                         */
240                        StringBuffer resultString = new StringBuffer();
241                        resultString.append(TAG_RESULT_START);
242                        resultString.append('\n');
243                        resultString.append(TAG_WARNING_START);
244                        resultString.append(errorHandler.getWarningCount());
245                        resultString.append(TAG_WARNING_END);
246                        resultString.append('\n');
247                        resultString.append(TAG_ERROR_START);
248                        resultString.append(errorHandler.getErrorCount());
249                        resultString.append(TAG_ERROR_END);
250                        resultString.append('\n');
251                        resultString.append(TAG_FATAL_START);
252                        resultString.append(errorHandler.getFatalErrorCount());
253                        resultString.append(TAG_FATAL_END);
254                        resultString.append('\n');
255                        resultString.append(TAG_RESULT_END);
256                        resultString.append('\n');
257                        fault.setContent(new StringSource(resultString.toString()));
258                    }
259    
260                    if (!handlingErrorMethod.equalsIgnoreCase(FAULT_FLOW)) {
261                        // HANDLE AS JBI FAULT
262                        throw new FaultException("Failed to validate against schema: " + schema, exchange, fault);
263                    } else {
264                        MessageUtil.transfer(fault, out);
265                    }
266                } else {
267                    // Retrieve the ouput of the validation
268                    // as it may have been changed by the validator
269                    out.setContent(new DOMSource(result.getNode(), result
270                            .getSystemId()));
271                }
272            } catch (SAXException e) {
273                throw new MessagingException(e);
274            } catch (IOException e) {
275                throw new MessagingException(e);
276            } catch (ParserConfigurationException e) {
277                throw new MessagingException(e);
278            } catch (TransformerException e) {
279                throw new MessagingException(e);
280            }
281        }
282    
283        /**
284         * does the validation
285         * 
286         * @param validator
287         * @param src
288         * @param result
289         * @throws SAXException
290         * @throws IOException
291         */
292        protected void doValidation(Validator validator, DOMSource src,
293                DOMResult result) throws SAXException, IOException {
294            validator.validate(src, result);
295        }
296    
297        /*
298         * (non-Javadoc)
299         * 
300         * @see org.apache.servicemix.common.endpoints.SimpleEndpoint#fail(javax.jbi.messaging.MessageExchange,
301         *      java.lang.Exception)
302         */
303        protected void fail(MessageExchange messageExchange, Exception e)
304            throws MessagingException {
305            super.fail(messageExchange, e);
306        }
307    
308        /*
309         * (non-Javadoc)
310         * 
311         * @see org.apache.servicemix.common.endpoints.SimpleEndpoint#send(javax.jbi.messaging.MessageExchange)
312         */
313        protected void send(MessageExchange messageExchange)
314            throws MessagingException {
315            super.send(messageExchange);
316        }
317    
318        /*
319         * (non-Javadoc)
320         * 
321         * @see org.apache.servicemix.common.endpoints.SimpleEndpoint#sendSync(javax.jbi.messaging.MessageExchange)
322         */
323        protected void sendSync(MessageExchange messageExchange)
324            throws MessagingException {
325            super.sendSync(messageExchange);
326        }
327    
328        /*
329         * (non-Javadoc)
330         * 
331         * @see org.apache.servicemix.common.endpoints.SimpleEndpoint#done(javax.jbi.messaging.MessageExchange)
332         */
333        protected void done(MessageExchange messageExchange)
334            throws MessagingException {
335            super.done(messageExchange);
336        }
337    
338        public String getHandlingErrorMethod() {
339            return handlingErrorMethod;
340        }
341    
342        public void setHandlingErrorMethod(String handlingErrorMethod) {
343            this.handlingErrorMethod = handlingErrorMethod;
344        }
345    
346        public Schema getSchema() {
347            return schema;
348        }
349    
350        public void setSchema(Schema schema) {
351            this.schema = schema;
352        }
353    
354        public String getSchemaLanguage() {
355            return schemaLanguage;
356        }
357    
358        public void setSchemaLanguage(String schemaLanguage) {
359            this.schemaLanguage = schemaLanguage;
360        }
361    
362        public Source getSchemaSource() {
363            return schemaSource;
364        }
365    
366        public void setSchemaSource(Source schemaSource) {
367            this.schemaSource = schemaSource;
368        }
369    
370        public Resource getSchemaResource() {
371            return schemaResource;
372        }
373    
374        public void setSchemaResource(Resource schemaResource) {
375            this.schemaResource = schemaResource;
376        }
377    
378        public MessageAwareErrorHandlerFactory getErrorHandlerFactory() {
379            return errorHandlerFactory;
380        }
381    
382        public void setErrorHandlerFactory(
383                MessageAwareErrorHandlerFactory errorHandlerFactory) {
384            this.errorHandlerFactory = errorHandlerFactory;
385        }
386    }