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