/** * The MIT License * Copyright (c) 2014 JMXTrans Team * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package org.jmxtrans.core.config; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.annotation.Nonnull; import javax.annotation.concurrent.ThreadSafe; import javax.management.MalformedObjectNameException; import javax.management.ObjectName; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Unmarshaller; import javax.xml.namespace.QName; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.stream.StreamSource; import javax.xml.validation.Schema; import javax.xml.validation.SchemaFactory; import org.jmxtrans.core.circuitbreaker.CircuitBreakerProxy; import org.jmxtrans.core.config.jaxb.InvocationType; import org.jmxtrans.core.config.jaxb.Jmxtrans; import org.jmxtrans.core.config.jaxb.OutputWriterType; import org.jmxtrans.core.config.jaxb.QueriesType; import org.jmxtrans.core.config.jaxb.QueryType; import org.jmxtrans.core.config.jaxb.ServerType; import org.jmxtrans.core.log.Logger; import org.jmxtrans.core.log.LoggerFactory; import org.jmxtrans.core.monitoring.ObjectNameFactory; import org.jmxtrans.core.output.MetricCollectingOutputWriter; import org.jmxtrans.core.output.OutputWriter; import org.jmxtrans.core.output.OutputWriterFactory; import org.jmxtrans.core.query.InProcessServer; import org.jmxtrans.core.query.Invocation; import org.jmxtrans.core.query.Query; import org.jmxtrans.core.query.QueryAttribute; import org.jmxtrans.core.query.RemoteServer; import org.jmxtrans.core.results.MetricType; import org.jmxtrans.utils.io.Resource; import org.jmxtrans.utils.io.StandardResource; import org.jmxtrans.utils.time.Clock; import org.jmxtrans.utils.time.Interval; import org.jmxtrans.utils.time.SystemClock; import org.w3c.dom.Document; import org.xml.sax.SAXException; import static java.lang.String.format; import static java.util.concurrent.TimeUnit.SECONDS; import static javax.xml.XMLConstants.W3C_XML_SCHEMA_NS_URI; import static org.jmxtrans.core.results.MetricType.UNKNOWN; @ThreadSafe public class XmlConfigParser implements ConfigParser { private final Logger logger = LoggerFactory.getLogger(getClass().getName()); public static final String JMXTRANS_XSD_PATH = "classpath:jmxtrans.xsd"; public static final int MAX_FAILURES = 5; public static final int DISABLE_DURATION_MILLIS = 60 * 1000; @Nonnull private final Clock clock; @Nonnull private final PropertyPlaceholderResolverXmlPreprocessor preprocessor; @Nonnull private final DocumentBuilder documentBuilder; @Nonnull private final Unmarshaller unmarshaller; @Nonnull private final ObjectNameFactory outputObjectNameFactory; private XmlConfigParser( @Nonnull DocumentBuilder documentBuilder, @Nonnull Unmarshaller unmarshaller, @Nonnull PropertyPlaceholderResolverXmlPreprocessor preprocessor, @Nonnull Clock clock, @Nonnull ObjectNameFactory outputObjectNameFactory) { this.documentBuilder = documentBuilder; this.unmarshaller = unmarshaller; this.preprocessor = preprocessor; this.clock = clock; this.outputObjectNameFactory = outputObjectNameFactory; } @Override public boolean supports(@Nonnull Resource resource) { return resource.getPath().endsWith(".xml"); } @Override @Nonnull public synchronized Configuration parseConfiguration(Resource source) throws IOException, SAXException, JAXBException, IllegalAccessException, ClassNotFoundException, InstantiationException, MalformedObjectNameException { try (InputStream in = source.getInputStream()) { Document document = documentBuilder.parse(in); document = preprocessor.preprocess(document); Jmxtrans jmxtrans = (Jmxtrans) unmarshaller.unmarshal(document); ModifiableConfiguration newConfiguration = new ModifiableConfiguration(); parse(jmxtrans, newConfiguration); return new StandardConfiguration(newConfiguration); } } private void parse(@Nonnull Jmxtrans jmxtrans, @Nonnull ModifiableConfiguration configuration) throws IllegalAccessException, InstantiationException, ClassNotFoundException, MalformedObjectNameException { if (jmxtrans.getCollectIntervalInSeconds() != null) { configuration.setPeriod(new Interval(jmxtrans.getCollectIntervalInSeconds(), SECONDS)); } if (jmxtrans.getQueries() != null) { configuration.addServer(new InProcessServer(parse(jmxtrans.getQueries()))); } if (jmxtrans.getServers() != null) { try { parse(jmxtrans.getServers(), configuration); } catch (MalformedURLException e) { logger.error("JMXUrl is not valid", e); } } if (jmxtrans.getInvocations() != null) { parse(jmxtrans.getInvocations(), configuration); } if (jmxtrans.getOutputWriters() != null) { parse(jmxtrans.getOutputWriters(), configuration); } } private void parse(Jmxtrans.Servers servers, ModifiableConfiguration configuration) throws MalformedURLException { for (ServerType server : servers.getServer()) { Collection<Query> queries = Collections.emptyList(); if (server.getQueries() != null) { queries = parse(server.getQueries()); } configuration.addServer(RemoteServer.builder() .withUrl(server.getJmxUrl()) .withHost(server.getHost()) .withPort(server.getPort()) .withUsername(server.getUsername()) .withPassword(server.getPassword()) .withProtocolProviderPackages(server.getProtocolProviderPackages()) .withQueries(queries) .build()); } } private List<Query> parse(@Nonnull QueriesType queries) { List<Query> result = new ArrayList<>(); for (QueryType query : queries.getQuery()) { Query.Builder queryBuilder = Query.builder() .withObjectName(query.getObjectName()) .withResultAlias(query.getResultAlias()) .withMaxResults(query.getMaxResults()); for (QueryType.QueryAttribute attribute : query.getQueryAttribute()) { QueryAttribute.Builder attributeBuilder = QueryAttribute .builder(attribute.getName()) .withResultAlias(attribute.getResultAlias()) .withType(parseMetricType(attribute.getType())); for (String key : attribute.getKey()) { attributeBuilder.addKey(key); } queryBuilder.addAttribute(attributeBuilder.build()); } result.add(queryBuilder.build()); } return result; } private MetricType parseMetricType(String type) { if (type == null) return UNKNOWN; if (type.isEmpty()) return UNKNOWN; return MetricType.valueOf(type.toUpperCase()); } private void parse(@Nonnull Jmxtrans.Invocations invocations, @Nonnull ModifiableConfiguration configuration) throws MalformedObjectNameException { for (InvocationType invocation : invocations.getInvocation()) { List<String> params = new ArrayList<>(); List<String> signature = new ArrayList<>(); for (InvocationType.Parameter parameter : invocation.getParameter()) { params.add(parameter.getValue()); signature.add(parameter.getType()); } configuration.getInvocations().add( new Invocation( new ObjectName(invocation.getObjectName()), invocation.getOperationName(), params.toArray(), signature.toArray(new String[0]), invocation.getResultAlias(), parseMetricType(invocation.getType()), new SystemClock())); } } private void parse(@Nonnull Jmxtrans.OutputWriters outputWriters, @Nonnull ModifiableConfiguration configuration) throws IllegalAccessException, ClassNotFoundException, InstantiationException, MalformedObjectNameException { for (OutputWriterType outputWriter : outputWriters.getOutputWriter()) { Map<String, String> settings = new HashMap<>(); for (Map.Entry<QName, String> attribute : outputWriter.getOtherAttributes().entrySet()) { settings.put(attribute.getKey().getLocalPart(), attribute.getValue()); } configuration.getOutputWriters().add(instantiateOutputWriter(outputWriter.getClazz(), settings)); } } @Nonnull private OutputWriter instantiateOutputWriter(@Nonnull String outputWriterClass, @Nonnull Map<String, String> settings) throws InstantiationException, IllegalAccessException, MalformedObjectNameException { try { @SuppressWarnings("unchecked") Class<OutputWriterFactory<?>> builderClass = (Class<OutputWriterFactory<?>>) Class.forName(outputWriterClass); OutputWriterFactory<?> builder = builderClass.newInstance(); return wrapInMetricCollectingOutputWriter( wrapInCircuitBreaker( builder.create(settings))); } catch (ClassNotFoundException e) { throw new JmxtransConfigurationException( format("Could not load class %s, this can happen if you use non standard outputwriters and did not" + " add the appropriate jar on the classpath", outputWriterClass), e); } } private OutputWriter wrapInMetricCollectingOutputWriter(OutputWriter outputWriter) throws MalformedObjectNameException { ObjectName objectName = outputObjectNameFactory.create(outputWriter.toString()); return new MetricCollectingOutputWriter(clock, outputWriter, objectName); } private OutputWriter wrapInCircuitBreaker(OutputWriter target) { return CircuitBreakerProxy.create( clock, OutputWriter.class, target, MAX_FAILURES, DISABLE_DURATION_MILLIS); } @Nonnull public static XmlConfigParser newInstance( @Nonnull PropertyPlaceholderResolverXmlPreprocessor preprocessor, @Nonnull Clock clock, @Nonnull ObjectNameFactory outputObjectNameFactory) throws JAXBException, ParserConfigurationException, SAXException, IOException { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); dbf.setNamespaceAware(true); JAXBContext jaxbContext = JAXBContext.newInstance(Jmxtrans.class); Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); unmarshaller.setSchema(loadSchema()); return new XmlConfigParser( dbf.newDocumentBuilder(), unmarshaller, preprocessor, clock, outputObjectNameFactory); } @Nonnull private static Schema loadSchema() throws SAXException, IOException { Resource xsd = new StandardResource(JMXTRANS_XSD_PATH); SchemaFactory schemaFactory = SchemaFactory.newInstance(W3C_XML_SCHEMA_NS_URI); try (InputStream in = xsd.getInputStream()) { return schemaFactory.newSchema(new StreamSource(in)); } } }