From: Marko Luukkainen Date: Wed, 4 Jul 2018 10:28:35 +0000 (+0300) Subject: Rest API for Historian data X-Git-Url: https://gerrit.simantics.org/r/gitweb?a=commitdiff_plain;h=321f52f3c951a801d79b6c6a01967bc570845e22;p=simantics%2Fplatform.git Rest API for Historian data gitlab #40 Change-Id: I4bc9cc2dec3d3c2dcf902d372fc895b073d5e767 (cherry picked from commit 286183f3501ea34badb28d05bd8de954eff9b8bc) --- diff --git a/bundles/org.simantics.history.rest/.classpath b/bundles/org.simantics.history.rest/.classpath new file mode 100644 index 000000000..eca7bdba8 --- /dev/null +++ b/bundles/org.simantics.history.rest/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/bundles/org.simantics.history.rest/.project b/bundles/org.simantics.history.rest/.project new file mode 100644 index 000000000..1422d66b1 --- /dev/null +++ b/bundles/org.simantics.history.rest/.project @@ -0,0 +1,28 @@ + + + org.simantics.history.rest + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/bundles/org.simantics.history.rest/.settings/org.eclipse.jdt.core.prefs b/bundles/org.simantics.history.rest/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 000000000..0c68a61dc --- /dev/null +++ b/bundles/org.simantics.history.rest/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,7 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.8 diff --git a/bundles/org.simantics.history.rest/META-INF/MANIFEST.MF b/bundles/org.simantics.history.rest/META-INF/MANIFEST.MF new file mode 100644 index 000000000..a1f311a5a --- /dev/null +++ b/bundles/org.simantics.history.rest/META-INF/MANIFEST.MF @@ -0,0 +1,37 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Simantics History REST API +Bundle-SymbolicName: org.simantics.history.rest +Bundle-Version: 1.0.0.qualifier +Bundle-Vendor: Semantum Oy +Automatic-Module-Name: org.simantics.history.rest +Bundle-RequiredExecutionEnvironment: JavaSE-1.8 +Require-Bundle: org.simantics.history;bundle-version="1.0.0", + org.eclipse.core.runtime;bundle-version="3.13.0", + org.glassfish.jersey.core.jersey-server, + javax.ws.rs-api, + org.simantics.scl.compiler, + org.simantics.scl.osgi, + org.eclipse.jetty.servlet, + org.glassfish.jersey.containers.jersey-container-servlet-core, + javax.servlet-api, + org.eclipse.jetty.server, + org.eclipse.jetty.util, + org.eclipse.jetty.io, + org.eclipse.jetty.servlets, + com.fasterxml.jackson.core.jackson-core;bundle-version="2.8.8", + com.fasterxml.jackson.core.jackson-annotations;bundle-version="2.8.0", + com.fasterxml.jackson.core.jackson-databind;bundle-version="2.8.8", + org.glassfish.jersey.media.jersey-media-json-jackson;bundle-version="2.25.1", + org.slf4j.api, + org.glassfish.jersey.core.jersey-client, + org.glassfish.jersey.core.jersey-common;bundle-version="2.25.1", + org.simantics.scl.runtime, + gnu.trove3;bundle-version="3.0.3", + org.simantics.charts;bundle-version="0.0.1", + org.simantics.db;bundle-version="1.1.0", + org.simantics.db.common;bundle-version="1.1.0", + org.simantics.db.layer0;bundle-version="1.1.0", + org.simantics;bundle-version="1.0.0", + org.eclipse.jetty.servlets +Bundle-ClassPath: . diff --git a/bundles/org.simantics.history.rest/build.properties b/bundles/org.simantics.history.rest/build.properties new file mode 100644 index 000000000..a4fd10d42 --- /dev/null +++ b/bundles/org.simantics.history.rest/build.properties @@ -0,0 +1,5 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + .,\ + scl/ diff --git a/bundles/org.simantics.history.rest/scl/Simantics/History/RESTServer.scl b/bundles/org.simantics.history.rest/scl/Simantics/History/RESTServer.scl new file mode 100644 index 000000000..b8dce5147 --- /dev/null +++ b/bundles/org.simantics.history.rest/scl/Simantics/History/RESTServer.scl @@ -0,0 +1,6 @@ +importJava "org.simantics.history.rest.HistoryRestServer" where + start :: Integer -> () + stop :: () + running :: Boolean + setAllowOrigin :: String -> () + address :: String \ No newline at end of file diff --git a/bundles/org.simantics.history.rest/src/org/simantics/history/rest/HistoryCache.java b/bundles/org.simantics.history.rest/src/org/simantics/history/rest/HistoryCache.java new file mode 100644 index 000000000..35d9e8db7 --- /dev/null +++ b/bundles/org.simantics.history.rest/src/org/simantics/history/rest/HistoryCache.java @@ -0,0 +1,115 @@ +package org.simantics.history.rest; + +import org.simantics.Simantics; +import org.simantics.charts.Charts; +import org.simantics.db.AsyncReadGraph; +import org.simantics.db.ReadGraph; +import org.simantics.db.Resource; +import org.simantics.db.common.request.ResourceRead; +import org.simantics.db.exception.DatabaseException; +import org.simantics.db.layer0.variable.Variable; +import org.simantics.db.procedure.AsyncListener; +import org.simantics.db.request.Read; +import org.simantics.db.service.SerialisationSupport; +import org.simantics.history.HistorySamplerItem; +import org.simantics.history.HistorySamplerItem2; +import org.simantics.layer0.Layer0; +import org.simantics.utils.datastructures.Pair; + +public class HistoryCache { + + long runId; + Variable run; + + gnu.trove.map.TLongObjectMap historySamplers = new gnu.trove.map.hash.TLongObjectHashMap<>(); + gnu.trove.map.TLongObjectMap historySamplers2 = new gnu.trove.map.hash.TLongObjectHashMap<>(); + + boolean disposed = false; + + public HistoryCache(long runId) throws DatabaseException{ + this.runId = runId; + Pair pair = Simantics.getSession().syncRequest(new Read>() { + @Override + public Pair perform(ReadGraph graph) throws DatabaseException { + Resource resource = getResource(graph, runId); + return new Pair<>(resource, graph.adapt(resource, Variable.class)); + } + }); + run = pair.second; + // Attach listener for automatic disposal + Simantics.getSession().async(new ResourceRead(pair.first) { + @Override + public Resource perform(ReadGraph graph) throws DatabaseException { + Layer0 L0 = Layer0.getInstance(graph); + return graph.getPossibleObject(resource, L0.PartOf); + } + }, new AsyncListener() { + @Override + public void execute(AsyncReadGraph graph, Resource result) { + if (result == null) + dispose(); + + } + + @Override + public void exception(AsyncReadGraph graph, Throwable throwable) { + dispose(); + } + @Override + public boolean isDisposed() { + return disposed; + } + }); + } + + public HistorySamplerItem getSamplerItem(final long itemId) throws DatabaseException { + HistorySamplerItem item = historySamplers.get(itemId); + if (item == null) { + item = Simantics.getSession().syncRequest(new Read() { + @Override + public HistorySamplerItem perform(ReadGraph graph) throws DatabaseException { + Resource item = getResource(graph, itemId); + return Charts.createHistorySamplerItem(graph,run, item); + } + }); + historySamplers.put(itemId, item); + } + return item; + } + + public HistorySamplerItem2 getSamplerItem2(final long itemId) throws DatabaseException { + HistorySamplerItem2 item = historySamplers2.get(itemId); + if (item == null) { + item = Simantics.getSession().syncRequest(new Read() { + @Override + public HistorySamplerItem2 perform(ReadGraph graph) throws DatabaseException { + Resource item = getResource(graph, itemId); + return Charts.createHistorySamplerItem2(graph,run, item); + } + }); + historySamplers2.put(itemId, item); + } + return item; + } + + public void dispose() { + disposed = true; + for (HistorySamplerItem i : historySamplers.valueCollection()) + i.close(); + historySamplers.clear(); + for (HistorySamplerItem2 i : historySamplers2.valueCollection()) + i.close(); + historySamplers2.clear(); + } + + public boolean isDisposed() { + return disposed; + } + + + private Resource getResource(ReadGraph graph, long id) throws DatabaseException { + SerialisationSupport ss = graph.getService(SerialisationSupport.class); + return ss.getResource(id); + } + +} 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 index 000000000..ec3ee2501 --- /dev/null +++ b/bundles/org.simantics.history.rest/src/org/simantics/history/rest/HistoryRestApi.java @@ -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 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 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 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 combined = new HashMap<>(); + synchronized (cache) { + for (long item : items) { + Map 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 items) { + + try { + HistoryCache cache = getOrCreateCache(run); + Map 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 _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 buildJSONResponse(Object... keyValues) { + if ((keyValues.length % 2) != 0) + throw new IllegalArgumentException("Invalid amount of arguments! " + Arrays.toString(keyValues)); + Map 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; + } + +} diff --git a/bundles/org.simantics.history.rest/src/org/simantics/history/rest/HistoryRestServer.java b/bundles/org.simantics.history.rest/src/org/simantics/history/rest/HistoryRestServer.java new file mode 100644 index 000000000..00b4a1dc7 --- /dev/null +++ b/bundles/org.simantics.history.rest/src/org/simantics/history/rest/HistoryRestServer.java @@ -0,0 +1,137 @@ +package org.simantics.history.rest; + +import java.util.EnumSet; +import java.util.concurrent.Semaphore; + +import javax.servlet.DispatcherType; + +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.servlet.FilterHolder; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.glassfish.jersey.jackson.JacksonFeature; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.servlet.ServletContainer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class HistoryRestServer { +private static final Logger LOGGER = LoggerFactory.getLogger(HistoryRestServer.class); + + private static HistoryRestServer INSTANCE = null; + private static Server server; + private static ServiceServerThread serverThread; + + private static String allowOriginUrl = null; + + private static String address = null; + + private HistoryRestServer(int port) { + ResourceConfig config = new ResourceConfig(); + // JSON serialization/deserialization + config.register(JacksonFeature.class); + // Actual API + config.register(HistoryRestApi.class); + + ServletHolder holder = new ServletHolder(new ServletContainer(config)); + + server = new Server(); + ServerConnector connector = new ServerConnector(server); + connector.setPort(port); + + server.setConnectors(new Connector[] { connector }); + + ServletContextHandler context = new ServletContextHandler(server, "/", ServletContextHandler.SESSIONS); + context.addServlet(holder, "/*"); + + if (allowOriginUrl != null){ + org.eclipse.jetty.servlets.CrossOriginFilter cfilter = new org.eclipse.jetty.servlets.CrossOriginFilter(); + FilterHolder filterHolder = new FilterHolder(cfilter); + filterHolder.setInitParameter(org.eclipse.jetty.servlets.CrossOriginFilter.ACCESS_CONTROL_ALLOW_ORIGIN_HEADER, allowOriginUrl); + context.addFilter(filterHolder, "/*", EnumSet.allOf(DispatcherType.class)); + } + } + + + private static class ServiceServerThread extends Thread { + + Semaphore s; + @Override + public void run() { + try { + server.start(); + address = server.getURI().toString()+"history/"; + if (s != null) + s.release(); + server.join(); + } catch (Exception e) { + LOGGER.error("Could not start server ", e); + } + } + + public void startAndWait() { + s = new Semaphore(0); + this.start(); + try { + s.acquire(); + } catch (InterruptedException e) {}; + } + } + + private static synchronized HistoryRestServer getInstance(int port) { + try { + if (INSTANCE == null) { + INSTANCE = new HistoryRestServer(port); + } + } catch (Exception e) { + LOGGER.error("Could not initialize SCL REST server", e); + } + return INSTANCE; + } + + public static HistoryRestServer getRunningInstance() { + return INSTANCE; + } + + public static void setAllowOrigin(String url) { + allowOriginUrl = url; + } + + public static synchronized void startAsync(int port) throws Exception { + // Ensure that an instance is created + getInstance(port); + if (serverThread == null && server != null) { + serverThread = new ServiceServerThread(); + serverThread.start(); + } + } + + public static synchronized void start(int port) throws Exception { + // Ensure that an instance is created + getInstance(port); + if (serverThread == null && server != null) { + serverThread = new ServiceServerThread(); + serverThread.startAndWait(); + } + } + + public static synchronized void stop() throws Exception { + if (server != null) + server.stop(); + serverThread = null; + INSTANCE = null; + address = null; + + } + + public static synchronized boolean running() { + return INSTANCE != null; + } + + public static synchronized String address() { + return address; + } + +} diff --git a/bundles/org.simantics.history/src/org/simantics/history/HistorySamplerItem2.java b/bundles/org.simantics.history/src/org/simantics/history/HistorySamplerItem2.java index 73d89fa56..cbd2cd245 100644 --- a/bundles/org.simantics.history/src/org/simantics/history/HistorySamplerItem2.java +++ b/bundles/org.simantics.history/src/org/simantics/history/HistorySamplerItem2.java @@ -41,6 +41,11 @@ public class HistorySamplerItem2 implements Comparable { this.items = items; this.chartDataId = identityHashCode; } + + public void flush() throws HistoryException { + if (collector != null) + collector.flush(); + } public void open() throws HistoryException { accessor = history.openStream(items[0].id, "r"); diff --git a/bundles/pom.xml b/bundles/pom.xml index bcefad659..2d595e364 100644 --- a/bundles/pom.xml +++ b/bundles/pom.xml @@ -144,6 +144,7 @@ org.simantics.help.ontology org.simantics.help.ui org.simantics.history + org.simantics.history.rest org.simantics.image.ontology org.simantics.image.ui org.simantics.image2.ontology diff --git a/features/org.simantics.history.rest.feature/.project b/features/org.simantics.history.rest.feature/.project new file mode 100644 index 000000000..a740f262e --- /dev/null +++ b/features/org.simantics.history.rest.feature/.project @@ -0,0 +1,17 @@ + + + org.simantics.history.rest.feature + + + + + + org.eclipse.pde.FeatureBuilder + + + + + + org.eclipse.pde.FeatureNature + + diff --git a/features/org.simantics.history.rest.feature/build.properties b/features/org.simantics.history.rest.feature/build.properties new file mode 100644 index 000000000..64f93a9f0 --- /dev/null +++ b/features/org.simantics.history.rest.feature/build.properties @@ -0,0 +1 @@ +bin.includes = feature.xml diff --git a/features/org.simantics.history.rest.feature/feature.xml b/features/org.simantics.history.rest.feature/feature.xml new file mode 100644 index 000000000..33dd73ef6 --- /dev/null +++ b/features/org.simantics.history.rest.feature/feature.xml @@ -0,0 +1,252 @@ + + + + + [Enter Feature Description here.] + + + + [Enter Copyright Description here.] + + + + [Enter License Description here.] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/features/pom.xml b/features/pom.xml index 0f4197b1a..4c19ccca4 100644 --- a/features/pom.xml +++ b/features/pom.xml @@ -109,6 +109,7 @@ org.simantics.g2d.feature org.simantics.help.feature org.simantics.help.addons.feature + org.simantics.history.rest.feature org.simantics.image.feature org.simantics.issues.feature org.simantics.issues.ui.feature