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