Add methods to Lock Anchor entity 82/128282/29
authoremaclee <lee.anjella.macabuhay@est.tech>
Mon, 4 Apr 2022 14:38:26 +0000 (15:38 +0100)
committerLee Anjella Macabuhay <lee.anjella.macabuhay@est.tech>
Wed, 27 Apr 2022 15:56:11 +0000 (15:56 +0000)
Added separate integration and mock-based unit tests

Issue-ID: CPS-898
Signed-off-by: emaclee <lee.anjella.macabuhay@est.tech>
Change-Id: I14d1d1c41759ce028e2417fdd2df001280e19ab2

12 files changed:
cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java
cps-ri/src/main/java/org/onap/cps/spi/utils/SessionManager.java
cps-ri/src/main/java/org/onap/cps/spi/utils/TimeLimiterProvider.java [new file with mode: 0644]
cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy
cps-ri/src/test/groovy/org/onap/cps/spi/utils/SessionManagerIntegrationSpec.groovy
cps-ri/src/test/groovy/org/onap/cps/spi/utils/SessionManagerSpec.groovy [new file with mode: 0644]
cps-service/src/main/java/org/onap/cps/api/CpsDataService.java
cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java
cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java
cps-service/src/main/java/org/onap/cps/spi/exceptions/SessionManagerException.java [new file with mode: 0644]
cps-service/src/main/java/org/onap/cps/spi/exceptions/SessionTimeoutException.java [new file with mode: 0644]
cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy

index 847a1d1..daf4dd7 100644 (file)
@@ -220,6 +220,12 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
         sessionManager.closeSession(sessionId);
     }
 
