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