/* * Copyright 2015 JBoss Inc * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.apiman.gateway.engine.influxdb; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import io.apiman.gateway.engine.async.AsyncResultImpl; import io.apiman.gateway.engine.async.IAsyncHandler; import io.apiman.gateway.engine.async.IAsyncResult; import io.apiman.gateway.engine.async.IAsyncResultHandler; import io.apiman.gateway.engine.components.IHttpClientComponent; import io.apiman.gateway.engine.components.http.HttpMethod; import io.apiman.gateway.engine.components.http.IHttpClientRequest; import io.apiman.gateway.engine.components.http.IHttpClientResponse; /** * A simple async HTTP impl of the influxdb driver. Contains only the subset of functionality we need. * * @author Marc Savy <msavy@redhat.com> */ public class InfluxDb09Driver { private IHttpClientComponent httpClient; private StringBuilder writeUrl; private StringBuilder queryUrl; private String username; private String password; private String database; private String retentionPolicy; private String timePrecision; private static ObjectMapper objectMapper = new ObjectMapper(); @SuppressWarnings("nls") public InfluxDb09Driver(IHttpClientComponent httpClient, String endpoint, String username, String password, String database, String retentionPolicy, String timePrecision) { this.httpClient = httpClient; this.username = username; this.password = password; this.database = database; this.retentionPolicy = retentionPolicy; this.timePrecision = timePrecision; StringBuilder writeEndpoint = new StringBuilder(); if (!endpoint.startsWith("http://") || !endpoint.startsWith("https://")) { writeEndpoint.append("http://"); } // domain + port writeEndpoint.append(endpoint); // Same basic structure, but with /query on end StringBuilder queryEndpoint = new StringBuilder().append(writeEndpoint).append("/query"); this.queryUrl = buildParams(queryEndpoint, "SHOW DATABASES"); // Add user-name, password, etc writeEndpoint.append("/write"); this.writeUrl = buildParams(writeEndpoint, null); } /** * Simple write to "/write". Must be valid Influx line format. * * @param lineDocument document to write, as string * @param failureHandler handler in case of failure */ public void write(String lineDocument, final IAsyncHandler<InfluxException> failureHandler) { // Make request to influx IHttpClientRequest request = httpClient.request(writeUrl.toString(), HttpMethod.POST, result -> { if (result.isError() || result.getResult().getResponseCode() < 200 || result.getResult().getResponseCode() > 299) { failureHandler.handle(new InfluxException(result.getResult())); } }); // For some reason Java's URLEncoding doesn't seem to be parseable by influx? //request.addHeader("Content-Type", "application/x-www-form-urlencoded"); request.addHeader("Content-Type", "text/plain"); //$NON-NLS-1$ //$NON-NLS-2$ request.write(lineDocument, StandardCharsets.UTF_8.name()); request.end(); } /** * List all databases * * @param handler the result handler */ @SuppressWarnings("nls") public void listDatabases(final IAsyncResultHandler<List<String>> handler) { IHttpClientRequest request = httpClient.request(queryUrl.toString(), HttpMethod.GET, result -> { try { if (result.isError() || result.getResult().getResponseCode() != 200) { handleError(result, handler); return; } List<String> results = new ArrayList<>(); // {"results": JsonNode arrNode = objectMapper.readTree(result.getResult().getBody()) .path("results").elements().next() // results: [ first-elem .path("series").elements().next(); // series: [ first-elem // values: [[db1], [db2], [...]] => db1, db2 flattenArrays(arrNode.get("values"), results); // send results handler.handle(AsyncResultImpl.create(results)); } catch (IOException e) { AsyncResultImpl.create(new RuntimeException( "Unable to parse Influx JSON response", e)); } }); request.end(); } protected <T> void handleError(IAsyncResult<IHttpClientResponse> result, IAsyncResultHandler<T> handler) { if (result.isError()) { handler.handle(AsyncResultImpl.<T> create(result.getError())); } else if (result.getResult().getResponseCode() != 200) { handler.handle(AsyncResultImpl.<T> create(new InfluxException("Influx: " //$NON-NLS-1$ + result.getResult().getResponseCode() + " " + result.getResult().getResponseMessage()))); //$NON-NLS-1$ } } private void flattenArrays(JsonNode arrNode, List<String> results) { if (arrNode.isArray()) { for (JsonNode entry : arrNode) { flattenArrays(entry, results); } } else { results.add(arrNode.textValue()); } } @SuppressWarnings("nls") private StringBuilder buildParams(StringBuilder url, String query) { addQueryParam(url, "db", database, "?"); addQueryParam(url, "u", username, "&"); addQueryParam(url, "p", password, "&"); addQueryParam(url, "rp", retentionPolicy, "&"); addQueryParam(url, "precision", timePrecision, "&"); addQueryParam(url, "q", query, "&"); return url; } @SuppressWarnings("nls") private void addQueryParam(StringBuilder url, String key, String value, String connector) { if (value == null) return; try { url.append(connector).append(key).append("=").append(URLEncoder.encode(value, StandardCharsets.UTF_8.name())); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } }