2 * ============LICENSE_START=======================================================
3 * Copyright (C) 2022 Nordix Foundation
4 * ================================================================================
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
17 * SPDX-License-Identifier: Apache-2.0
18 * ============LICENSE_END=========================================================
21 package org.onap.cps.spi.utils;
23 import com.google.common.util.concurrent.TimeLimiter;
24 import com.google.common.util.concurrent.UncheckedExecutionException;
25 import jakarta.annotation.PostConstruct;
26 import java.util.UUID;
27 import java.util.concurrent.ConcurrentHashMap;
28 import java.util.concurrent.ExecutionException;
29 import java.util.concurrent.ExecutorService;
30 import java.util.concurrent.Executors;
31 import java.util.concurrent.TimeUnit;
32 import java.util.concurrent.TimeoutException;
33 import lombok.RequiredArgsConstructor;
34 import lombok.SneakyThrows;
35 import lombok.extern.slf4j.Slf4j;
36 import org.hibernate.HibernateException;
37 import org.hibernate.LockMode;
38 import org.hibernate.Session;
39 import org.onap.cps.spi.config.CpsSessionFactory;
40 import org.onap.cps.spi.entities.AnchorEntity;
41 import org.onap.cps.spi.entities.DataspaceEntity;
42 import org.onap.cps.spi.exceptions.SessionManagerException;
43 import org.onap.cps.spi.exceptions.SessionTimeoutException;
44 import org.onap.cps.spi.repository.AnchorRepository;
45 import org.onap.cps.spi.repository.DataspaceRepository;
46 import org.springframework.beans.factory.config.ConfigurableBeanFactory;
47 import org.springframework.context.annotation.Scope;
48 import org.springframework.stereotype.Component;
50 @RequiredArgsConstructor
53 @Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
54 public class SessionManager {
56 private final CpsSessionFactory cpsSessionFactory;
57 private final TimeLimiterProvider timeLimiterProvider;
58 private final DataspaceRepository dataspaceRepository;
59 private final AnchorRepository anchorRepository;
60 private final ConcurrentHashMap<String, Session> sessionMap = new ConcurrentHashMap<>();
61 public static final boolean WITH_COMMIT = true;
62 public static final boolean WITH_ROLLBACK = false;
65 private void postConstruct() {
66 final Thread shutdownHook = new Thread(this::closeAllSessionsInShutdown);
67 Runtime.getRuntime().addShutdownHook(shutdownHook);
70 private void closeAllSessionsInShutdown() {
71 for (final String sessionId : sessionMap.keySet()) {
73 closeSession(sessionId, WITH_ROLLBACK);
74 log.info("Session with session ID {} rolled back and closed", sessionId);
75 } catch (final Exception e) {
76 log.warn("Session with session ID {} failed to close", sessionId);
79 cpsSessionFactory.closeSessionFactory();
83 * Starts a session which allows use of locks and batch interaction with the persistence service.
85 * @return Session ID string
87 public String startSession() {
88 final Session session = cpsSessionFactory.openSession();
89 final String sessionId = UUID.randomUUID().toString();
90 sessionMap.put(sessionId, session);
91 session.beginTransaction();
97 * Changes are committed when commit boolean is set to true.
98 * Rollback will execute when commit boolean is set to false.
100 * @param sessionId session ID
101 * @param commit indicator whether session will commit or rollback
103 public void closeSession(final String sessionId, final boolean commit) {
105 final Session session = getSession(sessionId);
107 session.getTransaction().commit();
109 session.getTransaction().rollback();
112 } catch (final HibernateException e) {
113 throw new SessionManagerException("Cannot close session",
114 String.format("Unable to close session with session ID '%s'", sessionId), e);
116 sessionMap.remove(sessionId);
122 * To release locks(s), the session holding the lock(s) must be closed.
124 * @param sessionId session ID
125 * @param dataspaceName dataspace name
126 * @param anchorName anchor name
127 * @param timeoutInMilliseconds lock attempt timeout in milliseconds
130 public void lockAnchor(final String sessionId, final String dataspaceName,
131 final String anchorName, final Long timeoutInMilliseconds) {
132 final ExecutorService executorService = Executors.newSingleThreadExecutor();
133 final TimeLimiter timeLimiter = timeLimiterProvider.getTimeLimiter(executorService);
136 timeLimiter.callWithTimeout(() -> {
137 applyPessimisticWriteLockOnAnchor(sessionId, dataspaceName, anchorName);
139 }, timeoutInMilliseconds, TimeUnit.MILLISECONDS);
140 } catch (final TimeoutException e) {
141 throw new SessionTimeoutException(
142 "Timeout: Anchor locking failed",
143 "The error could be caused by another session holding a lock on the specified table. "
144 + "Retrying the sending the request could be required.", e);
145 } catch (final InterruptedException e) {
146 Thread.currentThread().interrupt();
147 throw new SessionManagerException("Operation interrupted", "This thread was interrupted.", e);
148 } catch (final ExecutionException | UncheckedExecutionException e) {
149 if (e.getCause() != null) {
152 throw new SessionManagerException(
154 "The transaction request was aborted. "
155 + "Retrying and checking all details are correct could be required", e);
157 executorService.shutdownNow();
161 private void applyPessimisticWriteLockOnAnchor(final String sessionId, final String dataspaceName,
162 final String anchorName) {
163 final Session session = getSession(sessionId);
164 final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
165 final AnchorEntity anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName);
166 final long anchorId = anchorEntity.getId();
167 log.debug("Attempting to lock anchor {} for session {}", anchorName, sessionId);
168 session.get(AnchorEntity.class, anchorId, LockMode.PESSIMISTIC_WRITE);
169 log.info("Anchor {} successfully locked", anchorName);
172 private Session getSession(final String sessionId) {
173 final Session session = sessionMap.get(sessionId);
174 if (session == null) {
175 throw new SessionManagerException("Session not found",
176 String.format("Session with ID %s does not exist", sessionId));