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}