1 package org.simantics.history.rest;
3 import java.io.IOException;
4 import java.math.BigDecimal;
5 import java.util.Arrays;
6 import java.util.HashMap;
10 import javax.ws.rs.GET;
11 import javax.ws.rs.POST;
12 import javax.ws.rs.Path;
13 import javax.ws.rs.Produces;
14 import javax.ws.rs.QueryParam;
15 import javax.ws.rs.core.CacheControl;
16 import javax.ws.rs.core.MediaType;
17 import javax.ws.rs.core.Response;
18 import javax.ws.rs.core.Response.Status;
20 import org.simantics.db.exception.DatabaseException;
21 import org.simantics.history.HistoryException;
22 import org.simantics.history.HistorySampler;
23 import org.simantics.history.HistorySamplerItem;
24 import org.simantics.history.HistorySamplerItem2;
25 import org.simantics.history.csv.ExportInterpolation;
26 import org.simantics.history.util.HistoryExportUtil;
27 import org.simantics.history.util.StreamIterator;
28 import org.simantics.history.util.ValueBand;
30 import gnu.trove.list.array.TDoubleArrayList;
33 @Produces(MediaType.APPLICATION_JSON)
34 public class HistoryRestApi {
36 private static gnu.trove.map.TLongObjectMap<HistoryCache> historyCaches = new gnu.trove.map.hash.TLongObjectHashMap<>();
38 private static CacheControl cacheControl;
41 cacheControl = new CacheControl();
42 cacheControl.setNoCache(true);
43 cacheControl.setNoStore(true);
48 public static synchronized Response register(@QueryParam("runId") long run) {
49 if (historyCaches.containsKey(run))
50 return Response.ok().build();
52 HistoryCache cache = new HistoryCache(run);
53 historyCaches.put(run, cache);
54 return Response.ok().cacheControl(cacheControl).build();
55 } catch (DatabaseException e) {
56 return Response.status(Status.NOT_FOUND).build();
62 public static synchronized Response unregister(@QueryParam("runId") long run) {
63 HistoryCache cache = historyCaches.remove(run);
67 return Response.ok().cacheControl(cacheControl).build();
72 public static Response activeHistoryValuesAndTimesWithTimeStep(@QueryParam("runId") long run,@QueryParam("itemId") long item,@QueryParam("startTime") double startTime,@QueryParam("endTime") double endTime,@QueryParam("timeWindow") double timeWindow, @QueryParam("timeStep")double timeStep) {
74 HistoryCache cache = getOrCreateCache(run);
75 TDoubleArrayList doubles;
76 synchronized (cache) {
77 HistorySamplerItem hi = cache.getSamplerItem(item);
78 doubles= HistorySampler.sample(hi, startTime, endTime, timeWindow, timeStep, true);
80 return Response.ok(buildJSONResponse("values",doubles.toArray())).cacheControl(cacheControl).build();
81 } catch (HistoryException | IOException | DatabaseException e) {
82 return Response.serverError().build();
88 public static Response sampleHistory(@QueryParam("runId")long run,@QueryParam("itemId") long item,@QueryParam("endTime") double endTime, @QueryParam("timeWindow") double timeWindow,@QueryParam("maxSamples") int maxSamples,@QueryParam("resample") boolean resample) {
90 HistoryCache cache = getOrCreateCache(run);
91 TDoubleArrayList doubles = null;
92 synchronized (cache) {
93 HistorySamplerItem2 hi = cache.getSamplerItem2(item);
94 doubles = HistorySampler.sample(hi, endTime, timeWindow, maxSamples, resample);
96 return Response.ok(buildJSONResponse("values",doubles.toArray())).cacheControl(cacheControl).build();
97 } catch (HistoryException | IOException | DatabaseException e) {
98 return Response.serverError().build();
104 public static Response sampleHistory(@QueryParam("runId")long run,@QueryParam("itemId") long item,@QueryParam("startTime") double startTime, @QueryParam("endTime") double endTime, @QueryParam("maxSamples") int maxSamples) {
107 return Response.status(Status.BAD_REQUEST).build();
109 HistoryCache cache = getOrCreateCache(run);
110 Map<String, Object> result = null;
111 synchronized (cache) {
112 result = _sampleHistory(cache, item, startTime, endTime, maxSamples);
114 return Response.ok(result).cacheControl(cacheControl).build();
115 } catch (HistoryException | DatabaseException e) {
116 return Response.serverError().build();
120 @Path("/multisamples")
122 public static Response sampleHistories(@QueryParam("runId")long run,@QueryParam("itemId") final List<Long> items,@QueryParam("startTime") double startTime, @QueryParam("endTime") double endTime, @QueryParam("maxSamples") int maxSamples) {
125 return Response.status(Status.BAD_REQUEST).build();
127 HistoryCache cache = getOrCreateCache(run);
128 Map<String, Object> combined = new HashMap<>();
129 synchronized (cache) {
130 for (long item : items) {
131 Map<String, Object> result = _sampleHistory(cache, item, startTime, endTime, maxSamples);
132 combined.put(Long.toString(item), result);
135 return Response.ok(combined).cacheControl(cacheControl).build();
136 } catch (HistoryException | DatabaseException e) {
137 return Response.serverError().build();
143 public static Response ranges(@QueryParam("runId")long run,@QueryParam("itemId") final List<Long> items) {
146 HistoryCache cache = getOrCreateCache(run);
147 Map<String, Object> combined = new HashMap<>();
148 synchronized (cache) {
149 for (long item : items) {
150 HistorySamplerItem2 hi = null;
152 hi= cache.getSamplerItem2(item);
154 double first = hi.iter.getFirstTime();
155 double last = hi.iter.getLastTime();
156 combined.put(Long.toString(item), buildJSONResponse("start",first,"end",last));
164 return Response.ok(combined).cacheControl(cacheControl).build();
166 } catch (HistoryException | DatabaseException e) {
167 return Response.serverError().build();
170 private static synchronized HistoryCache getOrCreateCache(long run) throws DatabaseException {
171 HistoryCache cache = historyCaches.get(run);
172 if (cache == null || cache.isDisposed()) {
173 cache = new HistoryCache(run);
174 historyCaches.put(run, cache);
181 private static Map<String,Object> _sampleHistory(HistoryCache cache, long item, double startTime, double endTime, int maxSamples) throws DatabaseException, HistoryException {
182 HistorySamplerItem2 hi = null;
184 hi = cache.getSamplerItem2(item);
186 double timeWindow = endTime - startTime;
187 //double timeStep = 0.0;
188 double timeStep = timeWindow / maxSamples;
189 double secondsPerPixel = timeWindow / (double) maxSamples;
192 hi.open(secondsPerPixel);
193 StreamIterator iter = hi.iter;
194 if (iter.isEmpty()) {
195 buildJSONResponse("time",new double[0],"value",new double[0]);
197 double dataFrom = iter.getFirstTime();
198 double dataEnd = iter.getLastTime();
199 boolean hasAnyValues = dataFrom != Double.MAX_VALUE && dataEnd != -Double.MAX_VALUE;
201 return buildJSONResponse("time",new double[0],"value",new double[0]);
203 double from = Math.max(startTime, dataFrom);
204 double end = Math.min(endTime, dataEnd);
205 iter.gotoTime(startTime);
209 BigDecimal bigTime = new BigDecimal(String.valueOf(time));
210 BigDecimal bigTimeStep = new BigDecimal(String.valueOf(timeStep));
212 TDoubleArrayList times = new TDoubleArrayList();
213 TDoubleArrayList values = new TDoubleArrayList();
214 TDoubleArrayList min = new TDoubleArrayList();
215 TDoubleArrayList max = new TDoubleArrayList();
216 TDoubleArrayList avg = new TDoubleArrayList();
217 if (!iter.gotoTime(time)) {
218 return buildJSONResponse("time",new double[0],"value",new double[0]);
222 ExportInterpolation interpolation = ExportInterpolation.LINEAR_INTERPOLATION;
224 //System.out.println("process " + time + " " + iter.getValueBand() + " (ignore = " + ignore + ")");
226 // Check for valid value
227 if ( iter.hasValidValue() ) {
232 Object value = iter.getValueBand().getValue();
233 //System.out.print("Add value : " + value);
234 if (value instanceof Number) {
235 if (value instanceof Float || value instanceof Double) {
236 switch (interpolation) {
237 case PREVIOUS_SAMPLE:
238 values.add(((Number) value).doubleValue());
241 case LINEAR_INTERPOLATION:
242 if (time != iter.getValueBand().getTimeDouble() && iter.hasNext()) {
244 int currentIndex = iter.getIndex();
245 ValueBand band = iter.getValueBand();
246 //double t1 = band.getTimeDouble();
247 Number v1 = (Number) value;
248 double t12 = band.getEndTimeDouble();
250 double t2 = iter.getValueBand().getTimeDouble();
251 Number v2 = (Number) iter.getValueBand().getValue();
252 iter.gotoIndex(currentIndex);
254 double vs = v1.doubleValue();
256 vs = HistoryExportUtil.biglerp(t12, v1.doubleValue(), t2, v2.doubleValue(), time);
261 // Exact timestamp match, or last sample.
262 // Don't interpolate nor extrapolate.
263 values.add(((Number) value).doubleValue());
268 throw new UnsupportedOperationException("Unsupported interpolation: " + interpolation);
270 if (iter.getValueBand().hasMax())
271 max.add(((Number) iter.getValueBand().getMax()).doubleValue());
274 if (iter.getValueBand().hasMin())
275 min.add(((Number) iter.getValueBand().getMin()).doubleValue());
278 if (iter.getValueBand().hasAvg())
279 avg.add(((Number) iter.getValueBand().getAvg()).doubleValue());
283 throw new IllegalStateException("Value is not a number " + value);
285 } else if (value instanceof Boolean) {
286 values.add( (Boolean)value ? 1.0: 0.0);
288 throw new IllegalStateException("Value is not a number " + value);
294 // Read next values, and the following times
295 if ( timeStep>0.0 ) {
296 bigTime = bigTime.add(bigTimeStep);
297 time = bigTime.doubleValue();
299 // Get smallest end time that is larger than current time
300 Double nextTime = null;
301 //System.out.println(" time = "+time);
302 if(!iter.hasNext()) {
303 duplicateLastDataPoint(values);
307 Double itemNextTime = iter.getNextTime( time );
308 //System.out.println(" "+iter.toString()+" nextTime="+itemNextTime);
309 if ( nextTime == null || ( nextTime > itemNextTime && !itemNextTime.equals( time ) ) ) nextTime = itemNextTime;
310 if ( nextTime == null || nextTime.equals( time ) ) break;
314 boolean hasMore = false;
316 iter.proceedToTime(time);
317 if (HistoryExportUtil.contains(iter, time)) hasMore = true;
321 duplicateLastDataPoint(values);
327 } while (time <= end);
329 return buildJSONResponse("time", times.toArray(),"value",values.toArray(), "min", min.toArray(), "max", max.toArray(),"avg", avg.toArray());
334 } catch (Throwable err) {
340 private static void duplicateLastDataPoint(TDoubleArrayList data) {
341 double lastValue = data.get(data.size() - 1);
342 //System.out.println("Duplicating last sample value " + lastValue + " @ " + timestamp);
346 private static Map<String, Object> buildJSONResponse(Object... keyValues) {
347 if ((keyValues.length % 2) != 0)
348 throw new IllegalArgumentException("Invalid amount of arguments! " + Arrays.toString(keyValues));
349 Map<String, Object> results = new HashMap<>(keyValues.length / 2);
350 for (int i = 0; i < keyValues.length; i += 2) {
351 Object key = keyValues[i];
352 Object value = keyValues[i + 1];
353 if (!(key instanceof String))
354 throw new IllegalArgumentException("Key with index " + i + " is not String");
355 results.put((String) key, value);