1 package de.dlr.shepard.data.timeseries.utilities;
2
3 import com.opencsv.bean.CsvToBeanBuilder;
4 import de.dlr.shepard.common.exceptions.InvalidBodyException;
5 import de.dlr.shepard.common.exceptions.InvalidRequestException;
6 import de.dlr.shepard.data.timeseries.io.TimeseriesWithDataPoints;
7 import de.dlr.shepard.data.timeseries.model.TimeseriesDataPoint;
8 import de.dlr.shepard.data.timeseries.model.TimeseriesTuple;
9 import de.dlr.shepard.data.timeseries.model.enums.CsvFormat;
10 import io.quarkus.logging.Log;
11 import java.io.IOException;
12 import java.io.InputStream;
13 import java.io.InputStreamReader;
14 import java.nio.charset.StandardCharsets;
15 import java.util.ArrayList;
16 import java.util.HashMap;
17 import java.util.List;
18 import java.util.Locale;
19 import java.util.stream.Collectors;
20 import org.apache.commons.lang3.math.NumberUtils;
21
22 public final class CsvConverter {
23
24 private CsvConverter() {}
25
26 public static InputStream convertToCsv(
27 TimeseriesTuple timeseries,
28 List<TimeseriesDataPoint> dataPoints,
29 CsvFormat format
30 ) {
31 return convertToCsv(List.of(new TimeseriesWithDataPoints(timeseries, dataPoints)), format);
32 }
33
34 public static InputStream convertToCsv(
35 List<TimeseriesWithDataPoints> timeseriesWithDataPointsList,
36 CsvFormat format
37 ) {
38 CsvFormat formatNonNull = format != null ? format : CsvFormat.ROW;
39 CsvLineProvider provider =
40 switch (formatNonNull) {
41 case ROW -> new CsvRowLineProvider(timeseriesWithDataPointsList);
42 case COLUMN -> new CsvColumnLineProvider(timeseriesWithDataPointsList);
43 };
44 return new CsvInputStream(provider);
45 }
46
47 public static List<TimeseriesWithDataPoints> convertToTimeseriesWithData(InputStream stream) {
48 try (var reader = new InputStreamReader(stream)) {
49 var timeseriesDataBuilder = new CsvToBeanBuilder<CsvTimeseriesDataPoint>(reader)
50 .withType(CsvTimeseriesDataPoint.class)
51 .withErrorLocale(Locale.forLanguageTag("en"))
52 .withExceptionHandler(e -> {
53 var encoder = StandardCharsets.ISO_8859_1.newEncoder();
54 var message = encoder.canEncode(e.getMessage()) ? e.getMessage() : "Invalid CSV";
55 Log.errorf("CsvException while reading stream: %s", message);
56 throw new InvalidBodyException(message);
57 })
58 .build();
59
60 List<CsvTimeseriesDataPoint> result = timeseriesDataBuilder.parse();
61
62 return convertCsvToTimeseriesWithData(result);
63 } catch (IOException e) {
64 Log.error("IOException while reading the provided InputStream", e);
65 throw new InvalidRequestException();
66 }
67 }
68
69 private static List<TimeseriesWithDataPoints> convertCsvToTimeseriesWithData(
70 List<CsvTimeseriesDataPoint> csvInputList
71 ) {
72 HashMap<TimeseriesTuple, List<TimeseriesDataPoint>> result = new HashMap<
73 TimeseriesTuple,
74 List<TimeseriesDataPoint>
75 >();
76
77 for (var csvInputLine : csvInputList) {
78 var timeseries = new TimeseriesTuple(
79 csvInputLine.getMeasurement(),
80 csvInputLine.getDevice(),
81 csvInputLine.getLocation(),
82 csvInputLine.getSymbolicName(),
83 csvInputLine.getField()
84 );
85 var dataPoint = new TimeseriesDataPoint(csvInputLine.getTimestamp(), parseValue(csvInputLine.getValue()));
86
87 if (result.containsKey(timeseries)) {
88 result.get(timeseries).add(dataPoint);
89 } else {
90 var dataPoints = new ArrayList<TimeseriesDataPoint>();
91 dataPoints.add(dataPoint);
92 result.put(timeseries, dataPoints);
93 }
94 }
95 List<TimeseriesWithDataPoints> timeseriesWithDataPointsList = result
96 .entrySet()
97 .stream()
98 .map(entry -> new TimeseriesWithDataPoints(entry.getKey(), entry.getValue()))
99 .collect(Collectors.toList());
100 return timeseriesWithDataPointsList;
101 }
102
103 private static Object parseValue(Object input) {
104 List<String> boolString = List.of("true", "false");
105 if (input instanceof String sInput) {
106 if (NumberUtils.isCreatable(sInput)) {
107 try {
108 Integer intValue = Integer.parseInt(sInput);
109 return intValue;
110 } catch (NumberFormatException e) {
111 Double doubleValue = Double.parseDouble(sInput);
112 return doubleValue;
113 }
114 } else if (boolString.contains(sInput.toLowerCase())) {
115 return sInput.equalsIgnoreCase("true");
116 }
117 }
118 return input;
119 }
120 }