From cf15612c619be80b5e4fbf059f5369f6ba37459f Mon Sep 17 00:00:00 2001
From: pkupczyk <pkupczyk>
Date: Tue, 25 Sep 2012 07:26:43 +0000
Subject: [PATCH] BIS-185 / Make long-running method calls in IDataStoreService
 use service conversations BIS-196 / Make service conversations timeout
 configurable - junit tests and bugfixes

SVN: 26781
---
 .../client/ClientMessenger.java               |  18 +-
 .../ServiceConversationServerManager.java     |   8 +-
 .../systemtests/RmiConversationTest.java      | 413 ---------------
 .../systemtests/ServiceConversationTest.java  | 492 ++++++++++++++++++
 .../systemtests/SystemTestCase.java           |  19 -
 .../BaseServiceConversationServerManager.java |  16 +-
 .../ServiceConversationMethodInvocation.java  |  22 +-
 .../cisd/common/spring/WaitAction.java        |   8 +-
 .../ServiceConversationServerManager.java     |   8 +-
 9 files changed, 535 insertions(+), 469 deletions(-)
 delete mode 100644 datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/datastoreserver/systemtests/RmiConversationTest.java
 create mode 100644 datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/datastoreserver/systemtests/ServiceConversationTest.java

diff --git a/common/source/java/ch/systemsx/cisd/common/serviceconversation/client/ClientMessenger.java b/common/source/java/ch/systemsx/cisd/common/serviceconversation/client/ClientMessenger.java
index 1c97fe7fe81..250da484235 100644
--- a/common/source/java/ch/systemsx/cisd/common/serviceconversation/client/ClientMessenger.java
+++ b/common/source/java/ch/systemsx/cisd/common/serviceconversation/client/ClientMessenger.java
@@ -46,16 +46,15 @@ class ClientMessenger implements IServiceConversation
     private final ClientResponseMessageMultiplexer responseMessageMultiplexer;
 
     private final int serviceMessageTimeoutMillis;
-    
+
     private final int serverWorkQueueSizeAtStartup;
 
     private int outgoingMessageIdx;
 
     private final AtomicBoolean serviceExceptionSignaled = new AtomicBoolean();
-    
-    private final static Logger operationLog =
-            LogFactory.getLogger(LogCategory.OPERATION, ClientMessenger.class);
 
+    private final static Logger operationLog = LogFactory.getLogger(LogCategory.OPERATION,
+            ClientMessenger.class);
 
     ClientMessenger(ServiceConversationDTO serviceConversationDTO,
             IServiceMessageTransport transportToService,
@@ -151,9 +150,11 @@ class ClientMessenger implements IServiceConversation
         }
     }
 
