From 929dba92cd105bbb4c52cfddb3b9bb444d43ddbd Mon Sep 17 00:00:00 2001
From: anttil <anttil>
Date: Fri, 4 May 2012 13:57:46 +0000
Subject: [PATCH] SP-13: Change
 ch.systemsx.cisd.openbis.dss.generic.server.EncapsulatedOpenBISService.performEntityOperations()
 to use service conversation framework in AS/DSS communication

SVN: 25182
---
 common/.classpath                             |   5 +
 .../ConversationalClient.java                 |  25 +++++
 .../ConversationalServer.java                 |  23 ++++
 .../serviceconversation/MethodCall.java       |  76 +++++++++++++
 .../RpcMessageTransport.java                  |  30 +++++
 .../common/serviceconversation/RpcProxy.java  |  55 +++++++++
 .../RpcServiceFactory.java                    |  84 ++++++++++++++
 .../dss/generic/server/DataStoreService.java  |  55 +++++++++
 .../server/DataStoreServiceLogger.java        |  13 +++
 .../server/EncapsulatedOpenBISService.java    |  18 ++-
 .../source/java/dssApplicationContext.xml     |   4 +
 .../TransferredDataSetHandlerTest.java        |   5 +-
 .../EncapsulatedOpenBISServiceTest.java       |  10 +-
 .../openbis/generic/server/ETLService.java    |  32 ++++++
 .../generic/server/ETLServiceLogger.java      |  18 ++-
 .../generic/shared/IDataStoreService.java     |   3 +-
 .../generic/shared/IETLLIMSService.java       | 105 ++++++++++++------
 .../generic/server/ETLServiceTest.java        |  36 ++++++
 .../shared/api/v1/dto/SearchCriteria.java     |   8 ++
 19 files changed, 561 insertions(+), 44 deletions(-)
 create mode 100644 common/source/java/ch/systemsx/cisd/common/serviceconversation/ConversationalClient.java
 create mode 100644 common/source/java/ch/systemsx/cisd/common/serviceconversation/ConversationalServer.java
 create mode 100644 common/source/java/ch/systemsx/cisd/common/serviceconversation/MethodCall.java
 create mode 100644 common/source/java/ch/systemsx/cisd/common/serviceconversation/RpcMessageTransport.java
 create mode 100644 common/source/java/ch/systemsx/cisd/common/serviceconversation/RpcProxy.java
 create mode 100644 common/source/java/ch/systemsx/cisd/common/serviceconversation/RpcServiceFactory.java

diff --git a/common/.classpath b/common/.classpath
index 66fbbdbc5cc..c147fa5b451 100644
--- a/common/.classpath
+++ b/common/.classpath
@@ -35,5 +35,10 @@
 	<classpathentry kind="lib" path="/libraries/spring/spring-context.jar"/>
 	<classpathentry kind="lib" path="/libraries/spring/spring-aop.jar" sourcepath="/libraries/spring/src.jar"/>
 	<classpathentry kind="lib" path="/libraries/spring/third-party/aopalliance.jar"/>
+	<classpathentry kind="lib" path="/libraries/spring/spring-beans.jar"/>
+	<classpathentry kind="lib" path="/libraries/spring/spring-jdbc.jar"/>
+	<classpathentry kind="lib" path="/libraries/spring/spring-web.jar"/>
+	<classpathentry kind="lib" path="/libraries/spring/spring.jar"/>
+	<classpathentry kind="lib" path="/libraries/hibernate-core/hibernate-core.jar"/>
 	<classpathentry kind="output" path="targets/classes"/>
 </classpath>
