/* * (C) Copyright 2006-2015 Nuxeo SA (http://nuxeo.com/) and others. * * 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. * * Contributors: * Bogdan Stefanescu * Florent Guillaume */ package org.nuxeo.ecm.core.api.local; import java.util.Collections; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicLong; import javax.transaction.Status; import javax.transaction.Synchronization; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.nuxeo.ecm.core.api.AbstractSession; import org.nuxeo.ecm.core.api.CoreInstance; import org.nuxeo.ecm.core.api.CoreSession; import org.nuxeo.ecm.core.api.NuxeoException; import org.nuxeo.ecm.core.api.NuxeoPrincipal; import org.nuxeo.ecm.core.model.Session; import org.nuxeo.ecm.core.repository.RepositoryService; import org.nuxeo.runtime.api.Framework; import org.nuxeo.runtime.transaction.TransactionHelper; /** * Local Session: implementation of {@link CoreSession} beyond {@link AbstractSession}, dealing with low-level stuff. */ public class LocalSession extends AbstractSession implements Synchronization { private static final long serialVersionUID = 1L; private static final AtomicLong SID_COUNTER = new AtomicLong(); private static final Log log = LogFactory.getLog(LocalSession.class); protected String repositoryName; protected NuxeoPrincipal principal; /** Defined once at connect time. */ private String sessionId; /** * Thread-local session allocated. */ private final ThreadLocal<SessionInfo> sessionHolder = new ThreadLocal<>(); /** * All sessions allocated in all threads, in order to detect close leaks. */ private final Set<SessionInfo> allSessions = Collections.newSetFromMap(new ConcurrentHashMap<SessionInfo, Boolean>()); public LocalSession(String repositoryName, NuxeoPrincipal principal) { if (TransactionHelper.isTransactionMarkedRollback()) { throw new NuxeoException("Cannot create a CoreSession when transaction is marked rollback-only"); } if (!TransactionHelper.isTransactionActive()) { throw new NuxeoException("Cannot create a CoreSession outside a transaction"); } this.repositoryName = repositoryName; this.principal = principal; createMetrics(); // needs repo name sessionId = newSessionId(repositoryName, principal); if (log.isDebugEnabled()) { log.debug("Creating CoreSession: " + sessionId); } createSession(); // create first session for current thread } @Override public String getRepositoryName() { return repositoryName; } protected static String newSessionId(String repositoryName, NuxeoPrincipal principal) { return repositoryName + '/' + principal.getName() + '#' + SID_COUNTER.incrementAndGet(); } @Override public String getSessionId() { return sessionId; } @Override public Session getSession() { if (!TransactionHelper.isTransactionActiveOrMarkedRollback()) { throw new NuxeoException("Cannot use a CoreSession outside a transaction"); } TransactionHelper.checkTransactionTimeout(); SessionInfo si = sessionHolder.get(); if (si == null || !si.session.isLive()) { // close old one, previously completed closeInThisThread(); if (log.isDebugEnabled()) { log.debug("Reconnecting CoreSession: " + sessionId); } if (TransactionHelper.isTransactionMarkedRollback()) { throw new NuxeoException("Cannot reconnect a CoreSession when transaction is marked rollback-only"); } si = createSession(); } return si.session; } /** * Creates the session. It will be destroyed by calling {@link #destroy}. */ protected SessionInfo createSession() { RepositoryService repositoryService = Framework.getLocalService(RepositoryService.class); Session session = repositoryService.getSession(repositoryName); TransactionHelper.registerSynchronization(this); SessionInfo si = new SessionInfo(session); sessionHolder.set(si); allSessions.add(si); if (log.isDebugEnabled()) { log.debug("Adding thread " + Thread.currentThread().getName() + " for CoreSession: " + sessionId); } return si; } @Override public boolean isLive(boolean onThread) { if (!onThread) { return !allSessions.isEmpty(); } return sessionHolder.get() != null; } @Override public void close() { CoreInstance.closeCoreSession(this); // calls back destroy() } @Override public void beforeCompletion() { // insure the connection is closed before commit closeInThisThread(); } @Override public void afterCompletion(int status) { if (status == Status.STATUS_ROLLEDBACK) { // insure the connection is closed on roll-back also closeInThisThread(); } } protected void closeInThisThread() { SessionInfo si = sessionHolder.get(); if (si == null) { return; } if (log.isDebugEnabled()) { log.debug("Removing thread " + Thread.currentThread().getName() + " for CoreSession: " + sessionId); } try { si.session.close(); } finally { sessionHolder.remove(); allSessions.remove(si); } } // explicit close() @Override public void destroy() { if (log.isDebugEnabled()) { log.debug("Destroying CoreSession: " + sessionId); } closeInThisThread(); } @Override public NuxeoPrincipal getPrincipal() { return principal; } @Override public boolean isStateSharedByAllThreadSessions() { // by design we always share state when in the same thread (through the sessionHolder ThreadLocal) return true; } }