Skip to content

Commit

Permalink
Merge pull request #64 from AvanzaBank/properties-file
Browse files Browse the repository at this point in the history
Support for using a properties file for placeholder expansion
  • Loading branch information
Cyrille Le Clerc committed Feb 1, 2016
2 parents 352ce6e + 4abfb1a commit 230e17b
Show file tree
Hide file tree
Showing 12 changed files with 541 additions and 17 deletions.
17 changes: 16 additions & 1 deletion src/main/java/org/jmxtrans/agent/JmxTransAgent.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@
package org.jmxtrans.agent;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;

import org.jmxtrans.agent.properties.NoPropertiesSourcePropertiesLoader;
import org.jmxtrans.agent.properties.PropertiesLoader;
import org.jmxtrans.agent.properties.UrlOrFilePropertiesLoader;
import org.jmxtrans.agent.util.logging.Logger;

import javax.management.ObjectInstance;
Expand All @@ -45,6 +49,8 @@
public class JmxTransAgent {
private static Logger logger = Logger.getLogger(JmxTransAgent.class.getName());

private static final String PROPERTIES_SYSTEM_PROPERTY_NAME = "jmxtrans.agent.properties.file";

@SuppressFBWarnings("MS_SHOULD_BE_FINAL")
public static boolean DIAGNOSTIC = Boolean.valueOf(System.getProperty(JmxTransAgent.class.getName() + ".diagnostic", "false"));

Expand Down Expand Up @@ -81,7 +87,8 @@ private static void initializeAgent(String configFile) {
throw new IllegalStateException(msg);
}
try {
JmxTransConfigurationLoader configurationLoader = new JmxTransConfigurationXmlLoader(configFile);
PropertiesLoader propertiesLoader = creatPropertiesLoader();
JmxTransConfigurationLoader configurationLoader = new JmxTransConfigurationXmlLoader(configFile, propertiesLoader);
JmxTransExporter jmxTransExporter = new JmxTransExporter(configurationLoader);
//START
jmxTransExporter.start();
Expand All @@ -93,6 +100,14 @@ private static void initializeAgent(String configFile) {
}
}

private static PropertiesLoader creatPropertiesLoader() {
String propertiesFile = System.getProperty(PROPERTIES_SYSTEM_PROPERTY_NAME);
if (propertiesFile != null) {
return new UrlOrFilePropertiesLoader(propertiesFile);
}
return new NoPropertiesSourcePropertiesLoader();
}

public static void dumpDiagnosticInfo() {
if (!JmxTransAgent.DIAGNOSTIC)
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,11 @@
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.regex.Pattern;

import org.jmxtrans.agent.properties.NoPropertiesSourcePropertiesLoader;
import org.jmxtrans.agent.properties.PropertiesLoader;
import org.jmxtrans.agent.util.Preconditions2;
import org.jmxtrans.agent.util.PropertyPlaceholderResolver;
import org.jmxtrans.agent.util.io.IoUtils;
Expand All @@ -51,13 +54,22 @@ public class JmxTransConfigurationXmlLoader implements JmxTransConfigurationLoad

private static final Pattern ATTRIBUTE_SPLIT_PATTERN = Pattern.compile("\\s*,\\s*");
private Logger logger = Logger.getLogger(getClass().getName());
private PropertyPlaceholderResolver placeholderResolver = new PropertyPlaceholderResolver();

private final PropertiesLoader propertiesLoader;
@Nonnull
private final String configurationFilePath;

public JmxTransConfigurationXmlLoader(@Nonnull String configurationFilePath) {
public JmxTransConfigurationXmlLoader(@Nonnull String configurationFilePath, PropertiesLoader propertiesLoader) {
this.configurationFilePath = Preconditions2.checkNotNull(configurationFilePath, "configurationFilePath can not be null");
this.propertiesLoader = propertiesLoader;
}

/**
* Creates a JmxTransExporterBuilder with a PropertyLoader that does not use an
* external properties source.
*/
public JmxTransConfigurationXmlLoader(@Nonnull String configurationFilePath) {
this(configurationFilePath, new NoPropertiesSourcePropertiesLoader());
}

@Override
Expand All @@ -73,28 +85,45 @@ public long lastModified() {
protected JmxTransExporterConfiguration build(Document document) {
Element rootElement = document.getDocumentElement();

Map<String, String> loadedProperties = loadPropertiesOrEmptyOnError();
PropertyPlaceholderResolver resolver = new PropertyPlaceholderResolver(loadedProperties);
JmxTransExporterConfiguration jmxTransExporterConfiguration = new JmxTransExporterConfiguration(document);

Integer collectInterval = getIntegerElementValueOrNullIfNotSet(rootElement, "collectIntervalInSeconds");
Integer collectInterval = getIntegerElementValueOrNullIfNotSet(rootElement, "collectIntervalInSeconds", resolver);
if (collectInterval != null) {
jmxTransExporterConfiguration.withCollectInterval(collectInterval, TimeUnit.SECONDS);
}
Integer reloadConfigInterval = getIntegerElementValueOrNullIfNotSet(rootElement,
"reloadConfigurationCheckIntervalInSeconds");
"reloadConfigurationCheckIntervalInSeconds", resolver);
if (reloadConfigInterval != null) {
jmxTransExporterConfiguration.withConfigReloadInterval(reloadConfigInterval);
}

buildResultNameStrategy(rootElement, jmxTransExporterConfiguration);
buildResultNameStrategy(rootElement, jmxTransExporterConfiguration, resolver);
buildInvocations(rootElement, jmxTransExporterConfiguration);
buildQueries(rootElement, jmxTransExporterConfiguration);

buildOutputWriters(rootElement, jmxTransExporterConfiguration);
buildOutputWriters(rootElement, jmxTransExporterConfiguration, resolver);

return jmxTransExporterConfiguration;
}

private Integer getIntegerElementValueOrNullIfNotSet(Element rootElement, String elementName) {
private Map<String, String> loadPropertiesOrEmptyOnError() {
try {
return propertiesLoader.loadProperties();
} catch (Exception e) {
logger.log(Level.WARNING, "Error when loading properties from loader " + propertiesLoader + ", this source will be ignored", e);
return new HashMap<>();
}
}

public JmxTransExporterConfiguration build(JmxTransConfigurationLoader configurationDocumentLoader)
throws Exception {
JmxTransExporterConfiguration configuration = configurationDocumentLoader.loadConfiguration();
return build(configuration.getDocument());
}

private Integer getIntegerElementValueOrNullIfNotSet(Element rootElement, String elementName, PropertyPlaceholderResolver placeholderResolver) {
NodeList nodeList = rootElement.getElementsByTagName(elementName);
switch (nodeList.getLength()) {
case 0:
Expand Down Expand Up @@ -175,7 +204,7 @@ private void buildInvocations(Element rootElement, JmxTransExporterConfiguration
}
}

private void buildResultNameStrategy(Element rootElement, JmxTransExporterConfiguration configuration) {
private void buildResultNameStrategy(Element rootElement, JmxTransExporterConfiguration configuration, PropertyPlaceholderResolver placeholderResolver) {
NodeList resultNameStrategyNodeList = rootElement.getElementsByTagName("resultNameStrategy");

ResultNameStrategy resultNameStrategy;
Expand Down Expand Up @@ -210,7 +239,7 @@ private void buildResultNameStrategy(Element rootElement, JmxTransExporterConfig
configuration.resultNameStrategy = resultNameStrategy;
}

private void buildOutputWriters(Element rootElement, JmxTransExporterConfiguration configuration) {
private void buildOutputWriters(Element rootElement, JmxTransExporterConfiguration configuration, PropertyPlaceholderResolver placeholderResolver) {
NodeList outputWriterNodeList = rootElement.getElementsByTagName("outputWriter");
List<OutputWriter> outputWriters = new ArrayList<OutputWriter>();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright (c) 2010-2013 the original author or authors
*
* 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.agent.properties;

import java.util.HashMap;
import java.util.Map;

/**
* Properties loader that always returns an empty properties map.
*
* @author Kristoffer Erlandsson
*/
public class NoPropertiesSourcePropertiesLoader implements PropertiesLoader {

/**
* Always returns an empty map.
*/
@Override
public Map<String, String> loadProperties() {
return new HashMap<>();
}

}
44 changes: 44 additions & 0 deletions src/main/java/org/jmxtrans/agent/properties/PropertiesLoader.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright (c) 2010-2013 the original author or authors
*
* 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.agent.properties;

import java.util.Map;

import org.jmxtrans.agent.properties.UrlOrFilePropertiesLoader.FailedToLoadPropertiesException;

/**
* @author Kristoffer Erlandsson
*/
public interface PropertiesLoader {

/**
* Loads properties.
*
* @throws FailedToLoadPropertiesException
* if properties file to load.
* @return A map with property keys and values.
*/
Map<String, String> loadProperties();

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
/*
* Copyright (c) 2010-2013 the original author or authors
*
* 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.agent.properties;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

/**
* Loads properties from a URL or a file.
*
* Uses the same logic to locate a file as {@link org.jmxtrans.agent.JmxTransConfigurationDocumentLoader}.
*
* @author Kristoffer Erlandsson
*/
public class UrlOrFilePropertiesLoader implements PropertiesLoader {

private final String propertiesPath;

public UrlOrFilePropertiesLoader(String propertiesPath) {
this.propertiesPath = propertiesPath;
}

@Override
public Map<String, String> loadProperties() {
try {
return convertToMap(tryLoadProperties());
} catch (Exception e) {
throw new FailedToLoadPropertiesException("Failed to load properties file '" + propertiesPath + "'", e);
}
}

private Map<String, String> convertToMap(Properties properties) {
Map<String, String> m = new HashMap<>();
for (Object key : properties.keySet()) {
if (!(key instanceof String)) {
throw new IllegalArgumentException("Properties key is not string");
}
String stringKey = (String) key;
m.put(stringKey, properties.getProperty(stringKey));
}
return m;
}

private Properties tryLoadProperties() throws Exception {
Properties properties = new Properties();
if (isClassPathSpec()) {
loadFromClasspath(properties);
} else if (isUrl()) {
loadFromUrl(properties);
} else {
loadFromFile(properties);
}
return properties;
}

private boolean isClassPathSpec() {
return propertiesPath.toLowerCase().startsWith("classpath:");
}

private boolean isUrl() {
return propertiesPath.toLowerCase().startsWith("file://") ||
propertiesPath.toLowerCase().startsWith("http://") ||
propertiesPath.toLowerCase().startsWith("https://");
}

private void loadFromFile(Properties properties) throws IOException, FileNotFoundException {
File file = new File(propertiesPath);
if (!file.exists()) {
throw new IllegalArgumentException("Properties file '" + file.getAbsolutePath() + "' not found");
}
try (InputStream stream = new FileInputStream(file)) {
properties.load(stream);
}
}

private void loadFromUrl(Properties properties) throws MalformedURLException, IOException {
URL url = new URL(propertiesPath);
URLConnection connection = url.openConnection();
connection.setConnectTimeout(5000);
connection.setReadTimeout(15000);
connection.connect();
try (InputStream stream = connection.getInputStream()) {
properties.load(stream);
}
}

private void loadFromClasspath(Properties properties) throws IOException {
String classpathResourcePath = propertiesPath.substring("classpath:".length());
try (InputStream in = Thread.currentThread().getContextClassLoader()
.getResourceAsStream(classpathResourcePath)) {
properties.load(in);
}
}

@SuppressWarnings("serial")
public static class FailedToLoadPropertiesException extends RuntimeException {

public FailedToLoadPropertiesException(String message, Throwable cause) {
super(message, cause);
}

}

@Override
public String toString() {
return "UrlOrFilePropertiesLoader, path='" + propertiesPath + "'";
}

}
Loading

0 comments on commit 230e17b

Please sign in to comment.