diff --git a/common/source/java/ch/systemsx/cisd/common/serviceconversation/ConversationalClient.java b/common/source/java/ch/systemsx/cisd/common/serviceconversation/ConversationalClient.java
new file mode 100644
index 00000000000..a5ae65de34e
--- /dev/null
+++ b/common/source/java/ch/systemsx/cisd/common/serviceconversation/ConversationalClient.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2012 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.common.serviceconversation;
+
+public interface ConversationalClient
+{
+    public void send(ServiceMessage message);
+    
+    public <T extends ConversationalServer, U extends T> T getConversationClient(String sessionToken, U service, Class<T> reference);
+
+}
diff --git a/common/source/java/ch/systemsx/cisd/common/serviceconversation/ConversationalServer.java b/common/source/java/ch/systemsx/cisd/common/serviceconversation/ConversationalServer.java
new file mode 100644
index 00000000000..83f80c5f914
--- /dev/null
+++ b/common/source/java/ch/systemsx/cisd/common/serviceconversation/ConversationalServer.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2012 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.common.serviceconversation;
+
+public interface ConversationalServer extends IServiceMessageTransport
+{
+    public ServiceConversationDTO startConversation(String sessionToken, String clientUrl, String typeId);
+
+}
diff --git a/common/source/java/ch/systemsx/cisd/common/serviceconversation/MethodCall.java b/common/source/java/ch/systemsx/cisd/common/serviceconversation/MethodCall.java
new file mode 100644
index 00000000000..c44a6c227b4
--- /dev/null
+++ b/common/source/java/ch/systemsx/cisd/common/serviceconversation/MethodCall.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2012 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.common.serviceconversation;
+
+import java.io.Serializable;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class MethodCall implements Serializable
+{
+    private static final long serialVersionUID = 8679256131459236150L;
+
+    private String methodName;
+    private Object[] arguments;
+    
+    public MethodCall(String methodName, Object[] arguments) {
+        this.methodName = methodName;
+        this.arguments = arguments;
+    }
+
+    public Serializable executeOn(Object o) {
+
+        List<Class<?>> argClasses = new ArrayList<Class<?>>();
+        for (Object s : this.arguments) {
+            argClasses.add(s.getClass());
+        }
+        Exception ex;
+        try
+        {
+            Method m = o.getClass().getMethod(this.methodName, argClasses.toArray(new Class<?>[0]));
+            try
+            {
+                return (Serializable)m.invoke(o, this.arguments);
+            } catch (IllegalArgumentException e)
+            {
+                ex = e;
+            } catch (IllegalAccessException e)
+            {
+                ex = e;
+            } catch (InvocationTargetException e)
+            {
+                ex = e;
+            }
+        } catch (SecurityException e)
+        {
+            ex = e;
+        } catch (NoSuchMethodException e)
+        {
+            ex = e;
+        }
+        throw new RuntimeException("Method call failed", ex);
+    }
+    
+    @Override
+    public String toString() {
+        return "MethodCall "+this.methodName+"("+Arrays.asList(this.arguments)+")";
+    }
+    
+}
diff --git a/common/source/java/ch/systemsx/cisd/common/serviceconversation/RpcMessageTransport.java b/common/source/java/ch/systemsx/cisd/common/serviceconversation/RpcMessageTransport.java
new file mode 100644
index 00000000000..ea2a19ccd46
--- /dev/null
+++ b/common/source/java/ch/systemsx/cisd/common/serviceconversation/RpcMessageTransport.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2012 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.common.serviceconversation;
+
+public class RpcMessageTransport implements  IServiceMessageTransport
+{
+    private ConversationalClient client;
+    public RpcMessageTransport(ConversationalClient client) {
+        this.client = client;
+    }
+
+    public void send(ServiceMessage message)
+    {
+        this.client.send(message);
+    }
+}
diff --git a/common/source/java/ch/systemsx/cisd/common/serviceconversation/RpcProxy.java b/common/source/java/ch/systemsx/cisd/common/serviceconversation/RpcProxy.java
new file mode 100644
index 00000000000..8132c41312c
--- /dev/null
+++ b/common/source/java/ch/systemsx/cisd/common/serviceconversation/RpcProxy.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2012 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.common.serviceconversation;
+
+import java.io.Serializable;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.HashSet;
+
+import ch.systemsx.cisd.common.serviceconversation.client.IServiceConversation;
+import ch.systemsx.cisd.common.serviceconversation.client.ServiceConversationClient;
+
+public class RpcProxy implements InvocationHandler 
+{
+    private ServiceConversationClient client;
+    private String typeId;
+    
+    @SuppressWarnings("unchecked")
+    public static <T> T newInstance(Class<T> clazz, ServiceConversationClient client) {
+
+        Collection<Class<?>> interfaces = new HashSet<Class<?>>();
+        interfaces.add(clazz);
+        return (T)java.lang.reflect.Proxy.newProxyInstance(
+            clazz.getClassLoader(),
+            interfaces.toArray(new Class<?>[0]),
+            new RpcProxy(client, clazz.getName()));
+    }
+
+    private RpcProxy(ServiceConversationClient client, String typeId) {
+        this.client = client;
+        this.typeId = typeId;
+    }
+
+    public Object invoke(Object proxy, Method m, Object[] args) throws Throwable
+    {
+        IServiceConversation conversation = this.client.startConversation(this.typeId);
+        conversation.send(new MethodCall(m.getName(), args));
+        return conversation.receive(Serializable.class);
+    }
+}
diff --git a/common/source/java/ch/systemsx/cisd/common/serviceconversation/RpcServiceFactory.java b/common/source/java/ch/systemsx/cisd/common/serviceconversation/RpcServiceFactory.java
new file mode 100644
index 00000000000..30ef7bcda0f
--- /dev/null
+++ b/common/source/java/ch/systemsx/cisd/common/serviceconversation/RpcServiceFactory.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2012 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.common.serviceconversation;
+
+import java.io.Serializable;
+
+import org.hibernate.Session;
+import org.hibernate.SessionFactory;
+import org.hibernate.Transaction;
+import org.springframework.orm.hibernate3.SessionFactoryUtils;
+import org.springframework.orm.hibernate3.SessionHolder;
+import org.springframework.transaction.support.TransactionSynchronizationManager;
+
+
+import ch.systemsx.cisd.common.serviceconversation.server.IService;
+import ch.systemsx.cisd.common.serviceconversation.server.IServiceFactory;
+
+public final class RpcServiceFactory<T extends ConversationalServer> implements IServiceFactory 
+{
+
+    private T service;
+    private SessionFactory factory;
+    private Class<?> providedInterface;
+    private int timeout;
+    
+    public RpcServiceFactory(T service, Class<?> providedInterface, int clientTimeoutInMillis, SessionFactory factory) {
+        this.service = service;
+        this.providedInterface = providedInterface;
+        this.timeout = clientTimeoutInMillis;
+        this.factory = factory;
+    }
+
+    public final String getServiceTypeId() {
+        return this.providedInterface.getName();
+    }
+    
+    public IService create()
+    {
+        return new IService()
+        {
+
+            public void run(IServiceMessenger messenger)
+            {
+
+                MethodCall call =
+                        (MethodCall) messenger.receive(Serializable.class);
+
+                Session s = SessionFactoryUtils.doGetSession(factory, true);
+                TransactionSynchronizationManager.bindResource(factory, new SessionHolder(s));
+                Transaction tx = s.beginTransaction();
+                
+                Serializable result;
+                try {
+                    result = call.executeOn(service);
+                } finally {
+                    tx.commit();
+                    s.close();
+                    TransactionSynchronizationManager.unbindResource(factory);
+                }
+                messenger.send(result);
+            }
+
+        };
+    }
+
+    public int getClientTimeoutMillis()
+    {
+        return this.timeout;
+    }
+}
diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/DataStoreService.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/DataStoreService.java
index 6e747d7c4a0..56b838e49d4 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/DataStoreService.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/DataStoreService.java
@@ -34,6 +34,13 @@ import ch.systemsx.cisd.common.exceptions.ConfigurationFailureException;
 import ch.systemsx.cisd.common.exceptions.InvalidAuthenticationException;
 import ch.systemsx.cisd.common.exceptions.InvalidSessionException;
 import ch.systemsx.cisd.common.mail.MailClientParameters;
+import ch.systemsx.cisd.common.serviceconversation.ConversationalServer;
+import ch.systemsx.cisd.common.serviceconversation.IServiceMessageTransport;
+import ch.systemsx.cisd.common.serviceconversation.RpcProxy;
+import ch.systemsx.cisd.common.serviceconversation.ServiceConversationDTO;
+import ch.systemsx.cisd.common.serviceconversation.ServiceMessage;
+import ch.systemsx.cisd.common.serviceconversation.client.IRemoteServiceConversationServer;
+import ch.systemsx.cisd.common.serviceconversation.client.ServiceConversationClient;
 import ch.systemsx.cisd.common.spring.AbstractServiceWithLogger;
 import ch.systemsx.cisd.common.spring.IInvocationLoggerContext;
 import ch.systemsx.cisd.openbis.dss.generic.server.plugins.tasks.ArchiverPluginFactory;
@@ -93,6 +100,8 @@ public class DataStoreService extends AbstractServiceWithLogger<IDataStoreServic
     private File commandQueueDirOrNull;
 
     private IDataSetCommandExecutor commandExecutor;
+    
+    private String ownUrl;
 
     public DataStoreService(SessionTokenManager sessionTokenManager,
             MailClientParameters mailClientParameters, PluginTaskProviders pluginTaskParameters)
@@ -117,6 +126,47 @@ public class DataStoreService extends AbstractServiceWithLogger<IDataStoreServic
         storeRoot = pluginTaskParameters.getStoreRoot();
     }
 
+    public static class RpcServerStub implements IRemoteServiceConversationServer, IServiceMessageTransport {
+        
+        private ConversationalServer server;
+        private String callbackUrl;
+        private String sessionToken;
+        
+        public RpcServerStub(String sessionToken, ConversationalServer server, String callbackUrl) {
+            this.sessionToken = sessionToken;
+            this.server = server;
+            this.callbackUrl = callbackUrl;
+        }
+        
+        public ServiceConversationDTO startConversation(String typeId)
+        {
+            return this.server.startConversation(sessionToken, callbackUrl, typeId);
+        }
+        
+        public void send(ServiceMessage message)
+        {
+            this.server.send(message);
+        }
+    }
+    
+    
+    private ServiceConversationClient client;
+
+    public synchronized <T extends ConversationalServer, U extends T> T getConversationClient(String sessionToken, U service, Class<T> reference) {
+
+        RpcServerStub server = new RpcServerStub(sessionToken, service, this.ownUrl);
+        if (this.client == null) {
+            client = new ServiceConversationClient(server, server);
+        }
+        return RpcProxy.newInstance(reference, client);
+    }
+    
+    public void send(ServiceMessage message)
+    {
+        client.getIncomingResponseMessageTransport().send(message);
+    }
+
+    
     void setShareIdManager(IShareIdManager shareIdManager)
     {
         this.shareIdManager = shareIdManager;
@@ -466,4 +516,9 @@ public class DataStoreService extends AbstractServiceWithLogger<IDataStoreServic
         }
         return hierarchicalContentProvider;
     }
+
+    public void setOwnUrl(String ownUrl) {
+        this.ownUrl = ownUrl;
+    }
+    
 }
diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/DataStoreServiceLogger.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/DataStoreServiceLogger.java
index 52b65ac64ad..642d7e049eb 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/DataStoreServiceLogger.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/DataStoreServiceLogger.java
@@ -22,6 +22,8 @@ import java.util.Map;
 import org.apache.log4j.Logger;
 
 import ch.systemsx.cisd.common.exceptions.InvalidAuthenticationException;
+import ch.systemsx.cisd.common.serviceconversation.ConversationalServer;
+import ch.systemsx.cisd.common.serviceconversation.ServiceMessage;
 import ch.systemsx.cisd.common.spring.IInvocationLoggerContext;
 import ch.systemsx.cisd.common.utilities.IInitializable;
 import ch.systemsx.cisd.openbis.generic.shared.IDataStoreService;
@@ -84,6 +86,11 @@ class DataStoreServiceLogger implements IDataStoreService, IInitializable
         log("getVersion", "SESSION(%s)", sessionToken);
         return 0;
     }
+    
+    public void send(ServiceMessage message)
+    {
+        log("send", "", message);
+    }
 
     public List<String> getKnownDataSets(String sessionToken,
             List<? extends IDatasetLocation> dataSetLocations)
@@ -158,4 +165,10 @@ class DataStoreServiceLogger implements IDataStoreService, IInitializable
         return null;
     }
 
+    public <T extends ConversationalServer, U extends T> T getConversationClient(
+            String sessionToken, U service, Class<T> reference)
+    {
+        log("getConversationClient", "REFERENCE(%s)", reference);
+        return null;
+    }
 }
diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/EncapsulatedOpenBISService.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/EncapsulatedOpenBISService.java
index 057ccc1531c..360e24b5467 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/EncapsulatedOpenBISService.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/EncapsulatedOpenBISService.java
@@ -28,11 +28,13 @@ import ch.systemsx.cisd.common.api.client.ServiceFinder;
 import ch.systemsx.cisd.common.exceptions.UserFailureException;
 import ch.systemsx.cisd.common.logging.LogCategory;
 import ch.systemsx.cisd.common.logging.LogFactory;
+
 import ch.systemsx.cisd.openbis.dss.generic.server.openbisauth.OpenBISSessionHolder;
 import ch.systemsx.cisd.openbis.dss.generic.shared.IEncapsulatedOpenBISService;
 import ch.systemsx.cisd.openbis.dss.generic.shared.IShareIdManager;
 import ch.systemsx.cisd.openbis.dss.generic.shared.ServiceProvider;
 import ch.systemsx.cisd.openbis.dss.generic.shared.dto.DataSetInformation;
+import ch.systemsx.cisd.openbis.generic.shared.IDataStoreService;
 import ch.systemsx.cisd.openbis.generic.shared.IETLLIMSService;
 import ch.systemsx.cisd.openbis.generic.shared.ResourceNames;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.IGeneralInformationService;
@@ -98,6 +100,8 @@ public final class EncapsulatedOpenBISService implements IEncapsulatedOpenBISSer
     private OpenBISSessionHolder session;
 
     private IShareIdManager shareIdManager;
+    
+    private IDataStoreService dataStoreService;
 
     public static IETLLIMSService createOpenBisService(String openBISURL, String timeout)
     {
@@ -131,19 +135,20 @@ public final class EncapsulatedOpenBISService implements IEncapsulatedOpenBISSer
         return Integer.parseInt(timeout) * DateUtils.MILLIS_PER_MINUTE;
     }
     
-    public EncapsulatedOpenBISService(IETLLIMSService service, OpenBISSessionHolder sessionHolder)
+    public EncapsulatedOpenBISService(IETLLIMSService service, OpenBISSessionHolder sessionHolder, IDataStoreService dataStoreService)
     {
-        this(service, sessionHolder, null);
+        this(service, sessionHolder, dataStoreService, null);
     }
 
     public EncapsulatedOpenBISService(IETLLIMSService service, OpenBISSessionHolder sessionHolder,
-            IShareIdManager shareIdManager)
+            IDataStoreService dataStoreService, IShareIdManager shareIdManager)
     {
         this.shareIdManager = shareIdManager;
         assert service != null : "Given IETLLIMSService implementation can not be null.";
         assert sessionHolder != null : "Given OpenBISSessionHolder can not be null.";
         this.service = service;
         this.session = sessionHolder;
+        this.dataStoreService = dataStoreService;
     }
 
     private IShareIdManager getShareIdManager()
@@ -546,12 +551,15 @@ public final class EncapsulatedOpenBISService implements IEncapsulatedOpenBISSer
         setShareId(externalData);
         return sample;
     }
