]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.history.rest/src/org/simantics/history/rest/HistoryRestApi.java
Rest API for Historian data
[simantics/platform.git] / bundles / org.simantics.history.rest / src / org / simantics / history / rest / HistoryRestApi.java
1 package org.simantics.history.rest;
2
3 import java.io.IOException;
4 import java.math.BigDecimal;
5 import java.util.Arrays;
6 import java.util.HashMap;
7 import java.util.List;
8 import java.util.Map;
9
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;
19
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;
29
30 import gnu.trove.list.array.TDoubleArrayList;
31
32 @Path("history")
33 @Produces(MediaType.APPLICATION_JSON)
34 public class HistoryRestApi {
35
36         private static gnu.trove.map.TLongObjectMap<HistoryCache> historyCaches = new gnu.trove.map.hash.TLongObjectHashMap<>();
37         
38         private static CacheControl cacheControl;
39         
40         static {
41                 cacheControl = new CacheControl();
42                 cacheControl.setNoCache(true);
43                 cacheControl.setNoStore(true);
44         }
45
46         @Path("/register")
47         @POST
48         public static synchronized Response register(@QueryParam("runId") long run) {
49                 if (historyCaches.containsKey(run))
50                         return Response.ok().build();
51                 try {
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();
57                 }
58         }
59
60         @Path("/unregister")
61         @POST
62         public static synchronized Response unregister(@QueryParam("runId") long run) {
63                 HistoryCache cache = historyCaches.remove(run);
64                 if (cache != null) {
65                         cache.dispose();
66                 }
67                 return Response.ok().cacheControl(cacheControl).build();
68         }
69
70         @Path("/values")
71         @GET
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) {
73                 try {
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);
79                         }        
80                         return Response.ok(buildJSONResponse("values",doubles.toArray())).cacheControl(cacheControl).build();
81                 } catch (HistoryException | IOException | DatabaseException e) {
82                         return Response.serverError().build();
83                 }
84
85         }
86         @Path("/lvalues")
87         @GET
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) {
89                 try {
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);
95                         }
96                         return Response.ok(buildJSONResponse("values",doubles.toArray())).cacheControl(cacheControl).build();
97                 } catch (HistoryException | IOException | DatabaseException e) {
98                         return Response.serverError().build();
99                 }
100         }
101         
102         @Path("/samples")
103         @GET
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) {
105                 
106                 if (maxSamples <= 0)
107                         return Response.status(Status.BAD_REQUEST).build();
108                 try {
109                         HistoryCache cache = getOrCreateCache(run);
110                         Map<String, Object> result = null;
111                         synchronized (cache) {
112                                 result = _sampleHistory(cache, item, startTime, endTime, maxSamples);
113                         }
114                         return Response.ok(result).cacheControl(cacheControl).build();
115                 } catch (HistoryException | DatabaseException e) {
116                         return Response.serverError().build();
117                 }
118         }
119         
120         @Path("/multisamples")
121         @GET
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) {
123                 
124                 if (maxSamples <= 0)
125                         return Response.status(Status.BAD_REQUEST).build();
126                 try {
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);
133                                 }
134                         }
135                         return Response.ok(combined).cacheControl(cacheControl).build();
136                 } catch (HistoryException | DatabaseException e) {
137                         return Response.serverError().build();
138                 }
139         }
140         
141         @Path("/ranges")
142         @GET
143         public static Response ranges(@QueryParam("runId")long run,@QueryParam("itemId") final List<Long> items) {
144                 
145                 try {
146                         HistoryCache cache = getOrCreateCache(run);
147                         Map<String, Object> combined = new HashMap<>();
148                         synchronized (cache) {
149                                 for (long item : items) {
150                                         HistorySamplerItem2 hi = null;
151                                         try {
152                                                 hi= cache.getSamplerItem2(item);
153                                                 hi.open();
154                                                 double first = hi.iter.getFirstTime();
155                                                 double last = hi.iter.getLastTime();
156                                                 combined.put(Long.toString(item), buildJSONResponse("start",first,"end",last));
157                                                 
158                                         } finally {
159                                                 hi.close();
160                                         }
161                                         
162                                 }
163                         }
164                         return Response.ok(combined).cacheControl(cacheControl).build();
165                         
166                 } catch (HistoryException | DatabaseException e) {
167                         return Response.serverError().build();
168                 }
169         }
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);
175                 } 
176                 return cache;
177         }
178         
179         
180         
181         private static Map<String,Object> _sampleHistory(HistoryCache cache, long item, double startTime, double endTime, int maxSamples) throws DatabaseException, HistoryException {
182                 HistorySamplerItem2 hi = null;
183                 try {
184                         hi = cache.getSamplerItem2(item);
185                         
186                         double timeWindow = endTime - startTime;
187                         //double timeStep = 0.0; 
188                         double timeStep = timeWindow / maxSamples;
189                         double secondsPerPixel = timeWindow / (double) maxSamples;
190                         hi.flush();
191                         if (hi.iter == null)
192                                 hi.open(secondsPerPixel);
193                         StreamIterator iter = hi.iter;
194                         if (iter.isEmpty()) {
195                                 buildJSONResponse("time",new double[0],"value",new double[0]);
196                         }
197                         double dataFrom = iter.getFirstTime();
198                         double dataEnd = iter.getLastTime();
199                         boolean hasAnyValues = dataFrom != Double.MAX_VALUE && dataEnd != -Double.MAX_VALUE;
200                         if (!hasAnyValues) {
201                                 return buildJSONResponse("time",new double[0],"value",new double[0]);   
202                         }
203                         double from = Math.max(startTime, dataFrom);
204                         double end = Math.min(endTime, dataEnd);
205                         iter.gotoTime(startTime);
206                         
207                         double time = from;
208                         
209                         BigDecimal bigTime = new BigDecimal(String.valueOf(time));
210                         BigDecimal bigTimeStep = new BigDecimal(String.valueOf(timeStep));
211         
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]);   
219                         }
220         
221                         
222                         ExportInterpolation interpolation = ExportInterpolation.LINEAR_INTERPOLATION;
223                         do {
224                                 //System.out.println("process " + time + " " + iter.getValueBand() + " (ignore = " + ignore + ")");
225         
226                                 // Check for valid value
227                                 if ( iter.hasValidValue() ) {
228         
229                                         // Write time
230                                         times.add(time);
231                                         // Write value
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());
239                                                                 break;
240         
241                                                         case LINEAR_INTERPOLATION:
242                                                                 if (time != iter.getValueBand().getTimeDouble() && iter.hasNext()) {
243                                                                         // Interpolate
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();
249                                                                         iter.next();
250                                                                         double t2 = iter.getValueBand().getTimeDouble();
251                                                                         Number v2 = (Number) iter.getValueBand().getValue();
252                                                                         iter.gotoIndex(currentIndex);
253         
254                                                                         double vs = v1.doubleValue();
255                                                                         if (time > t12)
256                                                                                 vs = HistoryExportUtil.biglerp(t12, v1.doubleValue(), t2, v2.doubleValue(), time);
257         
258                                                                         values.add(vs);
259         
260                                                                 } else {
261                                                                         // Exact timestamp match, or last sample.
262                                                                         // Don't interpolate nor extrapolate.
263                                                                         values.add(((Number) value).doubleValue());
264                                                                 }
265                                                                 break;
266         
267                                                         default:
268                                                                 throw new UnsupportedOperationException("Unsupported interpolation: " + interpolation);
269                                                         }
270                                                         if (iter.getValueBand().hasMax())
271                                                                 max.add(((Number) iter.getValueBand().getMax()).doubleValue());
272                                                         else
273                                                                 max.add(Double.NaN);
274                                                         if (iter.getValueBand().hasMin())
275                                                                 min.add(((Number) iter.getValueBand().getMin()).doubleValue());
276                                                         else
277                                                                 min.add(Double.NaN);
278                                                         if (iter.getValueBand().hasAvg())
279                                                                 avg.add(((Number) iter.getValueBand().getAvg()).doubleValue());
280                                                         else
281                                                                 avg.add(Double.NaN);
282                                                 } else {
283                                                         throw new IllegalStateException("Value is not a number " + value);
284                                                 }
285                                         } else if (value instanceof Boolean) {
286                                                 values.add( (Boolean)value ? 1.0: 0.0);
287                                         } else {
288                                                 throw new IllegalStateException("Value is not a number " + value);
289                                         }
290                                         
291                                 }
292                                 
293         
294                                 // Read next values, and the following times
295                                 if ( timeStep>0.0 ) {
296                                         bigTime = bigTime.add(bigTimeStep);
297                                         time = bigTime.doubleValue();
298                                 } else {
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);
304                                                 times.add(end);
305                                                 break;
306                                         }
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;
311                                         time = nextTime;
312                                 }
313         
314                                 boolean hasMore = false;
315         
316                                 iter.proceedToTime(time);
317                                 if (HistoryExportUtil.contains(iter, time)) hasMore = true;
318         
319                                 if (!hasMore) {
320                                         if (time <= end) {
321                                                 duplicateLastDataPoint(values);
322                                                 times.add(end);
323                                         }
324                                         break;
325                                 }
326         
327                         } while (time <= end);
328                         
329                         return buildJSONResponse("time", times.toArray(),"value",values.toArray(), "min", min.toArray(), "max", max.toArray(),"avg", avg.toArray());
330                 } finally {
331                         if (hi != null) {
332                                 try {
333                                         hi.close();
334                                 } catch (Throwable err) {
335                                 }
336                         }
337                 }
338         }
339         
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);
343                 data.add(lastValue);
344         }
345
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);
356                 }
357                 return results;
358         }
359
360 }