/*
* Copyright 2015 the original author or authors.
*
* 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.pivotal.demo.smartgrid.frontend.timeseries;
import io.pivotal.demo.smartgrid.frontend.TimeSeriesDataRequest;
import java.net.HttpURLConnection;
import java.net.URL;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import javax.annotation.PostConstruct;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
/**
* @author Thomas Darimont
*/
@Component
public class AggregateCounterTimeSeriesRepository implements TimeSeriesRepository {
private static final Logger LOG = LoggerFactory.getLogger(AggregateCounterTimeSeriesRepository.class);
private final RestTemplate restTemplate = new RestTemplate();
@Value("${xdServerBaseUrl}") private String xdServerBaseUrl;
@Value("${aggregateCounterUrlPattern}") private String aggregateCounterUrlPattern;
@PostConstruct
public void init() {
pingXdServer();
}
private void pingXdServer() {
try {
HttpURLConnection con = (HttpURLConnection) new URL(xdServerBaseUrl).openConnection();
con.setRequestMethod("HEAD");
int timeout = 2000;
con.setReadTimeout(timeout);
con.setConnectTimeout(timeout);
int responseCode = con.getResponseCode();
if (responseCode != HttpURLConnection.HTTP_OK) {
LOG.error("Bad response from server: {} Response: {}", xdServerBaseUrl, responseCode);
}
} catch (Exception ex) {
LOG.error("Could not connect to server: {} Error: {}: {}", xdServerBaseUrl, ex.getClass().getSimpleName(), ex.getMessage());
}
}
@Override
public Map<String, TimeSeriesCollection> getTimeSeriesData(TimeSeriesDataRequest dataRequest) {
int houseId = dataRequest.getHouseId();
IntStream houseNumStream = houseId == GRID_HOUSE_ID ? IntStream.rangeClosed(HOUSE_ID_MIN, HOUSE_ID_MAX) : IntStream
.of(houseId);
List<AggregateCounterCollection> aggregateCounterCollections = houseNumStream.parallel()
.mapToObj(i -> new TimeSeriesDataRequest(dataRequest, i)).map(this::fetchAggregateCounterData)
.filter(acc -> acc != null && !acc.getAggregateCounters().isEmpty()).collect(Collectors.toList());
Map<String, TimeSeriesCollection> result = new HashMap<>();
for (AggregateCounterCollection acc : aggregateCounterCollections) {
TimeSeriesCollection tsc = convertToTimeSeriesCollection(acc);
result.put(tsc.getName(), tsc);
}
TimeSeriesCollection totalGridTimeSeriesCollection = aggreagteGridTotalTimeSeries(result);
result.put("h_-1", totalGridTimeSeriesCollection);
return result;
}
private TimeSeriesCollection aggreagteGridTotalTimeSeries(Map<String, TimeSeriesCollection> result) {
TimeSeriesCollection totalGridTimeSeriesCollection = new TimeSeriesCollection("grid_h-1");
for (Map.Entry<String, TimeSeriesCollection> entry : result.entrySet()) {
TimeSeriesCollection timeSeriesCollection = entry.getValue();
if (totalGridTimeSeriesCollection.getTimeSeries().isEmpty()) {
for (TimeSeries timeSeries : timeSeriesCollection.getTimeSeries()) {
TimeSeries newTimeSeries = new TimeSeries("grid" + timeSeries.getName(), new ArrayList<DataPoint>(
timeSeries.getData()));
totalGridTimeSeriesCollection.getTimeSeries().add(newTimeSeries);
}
continue;
}
List<TimeSeries> timeSeriesList = timeSeriesCollection.getTimeSeries();
for (int timeSeriesIndex = 0, timeSeriesCount = timeSeriesList.size(); timeSeriesIndex < timeSeriesCount; timeSeriesIndex++) {
TimeSeries timeSeries = timeSeriesList.get(timeSeriesIndex);
TimeSeries gridTimeSeries = totalGridTimeSeriesCollection.getTimeSeries().get(timeSeriesIndex);
List<DataPoint> gridDataPoints = gridTimeSeries.getData();
List<DataPoint> currentDataPoints = timeSeries.getData();
for (int dataPointIndex = 0, dataPointCount = currentDataPoints.size(); dataPointIndex < dataPointCount; dataPointIndex++) {
DataPoint currentDataPoint = currentDataPoints.get(dataPointIndex);
DataPoint gridDataPoint = gridDataPoints.get(dataPointIndex);
gridDataPoints.set(dataPointIndex, new DataPoint(gridDataPoint.getTs(), gridDataPoint.getValue()
+ currentDataPoint.getValue()));
}
}
}
return totalGridTimeSeriesCollection;
}
private TimeSeriesCollection convertToTimeSeriesCollection(AggregateCounterCollection acc) {
TimeSeriesCollection tsc = new TimeSeriesCollection(acc.getName());
for (Map.Entry<String, AggregateCounter> entry : acc.getAggregateCounters().entrySet()) {
String timeSeriesName = entry.getKey();
AggregateCounter aggregateCounter = entry.getValue();
List<String> timeAxis = new ArrayList<>();
List<String> valueAxis = new ArrayList<>();
for (Map.Entry<String, String> dataPoint : aggregateCounter.getCounts().entrySet()) {
String pit = dataPoint.getKey();
String value = dataPoint.getValue();
LocalDateTime ldt = LocalDateTime.parse(pit, DateTimeFormatter.ISO_DATE_TIME);
timeAxis.add("" + ldt.toEpochSecond(ZoneOffset.UTC));
valueAxis.add(value);
}
tsc.registerTimeSeries(timeSeriesName, timeAxis, valueAxis);
}
return tsc;
}
private String makeAggregateCounterUrl(TimeSeriesType timeSeriesType, TimeSeriesDataRequest dataRequest) {
String baseUrl = String.format(aggregateCounterUrlPattern, xdServerBaseUrl, dataRequest.getHouseId(),
timeSeriesType.name().toLowerCase());
UriComponentsBuilder ucb = UriComponentsBuilder.fromHttpUrl(baseUrl)
.queryParam("resolution", dataRequest.getResolution().name().toLowerCase())
.queryParam("from", dataRequest.getFromDateTime()).queryParam("to", dataRequest.getToDateTime());
String url = ucb.build().toString();
return url;
}
private AggregateCounterCollection fetchAggregateCounterData(TimeSeriesDataRequest request) {
AggregateCounterCollection acc = new AggregateCounterCollection(makeHouseKey(request.getHouseId()));
try {
AggregateCounter ac = restTemplate.getForObject(makeAggregateCounterUrl(TimeSeriesType.ACTUAL, request),
AggregateCounter.class);
if (ac != null) {
acc.register(ac.getName(), ac);
}
} catch (Exception ex) {
LOG.error("Error retrieving data for request: {}", request);
return null;
}
try {
AggregateCounter ac = restTemplate.getForObject(makeAggregateCounterUrl(TimeSeriesType.PREDICTED, request),
AggregateCounter.class);
if (ac != null) {
acc.register(ac.getName(), ac);
}
} catch (Exception ex) {
LOG.error("Error retrieving data for request: {}", request);
return null;
}
return acc;
}
private String makeHouseKey(int houseId) {
return "h_" + houseId;
}
}