diff --git a/CHANGELOG.md b/CHANGELOG.md index da1cdc53e..233d45cc7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## 2.13 [unreleased] +### Fixes + +- The InfluxDBResultMapper is able to handle results with a different time precision [PR #501](https://github.com/influxdata/influxdb-java/pull/501) + ### Features - Support for Basic Authentication [PR #492](https://github.com/influxdata/influxdb-java/pull/492) diff --git a/src/main/java/org/influxdb/impl/InfluxDBResultMapper.java b/src/main/java/org/influxdb/impl/InfluxDBResultMapper.java index efe3a991a..000f517b7 100644 --- a/src/main/java/org/influxdb/impl/InfluxDBResultMapper.java +++ b/src/main/java/org/influxdb/impl/InfluxDBResultMapper.java @@ -27,8 +27,8 @@ import java.time.temporal.ChronoField; import java.util.LinkedList; import java.util.List; -import java.util.Objects; import java.util.Map.Entry; +import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; @@ -84,9 +84,33 @@ public class InfluxDBResultMapper { * 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); + return this.toPOJO(queryResult, clazz, measurementName, precision); } /** @@ -110,6 +134,32 @@ public List toPOJO(final QueryResult queryResult, final Class clazz) t */ 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"); @@ -126,7 +176,7 @@ public List toPOJO(final QueryResult queryResult, final Class clazz, f internalResult.getSeries().stream() .filter(series -> series.getName().equals(measurementName)) .forEachOrdered(series -> { - parseSeriesAs(series, clazz, result); + parseSeriesAs(series, clazz, result, precision); }); }); @@ -177,6 +227,11 @@ String getMeasurementName(final Class clazz) { } 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(); ConcurrentMap colNameAndFieldMap = CLASS_FIELD_CACHE.get(clazz.getName()); try { @@ -188,7 +243,7 @@ List parseSeriesAs(final QueryResult.Series series, final Class clazz, if (object == null) { object = clazz.newInstance(); } - setFieldValue(object, correspondingField, row.get(i)); + setFieldValue(object, correspondingField, row.get(i), precision); } } // When the "GROUP BY" clause is used, "tags" are returned as Map and @@ -200,7 +255,7 @@ List parseSeriesAs(final QueryResult.Series series, final Class clazz, Field correspondingField = colNameAndFieldMap.get(entry.getKey()/*InfluxDB columnName*/); if (correspondingField != null) { // I don't think it is possible to reach here without a valid "object" - setFieldValue(object, correspondingField, entry.getValue()); + setFieldValue(object, correspondingField, entry.getValue(), precision); } } } @@ -223,10 +278,11 @@ List parseSeriesAs(final QueryResult.Series series, final Class clazz, * @param object * @param field * @param value + * @param precision * @throws IllegalArgumentException * @throws IllegalAccessException */ - void setFieldValue(final T object, final Field field, final Object value) + void setFieldValue(final T object, final Field field, final Object value, final TimeUnit precision) throws IllegalArgumentException, IllegalAccessException { if (value == null) { return; @@ -236,7 +292,7 @@ void setFieldValue(final T object, final Field field, final Object value) if (!field.isAccessible()) { field.setAccessible(true); } - if (fieldValueModified(fieldType, field, object, value) + if (fieldValueModified(fieldType, field, object, value, precision) || fieldValueForPrimitivesModified(fieldType, field, object, value) || fieldValueForPrimitiveWrappersModified(fieldType, field, object, value)) { return; @@ -252,7 +308,8 @@ void setFieldValue(final T object, final Field field, final Object value) } } - boolean fieldValueModified(final Class fieldType, final Field field, final T object, final Object value) + boolean fieldValueModified(final Class fieldType, final Field field, final T object, final Object value, + final TimeUnit precision) throws IllegalArgumentException, IllegalAccessException { if (String.class.isAssignableFrom(fieldType)) { field.set(object, String.valueOf(value)); @@ -263,9 +320,11 @@ boolean fieldValueModified(final Class fieldType, final Field field, fina if (value instanceof String) { instant = Instant.from(ISO8601_FORMATTER.parse(String.valueOf(value))); } else if (value instanceof Long) { - instant = Instant.ofEpochMilli((Long) value); + instant = Instant.ofEpochMilli(toMillis((long) value, precision)); } else if (value instanceof Double) { - instant = Instant.ofEpochMilli(((Double) value).longValue()); + instant = Instant.ofEpochMilli(toMillis(((Double) value).longValue(), precision)); + } else if (value instanceof Integer) { + instant = Instant.ofEpochMilli(toMillis(((Integer) value).longValue(), precision)); } else { throw new InfluxDBMapperException("Unsupported type " + field.getClass() + " for field " + field.getName()); } @@ -316,4 +375,9 @@ boolean fieldValueForPrimitiveWrappersModified(final Class fieldType, fin } return false; } + + private Long toMillis(final long value, final TimeUnit precision) { + + return TimeUnit.MILLISECONDS.convert(value, precision); + } } diff --git a/src/test/java/org/influxdb/impl/InfluxDBResultMapperTest.java b/src/test/java/org/influxdb/impl/InfluxDBResultMapperTest.java index 688ab9387..445528fb6 100644 --- a/src/test/java/org/influxdb/impl/InfluxDBResultMapperTest.java +++ b/src/test/java/org/influxdb/impl/InfluxDBResultMapperTest.java @@ -29,12 +29,12 @@ import java.util.Map; import java.util.Random; import java.util.UUID; +import java.util.concurrent.TimeUnit; import org.influxdb.InfluxDBMapperException; import org.influxdb.annotation.Column; import org.influxdb.annotation.Measurement; import org.influxdb.dto.QueryResult; -import org.influxdb.impl.InfluxDBResultMapper; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.platform.runner.JUnitPlatform; @@ -220,6 +220,26 @@ public void testFieldValueModified_DateAsISO8601() { Assertions.assertTrue(result.size() == 1); } + @Test + public void testFieldValueModified_DateAsInteger() { + // Given... + mapper.cacheMeasurementClass(MyCustomMeasurement.class); + + List columnList = Arrays.asList("time"); + List firstSeriesResult = Arrays.asList(1_000); + + QueryResult.Series series = new QueryResult.Series(); + series.setColumns(columnList); + series.setValues(Arrays.asList(firstSeriesResult)); + + //When... + List result = new LinkedList<>(); + mapper.parseSeriesAs(series, MyCustomMeasurement.class, result); + + //Then... + Assertions.assertTrue(result.size() == 1); + } + @Test public void testUnsupportedField() { // Given... @@ -335,6 +355,60 @@ public void testToPOJO_ticket363() { Assertions.assertEquals(1, result.get(0).time.getNano(), "incorrect value for the nanoseconds field"); } + @Test + void testToPOJO_Precision() { + // Given... + mapper.cacheMeasurementClass(MyCustomMeasurement.class); + + List columnList = Arrays.asList("time"); + List firstSeriesResult = Arrays.asList(1_500_000L); + + QueryResult.Series series = new QueryResult.Series(); + series.setName("CustomMeasurement"); + series.setColumns(columnList); + series.setValues(Arrays.asList(firstSeriesResult)); + + QueryResult.Result internalResult = new QueryResult.Result(); + internalResult.setSeries(Arrays.asList(series)); + + QueryResult queryResult = new QueryResult(); + queryResult.setResults(Arrays.asList(internalResult)); + + // When... + List result = mapper.toPOJO(queryResult, MyCustomMeasurement.class, TimeUnit.SECONDS); + + // Then... + Assertions.assertEquals(1, result.size(), "incorrect number of elements"); + Assertions.assertEquals(1_500_000_000L, result.get(0).time.toEpochMilli(), "incorrect value for the millis field"); + } + + @Test + void testToPOJO_SetMeasureName() { + // Given... + mapper.cacheMeasurementClass(MyCustomMeasurement.class); + + List columnList = Arrays.asList("uuid"); + List firstSeriesResult = Arrays.asList(UUID.randomUUID().toString()); + + QueryResult.Series series = new QueryResult.Series(); + series.setName("MySeriesName"); + series.setColumns(columnList); + series.setValues(Arrays.asList(firstSeriesResult)); + + QueryResult.Result internalResult = new QueryResult.Result(); + internalResult.setSeries(Arrays.asList(series)); + + QueryResult queryResult = new QueryResult(); + queryResult.setResults(Arrays.asList(internalResult)); + + //When... + List result = + mapper.toPOJO(queryResult, MyCustomMeasurement.class, "MySeriesName"); + + //Then... + Assertions.assertTrue(result.size() == 1); + } + @Measurement(name = "CustomMeasurement") static class MyCustomMeasurement {