org.influxdb.impl.InfluxDBResultMapper Maven / Gradle / Ivy
Show all versions of influxdb-java Show documentation
/*
* The MIT License (MIT)
*
* Copyright (c) 2017 azeti Networks AG ()
*
* 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.influxdb.impl;
import org.influxdb.InfluxDBMapperException;
import org.influxdb.annotation.Column;
import org.influxdb.annotation.Exclude;
import org.influxdb.annotation.Measurement;
import org.influxdb.dto.QueryResult;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.time.Instant;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.temporal.ChronoField;
import java.util.LinkedList;
import java.util.List;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
/**
* Main class responsible for mapping a QueryResult to a POJO.
*
* @author fmachado
*/
public class InfluxDBResultMapper {
/**
* Data structure used to cache classes used as measurements.
*/
private static class ClassInfo {
ConcurrentMap fieldMap;
ConcurrentMap typeMappers;
}
private static final
ConcurrentMap CLASS_INFO_CACHE = new ConcurrentHashMap<>();
private static final int FRACTION_MIN_WIDTH = 0;
private static final int FRACTION_MAX_WIDTH = 9;
private static final boolean ADD_DECIMAL_POINT = true;
/**
* When a query is executed without {@link TimeUnit}, InfluxDB returns the time
* column as a RFC3339 date.
*/
private static final DateTimeFormatter RFC3339_FORMATTER = new DateTimeFormatterBuilder()
.appendPattern("yyyy-MM-dd'T'HH:mm:ss")
.appendFraction(ChronoField.NANO_OF_SECOND, FRACTION_MIN_WIDTH, FRACTION_MAX_WIDTH, ADD_DECIMAL_POINT)
.appendZoneOrOffsetId()
.toFormatter();
/**
*
* Process a {@link QueryResult} object returned by the InfluxDB client inspecting the internal
* data structure and creating the respective object instances based on the Class passed as
* parameter.
*
*
* @param queryResult the InfluxDB result object
* @param clazz the Class that will be used to hold your measurement data
* @param the target type
*
* @return a {@link List} of objects from the same Class passed as parameter and sorted on the
* same order as received from InfluxDB.
*
* @throws InfluxDBMapperException If {@link QueryResult} parameter contain errors,
* clazz
parameter is not annotated with @Measurement or it was not
* possible to define the values of your POJO (e.g. due to an unsupported field type).
*/
public List toPOJO(final QueryResult queryResult, final Class clazz) throws InfluxDBMapperException {
return toPOJO(queryResult, clazz, TimeUnit.MILLISECONDS);
}
/**
*
* Process a {@link QueryResult} object returned by the InfluxDB client inspecting the internal
* data structure and creating the respective object instances based on the Class passed as
* parameter.
*
*
* @param queryResult the InfluxDB result object
* @param clazz the Class that will be used to hold your measurement data
* @param precision the time precision of results
* @param the target type
*
* @return a {@link List} of objects from the same Class passed as parameter and sorted on the
* same order as received from InfluxDB.
*
* @throws InfluxDBMapperException If {@link QueryResult} parameter contain errors,
* clazz
parameter is not annotated with @Measurement or it was not
* possible to define the values of your POJO (e.g. due to an unsupported field type).
*/
public List toPOJO(final QueryResult queryResult, final Class clazz,
final TimeUnit precision) throws InfluxDBMapperException {
throwExceptionIfMissingAnnotation(clazz);
String measurementName = getMeasurementName(clazz);
return this.toPOJO(queryResult, clazz, measurementName, precision);
}
/**
*
* Process a {@link QueryResult} object returned by the InfluxDB client inspecting the internal
* data structure and creating the respective object instances based on the Class passed as
* parameter.
*
*
* @param queryResult the InfluxDB result object
* @param clazz the Class that will be used to hold your measurement data
* @param the target type
* @param measurementName name of the Measurement
*
* @return a {@link List} of objects from the same Class passed as parameter and sorted on the
* same order as received from InfluxDB.
*
* @throws InfluxDBMapperException If {@link QueryResult} parameter contain errors,
* clazz
parameter is not annotated with @Measurement or it was not
* possible to define the values of your POJO (e.g. due to an unsupported field type).
*/
public List toPOJO(final QueryResult queryResult, final Class clazz, final String measurementName)
throws InfluxDBMapperException {
return toPOJO(queryResult, clazz, measurementName, TimeUnit.MILLISECONDS);
}
/**
*
* Process a {@link QueryResult} object returned by the InfluxDB client inspecting the internal
* data structure and creating the respective object instances based on the Class passed as
* parameter.
*
*
* @param queryResult the InfluxDB result object
* @param clazz the Class that will be used to hold your measurement data
* @param the target type
* @param measurementName name of the Measurement
* @param precision the time precision of results
*
* @return a {@link List} of objects from the same Class passed as parameter and sorted on the
* same order as received from InfluxDB.
*
* @throws InfluxDBMapperException If {@link QueryResult} parameter contain errors,
* clazz
parameter is not annotated with @Measurement or it was not
* possible to define the values of your POJO (e.g. due to an unsupported field type).
*/
public List toPOJO(final QueryResult queryResult, final Class clazz, final String measurementName,
final TimeUnit precision)
throws InfluxDBMapperException {
Objects.requireNonNull(measurementName, "measurementName");
Objects.requireNonNull(queryResult, "queryResult");
Objects.requireNonNull(clazz, "clazz");
throwExceptionIfResultWithError(queryResult);
cacheMeasurementClass(clazz);
List result = new LinkedList();
queryResult.getResults().stream()
.filter(internalResult -> Objects.nonNull(internalResult) && Objects.nonNull(internalResult.getSeries()))
.forEach(internalResult -> {
internalResult.getSeries().stream()
.filter(series -> series.getName().equals(measurementName))
.forEachOrdered(series -> {
parseSeriesAs(series, clazz, result, precision);
});
});
return result;
}
void throwExceptionIfMissingAnnotation(final Class> clazz) {
if (!clazz.isAnnotationPresent(Measurement.class)) {
throw new IllegalArgumentException(
"Class " + clazz.getName() + " is not annotated with @" + Measurement.class.getSimpleName());
}
}
void throwExceptionIfResultWithError(final QueryResult queryResult) {
if (queryResult.getError() != null) {
throw new InfluxDBMapperException("InfluxDB returned an error: " + queryResult.getError());
}
queryResult.getResults().forEach(seriesResult -> {
if (seriesResult.getError() != null) {
throw new InfluxDBMapperException("InfluxDB returned an error with Series: " + seriesResult.getError());
}
});
}
void cacheMeasurementClass(final Class>... classVarAgrs) {
for (Class> clazz : classVarAgrs) {
if (CLASS_INFO_CACHE.containsKey(clazz.getName())) {
continue;
}
ConcurrentMap fieldMap = new ConcurrentHashMap<>();
ConcurrentMap typeMappers = new ConcurrentHashMap<>();
Measurement measurement = clazz.getAnnotation(Measurement.class);
boolean allFields = measurement != null && measurement.allFields();
Class> c = clazz;
TypeMapper typeMapper = TypeMapper.empty();
while (c != null) {
for (Field field : c.getDeclaredFields()) {
Column colAnnotation = field.getAnnotation(Column.class);
if (colAnnotation == null && !(allFields
&& !field.isAnnotationPresent(Exclude.class) && !Modifier.isStatic(field.getModifiers()))) {
continue;
}
fieldMap.put(getFieldName(field, colAnnotation), field);
typeMappers.put(field, typeMapper);
}
Class> superclass = c.getSuperclass();
Type genericSuperclass = c.getGenericSuperclass();
if (genericSuperclass instanceof ParameterizedType) {
typeMapper = TypeMapper.of((ParameterizedType) genericSuperclass, superclass);
} else {
typeMapper = TypeMapper.empty();
}
c = superclass;
}
ClassInfo classInfo = new ClassInfo();
classInfo.fieldMap = fieldMap;
classInfo.typeMappers = typeMappers;
CLASS_INFO_CACHE.putIfAbsent(clazz.getName(), classInfo);
}
}
private static String getFieldName(final Field field, final Column colAnnotation) {
if (colAnnotation != null && !colAnnotation.name().isEmpty()) {
return colAnnotation.name();
}
return field.getName();
}
String getMeasurementName(final Class> clazz) {
return ((Measurement) clazz.getAnnotation(Measurement.class)).name();
}
String getDatabaseName(final Class> clazz) {
return ((Measurement) clazz.getAnnotation(Measurement.class)).database();
}
String getRetentionPolicy(final Class> clazz) {
return ((Measurement) clazz.getAnnotation(Measurement.class)).retentionPolicy();
}
List parseSeriesAs(final QueryResult.Series series, final Class clazz, final List result) {
return parseSeriesAs(series, clazz, result, TimeUnit.MILLISECONDS);
}
List parseSeriesAs(final QueryResult.Series series, final Class clazz, final List result,
final TimeUnit precision) {
int columnSize = series.getColumns().size();
ClassInfo classInfo = CLASS_INFO_CACHE.get(clazz.getName());
try {
T object = null;
for (List