package com.googlecode.jmxtrans.model.output;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.collect.ImmutableList;
import com.googlecode.jmxtrans.model.Query;
import com.googlecode.jmxtrans.model.Result;
import com.googlecode.jmxtrans.model.Server;
import com.googlecode.jmxtrans.model.ValidationException;
import com.googlecode.jmxtrans.model.naming.KeyUtils;
import org.apache.commons.pool.impl.GenericKeyedObjectPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.concurrent.NotThreadSafe;
import javax.inject.Inject;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import static com.google.common.base.Charsets.UTF_8;
import static com.googlecode.jmxtrans.model.PropertyResolver.resolveProps;

/**
 * This low latency and thread safe output writer sends data to a host/port combination
 * in the Graphite format.
 *
 * @see <a href="http://graphite.wikidot.com/getting-your-data-into-graphite">Getting your data into Graphite</a>
 *
 * @author jon
 */
@NotThreadSafe
public class GraphiteWriter extends BaseOutputWriter {
	private static final Logger log = LoggerFactory.getLogger(GraphiteWriter.class);

	private static final String DEFAULT_ROOT_PREFIX = "servers";

	private GenericKeyedObjectPool<InetSocketAddress, Socket> pool;

	private final String rootPrefix;
	private final InetSocketAddress address;

	@JsonCreator
	public GraphiteWriter(
			@JsonProperty("typeNames") ImmutableList<String> typeNames,
			@JsonProperty("booleanAsNumber") boolean booleanAsNumber,
			@JsonProperty("debug") Boolean debugEnabled,
			@JsonProperty("rootPrefix") String rootPrefix,
			@JsonProperty("host") String host,
			@JsonProperty("port") Integer port,
			@JsonProperty("settings") Map<String, Object> settings) {
		super(typeNames, booleanAsNumber, debugEnabled, settings);
		this.rootPrefix = resolveProps(
				firstNonNull(
						rootPrefix,
						(String) getSettings().get("rootPrefix"),
						DEFAULT_ROOT_PREFIX));
		host = resolveProps(host);
		if (host == null) {
			host = (String) getSettings().get(HOST);
		}
		if (host == null) {
			throw new NullPointerException("Host cannot be null.");
		}
		if (port == null) {
			port = Settings.getIntegerSetting(getSettings(), PORT, null);
		}
		if (port == null) {
			throw new NullPointerException("Port cannot be null.");
		}
		this.address = new InetSocketAddress(host, port);
	}

	public void validateSetup(Server server, Query query) throws ValidationException {
	}

	public void internalWrite(Server server, Query query, ImmutableList<Result> results) throws Exception {
		Socket socket = null;

		try {
			socket = pool.borrowObject(address);
			PrintWriter writer = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), UTF_8), true);

			List<String> typeNames = this.getTypeNames();

			for (Result result : results) {
				log.debug("Query result: {}", result);
				Map<String, Object> resultValues = result.getValues();
				if (resultValues != null) {
					for (Entry<String, Object> values : resultValues.entrySet()) {
						Object value = values.getValue();
						if (NumberUtils.isNumeric(value)) {

							String line = KeyUtils.getKeyString(server, query, result, values, typeNames, rootPrefix)
									.replaceAll("[()]", "_") + " " + value.toString() + " "
									+ result.getEpoch() / 1000 + "\n";
							log.debug("Graphite Message: {}", line);
							writer.write(line);
							writer.flush();
						} else {
							log.warn("Unable to submit non-numeric value to Graphite: [{}] from result [{}]", value, result);
						}
					}
				}
			}
		} finally {
			pool.returnObject(address, socket);
		}
	}

	public String getHost() {
		return address.getHostName();
	}

	public int getPort() {
		return address.getPort();
	}

	@Inject
	public void setPool(GenericKeyedObjectPool<InetSocketAddress, Socket> pool) {
		this.pool = pool;
	}

	public static Builder builder() {
		return new Builder();
	}

	public static final class Builder {
		private final ImmutableList.Builder<String> typeNames = ImmutableList.builder();
		private boolean booleanAsNumber;
		private Boolean debugEnabled;
		private String rootPrefix;
		private String host;
		private Integer port;

		private Builder() {}

		public Builder addTypeNames(List<String> typeNames) {
			this.typeNames.addAll(typeNames);
			return this;
		}

		public Builder addTypeName(String typeName) {
			typeNames.add(typeName);
			return this;
		}

		public Builder setBooleanAsNumber(boolean booleanAsNumber) {
			this.booleanAsNumber = booleanAsNumber;
			return this;
		}

		public Builder setDebugEnabled(boolean debugEnabled) {
			this.debugEnabled = debugEnabled;
			return this;
		}

		public Builder setRootPrefix(String rootPrefix) {
			this.rootPrefix = rootPrefix;
			return this;
		}

		public Builder setHost(String host) {
			this.host = host;
			return this;
		}

		public Builder setPort(int port) {
			this.port  = port;
			return this;
		}

		public GraphiteWriter build() {
			return new GraphiteWriter(
					typeNames.build(),
					booleanAsNumber,
					debugEnabled,
					rootPrefix,
					host,
					port,
					null
			);
		}
	}
}

@@ -3642,24 +3642,212 @@
 er.flush();%0A
+%09%09%09%09%09%09%09if (writer.checkError()) %7B%0A%09%09%09%09%09%09%09%09log.error(%22Error writing to Graphite, clearing Graphite socket pool%22);%0A%09%09%09%09%09%09%09%09pool.returnObject(address, socket);%0A%09%09%09%09%09%09%09%09pool.clear();%0A%09%09%09%09%09%09%09%7D%0A
 %09%09%09%09%09%09%7D else

Fix for jmxtrans losing connection to Graphite and never reconnecting