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.management.mbean;
018
019import java.io.InputStream;
020import java.util.Stack;
021
022import javax.xml.parsers.DocumentBuilder;
023import javax.xml.parsers.DocumentBuilderFactory;
024import javax.xml.parsers.SAXParser;
025import javax.xml.parsers.SAXParserFactory;
026
027import org.w3c.dom.Document;
028import org.w3c.dom.Element;
029import org.w3c.dom.Node;
030
031import org.xml.sax.Attributes;
032import org.xml.sax.Locator;
033import org.xml.sax.SAXException;
034import org.xml.sax.helpers.DefaultHandler;
035
036import org.apache.camel.CamelContext;
037import org.apache.camel.api.management.mbean.ManagedProcessorMBean;
038import org.apache.camel.api.management.mbean.ManagedRouteMBean;
039
040/**
041 * An XML parser that uses SAX to enrich route stats in the route dump.
042 * <p/>
043 * The coverage details:
044 * <ul>
045 *     <li>exchangesTotal - Total number of exchanges</li>
046 *     <li>totalProcessingTime - Total processing time in millis</li>
047 * </ul>
048 * Is included as attributes on the route nodes.
049 */
050public final class RouteCoverageXmlParser {
051
052    private RouteCoverageXmlParser() {
053    }
054
055    /**
056     * Parses the XML.
057     *
058     * @param camelContext the CamelContext
059     * @param is           the XML content as an input stream
060     * @return the DOM model of the routes with coverage information stored as attributes
061     * @throws Exception is thrown if error parsing
062     */
063    public static Document parseXml(final CamelContext camelContext, final InputStream is) throws Exception {
064        final SAXParserFactory factory = SAXParserFactory.newInstance();
065        final SAXParser parser = factory.newSAXParser();
066        final DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
067        final DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
068        final Document doc = docBuilder.newDocument();
069
070        final Stack<Element> elementStack = new Stack<>();
071        final StringBuilder textBuffer = new StringBuilder();
072        final DefaultHandler handler = new DefaultHandler() {
073
074            @Override
075            public void setDocumentLocator(final Locator locator) {
076                // noop
077            }
078
079            @Override
080            public void startElement(final String uri, final String localName, final String qName, final Attributes attributes) throws SAXException {
081                addTextIfNeeded();
082
083                final Element el = doc.createElement(qName);
084                // add other elements
085                for (int i = 0; i < attributes.getLength(); i++) {
086                    el.setAttribute(attributes.getQName(i), attributes.getValue(i));
087                }
088
089                String id = el.getAttribute("id");
090                if (id != null) {
091                    try {
092                        if ("route".equals(qName)) {
093                            ManagedRouteMBean route = camelContext.getManagedRoute(id, ManagedRouteMBean.class);
094                            if (route != null) {
095                                long total = route.getExchangesTotal();
096                                el.setAttribute("exchangesTotal", "" + total);
097                                long totalTime = route.getTotalProcessingTime();
098                                el.setAttribute("totalProcessingTime", "" + totalTime);
099                            }
100                        } else if ("from".equals(qName)) {
101                            // grab statistics from the parent route as from would be the same
102                            Element parent = elementStack.peek();
103                            if (parent != null) {
104                                String routeId = parent.getAttribute("id");
105                                ManagedRouteMBean route = camelContext.getManagedRoute(routeId, ManagedRouteMBean.class);
106                                if (route != null) {
107                                    long total = route.getExchangesTotal();
108                                    el.setAttribute("exchangesTotal", "" + total);
109                                    long totalTime = route.getTotalProcessingTime();
110                                    el.setAttribute("totalProcessingTime", "" + totalTime);
111                                    // from is index-0
112                                    el.setAttribute("index", "0");
113                                }
114                            }
115                        } else {
116                            ManagedProcessorMBean processor = camelContext.getManagedProcessor(id, ManagedProcessorMBean.class);
117                            if (processor != null) {
118                                long total = processor.getExchangesTotal();
119                                el.setAttribute("exchangesTotal", "" + total);
120                                long totalTime = processor.getTotalProcessingTime();
121                                el.setAttribute("totalProcessingTime", "" + totalTime);
122                                int index = processor.getIndex();
123                                el.setAttribute("index", "" + index);
124                            }
125                        }
126                    } catch (Exception e) {
127                        // ignore
128                    }
129                }
130
131                // we do not want customId in output of the EIPs
132                if (!"route".equals(qName)) {
133                    el.removeAttribute("customId");
134                }
135
136                elementStack.push(el);
137            }
138
139            @Override
140            public void endElement(final String uri, final String localName, final String qName) {
141                addTextIfNeeded();
142                final Element closedEl = elementStack.pop();
143                if (elementStack.isEmpty()) {
144                    // is this the root element?
145                    doc.appendChild(closedEl);
146                } else {
147                    final Element parentEl = elementStack.peek();
148                    parentEl.appendChild(closedEl);
149                }
150            }
151
152            @Override
153            public void characters(final char ch[], final int start, final int length) throws SAXException {
154                textBuffer.append(ch, start, length);
155            }
156
157            /**
158             * outputs text accumulated under the current node
159             */
160            private void addTextIfNeeded() {
161                if (textBuffer.length() > 0) {
162                    final Element el = elementStack.peek();
163                    final Node textNode = doc.createTextNode(textBuffer.toString());
164                    el.appendChild(textNode);
165                    textBuffer.delete(0, textBuffer.length());
166                }
167            }
168        };
169        parser.parse(is, handler);
170
171        return doc;
172    }
173}