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.handler;
018    
019    import java.io.IOException;
020    import java.io.PrintWriter;
021    import java.io.StringWriter;
022    
023    import javax.jbi.messaging.MessagingException;
024    import javax.xml.parsers.ParserConfigurationException;
025    import javax.xml.transform.TransformerException;
026    import javax.xml.transform.dom.DOMSource;
027    
028    import org.xml.sax.SAXException;
029    import org.xml.sax.SAXParseException;
030    
031    import org.apache.servicemix.jbi.jaxp.SourceTransformer;
032    import org.apache.servicemix.jbi.jaxp.StringSource;
033    import org.apache.servicemix.validation.ValidationEndpoint;
034    
035    /**
036     * An implementation of {@link ErrorHandler} which aggregates all warnings and
037     * error messages into a StringBuffer.
038     *
039     * @version $Revision: 359186 $
040     */
041    public class MessageAggregatingErrorHandler implements MessageAwareErrorHandler {
042        
043        private static final String OPEN_CDATA = "<![CDATA[";
044        private static final String CLOSE_CDATA = "]]>";
045        private static final String OPEN_ERROR = ValidationEndpoint.TAG_ERROR_START;
046        private static final String CLOSE_ERROR = ValidationEndpoint.TAG_ERROR_END;
047        private static final String OPEN_FATAL = ValidationEndpoint.TAG_FATAL_START;
048        private static final String CLOSE_FATAL = ValidationEndpoint.TAG_FATAL_END;
049        private static final String OPEN_WARNING = ValidationEndpoint.TAG_WARNING_START;
050        private static final String CLOSE_WARNING = ValidationEndpoint.TAG_WARNING_END;
051        
052        private String openRootElement;
053        private String closeRootElement;
054    
055        /**
056         * Number of warnings.
057         */
058        private int warningCount;
059        
060        /**
061         * Number of errors.
062         */
063        private int errorCount;
064        
065        /**
066         * Number of fatal errors.
067         */
068        private int fatalErrorCount;
069        
070        /**
071         * The root element name for the fault xml message
072         */
073        private String rootPath;
074        
075        /**
076         * The namespace for the fault xml message
077         */
078        private String namespace;
079        
080        /**
081         * Determines whether or not to include stacktraces in the fault xml message
082         */
083        private boolean includeStackTraces;
084        
085        /**
086         * Variable to hold the warning/error messages from the validator
087         */
088        private StringBuffer messages = new StringBuffer();
089        
090        private SourceTransformer sourceTransformer = new SourceTransformer();
091        
092        /**
093         * Constructor.
094         * 
095         * @param rootElement
096         *      The root element name of the fault xml message 
097         * @param namespace
098         *      The namespace for the fault xml message
099         * @param includeStackTraces
100         *      Include stracktraces in the final output
101         */
102        public MessageAggregatingErrorHandler(String rootPath, String namespace, boolean includeStackTraces) throws IllegalArgumentException {
103            if (rootPath == null || rootPath.trim().length() == 0) {
104                throw new IllegalArgumentException("rootPath must not be null or an empty string");
105            }
106            this.rootPath = rootPath;
107            this.namespace = namespace;
108            this.includeStackTraces = includeStackTraces;
109            createRootElementTags();
110        }
111    
112        /**
113         * Creates the root element tags for later use down to n depth.
114         * Note: the rootPath here is of the form:
115         * 
116         *      <code>rootElementName/elementName-1/../elementName-n</code>
117         * 
118         * The namespace will be appended to the root element if it is not
119         * null or empty.
120         */
121        private void createRootElementTags() {
122            /* 
123             * since the rootPath is constrained to be not null or empty
124             * then we have at least one path element.
125             */
126            String[] pathElements = rootPath.split("/");
127    
128            StringBuffer openRootElementSB = new StringBuffer().append("<").append(pathElements[0]);
129            StringBuffer closeRootElementSB = new StringBuffer();
130            
131            if (namespace != null && namespace.trim().length() > 0) {
132                openRootElementSB.append(" xmlns=\"").append(namespace).append("\">"); 
133            } else {
134                openRootElementSB.append(">");
135            }
136            
137            if (pathElements.length > 0) {
138                int j = pathElements.length - 1;
139                for (int i = 1; i < pathElements.length; i++, j--) {
140                    openRootElementSB.append("<").append(pathElements[i]).append(">");
141                    closeRootElementSB.append("</").append(pathElements[j]).append(">");
142                }
143            }
144            
145            // create the closing root element tag
146            closeRootElementSB.append("</").append(pathElements[0]).append(">");
147            
148            openRootElement = openRootElementSB.toString();
149            closeRootElement = closeRootElementSB.toString();
150        }
151        
152        /*  (non-Javadoc)
153         * @see org.apache.servicemix.components.validation.MessageAwareErrorHandler#hasErrors()
154         */
155        public boolean hasErrors() {
156            return getErrorCount() > 0 || getFatalErrorCount() > 0;
157        }
158    
159        /*  (non-Javadoc)
160         * @see org.apache.servicemix.components.validation.MessageAwareErrorHandler#getWarningCount()
161         */
162        public int getWarningCount() {
163            return warningCount;
164        }
165    
166        /*  (non-Javadoc)
167         * @see org.apache.servicemix.components.validation.MessageAwareErrorHandler#getErrorCount()
168         */
169        public int getErrorCount() {
170            return errorCount;
171        }
172    
173        /*  (non-Javadoc)
174         * @see org.apache.servicemix.components.validation.MessageAwareErrorHandler#getFatalErrorCount()
175         */
176        public int getFatalErrorCount() {
177            return fatalErrorCount;
178        }
179    
180        /*  (non-Javadoc)
181         * @see org.xml.sax.ErrorHandler#warning(org.xml.sax.SAXParseException)
182         */
183        public void warning(SAXParseException e) throws SAXException {
184            ++warningCount;
185    
186            // open warning and CDATA tags
187            messages.append(OPEN_WARNING).append(OPEN_CDATA);
188            
189            // append the fatal error message
190            appendErrorMessage(e);
191            
192            // close CDATA and warning tags
193            messages.append(CLOSE_CDATA).append(CLOSE_WARNING);
194        }
195    
196        /*  (non-Javadoc)
197         * @see org.xml.sax.ErrorHandler#error(org.xml.sax.SAXParseException)
198         */
199        public void error(SAXParseException e) throws SAXException {
200            ++errorCount;
201    
202            // open fatal error and CDATA tags
203            messages.append(OPEN_ERROR).append(OPEN_CDATA);
204            
205            // append the error message
206            appendErrorMessage(e);
207            
208            // close CDATA and error tags
209            messages.append(CLOSE_CDATA).append(CLOSE_ERROR);
210        }
211    
212        /*  (non-Javadoc)
213         * @see org.xml.sax.ErrorHandler#fatalError(org.xml.sax.SAXParseException)
214         */
215        public void fatalError(SAXParseException e) throws SAXException {
216            ++fatalErrorCount;
217            
218            // open fatal error and CDATA tags
219            messages.append(OPEN_FATAL).append(OPEN_CDATA);
220            
221            // append the fatal error message
222            appendErrorMessage(e);
223            
224            // close CDATA and fatal error tags
225            messages.append(CLOSE_CDATA).append(CLOSE_FATAL);
226        }
227    
228        /**
229         * Append the error message or stacktrace to the messages attribute.
230         * 
231         * @param e
232         */
233        private void appendErrorMessage(SAXParseException e) {
234            if (includeStackTraces) {
235                StringWriter sw = new StringWriter();
236                e.printStackTrace(new PrintWriter(sw));
237                messages.append(sw.toString());
238            } else {
239                messages.append(e.getLocalizedMessage());
240            }
241        }
242        
243        /*  (non-Javadoc)
244         * @see org.apache.servicemix.components.validation.MessageAwareErrorHandler#capturesMessages()
245         */
246        public boolean capturesMessages() {
247            return true;
248        }
249    
250        /* (non-Javadoc)
251         * @see org.apache.servicemix.components.validation.MessageAwareErrorHandler#getMessagesAs(java.lang.Class)
252         */
253        public Object getMessagesAs(Class format) throws MessagingException {
254            if (format == DOMSource.class) {
255                return getDOMSource();
256            } else if (format == StringSource.class) {
257                return getStringSource();
258            } else if (format == String.class) {
259                return getMessagesWithRootElement();
260            }
261            throw new MessagingException("Unsupported message format: " + format.getName());
262        }
263    
264        /* (non-Javadoc)
265         * @see org.apache.servicemix.components.validation.MessageAwareErrorHandler#supportsMessageFormat(java.lang.Class)
266         */
267        public boolean supportsMessageFormat(Class format) {
268            if (format == DOMSource.class) {
269                return true;
270            } else if (format == StringSource.class) {
271                return true;
272            } else if (format == String.class) {
273                return true;
274            }
275            return false;
276        }
277        
278        /**
279         * Return the messages encapsulated with the root element.
280         * 
281         * @return
282         */
283        private String getMessagesWithRootElement() {
284            return new StringBuffer().append(openRootElement).append(messages).append(closeRootElement).toString();
285        }
286        
287        /**
288         * Get the error messages as a String Source.
289         * 
290         * @return
291         */
292        private StringSource getStringSource() {
293            return new StringSource(getMessagesWithRootElement());
294        }
295        
296        /**
297         * Get the error messages as a DOMSource.
298         * 
299         * @return
300         * @throws MessagingException
301         */
302        private DOMSource getDOMSource() throws MessagingException {
303            try {
304                return sourceTransformer.toDOMSource(getStringSource());
305            } catch (ParserConfigurationException e) {
306                throw new MessagingException("Failed to create DOMSource for Schema validation messages: " + e, e);
307            } catch (IOException e) {
308                throw new MessagingException("Failed to create DOMSource for Schema validation messages: " + e, e);
309            } catch (SAXException e) {
310                throw new MessagingException("Failed to create DOMSource for Schema validation messages: " + e, e);
311            } catch (TransformerException e) {
312                throw new MessagingException("Failed to create DOMSource for Schema validation messages: " + e, e);
313            }
314        }
315        
316    }