-
+         
     public AtomicEntityOperationResult performEntityOperations(
             AtomicEntityOperationDetails operationDetails)
     {
+                
+        IETLLIMSService conversationalService = dataStoreService.getConversationClient(session.getToken(), service, IETLLIMSService.class);
+        
         AtomicEntityOperationResult operations =
-                service.performEntityOperations(session.getToken(), operationDetails);
+                conversationalService.performEntityOperations(session.getToken(), operationDetails);
         List<? extends NewExternalData> dataSets = operationDetails.getDataSetRegistrations();
         for (NewExternalData dataSet : dataSets)
         {
diff --git a/datastore_server/source/java/dssApplicationContext.xml b/datastore_server/source/java/dssApplicationContext.xml
index 8e936f5a5dc..f19a78f04bc 100644
--- a/datastore_server/source/java/dssApplicationContext.xml
+++ b/datastore_server/source/java/dssApplicationContext.xml
@@ -67,6 +67,7 @@
     <bean id="openBIS-service" class="ch.systemsx.cisd.openbis.dss.generic.server.EncapsulatedOpenBISService">
        <constructor-arg ref="etl-lims-service"/>
        <constructor-arg ref="sessionHolder"/>
+       <constructor-arg ref="data-store-service"/>       
     </bean>    
     
 	<bean id="query-service" class="ch.systemsx.cisd.etlserver.registrator.api.v1.impl.DataSourceQueryService"/>
@@ -91,9 +92,12 @@
             </bean>
         </constructor-arg>
         <constructor-arg ref="plugin-tasks" />
+        
         <property name="cifexAdminUserOrNull" value="${cifex-admin-username}" />
         <property name="cifexAdminPasswordOrNull" value="${cifex-admin-password}" />
         <property name="commandQueueDir" value="${commandqueue-dir}"/>
+        <property name="ownUrl" value="${download-url}"/>
+
     </bean>
     
     <bean id="data-store-server"
diff --git a/datastore_server/sourceTest/java/ch/systemsx/cisd/etlserver/TransferredDataSetHandlerTest.java b/datastore_server/sourceTest/java/ch/systemsx/cisd/etlserver/TransferredDataSetHandlerTest.java
index f607f96ca81..747fb938a3e 100644
--- a/datastore_server/sourceTest/java/ch/systemsx/cisd/etlserver/TransferredDataSetHandlerTest.java
+++ b/datastore_server/sourceTest/java/ch/systemsx/cisd/etlserver/TransferredDataSetHandlerTest.java
@@ -61,6 +61,7 @@ import ch.systemsx.cisd.openbis.dss.generic.shared.IEncapsulatedOpenBISService;
 import ch.systemsx.cisd.openbis.dss.generic.shared.IShareIdManager;
 import ch.systemsx.cisd.openbis.dss.generic.shared.dto.DataSetInformation;
 import ch.systemsx.cisd.openbis.dss.generic.shared.utils.DssPropertyParametersUtil;
+import ch.systemsx.cisd.openbis.generic.shared.IDataStoreService;
 import ch.systemsx.cisd.openbis.generic.shared.IETLLIMSService;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DataSetType;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DatabaseInstance;
@@ -271,10 +272,12 @@ public final class TransferredDataSetHandlerTest extends AbstractFileSystemTestC
                 new ETLServerPlugin(new MockDataSetInfoExtractor(dataSetInfoExtractor),
                         typeExtractor, storageProcessor);
 
+        IDataStoreService dataStoreService = context.mock(IDataStoreService.class);
+        
         OpenBISSessionHolder sessionHolder = new OpenBISSessionHolder();
         sessionHolder.setToken(SESSION_TOKEN);
         authorizedLimsService =
-                new EncapsulatedOpenBISService(limsService, sessionHolder, shareIdManager);
+                new EncapsulatedOpenBISService(limsService, sessionHolder, dataStoreService, shareIdManager);
         dataSetValidator = context.mock(IDataSetValidator.class);
 
         Properties threadProperties = new Properties();
diff --git a/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/EncapsulatedOpenBISServiceTest.java b/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/EncapsulatedOpenBISServiceTest.java
index e6dd35f7bcb..62e17512692 100644
--- a/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/EncapsulatedOpenBISServiceTest.java
+++ b/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/EncapsulatedOpenBISServiceTest.java
@@ -27,9 +27,11 @@ import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
+import ch.systemsx.cisd.common.serviceconversation.ConversationalServer;
 import ch.systemsx.cisd.openbis.dss.generic.server.openbisauth.OpenBISSessionHolder;
 import ch.systemsx.cisd.openbis.dss.generic.shared.IShareIdManager;
 import ch.systemsx.cisd.openbis.dss.generic.shared.dto.DataSetInformation;
+import ch.systemsx.cisd.openbis.generic.shared.IDataStoreService;
 import ch.systemsx.cisd.openbis.generic.shared.IETLLIMSService;
 import ch.systemsx.cisd.openbis.generic.shared.basic.TechId;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.IEntityProperty;
@@ -63,6 +65,8 @@ public class EncapsulatedOpenBISServiceTest
     private OpenBISSessionHolder session;
 
     private IShareIdManager shareIdManager;
+    
+    private IDataStoreService dataStoreService;
 
     @BeforeMethod
     public void setUp()
@@ -70,9 +74,10 @@ public class EncapsulatedOpenBISServiceTest
         context = new Mockery();
         limsService = context.mock(IETLLIMSService.class);
         shareIdManager = context.mock(IShareIdManager.class);
+        dataStoreService = context.mock(IDataStoreService.class);
         session = new OpenBISSessionHolder();
         session.setToken(SESSION_TOKEN);
-        encapsulatedLimsService = new EncapsulatedOpenBISService(limsService, session, shareIdManager);
+        encapsulatedLimsService = new EncapsulatedOpenBISService(limsService, session, dataStoreService, shareIdManager);
     }
 
     @AfterMethod
@@ -171,6 +176,9 @@ public class EncapsulatedOpenBISServiceTest
             {
                 one(limsService).performEntityOperations(SESSION_TOKEN, operationDetails);
                 one(shareIdManager).setShareId("ds1", "42");
+                
+                allowing(dataStoreService).getConversationClient(with(any(String.class)),with(any(ConversationalServer.class)),with(any(Class.class)));
+                will(returnValue(limsService));
             }
         });
         encapsulatedLimsService.performEntityOperations(operationDetails);
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/ETLService.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/ETLService.java
index 29aa3f5dfdd..ae1447d2292 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/ETLService.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/ETLService.java
@@ -34,6 +34,11 @@ import ch.systemsx.cisd.authentication.ISessionManager;
 import ch.systemsx.cisd.common.collections.CollectionUtils;
 import ch.systemsx.cisd.common.exceptions.ConfigurationFailureException;
 import ch.systemsx.cisd.common.exceptions.UserFailureException;
+import ch.systemsx.cisd.common.serviceconversation.RpcMessageTransport;
+import ch.systemsx.cisd.common.serviceconversation.RpcServiceFactory;
+import ch.systemsx.cisd.common.serviceconversation.ServiceConversationDTO;
+import ch.systemsx.cisd.common.serviceconversation.ServiceMessage;
+import ch.systemsx.cisd.common.serviceconversation.server.ServiceConversationServer;
 import ch.systemsx.cisd.common.spring.IInvocationLoggerContext;
 import ch.systemsx.cisd.openbis.generic.server.api.v1.SearchCriteriaToDetailedSearchCriteriaTranslator;
 import ch.systemsx.cisd.openbis.generic.server.business.IDataStoreServiceFactory;
@@ -176,6 +181,8 @@ public class ETLService extends AbstractCommonServer<IETLLIMSService> implements
 
     private final TrustedCrossOriginDomainsProvider trustedOriginDomainProvider;
 
+    private final ServiceConversationServer server;
+
     public ETLService(IAuthenticationService authenticationService,
             ISessionManager<Session> sessionManager, IDAOFactory daoFactory,
             ICommonBusinessObjectFactory boFactory, IDataStoreServiceFactory dssFactory,
@@ -195,6 +202,13 @@ public class ETLService extends AbstractCommonServer<IETLLIMSService> implements
         this.daoFactory = daoFactory;
         this.dssFactory = dssFactory;
         this.trustedOriginDomainProvider = trustedOriginDomainProvider;
+
+        org.hibernate.SessionFactory sessionFactory =
+                daoFactory.getPersistencyResources().getSessionFactoryOrNull();
+
+        server = new ServiceConversationServer();
+        server.addServiceType(new RpcServiceFactory<IETLLIMSService>(this, IETLLIMSService.class,
+                3600000, sessionFactory));
     }
 
     public IETLLIMSService createLogger(IInvocationLoggerContext context)
@@ -208,6 +222,22 @@ public class ETLService extends AbstractCommonServer<IETLLIMSService> implements
         return IServer.VERSION;
     }
 
+    public ServiceConversationDTO startConversation(String sessionToken, String clientUrl,
+            String typeId)
+    {
+        getSession(sessionToken);
+
+        final IDataStoreService service = dssFactory.create(clientUrl);
+        String clientId = sessionToken;
+        server.addClientResponseTransport(clientId, new RpcMessageTransport(service));
+        return server.startConversation(typeId, clientId);
+    }
+
+    public void send(ServiceMessage message)
+    {
+        server.getIncomingMessageTransport().send(message);
+    }
+
     public DatabaseInstance getHomeDatabaseInstance(String sessionToken)
     {
         return DatabaseInstanceTranslator.translate(getHomeDatabaseInstance());
@@ -1215,6 +1245,7 @@ public class ETLService extends AbstractCommonServer<IETLLIMSService> implements
     public AtomicEntityOperationResult performEntityOperations(String sessionToken,
             AtomicEntityOperationDetails operationDetails)
     {
+
         final Session session = getSession(sessionToken);
 
         List<Space> spacesCreated = createSpaces(session, operationDetails);
@@ -1643,4 +1674,5 @@ public class ETLService extends AbstractCommonServer<IETLLIMSService> implements
         }
         return result;
     }
+
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/ETLServiceLogger.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/ETLServiceLogger.java
index fef6c5b6e7f..25d9d77f383 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/ETLServiceLogger.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/ETLServiceLogger.java
@@ -25,6 +25,8 @@ import org.apache.log4j.Level;
 import ch.systemsx.cisd.authentication.ISessionManager;
 import ch.systemsx.cisd.common.collections.CollectionUtils;
 import ch.systemsx.cisd.common.exceptions.UserFailureException;
