]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - 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
diff --git a/bundles/org.simantics.history.rest/src/org/simantics/history/rest/HistoryRestApi.java b/bundles/org.simantics.history.rest/src/org/simantics/history/rest/HistoryRestApi.java
new file mode 100644 (file)
index 0000000..ec3ee25
--- /dev/null
@@ -0,0 +1,360 @@
+package org.simantics.history.rest;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.CacheControl;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+
+import org.simantics.db.exception.DatabaseException;
+import org.simantics.history.HistoryException;
+import org.simantics.history.HistorySampler;
+import org.simantics.history.HistorySamplerItem;
+import org.simantics.history.HistorySamplerItem2;
+import org.simantics.history.csv.ExportInterpolation;
+import org.simantics.history.util.HistoryExportUtil;
+import org.simantics.history.util.StreamIterator;
+import org.simantics.history.util.ValueBand;
+
+import gnu.trove.list.array.TDoubleArrayList;
+
+@Path("history")
+@Produces(MediaType.APPLICATION_JSON)
+public class HistoryRestApi {
+
+       private static gnu.trove.map.TLongObjectMap<HistoryCache> historyCaches = new gnu.trove.map.hash.TLongObjectHashMap<>();
+       
+       private static CacheControl cacheControl;
+       
+       static {
+               cacheControl = new CacheControl();
+               cacheControl.setNoCache(true);
+               cacheControl.setNoStore(true);
+       }
+
+       @Path("/register")
+       @POST
+       public static synchronized Response register(@QueryParam("runId") long run) {
+               if (historyCaches.containsKey(run))
+                       return Response.ok().build();
+               try {
+                       HistoryCache cache = new HistoryCache(run);
+                       historyCaches.put(run, cache);
+                       return Response.ok().cacheControl(cacheControl).build();
+               } catch (DatabaseException e) {
+                       return Response.status(Status.NOT_FOUND).build();
+               }
+       }
+
+       @Path("/unregister")
+       @POST
+       public static synchronized Response unregister(@QueryParam("runId") long run) {
+               HistoryCache cache = historyCaches.remove(run);
+               if (cache != null) {
+                       cache.dispose();
+               }
+               return Response.ok().cacheControl(cacheControl).build();
+       }
+
+       @Path("/values")
+       @GET
+       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) {
+               try {
+                       HistoryCache cache = getOrCreateCache(run);
+                       TDoubleArrayList doubles;
+                       synchronized (cache) {
+                               HistorySamplerItem hi = cache.getSamplerItem(item);
+                               doubles= HistorySampler.sample(hi, startTime, endTime, timeWindow, timeStep, true);
+                       }        
+                       return Response.ok(buildJSONResponse("values",doubles.toArray())).cacheControl(cacheControl).build();
+               } catch (HistoryException | IOException | DatabaseException e) {
+                       return Response.serverError().build();
+               }
+
+       }
+       @Path("/lvalues")
+       @GET
+       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) {
+               try {
+                       HistoryCache cache = getOrCreateCache(run);
+                       TDoubleArrayList doubles = null;
+                       synchronized (cache) {
+                               HistorySamplerItem2 hi = cache.getSamplerItem2(item);
+                               doubles = HistorySampler.sample(hi, endTime, timeWindow, maxSamples, resample);
+                       }
+                       return Response.ok(buildJSONResponse("values",doubles.toArray())).cacheControl(cacheControl).build();
+               } catch (HistoryException | IOException | DatabaseException e) {
+                       return Response.serverError().build();
+               }
+       }
+       
+       @Path("/samples")
+       @GET
+       public static Response sampleHistory(@QueryParam("runId")long run,@QueryParam("itemId") long item,@QueryParam("startTime") double startTime, @QueryParam("endTime") double endTime, @QueryParam("maxSamples") int maxSamples) {
+               
+               if (maxSamples <= 0)
+                       return Response.status(Status.BAD_REQUEST).build();
+               try {
+                       HistoryCache cache = getOrCreateCache(run);
+                       Map<String, Object> result = null;
+                       synchronized (cache) {
+                               result = _sampleHistory(cache, item, startTime, endTime, maxSamples);
+                       }
+                       return Response.ok(result).cacheControl(cacheControl).build();
+               } catch (HistoryException | DatabaseException e) {
+                       return Response.serverError().build();
+               }
+       }
+       
+       @Path("/multisamples")
+       @GET
+       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) {
+               
+               if (maxSamples <= 0)
+                       return Response.status(Status.BAD_REQUEST).build();
+               try {
+                       HistoryCache cache = getOrCreateCache(run);
+                       Map<String, Object> combined = new HashMap<>();
+                       synchronized (cache) {
+                               for (long item : items) {
+                                       Map<String, Object> result = _sampleHistory(cache, item, startTime, endTime, maxSamples);
+                                       combined.put(Long.toString(item), result);
+                               }
+                       }
+                       return Response.ok(combined).cacheControl(cacheControl).build();
+               } catch (HistoryException | DatabaseException e) {
+                       return Response.serverError().build();
+               }
+       }
+       
+       @Path("/ranges")
+       @GET
+       public static Response ranges(@QueryParam("runId")long run,@QueryParam("itemId") final List<Long> items) {
+               
+               try {
+                       HistoryCache cache = getOrCreateCache(run);
+                       Map<String, Object> combined = new HashMap<>();
+                       synchronized (cache) {
+                               for (long item : items) {
+                                       HistorySamplerItem2 hi = null;
+                                       try {
+                                               hi= cache.getSamplerItem2(item);
+                                               hi.open();
+                                               double first = hi.iter.getFirstTime();
+                                               double last = hi.iter.getLastTime();
+                                               combined.put(Long.toString(item), buildJSONResponse("start",first,"end",last));
+                                               
+                                       } finally {
+                                               hi.close();
+                                       }
+                                       
+                               }
+                       }
+                       return Response.ok(combined).cacheControl(cacheControl).build();
+                       
+               } catch (HistoryException | DatabaseException e) {
+                       return Response.serverError().build();
+               }
+       }
+       private static synchronized HistoryCache getOrCreateCache(long run) throws DatabaseException {
+               HistoryCache cache = historyCaches.get(run);
+               if (cache == null || cache.isDisposed()) {
+                       cache = new HistoryCache(run);
+                       historyCaches.put(run, cache);
+               } 
+               return cache;
+       }
+       
+       
+       
+       private static Map<String,Object> _sampleHistory(HistoryCache cache, long item, double startTime, double endTime, int maxSamples) throws DatabaseException, HistoryException {
+               HistorySamplerItem2 hi = null;
+               try {
+                       hi = cache.getSamplerItem2(item);
+                       
+                       double timeWindow = endTime - startTime;
+                       //double timeStep = 0.0; 
+                       double timeStep = timeWindow / maxSamples;
+                       double secondsPerPixel = timeWindow / (double) maxSamples;
+                       hi.flush();
+                       if (hi.iter == null)
+                               hi.open(secondsPerPixel);
+                       StreamIterator iter = hi.iter;
+                       if (iter.isEmpty()) {
+                               buildJSONResponse("time",new double[0],"value",new double[0]);
+                       }
+                       double dataFrom = iter.getFirstTime();
+                       double dataEnd = iter.getLastTime();
+                       boolean hasAnyValues = dataFrom != Double.MAX_VALUE && dataEnd != -Double.MAX_VALUE;
+                       if (!hasAnyValues) {
+                               return buildJSONResponse("time",new double[0],"value",new double[0]);   
+                       }
+                       double from = Math.max(startTime, dataFrom);
+                       double end = Math.min(endTime, dataEnd);
+                       iter.gotoTime(startTime);
+                       
+                       double time = from;
+                       
+                       BigDecimal bigTime = new BigDecimal(String.valueOf(time));
+                       BigDecimal bigTimeStep = new BigDecimal(String.valueOf(timeStep));
+       
+                       TDoubleArrayList times = new TDoubleArrayList();
+                       TDoubleArrayList values = new TDoubleArrayList();
+                       TDoubleArrayList min = new TDoubleArrayList();
+                       TDoubleArrayList max = new TDoubleArrayList();
+                       TDoubleArrayList avg = new TDoubleArrayList();
+                       if (!iter.gotoTime(time)) {
+                               return buildJSONResponse("time",new double[0],"value",new double[0]);   
+                       }
+       
+                       
+                       ExportInterpolation interpolation = ExportInterpolation.LINEAR_INTERPOLATION;
+                       do {
+                               //System.out.println("process " + time + " " + iter.getValueBand() + " (ignore = " + ignore + ")");
+       
+                               // Check for valid value
+                               if ( iter.hasValidValue() ) {
+       
+                                       // Write time
+                                       times.add(time);
+                                       // Write value
+                                       Object value = iter.getValueBand().getValue();
+                                       //System.out.print("Add value : " + value);
+                                       if (value instanceof Number) {
+                                               if (value instanceof Float || value instanceof Double) {
+                                                       switch (interpolation) {
+                                                       case PREVIOUS_SAMPLE:
+                                                               values.add(((Number) value).doubleValue());
+                                                               break;
+       
+                                                       case LINEAR_INTERPOLATION:
+                                                               if (time != iter.getValueBand().getTimeDouble() && iter.hasNext()) {
+                                                                       // Interpolate
+                                                                       int currentIndex = iter.getIndex();
+                                                                       ValueBand band = iter.getValueBand();
+                                                                       //double t1 = band.getTimeDouble();
+                                                                       Number v1 = (Number) value;
+                                                                       double t12 = band.getEndTimeDouble();
+                                                                       iter.next();
+                                                                       double t2 = iter.getValueBand().getTimeDouble();
+                                                                       Number v2 = (Number) iter.getValueBand().getValue();
+                                                                       iter.gotoIndex(currentIndex);
+       
+                                                                       double vs = v1.doubleValue();
+                                                                       if (time > t12)
+                                                                               vs = HistoryExportUtil.biglerp(t12, v1.doubleValue(), t2, v2.doubleValue(), time);
+       
+                                                                       values.add(vs);
+       
+                                                               } else {
+                                                                       // Exact timestamp match, or last sample.
+                                                                       // Don't interpolate nor extrapolate.
+                                                                       values.add(((Number) value).doubleValue());
+                                                               }
+                                                               break;
+       
+                                                       default:
+                                                               throw new UnsupportedOperationException("Unsupported interpolation: " + interpolation);
+                                                       }
+                                                       if (iter.getValueBand().hasMax())
+                                                               max.add(((Number) iter.getValueBand().getMax()).doubleValue());
+                                                       else
+                                                               max.add(Double.NaN);
+                                                       if (iter.getValueBand().hasMin())
+                                                               min.add(((Number) iter.getValueBand().getMin()).doubleValue());
+                                                       else
+                                                               min.add(Double.NaN);
+                                                       if (iter.getValueBand().hasAvg())
+                                                               avg.add(((Number) iter.getValueBand().getAvg()).doubleValue());
+                                                       else
+                                                               avg.add(Double.NaN);
+                                               } else {
+                                                       throw new IllegalStateException("Value is not a number " + value);
+                                               }
+                                       } else if (value instanceof Boolean) {
+                                               values.add( (Boolean)value ? 1.0: 0.0);
+                                       } else {
+                                               throw new IllegalStateException("Value is not a number " + value);
+                                       }
+                                       
+                               }
+                               
+       
+                               // Read next values, and the following times
+                               if ( timeStep>0.0 ) {
+                                       bigTime = bigTime.add(bigTimeStep);
+                                       time = bigTime.doubleValue();
+                               } else {
+                                       // Get smallest end time that is larger than current time
+                                       Double nextTime = null;
+                                       //System.out.println(" time = "+time);
+                                       if(!iter.hasNext()) {
+                                               duplicateLastDataPoint(values);
+                                               times.add(end);
+                                               break;
+                                       }
+                                       Double itemNextTime = iter.getNextTime( time );
+                                       //System.out.println("  "+iter.toString()+" nextTime="+itemNextTime);
+                                       if ( nextTime == null || ( nextTime > itemNextTime && !itemNextTime.equals( time ) ) ) nextTime = itemNextTime; 
+                                       if ( nextTime == null || nextTime.equals( time ) ) break;
+                                       time = nextTime;
+                               }
+       
+                               boolean hasMore = false;
+       
+                               iter.proceedToTime(time);
+                               if (HistoryExportUtil.contains(iter, time)) hasMore = true;
+       
+                               if (!hasMore) {
+                                       if (time <= end) {
+                                               duplicateLastDataPoint(values);
+                                               times.add(end);
+                                       }
+                                       break;
+                               }
+       
+                       } while (time <= end);
+                       
+                       return buildJSONResponse("time", times.toArray(),"value",values.toArray(), "min", min.toArray(), "max", max.toArray(),"avg", avg.toArray());
+               } finally {
+                       if (hi != null) {
+                               try {
+                                       hi.close();
+                               } catch (Throwable err) {
+                               }
+                       }
+               }
+       }
+       
+       private static void duplicateLastDataPoint(TDoubleArrayList data) {
+               double lastValue = data.get(data.size() - 1);
+               //System.out.println("Duplicating last sample value " + lastValue + " @ " + timestamp);
+               data.add(lastValue);
+       }
+
+       private static Map<String, Object> buildJSONResponse(Object... keyValues) {
+               if ((keyValues.length % 2) != 0)
+                       throw new IllegalArgumentException("Invalid amount of arguments! " + Arrays.toString(keyValues));
+               Map<String, Object> results = new HashMap<>(keyValues.length / 2);
+               for (int i = 0; i < keyValues.length; i += 2) {
+                       Object key = keyValues[i];
+                       Object value = keyValues[i + 1];
+                       if (!(key instanceof String))
+                               throw new IllegalArgumentException("Key with index " + i + " is not String");
+                       results.put((String) key, value);
+               }
+               return results;
+       }
+
+}