--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
+ <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>org.simantics.history.rest</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.ManifestBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.SchemaBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.pde.PluginNature</nature>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ </natures>
+</projectDescription>
--- /dev/null
+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
--- /dev/null
+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: .
--- /dev/null
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+ .,\
+ scl/
--- /dev/null
+importJava "org.simantics.history.rest.HistoryRestServer" where
+ start :: Integer -> <Proc> ()
+ stop :: <Proc> ()
+ running :: <Proc> Boolean
+ setAllowOrigin :: String -> <Proc>()
+ address :: <Proc> String
\ No newline at end of file
--- /dev/null
+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<HistorySamplerItem> historySamplers = new gnu.trove.map.hash.TLongObjectHashMap<>();
+ gnu.trove.map.TLongObjectMap<HistorySamplerItem2> historySamplers2 = new gnu.trove.map.hash.TLongObjectHashMap<>();
+
+ boolean disposed = false;
+
+ public HistoryCache(long runId) throws DatabaseException{
+ this.runId = runId;
+ Pair<Resource, Variable> pair = Simantics.getSession().syncRequest(new Read<Pair<Resource, Variable>>() {
+ @Override
+ public Pair<Resource, Variable> 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<Resource>(pair.first) {
+ @Override
+ public Resource perform(ReadGraph graph) throws DatabaseException {
+ Layer0 L0 = Layer0.getInstance(graph);
+ return graph.getPossibleObject(resource, L0.PartOf);
+ }
+ }, new AsyncListener<Resource>() {
+ @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<HistorySamplerItem>() {
+ @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<HistorySamplerItem2>() {
+ @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);
+ }
+
+}
--- /dev/null
+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;
+ }
+
+}
--- /dev/null
+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;
+ }
+
+}
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");
<module>org.simantics.help.ontology</module>
<module>org.simantics.help.ui</module>
<module>org.simantics.history</module>
+ <module>org.simantics.history.rest</module>
<module>org.simantics.image.ontology</module>
<module>org.simantics.image.ui</module>
<module>org.simantics.image2.ontology</module>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>org.simantics.history.rest.feature</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.pde.FeatureBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.pde.FeatureNature</nature>
+ </natures>
+</projectDescription>
--- /dev/null
+bin.includes = feature.xml
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<feature
+ id="org.simantics.history.rest.feature"
+ label="History REST Feature"
+ version="1.0.0.qualifier"
+ provider-name="Semantum">
+
+ <description url="http://www.example.com/description">
+ [Enter Feature Description here.]
+ </description>
+
+ <copyright url="http://www.example.com/copyright">
+ [Enter Copyright Description here.]
+ </copyright>
+
+ <license url="http://www.example.com/license">
+ [Enter License Description here.]
+ </license>
+
+ <requires>
+ <import plugin="org.simantics.history"/>
+ <import plugin="gnu.trove3"/>
+ <import plugin="org.simantics.charts"/>
+ <import plugin="org.simantics.db"/>
+ <import plugin="org.slf4j.api"/>
+ </requires>
+
+ <plugin
+ id="org.simantics.history.rest"
+ download-size="0"
+ install-size="0"
+ version="0.0.0"
+ unpack="false"/>
+
+ <plugin
+ id="org.glassfish.jersey.core.jersey-server"
+ download-size="0"
+ install-size="0"
+ version="0.0.0"
+ unpack="false"/>
+
+ <plugin
+ id="javax.ws.rs-api"
+ download-size="0"
+ install-size="0"
+ version="0.0.0"
+ unpack="false"/>
+
+ <plugin
+ id="org.eclipse.jetty.servlet"
+ download-size="0"
+ install-size="0"
+ version="0.0.0"
+ unpack="false"/>
+
+ <plugin
+ id="org.glassfish.jersey.containers.jersey-container-servlet-core"
+ download-size="0"
+ install-size="0"
+ version="0.0.0"
+ unpack="false"/>
+
+ <plugin
+ id="javax.servlet-api"
+ download-size="0"
+ install-size="0"
+ version="0.0.0"
+ unpack="false"/>
+
+ <plugin
+ id="org.eclipse.jetty.server"
+ download-size="0"
+ install-size="0"
+ version="0.0.0"
+ unpack="false"/>
+
+ <plugin
+ id="org.eclipse.jetty.util"
+ download-size="0"
+ install-size="0"
+ version="0.0.0"
+ unpack="false"/>
+
+ <plugin
+ id="org.eclipse.jetty.io"
+ download-size="0"
+ install-size="0"
+ version="0.0.0"
+ unpack="false"/>
+
+ <plugin
+ id="com.fasterxml.jackson.core.jackson-core"
+ download-size="0"
+ install-size="0"
+ version="0.0.0"
+ unpack="false"/>
+
+ <plugin
+ id="com.fasterxml.jackson.core.jackson-annotations"
+ download-size="0"
+ install-size="0"
+ version="0.0.0"
+ unpack="false"/>
+
+ <plugin
+ id="com.fasterxml.jackson.core.jackson-databind"
+ download-size="0"
+ install-size="0"
+ version="0.0.0"
+ unpack="false"/>
+
+ <plugin
+ id="org.glassfish.jersey.media.jersey-media-json-jackson"
+ download-size="0"
+ install-size="0"
+ version="0.0.0"
+ unpack="false"/>
+
+ <plugin
+ id="org.glassfish.jersey.media.jersey-media-multipart"
+ download-size="0"
+ install-size="0"
+ version="0.0.0"
+ unpack="false"/>
+
+ <plugin
+ id="org.slf4j.api"
+ download-size="0"
+ install-size="0"
+ version="0.0.0"
+ unpack="false"/>
+
+ <plugin
+ id="org.jvnet.mimepull"
+ download-size="0"
+ install-size="0"
+ version="0.0.0"
+ unpack="false"/>
+
+ <plugin
+ id="org.eclipse.core.runtime"
+ download-size="0"
+ install-size="0"
+ version="0.0.0"
+ unpack="false"/>
+
+ <plugin
+ id="org.glassfish.jersey.bundles.repackaged.jersey-guava"
+ download-size="0"
+ install-size="0"
+ version="0.0.0"
+ unpack="false"/>
+
+ <plugin
+ id="com.fasterxml.jackson.jaxrs.jackson-jaxrs-base"
+ download-size="0"
+ install-size="0"
+ version="0.0.0"
+ unpack="false"/>
+
+ <plugin
+ id="javax.validation.api"
+ download-size="0"
+ install-size="0"
+ version="0.0.0"
+ unpack="false"/>
+
+ <plugin
+ id="org.glassfish.hk2.api"
+ download-size="0"
+ install-size="0"
+ version="0.0.0"
+ unpack="false"/>
+
+ <plugin
+ id="com.fasterxml.jackson.jaxrs.jackson-jaxrs-json-provider"
+ download-size="0"
+ install-size="0"
+ version="0.0.0"
+ unpack="false"/>
+
+ <plugin
+ id="org.glassfish.hk2.external.aopalliance-repackaged"
+ download-size="0"
+ install-size="0"
+ version="0.0.0"
+ unpack="false"/>
+
+ <plugin
+ id="org.glassfish.hk2.locator"
+ download-size="0"
+ install-size="0"
+ version="0.0.0"
+ unpack="false"/>
+
+ <plugin
+ id="org.glassfish.hk2.osgi-resource-locator"
+ download-size="0"
+ install-size="0"
+ version="0.0.0"
+ unpack="false"/>
+
+ <plugin
+ id="org.glassfish.hk2.utils"
+ download-size="0"
+ install-size="0"
+ version="0.0.0"
+ unpack="false"/>
+
+ <plugin
+ id="org.glassfish.jersey.core.jersey-common"
+ download-size="0"
+ install-size="0"
+ version="0.0.0"
+ unpack="false"/>
+
+ <plugin
+ id="org.glassfish.jersey.ext.jersey-entity-filtering"
+ download-size="0"
+ install-size="0"
+ version="0.0.0"
+ unpack="false"/>
+
+ <plugin
+ id="org.glassfish.jersey.core.jersey-client"
+ download-size="0"
+ install-size="0"
+ version="0.0.0"
+ unpack="false"/>
+
+ <plugin
+ id="javassist"
+ download-size="0"
+ install-size="0"
+ version="0.0.0"
+ unpack="false"/>
+
+ <plugin
+ id="com.fasterxml.jackson.module.jackson-module-jaxb-annotations"
+ download-size="0"
+ install-size="0"
+ version="0.0.0"
+ unpack="false"/>
+
+ <plugin
+ id="org.eclipse.jetty.servlets"
+ download-size="0"
+ install-size="0"
+ version="0.0.0"
+ unpack="false"/>
+
+</feature>
<module>org.simantics.g2d.feature</module>
<module>org.simantics.help.feature</module>
<module>org.simantics.help.addons.feature</module>
+ <module>org.simantics.history.rest.feature</module>
<module>org.simantics.image.feature</module>
<module>org.simantics.issues.feature</module>
<module>org.simantics.issues.ui.feature</module>