+import ch.systemsx.cisd.common.serviceconversation.ServiceConversationDTO;
+import ch.systemsx.cisd.common.serviceconversation.ServiceMessage;
 import ch.systemsx.cisd.common.spring.IInvocationLoggerContext;
 import ch.systemsx.cisd.openbis.generic.shared.AbstractServerLogger;
 import ch.systemsx.cisd.openbis.generic.shared.IETLLIMSService;
@@ -86,6 +88,18 @@ public class ETLServiceLogger extends AbstractServerLogger implements IETLLIMSSe
         super(sessionManager, context);
     }
 
+    public ServiceConversationDTO startConversation(String sessionToken, String clientUrl,
+            String typeId)
+    {
+        logTracking(typeId, "startConversation", "");
+        return null;
+    }
+
+    public void send(ServiceMessage message)
+    {
+        logTracking(message.toString(), "send", "");
+    }
+
     public String createDataSetCode(String sessionToken) throws UserFailureException
     {
         logTracking(sessionToken, "createDataSetCode", "");
@@ -562,7 +576,9 @@ public class ETLServiceLogger extends AbstractServerLogger implements IETLLIMSSe
     public List<ExternalData> listDataSetsForPostRegistration(String sessionToken,
             String dataStoreCode)
     {
-        logAccess(Level.DEBUG, sessionToken, "listDataSetsForPostRegistration", "DATA_STORE(%s)", dataStoreCode);
+        logAccess(Level.DEBUG, sessionToken, "listDataSetsForPostRegistration", "DATA_STORE(%s)",
+                dataStoreCode);
         return null;
     }
+
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/IDataStoreService.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/IDataStoreService.java
index ca4de7bfa9f..46c04b2b77a 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/IDataStoreService.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/IDataStoreService.java
@@ -20,6 +20,7 @@ import java.util.List;
 import java.util.Map;
 
 import ch.systemsx.cisd.common.exceptions.InvalidAuthenticationException;
+import ch.systemsx.cisd.common.serviceconversation.ConversationalClient;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ExternalData;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.IDatasetLocation;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.LinkModel;
@@ -32,7 +33,7 @@ import ch.systemsx.cisd.openbis.generic.shared.dto.DatasetDescription;
  * 
  * @author Franz-Josef Elmer
  */
-public interface IDataStoreService
+public interface IDataStoreService extends ConversationalClient
 {
 
     /**
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/IETLLIMSService.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/IETLLIMSService.java
index 0266a1b05b0..4a0afad5945 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/IETLLIMSService.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/IETLLIMSService.java
@@ -23,6 +23,7 @@ import java.util.List;
 import org.springframework.transaction.annotation.Transactional;
 
 import ch.systemsx.cisd.common.exceptions.UserFailureException;
+import ch.systemsx.cisd.common.serviceconversation.ConversationalServer;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.SearchCriteria;
 import ch.systemsx.cisd.openbis.generic.shared.authorization.ISessionProvider;
 import ch.systemsx.cisd.openbis.generic.shared.authorization.annotation.AuthorizationGuard;
@@ -95,7 +96,7 @@ import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.SpaceIdentifier;
  * 
  * @author Christian Ribeaud
  */
-public interface IETLLIMSService extends IServer, ISessionProvider
+public interface IETLLIMSService extends IServer, ISessionProvider, ConversationalServer
 {
     /**
      * Returns the home database instance.
@@ -122,7 +123,8 @@ public interface IETLLIMSService extends IServer, ISessionProvider
         { RoleWithHierarchy.SPACE_OBSERVER, RoleWithHierarchy.SPACE_ETL_SERVER })
     public Experiment tryToGetExperiment(
             String sessionToken,
-            @AuthorizationGuard(guardClass = ExistingSpaceIdentifierPredicate.class) ExperimentIdentifier experimentIdentifier)
+            @AuthorizationGuard(guardClass = ExistingSpaceIdentifierPredicate.class)
+            ExperimentIdentifier experimentIdentifier)
             throws UserFailureException;
 
     /**
@@ -164,7 +166,8 @@ public interface IETLLIMSService extends IServer, ISessionProvider
     @RolesAllowed(RoleWithHierarchy.SPACE_ETL_SERVER)
     public Sample tryGetSampleWithExperiment(
             final String sessionToken,
-            @AuthorizationGuard(guardClass = ExistingSampleOwnerIdentifierPredicate.class) final SampleIdentifier sampleIdentifier)
+            @AuthorizationGuard(guardClass = ExistingSampleOwnerIdentifierPredicate.class)
+            final SampleIdentifier sampleIdentifier)
             throws UserFailureException;
 
     /**
@@ -202,7 +205,8 @@ public interface IETLLIMSService extends IServer, ISessionProvider
         { RoleWithHierarchy.SPACE_OBSERVER, RoleWithHierarchy.SPACE_ETL_SERVER })
     public List<ExternalData> listDataSetsByExperimentID(
             final String sessionToken,
-            @AuthorizationGuard(guardClass = ExperimentTechIdPredicate.class) final TechId experimentID)
+            @AuthorizationGuard(guardClass = ExperimentTechIdPredicate.class)
+            final TechId experimentID)
             throws UserFailureException;
 
     /**
@@ -214,7 +218,8 @@ public interface IETLLIMSService extends IServer, ISessionProvider
     @RolesAllowed(
         { RoleWithHierarchy.SPACE_OBSERVER, RoleWithHierarchy.SPACE_ETL_SERVER })
     public List<ExternalData> listDataSetsBySampleID(final String sessionToken,
-            @AuthorizationGuard(guardClass = SampleTechIdPredicate.class) final TechId sampleId,
+            @AuthorizationGuard(guardClass = SampleTechIdPredicate.class)
+            final TechId sampleId,
             final boolean showOnlyDirectlyConnected) throws UserFailureException;
 
     /**
@@ -227,7 +232,8 @@ public interface IETLLIMSService extends IServer, ISessionProvider
         { RoleWithHierarchy.SPACE_OBSERVER, RoleWithHierarchy.SPACE_ETL_SERVER })
     public List<ExternalData> listDataSetsByCode(
             String sessionToken,
-            @AuthorizationGuard(guardClass = DataSetCodeCollectionPredicate.class) List<String> dataSetCodes)
+            @AuthorizationGuard(guardClass = DataSetCodeCollectionPredicate.class)
+            List<String> dataSetCodes)
             throws UserFailureException;
 
     /**
@@ -241,7 +247,8 @@ public interface IETLLIMSService extends IServer, ISessionProvider
     @ReturnValueFilter(validatorClass = SampleValidator.class)
     public List<Sample> listSamples(
             final String sessionToken,
-            @AuthorizationGuard(guardClass = ListSampleCriteriaPredicate.class) final ListSampleCriteria criteria);
+            @AuthorizationGuard(guardClass = ListSampleCriteriaPredicate.class)
+            final ListSampleCriteria criteria);
 
     /**
      * Tries to return the properties of the top sample (e.g. master plate) registered for the
@@ -257,7 +264,8 @@ public interface IETLLIMSService extends IServer, ISessionProvider
         { RoleWithHierarchy.SPACE_OBSERVER, RoleWithHierarchy.SPACE_ETL_SERVER })
     public IEntityProperty[] tryToGetPropertiesOfTopSampleRegisteredFor(
             final String sessionToken,
-            @AuthorizationGuard(guardClass = SampleOwnerIdentifierPredicate.class) final SampleIdentifier sampleIdentifier)
+            @AuthorizationGuard(guardClass = SampleOwnerIdentifierPredicate.class)
+            final SampleIdentifier sampleIdentifier)
             throws UserFailureException;
 
     /**
@@ -277,7 +285,8 @@ public interface IETLLIMSService extends IServer, ISessionProvider
     @RolesAllowed(RoleWithHierarchy.SPACE_ETL_SERVER)
     @DatabaseCreateOrDeleteModification(value = ObjectKind.EXPERIMENT)
     public long registerExperiment(String sessionToken,
-            @AuthorizationGuard(guardClass = NewExperimentPredicate.class) NewExperiment experiment)
+            @AuthorizationGuard(guardClass = NewExperimentPredicate.class)
+            NewExperiment experiment)
             throws UserFailureException;
 
     /**
@@ -288,7 +297,8 @@ public interface IETLLIMSService extends IServer, ISessionProvider
     @DatabaseUpdateModification(value = ObjectKind.SAMPLE)
     public void registerSamples(
             final String sessionToken,
-            @AuthorizationGuard(guardClass = NewSamplesWithTypePredicate.class) final List<NewSamplesWithTypes> newSamplesWithType,
+            @AuthorizationGuard(guardClass = NewSamplesWithTypePredicate.class)
+            final List<NewSamplesWithTypes> newSamplesWithType,
             String userIdOrNull) throws UserFailureException;
 
     /**
@@ -298,7 +308,8 @@ public interface IETLLIMSService extends IServer, ISessionProvider
     @RolesAllowed(RoleWithHierarchy.SPACE_ETL_SERVER)
     @DatabaseCreateOrDeleteModification(value = ObjectKind.SAMPLE)
     public long registerSample(final String sessionToken,
-            @AuthorizationGuard(guardClass = NewSamplePredicate.class) final NewSample newSample,
+            @AuthorizationGuard(guardClass = NewSamplePredicate.class)
+            final NewSample newSample,
             String userIDOrNull) throws UserFailureException;
 
     /**
@@ -308,7 +319,8 @@ public interface IETLLIMSService extends IServer, ISessionProvider
     @RolesAllowed(RoleWithHierarchy.SPACE_ETL_SERVER)
     @DatabaseUpdateModification(value = ObjectKind.SAMPLE)
     public void updateSample(String sessionToken,
-            @AuthorizationGuard(guardClass = SampleUpdatesPredicate.class) SampleUpdatesDTO updates);
+            @AuthorizationGuard(guardClass = SampleUpdatesPredicate.class)
+            SampleUpdatesDTO updates);
 
     /**
      * Registers the specified data connected to a sample.
@@ -326,7 +338,8 @@ public interface IETLLIMSService extends IServer, ISessionProvider
     @DatabaseCreateOrDeleteModification(value = ObjectKind.DATA_SET)
     public void registerDataSet(
             final String sessionToken,
-            @AuthorizationGuard(guardClass = SampleOwnerIdentifierPredicate.class) final SampleIdentifier sampleIdentifier,
+            @AuthorizationGuard(guardClass = SampleOwnerIdentifierPredicate.class)
+            final SampleIdentifier sampleIdentifier,
             final NewExternalData externalData) throws UserFailureException;
 
     /**
@@ -345,7 +358,8 @@ public interface IETLLIMSService extends IServer, ISessionProvider
     @DatabaseCreateOrDeleteModification(value = ObjectKind.DATA_SET)
     public void registerDataSet(
             final String sessionToken,
-            @AuthorizationGuard(guardClass = SpaceIdentifierPredicate.class) final ExperimentIdentifier experimentIdentifier,
+            @AuthorizationGuard(guardClass = SpaceIdentifierPredicate.class)
+            final ExperimentIdentifier experimentIdentifier,
             final NewExternalData externalData) throws UserFailureException;
 
     /**
@@ -369,7 +383,8 @@ public interface IETLLIMSService extends IServer, ISessionProvider
     @Transactional(readOnly = true)
     @RolesAllowed(RoleWithHierarchy.SPACE_OBSERVER)
     public void checkDataSetAccess(String sessionToken,
-            @AuthorizationGuard(guardClass = DataSetCodePredicate.class) String dataSetCode)
+            @AuthorizationGuard(guardClass = DataSetCodePredicate.class)
+            String dataSetCode)
             throws UserFailureException;
 
     /**
@@ -382,7 +397,8 @@ public interface IETLLIMSService extends IServer, ISessionProvider
     @RolesAllowed(RoleWithHierarchy.SPACE_OBSERVER)
     public void checkDataSetCollectionAccess(
             String sessionToken,
-            @AuthorizationGuard(guardClass = DataSetCodeCollectionPredicate.class) List<String> dataSetCodes);
+            @AuthorizationGuard(guardClass = DataSetCodeCollectionPredicate.class)
+            List<String> dataSetCodes);
 
     /**
      * Tries to return the data set specified by its code.
@@ -391,7 +407,8 @@ public interface IETLLIMSService extends IServer, ISessionProvider
     @RolesAllowed(value =
         { RoleWithHierarchy.SPACE_OBSERVER, RoleWithHierarchy.SPACE_ETL_SERVER })
     public ExternalData tryGetDataSet(String sessionToken,
-            @AuthorizationGuard(guardClass = DataSetCodePredicate.class) String dataSetCode)
+            @AuthorizationGuard(guardClass = DataSetCodePredicate.class)
+            String dataSetCode)
             throws UserFailureException;
 
     /**
@@ -426,7 +443,8 @@ public interface IETLLIMSService extends IServer, ISessionProvider
         { RoleWithHierarchy.SPACE_OBSERVER, RoleWithHierarchy.SPACE_ETL_SERVER })
     public List<Sample> listSamplesByCriteria(
             final String sessionToken,
-            @AuthorizationGuard(guardClass = ListSamplesByPropertyPredicate.class) final ListSamplesByPropertyCriteria criteria)
+            @AuthorizationGuard(guardClass = ListSamplesByPropertyPredicate.class)
+            final ListSamplesByPropertyCriteria criteria)
             throws UserFailureException;
 
     /**
@@ -477,7 +495,8 @@ public interface IETLLIMSService extends IServer, ISessionProvider
         { RoleWithHierarchy.SPACE_OBSERVER, RoleWithHierarchy.SPACE_ETL_SERVER })
     public List<Experiment> listExperiments(
             String sessionToken,
-            @AuthorizationGuard(guardClass = SpaceIdentifierPredicate.class) ProjectIdentifier projectIdentifier);
+            @AuthorizationGuard(guardClass = SpaceIdentifierPredicate.class)
+            ProjectIdentifier projectIdentifier);
 
     /**
      * List experiments for a given list of experiment identifiers.
@@ -487,7 +506,8 @@ public interface IETLLIMSService extends IServer, ISessionProvider
         { RoleWithHierarchy.SPACE_OBSERVER, RoleWithHierarchy.SPACE_ETL_SERVER })
     public List<Experiment> listExperiments(
             String sessionToken,
-            @AuthorizationGuard(guardClass = SpaceIdentifierPredicate.class) List<ExperimentIdentifier> experimentIdentifiers,
+            @AuthorizationGuard(guardClass = SpaceIdentifierPredicate.class)
+            List<ExperimentIdentifier> experimentIdentifiers,
             ExperimentFetchOptions experimentFetchOptions);
 
     /**
@@ -498,7 +518,8 @@ public interface IETLLIMSService extends IServer, ISessionProvider
         { RoleWithHierarchy.SPACE_OBSERVER, RoleWithHierarchy.SPACE_ETL_SERVER })
     public List<Experiment> listExperimentsForProjects(
             String sessionToken,
-            @AuthorizationGuard(guardClass = SpaceIdentifierPredicate.class) List<ProjectIdentifier> projectIdentifiers,
+            @AuthorizationGuard(guardClass = SpaceIdentifierPredicate.class)
+            List<ProjectIdentifier> projectIdentifiers,
             ExperimentFetchOptions experimentFetchOptions);
 
     /**
@@ -531,7 +552,8 @@ public interface IETLLIMSService extends IServer, ISessionProvider
             String sessionToken,
             List<NewProperty> properties,
             String dataSetCode,
-            @AuthorizationGuard(guardClass = SpaceIdentifierPredicate.class) final SpaceIdentifier identifier)
+            @AuthorizationGuard(guardClass = SpaceIdentifierPredicate.class)
+            final SpaceIdentifier identifier)
             throws UserFailureException;
 
     /**
@@ -541,7 +563,8 @@ public interface IETLLIMSService extends IServer, ISessionProvider
     @RolesAllowed(RoleWithHierarchy.SPACE_ETL_SERVER)
     @DatabaseUpdateModification(value = ObjectKind.DATA_SET)
     public void updateShareIdAndSize(String sessionToken,
-            @AuthorizationGuard(guardClass = DataSetCodePredicate.class) String dataSetCode,
+            @AuthorizationGuard(guardClass = DataSetCodePredicate.class)
+            String dataSetCode,
             String shareId, long size) throws UserFailureException;
 
     /**
@@ -552,7 +575,8 @@ public interface IETLLIMSService extends IServer, ISessionProvider
     @DatabaseUpdateModification(value = ObjectKind.DATA_SET)
     public void updateDataSetStatuses(
             String sessionToken,
-            @AuthorizationGuard(guardClass = DataSetCodeCollectionPredicate.class) List<String> dataSetCodes,
+            @AuthorizationGuard(guardClass = DataSetCodeCollectionPredicate.class)
+            List<String> dataSetCodes,
             final DataSetArchivingStatus newStatus, boolean presentInArchive)
             throws UserFailureException;
 
@@ -582,7 +606,8 @@ public interface IETLLIMSService extends IServer, ISessionProvider
     @DatabaseUpdateModification(value = ObjectKind.DATA_SET)
     public int archiveDatasets(
             String sessionToken,
-            @AuthorizationGuard(guardClass = DataSetCodeCollectionPredicate.class) List<String> datasetCodes,
+            @AuthorizationGuard(guardClass = DataSetCodeCollectionPredicate.class)
+            List<String> datasetCodes,
             boolean removeFromDataStore);
 
     /**
@@ -595,7 +620,8 @@ public interface IETLLIMSService extends IServer, ISessionProvider
     @DatabaseUpdateModification(value = ObjectKind.DATA_SET)
     public int unarchiveDatasets(
             String sessionToken,
-            @AuthorizationGuard(guardClass = DataSetCodeCollectionPredicate.class) List<String> datasetCodes);
+            @AuthorizationGuard(guardClass = DataSetCodeCollectionPredicate.class)
+            List<String> datasetCodes);
 
     /**
      * Check if the user has USER access on the space
@@ -606,7 +632,8 @@ public interface IETLLIMSService extends IServer, ISessionProvider
     @Transactional(readOnly = true)
     @RolesAllowed(RoleWithHierarchy.SPACE_USER)
     public void checkSpaceAccess(String sessionToken,
-            @AuthorizationGuard(guardClass = SpaceIdentifierPredicate.class) SpaceIdentifier spaceId);
+            @AuthorizationGuard(guardClass = SpaceIdentifierPredicate.class)
+            SpaceIdentifier spaceId);
 
     /**
      * For the ETL Server to get data sets.
@@ -614,7 +641,8 @@ public interface IETLLIMSService extends IServer, ISessionProvider
     @Transactional(readOnly = true)
     @RolesAllowed(RoleWithHierarchy.SPACE_ETL_SERVER)
     public ExternalData tryGetDataSetForServer(String sessionToken,
-            @AuthorizationGuard(guardClass = DataSetCodePredicate.class) String dataSetCode)
+            @AuthorizationGuard(guardClass = DataSetCodePredicate.class)
+            String dataSetCode)
             throws UserFailureException;
 
     /**
@@ -656,7 +684,8 @@ public interface IETLLIMSService extends IServer, ISessionProvider
     @DatabaseCreateOrDeleteModification(value =
         { ObjectKind.SAMPLE, ObjectKind.DATA_SET })
     public Sample registerSampleAndDataSet(final String sessionToken,
-            @AuthorizationGuard(guardClass = NewSamplePredicate.class) final NewSample newSample,
+            @AuthorizationGuard(guardClass = NewSamplePredicate.class)
+            final NewSample newSample,
             final NewExternalData externalData, String userIdOrNull) throws UserFailureException;
 
     /**
@@ -676,7 +705,8 @@ public interface IETLLIMSService extends IServer, ISessionProvider
     @DatabaseCreateOrDeleteModification(value = ObjectKind.DATA_SET)
     public Sample updateSampleAndRegisterDataSet(
             String sessionToken,
-            @AuthorizationGuard(guardClass = SampleUpdatesPredicate.class) SampleUpdatesDTO updates,
+            @AuthorizationGuard(guardClass = SampleUpdatesPredicate.class)
+            SampleUpdatesDTO updates,
             NewExternalData externalData);
 
     /**
@@ -696,7 +726,8 @@ public interface IETLLIMSService extends IServer, ISessionProvider
                 ObjectKind.DATA_SET })
     public AtomicEntityOperationResult performEntityOperations(
             String sessionToken,
-            @AuthorizationGuard(guardClass = AtomicOperationsPredicate.class) AtomicEntityOperationDetails operationDetails);
+            @AuthorizationGuard(guardClass = AtomicOperationsPredicate.class)
+            AtomicEntityOperationDetails operationDetails);
 
     /**
      * Tries to return the space specified by its identifier.
@@ -706,7 +737,8 @@ public interface IETLLIMSService extends IServer, ISessionProvider
         { RoleWithHierarchy.SPACE_ETL_SERVER })
     public Space tryGetSpace(
             String sessionToken,
-            @AuthorizationGuard(guardClass = ExistingSpaceIdentifierPredicate.class) SpaceIdentifier spaceIdentifier);
+            @AuthorizationGuard(guardClass = ExistingSpaceIdentifierPredicate.class)
+            SpaceIdentifier spaceIdentifier);
 
     /**
      * Tries to return the project specified by its identifier.
@@ -716,7 +748,8 @@ public interface IETLLIMSService extends IServer, ISessionProvider
         { RoleWithHierarchy.SPACE_ETL_SERVER })
     public Project tryGetProject(
             String sessionToken,
-            @AuthorizationGuard(guardClass = ExistingSpaceIdentifierPredicate.class) ProjectIdentifier projectIdentifier);
+            @AuthorizationGuard(guardClass = ExistingSpaceIdentifierPredicate.class)
+            ProjectIdentifier projectIdentifier);
 
     /**
      * Search for samples matching the provided criteria.
@@ -751,7 +784,8 @@ public interface IETLLIMSService extends IServer, ISessionProvider
         { ObjectKind.DATA_SET })
     public void removeDataSetsPermanently(
             String sessionToken,
-            @AuthorizationGuard(guardClass = DataSetCodeCollectionPredicate.class) List<String> dataSetCodes,
+            @AuthorizationGuard(guardClass = DataSetCodeCollectionPredicate.class)
+            List<String> dataSetCodes,
             String reason);
 
     /**
@@ -763,7 +797,8 @@ public interface IETLLIMSService extends IServer, ISessionProvider
         { ObjectKind.EXPERIMENT, ObjectKind.SAMPLE, ObjectKind.DATA_SET })
     public void updateDataSet(
             String sessionToken,
-            @AuthorizationGuard(guardClass = DataSetUpdatesPredicate.class) DataSetUpdatesDTO dataSetUpdates);
+            @AuthorizationGuard(guardClass = DataSetUpdatesPredicate.class)
+            DataSetUpdatesDTO dataSetUpdates);
 
     /**
      * Returns a list of configured trusted domains which can host external shared web resources.
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/ETLServiceTest.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/ETLServiceTest.java
index 7ea609be025..f737c2e1998 100644
--- a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/ETLServiceTest.java
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/ETLServiceTest.java
@@ -41,6 +41,7 @@ import ch.systemsx.cisd.common.exceptions.ConfigurationFailureException;
 import ch.systemsx.cisd.common.exceptions.UserFailureException;
 import ch.systemsx.cisd.openbis.generic.server.business.IDataStoreServiceFactory;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.ICommonBusinessObjectFactory;
+import ch.systemsx.cisd.openbis.generic.server.dataaccess.PersistencyResources;
 import ch.systemsx.cisd.openbis.generic.shared.AbstractServerTestCase;
 import ch.systemsx.cisd.openbis.generic.shared.CommonTestUtils;
 import ch.systemsx.cisd.openbis.generic.shared.IDataStoreService;
@@ -172,6 +173,9 @@ public class ETLServiceTest extends AbstractServerTestCase
                                             new ExperimentBuilder().identifier("DB:/G1/P/EXP1")
                                                     .getExperiment());
                     will(returnValue(Arrays.asList(ds1.getDataSet())));
+                    allowing(daoFactory).getPersistencyResources();
+                    will(returnValue(new PersistencyResources(null, null, null, null)));
+
                 }
             });
 
@@ -214,6 +218,10 @@ public class ETLServiceTest extends AbstractServerTestCase
 
                     one(datasetLister).listAllDataSetShareIdsByDataStore(DSS_ID);
                     will(returnValue(list));
+
+                    allowing(daoFactory).getPersistencyResources();
+                    will(returnValue(new PersistencyResources(null, null, null, null)));
+
                 }
             });
 
@@ -246,6 +254,9 @@ public class ETLServiceTest extends AbstractServerTestCase
                     one(dataStoreService).getVersion(DSS_SESSION_TOKEN);
                     will(returnValue(IDataStoreService.VERSION));
 
+                    allowing(daoFactory).getPersistencyResources();
+                    will(returnValue(new PersistencyResources(null, null, null, null)));
+
                     prepareFindDatasetTypes(this);
 
                     allowing(dataStoreDAO).createOrUpdateDataStore(
@@ -299,6 +310,9 @@ public class ETLServiceTest extends AbstractServerTestCase
 
                     prepareFindDatasetTypes(this);
 
+                    allowing(daoFactory).getPersistencyResources();
+                    will(returnValue(new PersistencyResources(null, null, null, null)));
+
                     allowing(dataStoreDAO).createOrUpdateDataStore(
                             with(new BaseMatcher<DataStorePE>()
                                 {
@@ -381,6 +395,9 @@ public class ETLServiceTest extends AbstractServerTestCase
 
                     one(permIdDAO).createPermId();
                     will(returnValue("abc"));
+
+                    allowing(daoFactory).getPersistencyResources();
+                    will(returnValue(new PersistencyResources(null, null, null, null)));
                 }
             });
 
@@ -403,6 +420,10 @@ public class ETLServiceTest extends AbstractServerTestCase
 
                     one(permIdDAO).createPermId();
                     will(returnValue("permId"));
+
+                    allowing(daoFactory).getPersistencyResources();
+                    will(returnValue(new PersistencyResources(null, null, null, null)));
+
                 }
             });
 
@@ -470,6 +491,9 @@ public class ETLServiceTest extends AbstractServerTestCase
                     SamplePE sample = new SamplePE();
                     sample.setCode("S42");
                     will(returnValue(sample));
+                    allowing(daoFactory).getPersistencyResources();
+                    will(returnValue(new PersistencyResources(null, null, null, null)));
+
                 }
             });
 
@@ -496,6 +520,9 @@ public class ETLServiceTest extends AbstractServerTestCase
                     sampleTypePE.setAutoGeneratedCode(Boolean.FALSE);
                     sampleTypePE.setSubcodeUnique(Boolean.FALSE);
                     will(returnValue(sampleTypePE));
+                    allowing(daoFactory).getPersistencyResources();
+                    will(returnValue(new PersistencyResources(null, null, null, null)));
+
                 }
             });
         SampleType sampleType = createService().getSampleType(SESSION_TOKEN, "MY_TYPE");
@@ -795,6 +822,9 @@ public class ETLServiceTest extends AbstractServerTestCase
                     assignRoles(personPE);
                     one(personDAO).listPersons();
                     will(returnValue(Arrays.asList(personPE)));
+                    allowing(daoFactory).getPersistencyResources();
+                    will(returnValue(new PersistencyResources(null, null, null, null)));
+
                 }
             });
 
@@ -929,6 +959,9 @@ public class ETLServiceTest extends AbstractServerTestCase
                     ExternalDataPE externalDataPE = new ExternalDataPE();
                     externalDataPE.setCode(externalData.getCode());
                     will(returnValue(externalDataPE));
+
+                    allowing(daoFactory).getPersistencyResources();
+                    will(returnValue(new PersistencyResources(null, null, null, null)));
                 }
             });
 
@@ -1212,6 +1245,9 @@ public class ETLServiceTest extends AbstractServerTestCase
                     one(sampleBO).tryToLoadBySampleIdentifier(identifier);
                     one(sampleBO).tryToGetSample();
                     will(returnValue(sample));
+                    allowing(daoFactory).getPersistencyResources();
+                    will(returnValue(new PersistencyResources(null, null, null, null)));
+
                 }
             });
     }
diff --git a/openbis_api/source/java/ch/systemsx/cisd/openbis/generic/shared/api/v1/dto/SearchCriteria.java b/openbis_api/source/java/ch/systemsx/cisd/openbis/generic/shared/api/v1/dto/SearchCriteria.java
index fb448b9cd49..3f7fef7fdab 100644
--- a/openbis_api/source/java/ch/systemsx/cisd/openbis/generic/shared/api/v1/dto/SearchCriteria.java
+++ b/openbis_api/source/java/ch/systemsx/cisd/openbis/generic/shared/api/v1/dto/SearchCriteria.java
@@ -166,6 +166,14 @@ public class SearchCriteria implements Serializable
             return new AttributeMatchClause(attribute, desiredValue);
         }
 
+        /**
+         * Factory method to create a MatchClause matching against registration or modification date.
+         * 
+         * @param attribute The attribute to compare against
+         * @param mode The kind of comparison (<=, ==, >=)
+         * @param date The date to compare against, format YYYY-MM-DD
+         * @timezone The time zone of the date ("+1", "-5", "0", etc.)
+         */
         public static MatchClause createTimeAttributeMatch(MatchClauseTimeAttribute attribute,
                 CompareMode mode, String date, String timezone)
         {
-- 
GitLab