> handlers) {
+ return new StoreUpdateRunnable<>(store, changeProvider, lastUpdate, message, handlers);
+ }
+}
diff --git a/src/store-sync/src/main/java/org/eclipse/lyo/store/sync/StoreUpdateRunnable.java b/src/store-sync/src/main/java/org/eclipse/lyo/store/sync/StoreUpdateRunnable.java
new file mode 100644
index 0000000..88aa112
--- /dev/null
+++ b/src/store-sync/src/main/java/org/eclipse/lyo/store/sync/StoreUpdateRunnable.java
@@ -0,0 +1,94 @@
+package org.eclipse.lyo.store.sync;
+
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+import org.eclipse.lyo.store.Store;
+import org.eclipse.lyo.store.sync.change.Change;
+import org.eclipse.lyo.store.sync.change.ChangeProvider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Requests latest {@link Change} collection from {@link ChangeProvider} and triggers each
+ * handler afterwards. Stores the timestamp of the newest change to request new updates starting
+ * from that point in time.
+ *
+ *
Handlers will be notified with different message per service provider as accessible via
+ * {@link OSLCMessage#getServiceProviderId()}.
+ *
+ * @author Andrew Berezovskyi (andriib@kth.se)
+ * @version $version-stub$
+ * @since 0.18.0
+ */
+class StoreUpdateRunnable implements Runnable {
+
+ private static final ZonedDateTime LAST_UPDATE_DEFAULT = ZonedDateTime.of(1971, 1, 1, 1, 1, 1,
+ 1, ZoneId.of("UTC"));
+ private final Logger log = LoggerFactory.getLogger(StoreUpdateRunnable.class);
+ private final ChangeProvider changeProvider;
+ private final M message;
+ private final Store store;
+ private final List> handlers;
+ private ZonedDateTime lastUpdate;
+
+ /**
+ * @param store Initialised Store
+ * @param changeProvider Change provider
+ * @param lastUpdate Initial timestamp. If null, will be set to {@link
+ * StoreUpdateRunnable#LAST_UPDATE_DEFAULT}
+ * @param message Initial message to the {@link ChangeProvider}
+ * @param handlers List of handlers to be notified.
+ */
+ StoreUpdateRunnable(final Store store, final ChangeProvider changeProvider,
+ final ZonedDateTime lastUpdate, final M message, final List> handlers) {
+ this.store = store;
+ this.changeProvider = changeProvider;
+ this.message = message;
+ this.handlers = handlers;
+ if (lastUpdate != null) {
+ this.lastUpdate = lastUpdate;
+ } else {
+ this.lastUpdate = StoreUpdateRunnable.LAST_UPDATE_DEFAULT;
+ }
+ }
+
+ @Override
+ public void run() {
+ try {
+ log.trace("Running background update");
+ Collection> changes = null;
+ try {
+ changes = changeProvider.getChangesSince(lastUpdate, message);
+ } catch (final Exception e) {
+ log.error("ChangeProvider threw an exception", e);
+ }
+ if (changes != null && !changes.isEmpty()) {
+ for (final Handler handler : handlers) {
+ log.trace("Notifying {}", handler);
+ try {
+ handler.handle(store, changes);
+ } catch (final Exception e) {
+ log.warn("Handler {} threw an exception", handler, e);
+ }
+ }
+ for (final Change> change : changes) {
+ if (change != null) {
+ final Date date = change.getHistoryResource().getTimestamp();
+ final ZonedDateTime dateTime = date.toInstant()
+ .atZone(ZoneId.systemDefault());
+ if (lastUpdate.isBefore(dateTime)) {
+ lastUpdate = dateTime;
+ }
+ }
+ }
+ log.trace("Setting previous revision to {}", lastUpdate);
+ }
+ } catch (final Exception e) {
+ // ExecutorService will terminate the whole schedule if a Runnable throws an exception
+ log.error("A handler threw an exception!", e);
+ }
+ }
+}
diff --git a/src/store-sync/src/main/java/org/eclipse/lyo/store/sync/UpdateMessage.java b/src/store-sync/src/main/java/org/eclipse/lyo/store/sync/UpdateMessage.java
new file mode 100644
index 0000000..0bb4cef
--- /dev/null
+++ b/src/store-sync/src/main/java/org/eclipse/lyo/store/sync/UpdateMessage.java
@@ -0,0 +1,5 @@
+package org.eclipse.lyo.store.sync;
+
+public interface UpdateMessage {
+
+}
diff --git a/src/store-sync/src/main/java/org/eclipse/lyo/store/sync/change/Change.java b/src/store-sync/src/main/java/org/eclipse/lyo/store/sync/change/Change.java
new file mode 100644
index 0000000..d891df9
--- /dev/null
+++ b/src/store-sync/src/main/java/org/eclipse/lyo/store/sync/change/Change.java
@@ -0,0 +1,37 @@
+package org.eclipse.lyo.store.sync.change;
+
+import org.eclipse.lyo.oslc4j.core.model.AbstractResource;
+
+/**
+ * Change is .
+ *
+ * @author Andrew Berezovskyi (andriib@kth.se)
+ * @version $version-stub$
+ * @since 0.0.0
+ */
+public class Change {
+ /**
+ * Not IExtendedResource due to the use of AbstractResource throughout Lyo generated code.
+ */
+ private final AbstractResource resource;
+ private final HistoryResource historyResource;
+ private final T message;
+
+ public Change(AbstractResource resource, HistoryResource historyResource, T message) {
+ this.resource = resource;
+ this.historyResource = historyResource;
+ this.message = message;
+ }
+
+ public AbstractResource getResource() {
+ return resource;
+ }
+
+ public HistoryResource getHistoryResource() {
+ return historyResource;
+ }
+
+ public T getMessage() {
+ return message;
+ }
+}
diff --git a/src/store-sync/src/main/java/org/eclipse/lyo/store/sync/change/ChangeHelper.java b/src/store-sync/src/main/java/org/eclipse/lyo/store/sync/change/ChangeHelper.java
new file mode 100644
index 0000000..ff3753c
--- /dev/null
+++ b/src/store-sync/src/main/java/org/eclipse/lyo/store/sync/change/ChangeHelper.java
@@ -0,0 +1,37 @@
+package org.eclipse.lyo.store.sync.change;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * ChangeHelper is .
+ *
+ * @author Andrew Berezovskyi (andriib@kth.se)
+ * @version $version-stub$
+ * @since 0.0.0
+ */
+public class ChangeHelper {
+
+ public static List changesCreated(Collection allChanges) {
+ return allChanges.stream()
+ .filter(change -> Objects.equals(change.getHistoryResource().getChangeKindEnum(), ChangeKind.CREATION))
+ .collect(Collectors.toList());
+ }
+
+ public static List changesModified(Collection allChanges) {
+ return allChanges.stream().filter(
+ change -> Objects.equals(change.getHistoryResource().getChangeKindEnum(), ChangeKind.MODIFICATION))
+ .collect(Collectors.toList());
+ }
+
+ public static List historyFrom(Collection changes) {
+ return ChangeHelper.mapFn(changes, Change::getHistoryResource);
+ }
+
+ public static List mapFn(Collection changes, Function mapper) {
+ return changes.stream().map(mapper).collect(Collectors.toList());
+ }
+}
diff --git a/src/store-sync/src/main/java/org/eclipse/lyo/store/sync/change/ChangeKind.java b/src/store-sync/src/main/java/org/eclipse/lyo/store/sync/change/ChangeKind.java
new file mode 100644
index 0000000..d063b1b
--- /dev/null
+++ b/src/store-sync/src/main/java/org/eclipse/lyo/store/sync/change/ChangeKind.java
@@ -0,0 +1,45 @@
+package org.eclipse.lyo.store.sync.change;
+
+import org.eclipse.lyo.oslc4j.core.annotation.OslcName;
+import org.eclipse.lyo.oslc4j.core.annotation.OslcNamespace;
+import org.eclipse.lyo.oslc4j.core.annotation.OslcResourceShape;
+
+/**
+ * ChangeKind is .
+ *
+ * @author Andrew Berezovskyi (andriib@kth.se)
+ * @version $version-stub$
+ * @since 0.0.0
+ */
+@OslcNamespace(HistoryResource.NS_TRS)
+@OslcName(ChangeKind.NAME)
+@OslcResourceShape(title = "Change Kind Resource Shape", describes = HistoryResource.NS_TRS + ChangeKind.NAME)
+public enum ChangeKind {
+ // Strings taken from the TRS provider implementation
+ CREATION("Created"),
+ MODIFICATION("Modified"),
+ DELETION("Deleted");
+
+ public static final String NAME = "ChangeKind";
+ private final String created;
+
+ ChangeKind(String created) {
+ this.created = created;
+ }
+
+ public static ChangeKind fromString(String text) {
+ if (text != null) {
+ for (ChangeKind kind : ChangeKind.values()) {
+ if (text.equalsIgnoreCase(kind.created)) {
+ return kind;
+ }
+ }
+ }
+ throw new IllegalArgumentException("No constant with text " + text + " found");
+ }
+
+ @Override
+ public String toString() {
+ return created;
+ }
+}
diff --git a/src/store-sync/src/main/java/org/eclipse/lyo/store/sync/change/ChangeProvider.java b/src/store-sync/src/main/java/org/eclipse/lyo/store/sync/change/ChangeProvider.java
new file mode 100644
index 0000000..9f38ced
--- /dev/null
+++ b/src/store-sync/src/main/java/org/eclipse/lyo/store/sync/change/ChangeProvider.java
@@ -0,0 +1,15 @@
+package org.eclipse.lyo.store.sync.change;
+
+import java.time.ZonedDateTime;
+import java.util.Collection;
+
+/**
+ * ChangeProvider is .
+ *
+ * @author Andrew Berezovskyi (andriib@kth.se)
+ * @version $version-stub$
+ * @since 0.0.0
+ */
+public interface ChangeProvider {
+ Collection> getChangesSince(ZonedDateTime lastUpdate, T message);
+}
diff --git a/src/store-sync/src/main/java/org/eclipse/lyo/store/sync/change/HistoryResource.java b/src/store-sync/src/main/java/org/eclipse/lyo/store/sync/change/HistoryResource.java
new file mode 100644
index 0000000..c95fc1b
--- /dev/null
+++ b/src/store-sync/src/main/java/org/eclipse/lyo/store/sync/change/HistoryResource.java
@@ -0,0 +1,87 @@
+package org.eclipse.lyo.store.sync.change;
+
+import org.eclipse.lyo.oslc4j.core.annotation.OslcName;
+import org.eclipse.lyo.oslc4j.core.annotation.OslcNamespace;
+import org.eclipse.lyo.oslc4j.core.annotation.OslcPropertyDefinition;
+import org.eclipse.lyo.oslc4j.core.annotation.OslcResourceShape;
+import org.eclipse.lyo.oslc4j.core.model.AbstractResource;
+
+import java.net.URI;
+import java.time.ZonedDateTime;
+import java.util.Date;
+
+/**
+ * HistoryResource is a wrapper OSLC Resource around org.eclipse.lyo.oslc4j.trs.provider.HistoryData
.
+ *
+ * @author Andrew Berezovskyi (andriib@kth.se)
+ * @version $version-stub$
+ * @since 0.0.0
+ */
+@OslcNamespace(HistoryResource.NS_TRS)
+@OslcName(HistoryResource.NAME)
+@OslcResourceShape(title = "TRS History Resource Shape", describes = HistoryResource.TYPE)
+public class HistoryResource extends AbstractResource {
+ public static final String NS_TRS = "http://open-services.net/ns/core/trs#";
+ public static final String NAME = "TrsHistoryResource";
+ public static final String TYPE = NS_TRS + NAME;
+ private ChangeKind changeKind;
+ private Date timestamp;
+ private URI resourceURI;
+
+ /**
+ * Shall be used only by the OSLC Jena Model Helper
+ */
+ @Deprecated
+ public HistoryResource() {
+ }
+
+ public HistoryResource(ChangeKind changeKind, Date timestamp, URI resourceURI) {
+ this.changeKind = changeKind;
+ this.timestamp = timestamp;
+ this.resourceURI = resourceURI;
+ }
+
+ public HistoryResource(ChangeKind changeKind, ZonedDateTime timestamp, URI resourceURI) {
+ this.changeKind = changeKind;
+ this.timestamp = Date.from(timestamp.toInstant());
+ this.resourceURI = resourceURI;
+ }
+
+ @OslcName("change_kind")
+ @OslcPropertyDefinition(NS_TRS + "change_kind")
+ public String getChangeKind() {
+ return changeKind.toString();
+ }
+
+ public void setChangeKind(ChangeKind changeKind) {
+ this.changeKind = changeKind;
+ }
+
+ public ChangeKind getChangeKindEnum() {
+ return changeKind;
+ }
+
+ public void setChangeKind(String changeKind) {
+ this.changeKind = ChangeKind.fromString(changeKind);
+ }
+
+ @OslcName("timestamp")
+ @OslcPropertyDefinition(NS_TRS + "timestamp")
+ public Date getTimestamp() {
+ return timestamp;
+ }
+
+ public void setTimestamp(Date timestamp) {
+ this.timestamp = timestamp;
+ }
+
+ @OslcName("uri")
+ @OslcPropertyDefinition(NS_TRS + "uri")
+ public URI getResourceURI() {
+ return resourceURI;
+ }
+
+ public void setResourceURI(URI resourceURI) {
+ this.resourceURI = resourceURI;
+ }
+}
diff --git a/src/store-sync/src/main/java/org/eclipse/lyo/store/sync/change/package-info.java b/src/store-sync/src/main/java/org/eclipse/lyo/store/sync/change/package-info.java
new file mode 100644
index 0000000..550f3de
--- /dev/null
+++ b/src/store-sync/src/main/java/org/eclipse/lyo/store/sync/change/package-info.java
@@ -0,0 +1,9 @@
+/**
+ * Classes for defining changes for handlers.
+ * {@link org.eclipse.lyo.store.sync.change.HistoryResource} can be persisted in a
+ * triplestore.
+ * @author Andrew Berezovskyi (andriib@kth.se)
+ * @version $version-stub$
+ * @since 0.18.0
+ */
+package org.eclipse.lyo.store.sync.change;
diff --git a/src/store-sync/src/main/java/org/eclipse/lyo/store/sync/handlers/SimpleStoreHandler.java b/src/store-sync/src/main/java/org/eclipse/lyo/store/sync/handlers/SimpleStoreHandler.java
new file mode 100644
index 0000000..7bf12b1
--- /dev/null
+++ b/src/store-sync/src/main/java/org/eclipse/lyo/store/sync/handlers/SimpleStoreHandler.java
@@ -0,0 +1,103 @@
+package org.eclipse.lyo.store.sync.handlers;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+import org.eclipse.lyo.oslc4j.core.model.AbstractResource;
+import org.eclipse.lyo.oslc4j.core.model.ServiceProvider;
+import org.eclipse.lyo.store.Store;
+import org.eclipse.lyo.store.StoreAccessException;
+import org.eclipse.lyo.store.sync.Handler;
+import org.eclipse.lyo.store.sync.ServiceProviderMessage;
+import org.eclipse.lyo.store.sync.change.Change;
+import org.eclipse.lyo.store.sync.change.ChangeKind;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This is a simple handler that puts resources into named graphs per
+ * {@link ServiceProvider} if the message implements
+ * {@link ServiceProviderMessage}.
+ *
+ * @author Andrew Berezovskyi
+ * @version $version-stub$
+ * @param
+ * @since 2.4.0
+ *
+ */
+public class SimpleStoreHandler implements Handler {
+
+ private final URI defaultGraph;
+
+ private final Logger log = LoggerFactory.getLogger(SimpleStoreHandler.class);
+
+ private final Store store;
+
+ public SimpleStoreHandler(Store store) {
+ this.store = store;
+ try {
+ this.defaultGraph = new URI("urn:x-arq:DefaultGraph");
+ } catch (URISyntaxException e) {
+ // this should never happen - we don't want the exception trail
+ // so we cheat with wrapping this in an unmanaged exception
+ // that will halt the execution.
+ IllegalStateException exception = new IllegalStateException(e);
+ log.error("Failed to generate default graph URI");
+ throw exception;
+ }
+ }
+
+ @Override
+ public Collection> handle(Store store, Collection> changes) {
+ if (changes == null || changes.isEmpty()) {
+ log.warn("Empty change list cannot be handled");
+ return changes;
+ }
+
+ Optional> firstChange = changes.stream().findFirst();
+ if (firstChange.get().getMessage() instanceof ServiceProviderMessage) {
+ Map, List>> map = changes.stream()
+ .collect(Collectors.groupingBy(c -> c.getMessage().getClass()));
+ for (Entry, List>> changeSet : map.entrySet()) {
+ URI spURI = ((ServiceProviderMessage) changeSet.getKey()).getServiceProviderUri();
+ persistChanges(spURI, changeSet.getValue());
+ }
+ } else {
+ persistChanges(defaultGraph, changes);
+ }
+
+ return changes;
+ }
+
+ private void persistChanges(URI spURI, Collection> value) {
+ try {
+ persistUpdates(spURI, value);
+ } catch (StoreAccessException e) {
+ log.error("Failed to persist updates", e);
+ }
+
+ persistDeletions(spURI, value);
+ }
+
+ private void persistDeletions(URI spURI, Collection> value) {
+ Collection deletedResources = value.stream()
+ .filter(c -> c.getHistoryResource().getChangeKindEnum() == ChangeKind.DELETION)
+ .map(c -> c.getResource().getAbout()).collect(Collectors.toList());
+ store.deleteResources(spURI, deletedResources.toArray(new URI[0]));
+ }
+
+ private void persistUpdates(URI spURI, Collection> value) throws StoreAccessException {
+ Collection updatedResources = value.stream()
+ .filter(c -> c.getHistoryResource().getChangeKindEnum() == ChangeKind.CREATION
+ || c.getHistoryResource().getChangeKindEnum() == ChangeKind.MODIFICATION)
+ .map(c -> c.getResource()).collect(Collectors.toList());
+ store.appendResources(spURI, updatedResources);
+ }
+
+}
diff --git a/src/store-sync/src/main/java/org/eclipse/lyo/store/sync/handlers/TrsHandler.java b/src/store-sync/src/main/java/org/eclipse/lyo/store/sync/handlers/TrsHandler.java
new file mode 100644
index 0000000..f71eea8
--- /dev/null
+++ b/src/store-sync/src/main/java/org/eclipse/lyo/store/sync/handlers/TrsHandler.java
@@ -0,0 +1,58 @@
+package org.eclipse.lyo.store.sync.handlers;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.eclipse.lyo.oslc4j.trs.provider.ChangeHistories;
+import org.eclipse.lyo.oslc4j.trs.provider.HistoryData;
+import org.eclipse.lyo.store.Store;
+import org.eclipse.lyo.store.sync.Handler;
+import org.eclipse.lyo.store.sync.change.Change;
+import org.eclipse.lyo.store.sync.change.ChangeKind;
+import org.eclipse.lyo.store.sync.change.HistoryResource;
+
+public class TrsHandler implements Handler {
+
+ private ChangeHistories changeLog;
+
+ public TrsHandler(ChangeHistories changeLog) {
+ this.changeLog = changeLog;
+ }
+
+ @Override
+ public Collection> handle(Store store, Collection> changes) {
+ updateChangelogHistory(changes);
+ return changes;
+ }
+
+ protected List updateChangelogHistory(Collection> changes) {
+ List changesHistory = changesHistory(changes);
+ changeLog.updateHistories(changesHistory);
+ return changesHistory;
+ }
+
+ private List changesHistory(Collection> changes) {
+ return changes.stream().map(this::historyElementFromChange).collect(Collectors.toList());
+ }
+
+ private HistoryData historyElementFromChange(Change change) {
+ HistoryResource h = change.getHistoryResource();
+ HistoryData historyData = HistoryData.getInstance(h.getTimestamp(), h.getResourceURI(),
+ historyDataType(h.getChangeKindEnum()));
+ return historyData;
+ }
+
+ private String historyDataType(ChangeKind changeKind) {
+ if (changeKind.equals(ChangeKind.CREATION)) {
+ return HistoryData.CREATED;
+ } else if (changeKind.equals(ChangeKind.MODIFICATION)) {
+ return HistoryData.MODIFIED;
+ } else if (changeKind.equals(ChangeKind.DELETION)) {
+ return HistoryData.DELETED;
+ } else {
+ throw new IllegalArgumentException("Illegal ChangeKind value");
+ }
+ }
+
+}
diff --git a/src/store-sync/src/main/java/org/eclipse/lyo/store/sync/handlers/TrsMqttChangeLogHandler.java b/src/store-sync/src/main/java/org/eclipse/lyo/store/sync/handlers/TrsMqttChangeLogHandler.java
new file mode 100644
index 0000000..307910a
--- /dev/null
+++ b/src/store-sync/src/main/java/org/eclipse/lyo/store/sync/handlers/TrsMqttChangeLogHandler.java
@@ -0,0 +1,100 @@
+package org.eclipse.lyo.store.sync.handlers;
+
+import java.lang.reflect.InvocationTargetException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Collection;
+import java.util.List;
+import java.util.TimeZone;
+
+import javax.xml.datatype.DatatypeConfigurationException;
+
+import org.eclipse.lyo.core.trs.ChangeEvent;
+import org.eclipse.lyo.core.trs.Creation;
+import org.eclipse.lyo.core.trs.Deletion;
+import org.eclipse.lyo.core.trs.Modification;
+import org.eclipse.lyo.oslc4j.core.exception.OslcCoreApplicationException;
+import org.eclipse.lyo.oslc4j.core.model.AbstractResource;
+import org.eclipse.lyo.oslc4j.provider.jena.JenaModelHelper;
+import org.eclipse.lyo.oslc4j.trs.provider.ChangeHistories;
+import org.eclipse.lyo.oslc4j.trs.provider.HistoryData;
+import org.eclipse.lyo.store.sync.change.Change;
+import org.eclipse.paho.client.mqttv3.MqttClient;
+import org.eclipse.paho.client.mqttv3.MqttException;
+import org.eclipse.paho.client.mqttv3.MqttMessage;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.jena.rdf.model.Model;
+
+public class TrsMqttChangeLogHandler extends TrsHandler {
+ private final Logger log = LoggerFactory.getLogger(TrsMqttChangeLogHandler.class);
+
+ private final MqttClient mqttClient;
+ private final String topic;
+ private int order;
+
+ public TrsMqttChangeLogHandler(final ChangeHistories changeLog, final MqttClient mqttClient, final String topic) {
+ super(changeLog);
+ this.mqttClient = mqttClient;
+ this.topic = topic;
+ // FIXME Andrew@2018-03-11: may and should blow up, but bringing back
+ this.order = changeLog.getHistory(null, null).length;
+ }
+
+ @Override
+ protected List updateChangelogHistory(Collection> changes) {
+ List updateChangelogHistory = super.updateChangelogHistory(changes);
+ for (HistoryData historyData : updateChangelogHistory) {
+ AbstractResource res = trsChangeResourceFrom(historyData);
+ MqttMessage message = buildMqttMessage(res);
+ try {
+ mqttClient.publish(topic, message);
+ } catch (MqttException e) {
+ log.error("Can't publish the message to the MQTT channel", e);
+ }
+ }
+ return updateChangelogHistory;
+ }
+
+ private MqttMessage buildMqttMessage(AbstractResource res) {
+ try {
+ Model changeEventJenaModel = JenaModelHelper.createJenaModel(new Object[] { res });
+ MqttMessage message = new MqttMessage();
+ message.setPayload(changeEventJenaModel.toString().getBytes());
+ return message;
+ } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException
+ | DatatypeConfigurationException | OslcCoreApplicationException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ private AbstractResource trsChangeResourceFrom(HistoryData historyData) {
+ // FIXME Andrew@2018-03-11: not thread-safe
+ this.order += 1;
+ String histDataType = historyData.getType();
+ URI uri = historyData.getUri();
+ URI changedUri;
+ try {
+ TimeZone tz = TimeZone.getTimeZone("UTC");
+ DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'"); // Quoted "Z" to indicate UTC, no timezone offset
+ df.setTimeZone(tz);
+ String nowAsISO = df.format(historyData.getTimestamp());
+ changedUri = new URI("urn:x-trs:" + nowAsISO + ":" + this.order);
+ ChangeEvent ce;
+ if (histDataType == HistoryData.CREATED) {
+ ce = new Creation(changedUri, uri, this.order);
+ } else if (histDataType == HistoryData.MODIFIED) {
+ ce = new Modification(changedUri, uri, this.order);
+ } else {
+ ce = new Deletion(changedUri, uri, this.order);
+ }
+ return ce;
+ } catch (URISyntaxException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+}
diff --git a/src/store-sync/src/main/java/org/eclipse/lyo/store/sync/handlers/package-info.java b/src/store-sync/src/main/java/org/eclipse/lyo/store/sync/handlers/package-info.java
new file mode 100644
index 0000000..c27aae5
--- /dev/null
+++ b/src/store-sync/src/main/java/org/eclipse/lyo/store/sync/handlers/package-info.java
@@ -0,0 +1,8 @@
+/**
+ *
+ */
+/**
+ * @author andrew
+ *
+ */
+package org.eclipse.lyo.store.sync.handlers;
diff --git a/src/store-sync/src/main/java/org/eclipse/lyo/store/sync/package-info.java b/src/store-sync/src/main/java/org/eclipse/lyo/store/sync/package-info.java
new file mode 100644
index 0000000..74482ab
--- /dev/null
+++ b/src/store-sync/src/main/java/org/eclipse/lyo/store/sync/package-info.java
@@ -0,0 +1,10 @@
+/**
+ * Common primitives for updating store.
+ * {@link org.eclipse.lyo.store.sync.StoreUpdateRunnable} is scheduled by
+ * {@link org.eclipse.lyo.store.sync.StoreUpdateManager}.
+ *
+ * @author Andrew Berezovskyi (andriib@kth.se)
+ * @version $version-stub$
+ * @since 0.18.0
+ */
+package org.eclipse.lyo.store.sync;
diff --git a/src/store-sync/src/test/java/org/eclipse/lyo/store/sync/TestHistoryResource.java b/src/store-sync/src/test/java/org/eclipse/lyo/store/sync/TestHistoryResource.java
new file mode 100644
index 0000000..882db4c
--- /dev/null
+++ b/src/store-sync/src/test/java/org/eclipse/lyo/store/sync/TestHistoryResource.java
@@ -0,0 +1,86 @@
+package org.eclipse.lyo.store.sync;
+
+import org.apache.jena.rdf.model.Model;
+import java.net.URI;
+import java.time.Instant;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import static org.assertj.core.api.Assertions.assertThat;
+import org.eclipse.lyo.oslc4j.provider.jena.JenaModelHelper;
+import org.eclipse.lyo.store.Store;
+import org.eclipse.lyo.store.StoreFactory;
+import org.eclipse.lyo.store.sync.change.ChangeKind;
+import org.eclipse.lyo.store.sync.change.HistoryResource;
+import org.junit.Test;
+
+/**
+ * TestTrsHistoryResource is .
+ *
+ * @author Andrew Berezovskyi (andriib@kth.se)
+ * @version $version-stub$
+ * @since 0.0.0
+ */
+public class TestHistoryResource {
+ public static final URI RESOURCE_URI = URI.create("test:test");
+ private final Store store = StoreFactory.inMemory();
+
+ @Test
+ public void testResourceIsMarshalled() throws Exception {
+ HistoryResource resource = new HistoryResource(ChangeKind.CREATION,
+ Date.from(Instant.now()), TestHistoryResource.RESOURCE_URI);
+
+ Model model = JenaModelHelper.createJenaModel(new Object[] { resource });
+ assertThat(model.size()).isGreaterThan(0);
+ }
+
+ @Test
+ public void testResourceContainsStatements() throws Exception {
+ HistoryResource resource = new HistoryResource(ChangeKind.CREATION,
+ Date.from(Instant.now()), TestHistoryResource.RESOURCE_URI);
+ resource.setAbout(TestHistoryResource.RESOURCE_URI);
+
+ Model model = JenaModelHelper.createJenaModel(new Object[] { resource });
+ assertThat(model.size()).isGreaterThan(1);
+ }
+
+ @Test
+ public void testResourceIsPersisted() throws Exception {
+ HistoryResource resource = new HistoryResource(ChangeKind.CREATION,
+ Date.from(Instant.now()), TestHistoryResource.RESOURCE_URI);
+ resource.setAbout(TestHistoryResource.RESOURCE_URI);
+
+ assertThat(store.keySet()).hasSize(0);
+ store.putResources(resource.getAbout(), Collections.singletonList(resource));
+ assertThat(store.keySet()).hasSize(1);
+ }
+
+ @Test
+ public void testResourceIsRestored() throws Exception {
+ HistoryResource resource = new HistoryResource(ChangeKind.CREATION,
+ Date.from(Instant.now()), TestHistoryResource.RESOURCE_URI);
+ resource.setAbout(TestHistoryResource.RESOURCE_URI);
+
+ store.putResources(resource.getAbout(), Collections.singletonList(resource));
+ List resources = store.getResources(TestHistoryResource.RESOURCE_URI, HistoryResource.class);
+ assertThat(resources).hasSize(1);
+ }
+
+ @Test
+ public void testResourceIsRestoredWithProperties() throws Exception {
+ String testResourceURI = "lyo:testtest";
+ Date timestamp = Date.from(Instant.now());
+ HistoryResource resource = new HistoryResource(ChangeKind.CREATION, timestamp,
+ URI.create(testResourceURI));
+ resource.setAbout(TestHistoryResource.RESOURCE_URI);
+
+ store.putResources(resource.getAbout(), Collections.singletonList(resource));
+
+ List resources = store.getResources(TestHistoryResource.RESOURCE_URI, HistoryResource.class);
+ HistoryResource storeResource = resources.get(0);
+
+ assertThat(storeResource.getChangeKind()).isEqualToIgnoringCase(String.valueOf(ChangeKind.CREATION));
+ assertThat(storeResource.getTimestamp()).isEqualTo(timestamp);
+ assertThat(storeResource.getResourceURI().toASCIIString()).isEqualTo(testResourceURI);
+ }
+}
diff --git a/src/store-update/src/test/resources/log4j.properties b/src/store-sync/src/test/resources/log4j.properties
similarity index 100%
rename from src/store-update/src/test/resources/log4j.properties
rename to src/store-sync/src/test/resources/log4j.properties