+    @Override
+    public void lockAnchor(final String sessionId, final String dataspaceName,
+                            final String anchorName, final Long timeoutInMilliseconds) {
+        sessionManager.lockAnchor(sessionId, dataspaceName, anchorName, timeoutInMilliseconds);
+    }
+
     private static Set<String> processAncestorXpath(final List<FragmentEntity> fragmentEntities,
         final CpsPathQuery cpsPathQuery) {
         final Set<String> ancestorXpath = new HashSet<>();
index eb535ec..e278688 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2021-2022 Nordix Foundation
+ *  Copyright (C) 2022 Nordix Foundation
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
 
 package org.onap.cps.spi.utils;
 
-import java.util.HashMap;
-import java.util.Map;
+import com.google.common.util.concurrent.TimeLimiter;
+import com.google.common.util.concurrent.UncheckedExecutionException;
 import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import lombok.RequiredArgsConstructor;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
 import org.hibernate.HibernateException;
+import org.hibernate.LockMode;
 import org.hibernate.Session;
-import org.hibernate.SessionException;
 import org.hibernate.SessionFactory;
 import org.hibernate.cfg.Configuration;
 import org.onap.cps.spi.entities.AnchorEntity;
 import org.onap.cps.spi.entities.DataspaceEntity;
 import org.onap.cps.spi.entities.SchemaSetEntity;
 import org.onap.cps.spi.entities.YangResourceEntity;
+import org.onap.cps.spi.exceptions.SessionManagerException;
+import org.onap.cps.spi.exceptions.SessionTimeoutException;
+import org.onap.cps.spi.repository.AnchorRepository;
+import org.onap.cps.spi.repository.DataspaceRepository;
 import org.springframework.stereotype.Component;
 
+@RequiredArgsConstructor
+@Slf4j
 @Component
 public class SessionManager {
 
+    private final TimeLimiterProvider timeLimiterProvider;
+    private final DataspaceRepository dataspaceRepository;
+    private final AnchorRepository anchorRepository;
     private static SessionFactory sessionFactory;
-    private static Map<String, Session> sessionMap = new HashMap<>();
+    private static ConcurrentHashMap<String, Session> sessionMap = new ConcurrentHashMap<>();
 
     private synchronized void buildSessionFactory() {
         if (sessionFactory == null) {
@@ -67,20 +85,81 @@ public class SessionManager {
 
     /**
      * Close session.
+     * Locks will be released and changes will be committed.
      *
      * @param sessionId session ID
      */
     public void closeSession(final String sessionId) {
         try {
-            final Session currentSession = sessionMap.get(sessionId);
-            currentSession.getTransaction().commit();
-            currentSession.close();
-        } catch (final NullPointerException e) {
-            throw new SessionException(String.format("Session with session ID %s does not exist", sessionId));
+            final Session session = getSession(sessionId);
+            session.getTransaction().commit();
+            session.close();
         } catch (final HibernateException e) {
-            throw new SessionException(String.format("Unable to close session with session ID %s", sessionId));
+            throw new SessionManagerException("Cannot close session",
+                String.format("Unable to close session with session ID '%s'", sessionId), e);
+        } finally {
+            sessionMap.remove(sessionId);
         }
-        sessionMap.remove(sessionId);
     }
 
-}
\ No newline at end of file
+    /**
+     * Lock Anchor.
+     * To release locks(s), the session holding the lock(s) must be closed.
+     *
+     * @param sessionId session ID
+     * @param dataspaceName dataspace name
+     * @param anchorName anchor name
+     * @param timeoutInMilliseconds lock attempt timeout in milliseconds
+     */
+    @SneakyThrows
+    public void lockAnchor(final String sessionId, final String dataspaceName,
+                           final String anchorName, final Long timeoutInMilliseconds) {
+        final ExecutorService executorService = Executors.newSingleThreadExecutor();
+        final TimeLimiter timeLimiter = timeLimiterProvider.getTimeLimiter(executorService);
+
+        try {
+            timeLimiter.callWithTimeout(() -> {
+                applyPessimisticWriteLockOnAnchor(sessionId, dataspaceName, anchorName);
+                return null;
+            }, timeoutInMilliseconds, TimeUnit.MILLISECONDS);
+        } catch (final TimeoutException e) {
+            throw new SessionTimeoutException(
+                    "Timeout: Anchor locking failed",
+                    "The error could be caused by another session holding a lock on the specified table. "
+                            + "Retrying the sending the request could be required.", e);
+        } catch (final InterruptedException e) {
+            Thread.currentThread().interrupt();
+            throw new SessionManagerException("Operation interrupted", "This thread was interrupted.", e);
+        } catch (final ExecutionException | UncheckedExecutionException e) {
+            if (e.getCause() != null) {
+                throw e.getCause();
+            }
+            throw new SessionManagerException(
+                    "Operation Aborted",
+                    "The transaction request was aborted. "
+                            + "Retrying and checking all details are correct could be required", e);
+        } finally {
+            executorService.shutdownNow();
+        }
+    }
+
+    private void applyPessimisticWriteLockOnAnchor(final String sessionId, final String dataspaceName,
+                                                   final String anchorName) {
+        final Session session = getSession(sessionId);
+        final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
+        final AnchorEntity anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName);
+        final int anchorId = anchorEntity.getId();
+        log.debug("Attempting to lock anchor {} for session {}", anchorName, sessionId);
+        session.get(AnchorEntity.class, anchorId, LockMode.PESSIMISTIC_WRITE);
+        log.info("Anchor {} successfully locked", anchorName);
+    }
+
+    private Session getSession(final String sessionId) {
+        final Session session = sessionMap.get(sessionId);
+        if (session == null) {
+            throw new SessionManagerException("Session not found",
+                String.format("Session with ID %s does not exist", sessionId));
+        }
+        return session;
+    }
+}
diff --git a/cps-ri/src/main/java/org/onap/cps/spi/utils/TimeLimiterProvider.java b/cps-ri/src/main/java/org/onap/cps/spi/utils/TimeLimiterProvider.java
new file mode 100644 (file)
index 0000000..2bd7ac3
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2022 Nordix Foundation
+ *  ================================================================================
+ *  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.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.spi.utils;
+
+import com.google.common.util.concurrent.SimpleTimeLimiter;
+import com.google.common.util.concurrent.TimeLimiter;
+import java.util.concurrent.ExecutorService;
+import org.springframework.stereotype.Component;
+
+@Component
+public class TimeLimiterProvider {
+    public TimeLimiter getTimeLimiter(final ExecutorService executorService) {
+        return SimpleTimeLimiter.create(executorService);
+    }
+}
index 52f2309..b37f471 100644 (file)
@@ -129,4 +129,11 @@ class CpsDataPersistenceServiceSpec extends Specification {
         then: 'the session manager method to close session is invoked with parameter'
             1 * mockSessionManager.closeSession(someSessionId)
     }
+
+    def 'Lock anchor.'(){
+        when: 'lock anchor method is called with anchor entity details'
+            objectUnderTest.lockAnchor('mySessionId', 'myDataspaceName', 'myAnchorName', 123L)
+        then: 'the session manager method to lock anchor is invoked with same parameters'
+            1 * mockSessionManager.lockAnchor('mySessionId', 'myDataspaceName', 'myAnchorName', 123L)
+    }
 }
\ No newline at end of file
index c46092f..9b58c8b 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2021-2022 Nordix Foundation
+ *  Copyright (C) 2022 Nordix Foundation
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
 
 package org.onap.cps.spi.utils
 
-import org.hibernate.SessionException
+import org.onap.cps.spi.exceptions.SessionManagerException
 import org.onap.cps.spi.impl.CpsPersistenceSpecBase
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.test.context.jdbc.Sql
 
 class SessionManagerIntegrationSpec extends CpsPersistenceSpecBase{
 
-    def objectUnderTest = new SessionManager();
+    final static String SET_DATA = '/data/anchor.sql'
 
-    def 'start session'() {
-        when: 'start session'
-            def result = objectUnderTest.startSession()
-        then: 'session ID is returned'
-            assert result instanceof String
-            objectUnderTest.closeSession(result)
+    @Autowired
+    SessionManager objectUnderTest
+
+    def sessionId
+    def shortTimeoutForTesting = 200L
+
+    def setup(){
+        sessionId = objectUnderTest.startSession()
     }
 
-    def 'close session'(){
-        given: 'session Id from calling the start session method'
-            def sessionId = objectUnderTest.startSession()
-        when: 'close session method is called'
-            objectUnderTest.closeSession(sessionId)
+    def cleanup(){
+        objectUnderTest.closeSession(sessionId)
+    }
+
+    @Sql([CLEAR_DATA, SET_DATA])
+    def 'Lock anchor.'(){
+        when: 'session tries to acquire anchor lock by passing anchor entity details'
+            objectUnderTest.lockAnchor(sessionId, DATASPACE_NAME, ANCHOR_NAME1, shortTimeoutForTesting)
         then: 'no exception is thrown'
             noExceptionThrown()
     }
 
-    def 'close session that does not exist' (){
-        given: 'session Id that does not exist'
-            def unknownSessionId = 'unknown session id'
-        when: 'close session method is called'
-            objectUnderTest.closeSession(unknownSessionId)
-        then: 'a session exception is thrown'
-            def thrown = thrown(SessionException)
-            assert thrown.message.contains(unknownSessionId)
+    @Sql([CLEAR_DATA, SET_DATA])
+    def 'Attempt to lock anchor when another session is holding the lock.'(){
+        given: 'another session that holds an anchor lock'
+            def otherSessionId = objectUnderTest.startSession()
+            objectUnderTest.lockAnchor(otherSessionId,DATASPACE_NAME,ANCHOR_NAME1,shortTimeoutForTesting)
+        when: 'a session tries to acquire the same anchor lock'
+            objectUnderTest.lockAnchor(sessionId,DATASPACE_NAME,ANCHOR_NAME1,shortTimeoutForTesting)
+        then: 'a session manager exception is thrown specifying operation reached timeout'
+            def thrown = thrown(SessionManagerException)
+            thrown.message.contains('Timeout')
+        then: 'when the other session holding the lock is closed, lock can finally be acquired'
+            objectUnderTest.closeSession(otherSessionId)
+            objectUnderTest.lockAnchor(sessionId,DATASPACE_NAME,ANCHOR_NAME1,shortTimeoutForTesting)
     }
+
 }
diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/utils/SessionManagerSpec.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/utils/SessionManagerSpec.groovy
new file mode 100644 (file)
index 0000000..a2df06e
--- /dev/null
@@ -0,0 +1,99 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2022 Nordix Foundation
+ *  ================================================================================
+ *  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.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.spi.utils
+
+import com.google.common.util.concurrent.TimeLimiter
+import org.hibernate.HibernateException
+import org.hibernate.Transaction
+import org.onap.cps.spi.entities.AnchorEntity
+import org.onap.cps.spi.exceptions.SessionManagerException
+import org.onap.cps.spi.repository.AnchorRepository
+import org.onap.cps.spi.repository.DataspaceRepository
+import org.testcontainers.shaded.com.google.common.util.concurrent.UncheckedExecutionException
+import spock.lang.Specification
+import org.hibernate.Session
+
+import java.util.concurrent.ExecutionException
+
+class SessionManagerSpec extends Specification {
+
+    def spiedTimeLimiterProvider = Spy(TimeLimiterProvider)
+    def mockDataspaceRepository = Mock(DataspaceRepository)
+    def mockAnchorRepository = Mock(AnchorRepository)
+    def mockSession = Mock(Session)
+
+    def objectUnderTest = new SessionManager(spiedTimeLimiterProvider, mockDataspaceRepository, mockAnchorRepository)
+
+    def 'Lock anchor entity with #exceptionDuringTest exception.'(){
+        given: 'a dummy session'
+            objectUnderTest.sessionMap.put('dummySession', mockSession)
+        and: 'the anchor name can be resolved'
+            def mockAnchorEntity = Mock(AnchorEntity)
+            mockAnchorEntity.getId() > 456
+            mockAnchorRepository.getByDataspaceAndName(_, _) >> mockAnchorEntity
+        and: 'timeLimiter throws an #exceptionDuringTest exception'
+            def mockTimeLimiter = Mock(TimeLimiter)
+            spiedTimeLimiterProvider.getTimeLimiter(_) >> mockTimeLimiter
+            mockTimeLimiter.callWithTimeout(*_) >> { throw exceptionDuringTest }
+        when: 'session tries to acquire anchor lock'
+            objectUnderTest.lockAnchor('dummySession', 'some-dataspace','some-anchor', 123L)
+        then: 'a session manager exception is thrown with the expected detail'
+            def thrown = thrown(SessionManagerException)
+            thrown.details.contains(expectedExceptionDetail)
+        where:
+            exceptionDuringTest               || expectedExceptionDetail
+            new InterruptedException()        || 'interrupted'
+            new ExecutionException()          || 'aborted'
+    }
+
+    def 'Close session that does not exist.'() {
+        when: 'attempt to close session that does not exist'
+            objectUnderTest.closeSession('unknown session id')
+        then: 'a session manager exception is thrown with the unknown id in the details'
+            def thrown = thrown(SessionManagerException)
+            assert thrown.details.contains('unknown session id')
+    }
+
+    def 'Hibernate exception while closing session.'() {
+        given: 'a test session with a transaction'
+            objectUnderTest.sessionMap.put('testSessionId', mockSession)
+            mockSession.getTransaction() >> Mock(Transaction)
+        and: 'an hibernate exception when closing that session'
+            def hibernateException = new HibernateException('test')
+            mockSession.close() >> { throw hibernateException }
+        when: 'attempt to close session'
+            objectUnderTest.closeSession('testSessionId')
+        then: 'a session manager exception is thrown with the session id in the details'
+            def thrown = thrown(SessionManagerException)
+            assert thrown.details.contains('testSessionId')
+        and: 'the original exception as cause'
+            assert thrown.cause == hibernateException
+    }
+
+    def 'Attempt to lock anchor entity with session Id that does not exists'(){
+        when: 'attempt to acquire anchor lock with session that does not exists'
+            objectUnderTest.lockAnchor('unknown session id','','',123L)
+        then: 'a session manager exception is thrown with the unknown id in the details'
+            def thrown = thrown(SessionManagerException)
+            thrown.details.contains('unknown session id')
+    }
+
+}
index 35caf95..93c96ec 100644 (file)
@@ -189,4 +189,26 @@ public interface CpsDataService {
      *
      */
     void closeSession(String sessionId);
+
+    /**
+     * Lock anchor with default timeout.
+     * To release locks(s), the session holding the lock(s) must be closed.
+     *
+     * @param sessionID session ID
+     * @param dataspaceName dataspace name
+     * @param anchorName anchor name
+     */
+    void lockAnchor(String sessionID, String dataspaceName, String anchorName);
+
+    /**
+     * Lock anchor with custom timeout.
+     * To release locks(s), the session holding the lock(s) must be closed.
+     *
+     * @param sessionID session ID
+     * @param dataspaceName dataspace name
+     * @param anchorName anchor name
+     * @param timeoutInMilliseconds lock attempt timeout in milliseconds
+     */
+    void lockAnchor(String sessionID, String dataspaceName, String anchorName, Long timeoutInMilliseconds);
+
 }
index b95ad05..2f1067a 100755 (executable)
@@ -48,6 +48,7 @@ import org.springframework.stereotype.Service;
 public class CpsDataServiceImpl implements CpsDataService {
 
     private static final String ROOT_NODE_XPATH = "/";
+    private static final long DEFAULT_LOCK_TIMEOUT_IN_MILLISECONDS = 300L;
 
     private final CpsDataPersistenceService cpsDataPersistenceService;
     private final CpsAdminService cpsAdminService;
@@ -125,6 +126,17 @@ public class CpsDataServiceImpl implements CpsDataService {
         cpsDataPersistenceService.closeSession(sessionId);
     }
 
+    @Override
+    public void lockAnchor(final String sessionID, final String dataspaceName, final String anchorName) {
+        lockAnchor(sessionID, dataspaceName, anchorName, DEFAULT_LOCK_TIMEOUT_IN_MILLISECONDS);
+    }
+
+    @Override
+    public void lockAnchor(final String sessionID, final String dataspaceName,
+                           final String anchorName, final Long timeoutInMilliseconds) {
+        cpsDataPersistenceService.lockAnchor(sessionID, dataspaceName, anchorName, timeoutInMilliseconds);
+    }
+
     @Override
     public void replaceNodeTree(final String dataspaceName, final String anchorName, final String parentNodeXpath,
         final String jsonData, final OffsetDateTime observedTimestamp) {
index fdcf15b..43cfffe 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * ============LICENSE_START=======================================================
- *  Copyright (C) 2020 Nordix Foundation.
+ *  Copyright (C) 2020-2022 Nordix Foundation.
  *  Modifications Copyright (C) 2021 Pantheon.tech
  *  Modifications Copyright (C) 2022 Bell Canada
  * ================================================================================
@@ -161,4 +161,16 @@ public interface CpsDataPersistenceService {
      * @param sessionId session ID
      */
     void closeSession(String sessionId);
+
+    /**
+     * Lock anchor.
+     * To release locks(s), the session holding the lock(s) must be closed.
+     *
+     * @param sessionID session ID
+     * @param dataspaceName dataspace name
+     * @param anchorName anchor name
+     * @param timeoutInMilliseconds lock attempt timeout in milliseconds
+     */
+    void lockAnchor(String sessionID, String dataspaceName, String anchorName, Long timeoutInMilliseconds);
+
 }
diff --git a/cps-service/src/main/java/org/onap/cps/spi/exceptions/SessionManagerException.java b/cps-service/src/main/java/org/onap/cps/spi/exceptions/SessionManagerException.java
new file mode 100644 (file)
index 0000000..4000bfc
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2022 Nordix Foundation
+ *  ================================================================================
+ *  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.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.spi.exceptions;
+
+
+public class SessionManagerException extends CpsException {
+
+    private static final long serialVersionUID = 7957090904519019500L;
+
+    /**
+     * Constructor.
+     *
+     * @param message the error message
+     * @param details the error details
+     * @param cause   the cause of the exception
+     */
+    public SessionManagerException(final String message, final String details, final Throwable cause) {
+        super(message, details, cause);
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param message the error message
+     * @param details the error details
+     */
+    public SessionManagerException(final String message, final String details) {
+        super(message, details);
+    }
+}
diff --git a/cps-service/src/main/java/org/onap/cps/spi/exceptions/SessionTimeoutException.java b/cps-service/src/main/java/org/onap/cps/spi/exceptions/SessionTimeoutException.java
new file mode 100644 (file)
index 0000000..92b4aa7
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2022 Nordix Foundation
+ *  ================================================================================
+ *  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.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.spi.exceptions;
+
+@SuppressWarnings("squid:S110")  // Team agreed to accept 6 levels of inheritance for CPS Exceptions
+public class SessionTimeoutException extends SessionManagerException {
+
+    private static final long serialVersionUID = -8809577494038691360L;
+
+    public SessionTimeoutException(final String message, final String details, final Throwable cause) {
+        super(message, details, cause);
+    }
+}
index faeba8d..8b9d545 100644 (file)
@@ -430,4 +430,21 @@ class CpsDataServiceImplSpec extends Specification {
         then: 'the persistence service method to close session is invoked'
             1 * mockCpsDataPersistenceService.closeSession(sessionId)
     }
+
+    def 'lock anchor with no timeout parameter'(){
+        when: 'lock anchor method with no timeout parameter with details of anchor entity to lock'
+            objectUnderTest.lockAnchor('some-sessionId', 'some-dataspaceName', 'some-anchorName')
+        then: 'the persistence service method to lock anchor is invoked with default timeout'
+            1 * mockCpsDataPersistenceService.lockAnchor('some-sessionId', 'some-dataspaceName',
+                    'some-anchorName', 300L)
+    }
+
+    def 'lock anchor with timeout parameter'(){
+        when: 'lock anchor method with timeout parameter is called with details of anchor entity to lock'
+            objectUnderTest.lockAnchor('some-sessionId', 'some-dataspaceName',
+                    'some-anchorName', 250L)
+        then: 'the persistence service method to lock anchor is invoked with the given timeout'
+            1 * mockCpsDataPersistenceService.lockAnchor('some-sessionId', 'some-dataspaceName',
+                    'some-anchorName', 250L)
+    }
 }