-    private ServiceMessage getMessage(int timeout) throws InterruptedException {
+    private ServiceMessage getMessage(int timeout) throws InterruptedException
+    {
         ServiceMessage message = responseMessageQueue.poll(timeout);
-        while (message != null && message.getProgress() != null) {
+        while (message != null && message.getProgress() != null)
+        {
             operationLog.info(message.getProgress());
             message = responseMessageQueue.poll(timeout);
         }
@@ -186,9 +187,10 @@ class ClientMessenger implements IServiceConversation
             throw new ServiceExecutionException(message.getConversationId(),
                     message.tryGetExceptionDescription());
         }
-                
+
         final Object payload = message.getPayload();
-        if (messageClass != null && messageClass.isAssignableFrom(payload.getClass()) == false)
+        if (messageClass != null && payload != null
+                && messageClass.isAssignableFrom(payload.getClass()) == false)
         {
             throw new UnexpectedMessagePayloadException(payload.getClass(), messageClass);
         }
diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ServiceConversationServerManager.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ServiceConversationServerManager.java
index 045ab76f03d..e2a0389b6ae 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ServiceConversationServerManager.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ServiceConversationServerManager.java
@@ -16,6 +16,8 @@
 
 package ch.systemsx.cisd.openbis.dss.generic.server;
 
+import org.springframework.beans.factory.InitializingBean;
+
 import ch.systemsx.cisd.common.conversation.client.ServiceConversationClientDetails;
 import ch.systemsx.cisd.common.conversation.manager.BaseServiceConversationServerManager;
 import ch.systemsx.cisd.common.spring.PropertyPlaceholderUtils;
@@ -27,7 +29,7 @@ import ch.systemsx.cisd.openbis.generic.shared.conversation.ServiceConversationA
  * @author pkupczyk
  */
 public class ServiceConversationServerManager extends BaseServiceConversationServerManager
-        implements IServiceConversationServerManagerLocal
+        implements IServiceConversationServerManagerLocal, InitializingBean
 {
 
     private IDataStoreService dataStoreService;
@@ -37,9 +39,9 @@ public class ServiceConversationServerManager extends BaseServiceConversationSer
     private int applicationServerTimeoutInMillis;
 
     @Override
-    protected void initializeServices()
+    public void afterPropertiesSet() throws Exception
     {
-        addService(IDataStoreService.class.getName(), dataStoreService);
+        addService(IDataStoreService.class, dataStoreService);
     }
 
     @Override
diff --git a/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/datastoreserver/systemtests/RmiConversationTest.java b/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/datastoreserver/systemtests/RmiConversationTest.java
deleted file mode 100644
index 3fc7a813d30..00000000000
--- a/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/datastoreserver/systemtests/RmiConversationTest.java
+++ /dev/null
@@ -1,413 +0,0 @@
-/*
- * 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.openbis.datastoreserver.systemtests;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.is;
-
-import java.util.Date;
-import java.util.List;
-import java.util.UUID;
-
-import javax.annotation.Resource;
-
-import org.eclipse.jetty.server.Connector;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
-import org.eclipse.jetty.servlet.ServletContextHandler;
-import org.eclipse.jetty.servlet.ServletHolder;
-import org.hibernate.Criteria;
-import org.hibernate.SessionFactory;
-import org.hibernate.criterion.Restrictions;
-import org.springframework.beans.factory.support.GenericBeanDefinition;
-import org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter;
-import org.springframework.transaction.annotation.Transactional;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.context.WebApplicationContext;
-import org.springframework.web.context.support.GenericWebApplicationContext;
-import org.springframework.web.servlet.DispatcherServlet;
-import org.testng.annotations.AfterClass;
-import org.testng.annotations.BeforeClass;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
-import ch.systemsx.cisd.base.exceptions.TimeoutExceptionUnchecked;
-import ch.systemsx.cisd.common.conversation.manager.IServiceConversationClientManagerRemote;
-import ch.systemsx.cisd.common.conversation.manager.IServiceConversationServerManagerRemote;
-import ch.systemsx.cisd.common.conversation.progress.IServiceConversationProgressListener;
-import ch.systemsx.cisd.common.logging.LogInitializer;
-import ch.systemsx.cisd.common.server.ISessionTokenProvider;
-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.HttpInvokerUtils;
-import ch.systemsx.cisd.openbis.generic.shared.dto.DatabaseInstancePE;
-import ch.systemsx.cisd.openbis.generic.shared.dto.OpenBISSessionHolder;
-
-@Test(groups =
-    { "system test", "broken" })
-public class RmiConversationTest extends SystemTestCase
-{
-    /*
-    private static RmiConversationController cont;
-
-    private EchoService echo;
-
-    private Server conversationClient;
-
-    @BeforeClass
-    public void beforeClass() throws Exception
-    {
-        LogInitializer.init();
-
-        conversationClient = new Server();
-        Connector clientConnector = new SelectChannelConnector();
-        clientConnector.setPort(8882);
-        conversationClient.addConnector(clientConnector);
-        DispatcherServlet clientDispatcherServlet = new DispatcherServlet()
-            {
-                private static final long serialVersionUID = 1L;
-
-                @Override
-                protected WebApplicationContext findWebApplicationContext()
-                {
-                    GenericWebApplicationContext ctx = new GenericWebApplicationContext();
-
-                    GenericBeanDefinition definition = new GenericBeanDefinition();
-                    definition.setBeanClass(ClientBean.class);
-                    ctx.registerBeanDefinition("client", definition);
-
-                    GenericBeanDefinition exporter = new GenericBeanDefinition();
-                    exporter.setBeanClass(ClientExporter.class);
-                    ctx.registerBeanDefinition("clientExporter", exporter);
-
-                    ctx.refresh();
-
-                    return ctx;
-                }
-            };
-        ServletContextHandler clientSch =
-                new ServletContextHandler(conversationClient, "/", ServletContextHandler.SESSIONS);
-        clientSch.addServlet(new ServletHolder(clientDispatcherServlet), "/*");
-        conversationClient.start();
-
-    }
-
-    @BeforeMethod
-    public void beforeMethod() throws Exception
-    {
-        EchoService httpEcho =
-                HttpInvokerUtils.createServiceStub(EchoService.class,
-                        "http://localhost:8888/openbis/rmi-echoservice", 5000);
-        cont = new RmiConversationController("http://localhost:8882");
-        OpenBISSessionHolder sessionHolder = new OpenBISSessionHolder();
-        sessionHolder.setSessionToken("");
-        echo = cont.getConversationalReference(sessionHolder, httpEcho, EchoService.class);
-    }
-
-    @AfterClass
-    public void afterClass() throws Exception
-    {
-        conversationClient.getGracefulShutdown();
-    }
-
-    @Test
-    public void callThroughRpcServiceConversationWorks() throws Exception
-    {
-        assertThat(echo.echo("echo", 0), is("echo"));
-    }
-
-    @Test(expectedExceptions = TimeoutExceptionUnchecked.class)
-    public void clientTimeoutsIfNoProgressMade()
-    {
-        echo.echoWithoutProgress("echo", 5000);
-    }
-
-    @Test
-    public void clientDoesNotTimeOutIfProgressIsReported()
-    {
-        assertThat(echo.echo("echo", 5000), is("echo"));
-    }
-
-    // Disabled because I could not make transactions work with this test spring context.
-    @Test(enabled = false)
-    public void transactionIsRolledBackIfThereIsAnExceptionDuringRequestProcessing()
-            throws Exception
-    {
-        try
-        {
-            echo.echoWithStoreAndProcessingException("echo");
-            assertThat(true, is(false));
-        } catch (Exception e)
-        {
-        }
-
-        assertThat(echo.exists("echo"), is(false));
-    }
-
-    // Disabled because I could not make transactions work with this test spring context.
-    @Test(enabled = false)
-    public void transactionIsCommitedAfterSuccessfulRequestProcessing() throws Exception
-    {
-        assertThat(echo.echoWithStore("stored"), is("stored"));
-        assertThat(echo.exists("stored"), is(true));
-    }
-
-    public interface EchoService extends IServiceConversationServerManagerRemote
-    {
-        @Transactional
-        public String echo(String input, Integer delayInMillis);
-
-        @Transactional
-        public String echo(String input, Integer delayInMillis,
-                IServiceConversationProgressListener listener);
-
-        @Transactional
-        public String echoWithoutProgress(String input, Integer delayInMillis);
-
-        @Transactional
-        public String echoWithoutProgress(String input, Integer delayInMillis,
-                IServiceConversationProgressListener listener);
-
-        @Transactional
-        public String echoWithStore(String input);
-
-        @Transactional
-        public String echoWithStore(String input, IServiceConversationProgressListener listener);
-
-        @Transactional
-        public String echoWithStoreAndProcessingException(String input);
-
-        @Transactional
-        public String echoWithStoreAndProcessingException(String input,
-                IServiceConversationProgressListener listener);
-
-        @Transactional
-        public boolean exists(String code);
-
-        @Transactional
-        public boolean exists(String code, IServiceConversationProgressListener listener);
-    }
-
-    public static class EchoServiceBean implements EchoService
-    {
-
-        private ServiceConversationServer server;
-
-        private SessionFactory sessionFactory;
-
-        public EchoServiceBean()
-        {
-        }
-
-        @Override
-        public ServiceConversationDTO startConversation(ISessionTokenProvider sessionTokenProvider,
-                String clientUrl, String typeId)
-        {
-            IServiceConversationClientManagerRemote client =
-                    HttpInvokerUtils.createServiceStub(
-                            IServiceConversationClientManagerRemote.class,
-                            "http://localhost:8882/client", 5000);
-            server.addClientResponseTransport("test-client-id", client);
-            return this.server.startConversation(typeId, "test-client-id");
-        }
-
-        @Override
-        public void send(ServiceMessage message)
-        {
-            server.getIncomingMessageTransport().send(message);
-        }
-
-        @Override
-        public String echo(String input, Integer delayInMillis)
-        {
-            return echo(input, delayInMillis, null);
-        }
-
-        @Override
-        public String echo(String input, Integer delayInMillis,
-                IServiceConversationProgressListener progress)
-        {
-
-            long startTime = System.currentTimeMillis();
-            int total = 50;
-            int unit = delayInMillis / total;
-            int i = 0;
-
-            progress.update("progress", total, i);
-            while (System.currentTimeMillis() - startTime < delayInMillis)
-            {
-                try
-                {
-                    Thread.sleep(unit);
-                } catch (InterruptedException ex)
-                {
-                    ex.printStackTrace();
-                }
-                progress.update("progress", total, ++i);
-
-            }
-            return input;
-        }
-
-        @Override
-        public String echoWithoutProgress(String input, Integer delayInMillis)
-        {
-            return echoWithoutProgress(input, delayInMillis, null);
-        }
-
-        @Override
-        public String echoWithoutProgress(String input, Integer delayInMillis,
-                IServiceConversationProgressListener progress)
-        {
-            try
-            {
-                Thread.sleep(delayInMillis);
-            } catch (InterruptedException ex)
-            {
-                ex.printStackTrace();
-            }
-            return input;
-        }
-
-        @Override
-        public String echoWithStore(String input)
-        {
-            return echoWithStore(input, null);
-        }
-
-        @Override
-        public String echoWithStore(String input, IServiceConversationProgressListener progress)
-        {
-
-            DatabaseInstancePE db = new DatabaseInstancePE();
-            db.setCode(input);
-            db.setOriginalSource(false);
-            db.setRegistrationDate(new Date());
-            db.setUuid(UUID.randomUUID().toString());
-            sessionFactory.getCurrentSession().persist(db);
-            return input;
-        }
-
-        @Override
-        public String echoWithStoreAndProcessingException(String input)
-        {
-            return echoWithStoreAndProcessingException(input, null);
-        }
-
-        @Override
-        public String echoWithStoreAndProcessingException(String input,
-                IServiceConversationProgressListener progress)
-        {
-
-            DatabaseInstancePE db = new DatabaseInstancePE();
-            db.setCode(input);
-            db.setOriginalSource(false);
-            db.setRegistrationDate(new Date());
-            db.setUuid(UUID.randomUUID().toString());
-            sessionFactory.getCurrentSession().persist(db);
-
-            throw new NullPointerException("Exception");
-        }
-
-        @Override
-        public boolean exists(String code)
-        {
-            return exists(code, null);
-        }
-
-        @Override
-        public boolean exists(String code, IServiceConversationProgressListener progress)
-        {
-
-            Criteria criteria =
-                    sessionFactory.getCurrentSession().createCriteria(DatabaseInstancePE.class);
-            criteria.add(Restrictions.eq("code", code));
-
-            @SuppressWarnings(
-                { "unchecked", "cast" })
-            List<DatabaseInstancePE> list = (List<DatabaseInstancePE>) criteria.list();
-            boolean result = list.size() > 0;
-            if (result)
-            {
-                sessionFactory.getCurrentSession().delete(list.get(0));
-            }
-            return result;
-        }
-
-        public void setSessionFactory(SessionFactory sessionFactory)
-        {
-            this.sessionFactory = sessionFactory;
-        }
-
-        private EchoService echoService;
-
-        private ServiceConversationServiceFactory<EchoService> rmiServiceFactory;
-
-        public void setEchoService(EchoService echoService)
-        {
-            this.echoService = echoService;
-            this.server = new ServiceConversationServer();
-            this.rmiServiceFactory =
-                    new ServiceConversationServiceFactory<EchoService>(this.server,
-                            this.echoService, EchoService.class, 1000);
-            this.server.addServiceType(rmiServiceFactory);
-
-        }
-    }
-
-    public static class ClientBean implements IServiceConversationClientManagerRemote
-    {
-
-        @Override
-        public void send(ServiceMessage message)
-        {
-            cont.process(message);
-        }
-    }
-
-    @RequestMapping(
-        { "/client" })
-    public static class ClientExporter extends HttpInvokerServiceExporter
-    {
-        @Override
-        public void afterPropertiesSet()
-        {
-            setServiceInterface(IServiceConversationClientManagerRemote.class);
-            setService(new ClientBean());
-            super.afterPropertiesSet();
-        }
-    }
-
-    @RequestMapping(
-        { "/openbis/rmi-echoservice" })
-    public static class EchoServiceExporter extends HttpInvokerServiceExporter
-    {
-
-        @Resource(name = "echoService")
-        private EchoService echoService;
-
-        @Override
-        public void afterPropertiesSet()
-        {
-            setServiceInterface(EchoService.class);
-            setService(echoService);
-            super.afterPropertiesSet();
-        }
-    }
-    */
-}
diff --git a/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/datastoreserver/systemtests/ServiceConversationTest.java b/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/datastoreserver/systemtests/ServiceConversationTest.java
new file mode 100644
index 00000000000..c762e993c8f
--- /dev/null
+++ b/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/datastoreserver/systemtests/ServiceConversationTest.java
@@ -0,0 +1,492 @@
+/*
+ * 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.openbis.datastoreserver.systemtests;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.nio.SelectChannelConnector;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.jmock.Expectations;
+import org.jmock.Mockery;
+import org.springframework.beans.MutablePropertyValues;
+import org.springframework.beans.factory.support.GenericBeanDefinition;
+import org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter;
+import org.springframework.web.context.WebApplicationContext;
+import org.springframework.web.context.support.GenericWebApplicationContext;
+import org.springframework.web.servlet.DispatcherServlet;
+import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import ch.systemsx.cisd.base.exceptions.TimeoutExceptionUnchecked;
+import ch.systemsx.cisd.common.conversation.annotation.Conversational;
+import ch.systemsx.cisd.common.conversation.annotation.Progress;
+import ch.systemsx.cisd.common.conversation.client.ServiceConversationClientDetails;
+import ch.systemsx.cisd.common.conversation.manager.BaseServiceConversationClientManager;
+import ch.systemsx.cisd.common.conversation.manager.BaseServiceConversationServerManager;
+import ch.systemsx.cisd.common.conversation.manager.IServiceConversationClientManagerRemote;
+import ch.systemsx.cisd.common.conversation.manager.IServiceConversationServerManagerRemote;
+import ch.systemsx.cisd.common.logging.LogInitializer;
+import ch.systemsx.cisd.common.serviceconversation.client.ServiceExecutionException;
+import ch.systemsx.cisd.common.spring.WaitAction;
+
+@Test
+public class ServiceConversationTest
+{
+
+    private static final int SERVER_PORT = 9000;
+
+    private static final String SERVER_PATH = "/conversationServer";
+
+    private static final String SERVER_URL = "http://localhost:" + SERVER_PORT + SERVER_PATH;
+
+    private static final int CLIENT_PORT_1 = 9001;
+
+    private static final int CLIENT_PORT_2 = 9002;
+
+    private static final String CLIENT_PATH = "/conversationClient";
+
+    private static final String CLIENT_URL_1 = "http://localhost:" + CLIENT_PORT_1 + CLIENT_PATH;
+
+    private static final String CLIENT_URL_2 = "http://localhost:" + CLIENT_PORT_2 + CLIENT_PATH;
+
+    private static final String SESSION_TOKEN_1 = "test-session-token-1";
+
+    private static final String SESSION_TOKEN_2 = "test-session-token-2";
+
+    private static final int TIMEOUT = 500;
+
+    private static final Integer CLIENT_ID_1 = Integer.valueOf(1);
+
+    private static final Integer CLIENT_ID_2 = Integer.valueOf(2);
+
+    private Mockery context;
+
+    private TestServiceWrapper1 serviceOnServerSideWrapper1;
+
+    private TestServiceWrapper2 serviceOnServerSideWrapper2;
+
+    private BaseServiceConversationClientManager clientManager1;
+
+    private BaseServiceConversationClientManager clientManager2;
+
+    private BaseServiceConversationServerManager serverManager;
+
+    private ServiceExporter<?> clientExporter1;
+
+    private ServiceExporter<?> clientExporter2;
+
+    private ServiceExporter<?> serverExporter;
+
+    @BeforeClass(alwaysRun = true)
+    public void beforeClass() throws Exception
+    {
+        LogInitializer.init();
+
+        serviceOnServerSideWrapper1 = new TestServiceWrapper1();
+        serviceOnServerSideWrapper2 = new TestServiceWrapper2();
+
+        clientManager1 = createClientManager();
+        clientManager2 = createClientManager();
+        serverManager = createServerManager();
+
+        clientExporter1 = createClientExporter(CLIENT_PORT_1, clientManager1);
+        clientExporter2 = createClientExporter(CLIENT_PORT_2, clientManager2);
+        serverExporter = createServerExporter(serverManager);
+
+        clientExporter1.getServer().start();
+        clientExporter2.getServer().start();
+        serverExporter.getServer().start();
+    }
+
+    @AfterClass(alwaysRun = true)
+    public void afterClass() throws Exception
+    {
+        clientExporter1.getServer().stop();
+        clientExporter2.getServer().stop();
+        serverExporter.getServer().stop();
+    }
+
+    @BeforeMethod
+    public void beforeMethod() throws Exception
+    {
+        context = new Mockery();
+
+        serviceOnServerSideWrapper1.setService(context.mock(TestService1.class));
+        serviceOnServerSideWrapper2.setService(context.mock(TestService2.class));
+    }
+
+    private <S> S getServiceOnClientSide1(Class<S> serviceInterface)
+    {
+        return clientManager1.getService(SERVER_URL, serviceInterface, SESSION_TOKEN_1,
+                CLIENT_ID_1, TIMEOUT);
+    }
+
+    private <S> S getServiceOnClientSide2(Class<S> serviceInterface)
+    {
+        return clientManager2.getService(SERVER_URL, serviceInterface, SESSION_TOKEN_2,
+                CLIENT_ID_2, TIMEOUT);
+    }
+
+    @Test(expectedExceptions = ServiceExecutionException.class)
+    public void testNonConversationalMethod() throws Exception
+    {
+        getServiceOnClientSide1(TestService1.class).nonConversationalMethod();
+    }
+
+    @Test
+    public void testConversationalMethodWithoutReturnValue()
+    {
+        context.checking(new Expectations()
+            {
+                {
+                    one(serviceOnServerSideWrapper1.getService()).methodWithoutReturnValue();
+                }
+            });
+        getServiceOnClientSide1(TestService1.class).methodWithoutReturnValue();
+    }
+
+    @Test
+    public void testConversationalMethodWithPrimitiveReturnValue()
+    {
+        context.checking(new Expectations()
+            {
+                {
+                    one(serviceOnServerSideWrapper1.getService()).methodWithPrimitiveReturnValue();
+                    will(returnValue(1));
+                }
+            });
+
+        Assert.assertEquals(getServiceOnClientSide1(TestService1.class)
+                .methodWithPrimitiveReturnValue(), 1);
+    }
+
+    @Test
+    public void testConversationalMethodWithSerializableReturnValue()
+    {
+        context.checking(new Expectations()
+            {
+                {
+                    one(serviceOnServerSideWrapper1.getService())
+                            .methodWithSerializableReturnValue();
+                    will(returnValue("abc"));
+
+                }
+            });
+        Assert.assertEquals(getServiceOnClientSide1(TestService1.class)
+                .methodWithSerializableReturnValue(), "abc");
+    }
+
+    @Test(expectedExceptions = TimeoutExceptionUnchecked.class)
+    public void testTimeout()
+    {
+        context.checking(new Expectations()
+            {
+                {
+                    one(serviceOnServerSideWrapper1.getService()).methodWithoutReturnValue();
+                    will(new WaitAction(2 * TIMEOUT));
+                }
+            });
+        getServiceOnClientSide1(TestService1.class).methodWithoutReturnValue();
+    }
+
+    @Test
+    public void testMultipleClientsWithSameService()
+    {
+        context.checking(new Expectations()
+            {
+                {
+                    one(serviceOnServerSideWrapper2.getService()).methodWithPrimitiveParameter(1);
+                    will(returnValue(1));
+
+                    one(serviceOnServerSideWrapper2.getService()).methodWithPrimitiveParameter(2);
+                    will(returnValue(2));
+
+                    one(serviceOnServerSideWrapper2.getService()).methodWithPrimitiveParameter(3);
+                    will(returnValue(3));
+
+                    one(serviceOnServerSideWrapper2.getService()).methodWithPrimitiveParameter(4);
+                    will(returnValue(4));
+                }
+            });
+        Assert.assertEquals(getServiceOnClientSide1(TestService2.class)
+                .methodWithPrimitiveParameter(1), 1);
+        Assert.assertEquals(getServiceOnClientSide2(TestService2.class)
+                .methodWithPrimitiveParameter(2), 2);
+        Assert.assertEquals(getServiceOnClientSide2(TestService2.class)
+                .methodWithPrimitiveParameter(3), 3);
+        Assert.assertEquals(getServiceOnClientSide1(TestService2.class)
+                .methodWithPrimitiveParameter(4), 4);
+    }
+
+    @Test
+    public void testMultipleClientsWithDifferentService()
+    {
+        context.checking(new Expectations()
+            {
+                {
+                    one(serviceOnServerSideWrapper1.getService()).methodWithPrimitiveReturnValue();
+                    will(returnValue(1));
+
+                    one(serviceOnServerSideWrapper2.getService()).methodWithPrimitiveParameter(2);
+                    will(returnValue(2));
+                }
+            });
+        Assert.assertEquals(getServiceOnClientSide1(TestService1.class)
+                .methodWithPrimitiveReturnValue(), 1);
+        Assert.assertEquals(getServiceOnClientSide2(TestService2.class)
+                .methodWithPrimitiveParameter(2), 2);
+    }
+
+    private BaseServiceConversationClientManager createClientManager()
+    {
+        return new BaseServiceConversationClientManager();
+    }
+
+    private BaseServiceConversationServerManager createServerManager()
+    {
+        return new BaseServiceConversationServerManager()
+            {
+                {
+                    addService(TestService1.class, serviceOnServerSideWrapper1);
+                    addService(TestService2.class, serviceOnServerSideWrapper2);
+                }
+
+                @Override
+                protected ServiceConversationClientDetails getClientDetailsForClientId(
+                        Object clientId)
+                {
+                    if (clientId.equals(CLIENT_ID_1))
+                    {
+                        return new ServiceConversationClientDetails(CLIENT_URL_1, TIMEOUT);
+                    } else if (clientId.equals(CLIENT_ID_2))
+                    {
+                        return new ServiceConversationClientDetails(CLIENT_URL_2, TIMEOUT);
+                    } else
+                    {
+                        throw new IllegalArgumentException("Unknown client");
+                    }
+                }
+            };
+    }
+
+    private ServiceExporter<IServiceConversationClientManagerRemote> createClientExporter(int port,
+            BaseServiceConversationClientManager manager)
+    {
+        try
+        {
+            ServiceExporter<IServiceConversationClientManagerRemote> exporter =
+                    new ServiceExporter<IServiceConversationClientManagerRemote>(port, CLIENT_PATH,
+                            IServiceConversationClientManagerRemote.class, manager);
+            return exporter;
+        } catch (Exception e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private ServiceExporter<IServiceConversationServerManagerRemote> createServerExporter(
+            BaseServiceConversationServerManager manager)
+    {
+        try
+        {
+            ServiceExporter<IServiceConversationServerManagerRemote> exporter =
+                    new ServiceExporter<IServiceConversationServerManagerRemote>(SERVER_PORT,
+                            SERVER_PATH, IServiceConversationServerManagerRemote.class, manager);
+            return exporter;
+        } catch (Exception e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private class ServiceExporter<S>
+    {
+
+        private Server server;
+
+        public ServiceExporter(final int port, final String path, final Class<?> serviceInterface,
+                final S service)
+        {
+            SelectChannelConnector connector = new SelectChannelConnector();
+            connector.setPort(port);
+
+            this.server = new Server();
+            this.server.addConnector(connector);
+
+            DispatcherServlet dispatcher = new DispatcherServlet()
+                {
+                    private static final long serialVersionUID = 1L;
+
+                    @Override
+                    protected WebApplicationContext findWebApplicationContext()
+                    {
+                        GenericWebApplicationContext ctx = new GenericWebApplicationContext();
+
+                        GenericBeanDefinition exporterBean = new GenericBeanDefinition();
+                        exporterBean.setBeanClass(HttpInvokerServiceExporter.class);
+                        MutablePropertyValues exporterProperties = new MutablePropertyValues();
+                        exporterProperties.addPropertyValue("service", service);
+                        exporterProperties.addPropertyValue("serviceInterface", serviceInterface);
+                        exporterBean.setPropertyValues(exporterProperties);
+                        ctx.registerBeanDefinition("serviceExporter", exporterBean);
+
+                        GenericBeanDefinition mappingBean = new GenericBeanDefinition();
+                        mappingBean.setBeanClass(SimpleUrlHandlerMapping.class);
+                        Map<String, String> urlMap = new HashMap<String, String>();
+                        urlMap.put(path, "serviceExporter");
+                        MutablePropertyValues mappingProperties = new MutablePropertyValues();
+                        mappingProperties.addPropertyValue("urlMap", urlMap);
+                        mappingBean.setPropertyValues(mappingProperties);
+                        ctx.registerBeanDefinition("mapping", mappingBean);
+
+                        ctx.refresh();
+                        return ctx;
+                    }
+                };
+
+            ServletContextHandler servletCtx =
+                    new ServletContextHandler(server, "/", ServletContextHandler.SESSIONS);
+            servletCtx.addServlet(new ServletHolder(dispatcher), "/*");
+        }
+
+        public Server getServer()
+        {
+            return server;
+        }
+
+    }
+
+    public static interface TestService1
+    {
+
+        public void nonConversationalMethod();
+
+        @Conversational(progress = Progress.MANUAL)
+        public void methodWithoutReturnValue();
+
+        @Conversational(progress = Progress.MANUAL)
+        public int methodWithPrimitiveReturnValue();
+
+        @Conversational(progress = Progress.MANUAL)
+        public Serializable methodWithSerializableReturnValue();
+
+    }
+
+    public static interface TestService2
+    {
+        @Conversational(progress = Progress.MANUAL)
+        public int methodWithPrimitiveParameter(int parameter);
+
+        @Conversational(progress = Progress.MANUAL)
+        public Serializable methodWithSerializableParameter(Serializable parameter);
+
+    }
+
+    public static interface TestServiceLocal
+    {
+        public void someLocalMethod();
+    }
+
+    public static class TestServiceWrapper1 implements TestServiceLocal, TestService1
+    {
+
+        private TestService1 service;
+
+        @Override
+        public void nonConversationalMethod()
+        {
+            service.nonConversationalMethod();
+        }
+
+        @Override
+        public void methodWithoutReturnValue()
+        {
+            service.methodWithoutReturnValue();
+        }
+
+        @Override
+        public int methodWithPrimitiveReturnValue()
+        {
+            return service.methodWithPrimitiveReturnValue();
+        }
+
+        @Override
+        public Serializable methodWithSerializableReturnValue()
+        {
+            return service.methodWithSerializableReturnValue();
+        }
+
+        @Override
+        public void someLocalMethod()
+        {
+        }
+
+        public TestService1 getService()
+        {
+            return service;
+        }
+
+        public void setService(TestService1 service)
+        {
+            this.service = service;
+        }
+
+    }
+
+    public static class TestServiceWrapper2 implements TestServiceLocal, TestService2
+    {
+
+        private TestService2 service;
+
+        @Override
+        public int methodWithPrimitiveParameter(int parameter)
+        {
+            return service.methodWithPrimitiveParameter(parameter);
+        }
+
+        @Override
+        public Serializable methodWithSerializableParameter(Serializable parameter)
+        {
+            return service.methodWithSerializableParameter(parameter);
+        }
+
+        @Override
+        public void someLocalMethod()
+        {
+        }
+
+        public TestService2 getService()
+        {
+            return service;
+        }
+
+        public void setService(TestService2 service)
+        {
+            this.service = service;
+        }
+
+    }
+
+}
diff --git a/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/datastoreserver/systemtests/SystemTestCase.java b/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/datastoreserver/systemtests/SystemTestCase.java
index 6721f7c77cc..843f16877aa 100644
--- a/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/datastoreserver/systemtests/SystemTestCase.java
+++ b/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/datastoreserver/systemtests/SystemTestCase.java
@@ -147,25 +147,6 @@ public abstract class SystemTestCase extends AssertJUnit
                     applicationContext = new GenericWebApplicationContext(f);
                     applicationContext.setParent(new ClassPathXmlApplicationContext(
                             getApplicationContextLocation()));
-
-                    /* Needed for RmiConversationTest */
-                    /*
-                     * GenericBeanDefinition definition = new GenericBeanDefinition();
-                     * MutablePropertyValues values = new MutablePropertyValues();
-                     * values.addPropertyValue("sessionFactory", new RuntimeBeanReference(
-                     * "hibernate-session-factory")); values.addPropertyValue("echoService", new
-                     * RuntimeBeanReference( "echoService")); definition.setPropertyValues(values);
-                     * definition.setBeanClass(EchoServiceBean.class);
-                     * applicationContext.registerBeanDefinition("echoService", definition);
-                     */
-
-                    /* Needed for RmiConversationTest */
-                    /*
-                     * GenericBeanDefinition exporter = new GenericBeanDefinition();
-                     * exporter.setBeanClass(EchoServiceExporter.class);
-                     * applicationContext.registerBeanDefinition("echoServiceExporter", exporter);
-                     */
-
                     applicationContext.refresh();
                     return applicationContext;
                 }
diff --git a/openbis-common/source/java/ch/systemsx/cisd/common/conversation/manager/BaseServiceConversationServerManager.java b/openbis-common/source/java/ch/systemsx/cisd/common/conversation/manager/BaseServiceConversationServerManager.java
index 2e340d8923e..06895711641 100644
--- a/openbis-common/source/java/ch/systemsx/cisd/common/conversation/manager/BaseServiceConversationServerManager.java
+++ b/openbis-common/source/java/ch/systemsx/cisd/common/conversation/manager/BaseServiceConversationServerManager.java
@@ -19,8 +19,6 @@ package ch.systemsx.cisd.common.conversation.manager;
 import java.util.HashMap;
 import java.util.Map;
 
-import org.springframework.beans.factory.InitializingBean;
-
 import ch.systemsx.cisd.common.conversation.client.ServiceConversationClientDetails;
 import ch.systemsx.cisd.common.serviceconversation.ServiceConversationDTO;
 import ch.systemsx.cisd.common.serviceconversation.ServiceMessage;
@@ -32,7 +30,7 @@ import ch.systemsx.cisd.common.spring.HttpInvokerUtils;
  * @author pkupczyk
  */
 public abstract class BaseServiceConversationServerManager implements
-        IServiceConversationServerManagerRemote, InitializingBean
+        IServiceConversationServerManagerRemote
 {
 
     private ServiceConversationServer server;
@@ -48,18 +46,10 @@ public abstract class BaseServiceConversationServerManager implements
         server = new ServiceConversationServer();
     }
 
-    @Override
-    public void afterPropertiesSet() throws Exception
-    {
-        initializeServices();
-    }
-
-    protected abstract void initializeServices();
-
-    protected void addService(String serviceName, Object service)
+    protected void addService(Class<?> serviceInterface, Object service)
     {
         ServiceConversationServiceFactory serviceFactory =
-                new ServiceConversationServiceFactory(server, serviceName, service)
+                new ServiceConversationServiceFactory(server, serviceInterface.getName(), service)
                     {
                         @Override
                         protected int getProgressInterval(String conversationId)
diff --git a/openbis-common/source/java/ch/systemsx/cisd/common/conversation/message/ServiceConversationMethodInvocation.java b/openbis-common/source/java/ch/systemsx/cisd/common/conversation/message/ServiceConversationMethodInvocation.java
index ff31ff734c4..47a293bb4d9 100644
--- a/openbis-common/source/java/ch/systemsx/cisd/common/conversation/message/ServiceConversationMethodInvocation.java
+++ b/openbis-common/source/java/ch/systemsx/cisd/common/conversation/message/ServiceConversationMethodInvocation.java
@@ -19,6 +19,7 @@ package ch.systemsx.cisd.common.conversation.message;
 import java.io.Serializable;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
+import java.util.Arrays;
 
 import org.springframework.remoting.support.RemoteInvocation;
 
@@ -100,19 +101,30 @@ public class ServiceConversationMethodInvocation implements Serializable
 
     private Method findMethodOn(Object o) throws SecurityException, NoSuchMethodException
     {
-        Method method = null;
+        Method methodFound = null;
 
         for (Class<?> inter : o.getClass().getInterfaces())
         {
-            method = inter.getMethod(invocation.getMethodName(), invocation.getParameterTypes());
+            Method[] methods = inter.getMethods();
 
-            if (method != null && method.isAnnotationPresent(Conversational.class))
+            for (Method method : methods)
             {
-                return method;
+                if (method.getName().equals(invocation.getMethodName())
+                        && Arrays
+                                .equals(method.getParameterTypes(), invocation.getParameterTypes()))
+                {
+                    methodFound = method;
+                    break;
+                }
+            }
+
+            if (methodFound != null && methodFound.isAnnotationPresent(Conversational.class))
+            {
+                return methodFound;
             }
         }
 
-        if (method == null)
+        if (methodFound == null)
         {
             throw new NoSuchMethodException(
                     "No method found for the service conversation invocation: " + invocation);
diff --git a/openbis-common/sourceTest/java/ch/systemsx/cisd/common/spring/WaitAction.java b/openbis-common/sourceTest/java/ch/systemsx/cisd/common/spring/WaitAction.java
index 8ec19e11fe8..4e273b301e2 100644
--- a/openbis-common/sourceTest/java/ch/systemsx/cisd/common/spring/WaitAction.java
+++ b/openbis-common/sourceTest/java/ch/systemsx/cisd/common/spring/WaitAction.java
@@ -5,19 +5,17 @@ import org.jmock.api.Action;
 import org.jmock.api.Invocation;
 
 /**
- * 
- *
  * @author Franz-Josef Elmer
  */
-final class WaitAction implements Action
+public final class WaitAction implements Action
 {
     private final long waitingTime;
 
-    WaitAction(long waitingTime)
+    public WaitAction(long waitingTime)
     {
         this.waitingTime = waitingTime;
     }
-    
+
     @Override
     public Object invoke(Invocation inv) throws Throwable
     {
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/ServiceConversationServerManager.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/ServiceConversationServerManager.java
index 42784d6fb38..a3e51731a48 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/ServiceConversationServerManager.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/ServiceConversationServerManager.java
@@ -19,6 +19,8 @@ package ch.systemsx.cisd.openbis.generic.server;
 import java.util.HashMap;
 import java.util.Map;
 
+import org.springframework.beans.factory.InitializingBean;
+
 import ch.systemsx.cisd.common.conversation.client.ServiceConversationClientDetails;
 import ch.systemsx.cisd.common.conversation.manager.BaseServiceConversationServerManager;
 import ch.systemsx.cisd.openbis.generic.server.business.IServiceConversationServerManagerLocal;
@@ -31,7 +33,7 @@ import ch.systemsx.cisd.openbis.generic.shared.conversation.ServiceConversationD
  */
 
 public class ServiceConversationServerManager extends BaseServiceConversationServerManager
-        implements IServiceConversationServerManagerLocal
+        implements IServiceConversationServerManagerLocal, InitializingBean
 {
 
     private Map<Object, ServiceConversationClientDetails> dataStoreIdToDataStoreDetailsMap =
@@ -40,9 +42,9 @@ public class ServiceConversationServerManager extends BaseServiceConversationSer
     private IETLLIMSService etlService;
 
     @Override
-    protected void initializeServices()
+    public void afterPropertiesSet() throws Exception
     {
-        addService(IETLLIMSService.class.getName(), etlService);
+        addService(IETLLIMSService.class, etlService);
     }
 
     @Override
-- 
GitLab