001/* 002 * (C) Copyright 2006-2015 Nuxeo SA (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * Bogdan Stefanescu 018 * Florent Guillaume 019 */ 020 021package org.nuxeo.ecm.core.api.local; 022 023import java.util.Collections; 024import java.util.Set; 025import java.util.concurrent.ConcurrentHashMap; 026import java.util.concurrent.atomic.AtomicLong; 027 028import javax.transaction.Status; 029import javax.transaction.Synchronization; 030import org.apache.commons.logging.Log; 031import org.apache.commons.logging.LogFactory; 032import org.nuxeo.ecm.core.api.AbstractSession; 033import org.nuxeo.ecm.core.api.CoreInstance; 034import org.nuxeo.ecm.core.api.CoreSession; 035import org.nuxeo.ecm.core.api.NuxeoException; 036import org.nuxeo.ecm.core.api.NuxeoPrincipal; 037import org.nuxeo.ecm.core.model.Session; 038import org.nuxeo.ecm.core.repository.RepositoryService; 039import org.nuxeo.runtime.api.Framework; 040import org.nuxeo.runtime.transaction.TransactionHelper; 041 042/** 043 * Local Session: implementation of {@link CoreSession} beyond {@link AbstractSession}, dealing with low-level stuff. 044 */ 045public class LocalSession extends AbstractSession implements Synchronization { 046 047 private static final long serialVersionUID = 1L; 048 049 private static final AtomicLong SID_COUNTER = new AtomicLong(); 050 051 private static final Log log = LogFactory.getLog(LocalSession.class); 052 053 protected String repositoryName; 054 055 protected NuxeoPrincipal principal; 056 057 /** Defined once at connect time. */ 058 private String sessionId; 059 060 /** 061 * Thread-local session allocated. 062 */ 063 private final ThreadLocal<SessionInfo> sessionHolder = new ThreadLocal<>(); 064 065 /** 066 * All sessions allocated in all threads, in order to detect close leaks. 067 */ 068 private final Set<SessionInfo> allSessions = Collections.newSetFromMap(new ConcurrentHashMap<SessionInfo, Boolean>()); 069 070 public LocalSession(String repositoryName, NuxeoPrincipal principal) { 071 if (TransactionHelper.isTransactionMarkedRollback()) { 072 throw new NuxeoException("Cannot create a CoreSession when transaction is marked rollback-only"); 073 } 074 if (!TransactionHelper.isTransactionActive()) { 075 throw new NuxeoException("Cannot create a CoreSession outside a transaction"); 076 } 077 this.repositoryName = repositoryName; 078 this.principal = principal; 079 createMetrics(); // needs repo name 080 sessionId = newSessionId(repositoryName, principal); 081 if (log.isDebugEnabled()) { 082 log.debug("Creating CoreSession: " + sessionId); 083 } 084 createSession(); // create first session for current thread 085 } 086 087 @Override 088 public String getRepositoryName() { 089 return repositoryName; 090 } 091 092 protected static String newSessionId(String repositoryName, NuxeoPrincipal principal) { 093 return repositoryName + '/' + principal.getName() + '#' + SID_COUNTER.incrementAndGet(); 094 } 095 096 @Override 097 public String getSessionId() { 098 return sessionId; 099 } 100 101 @Override 102 public Session getSession() { 103 if (!TransactionHelper.isTransactionActiveOrMarkedRollback()) { 104 throw new NuxeoException("Cannot use a CoreSession outside a transaction"); 105 } 106 TransactionHelper.checkTransactionTimeout(); 107 SessionInfo si = sessionHolder.get(); 108 if (si == null || !si.session.isLive()) { 109 // close old one, previously completed 110 closeInThisThread(); 111 if (log.isDebugEnabled()) { 112 log.debug("Reconnecting CoreSession: " + sessionId); 113 } 114 if (TransactionHelper.isTransactionMarkedRollback()) { 115 throw new NuxeoException("Cannot reconnect a CoreSession when transaction is marked rollback-only"); 116 } 117 si = createSession(); 118 } 119 return si.session; 120 } 121 122 /** 123 * Creates the session. It will be destroyed by calling {@link #destroy}. 124 */ 125 protected SessionInfo createSession() { 126 RepositoryService repositoryService = Framework.getLocalService(RepositoryService.class); 127 Session session = repositoryService.getSession(repositoryName); 128 TransactionHelper.registerSynchronization(this); 129 SessionInfo si = new SessionInfo(session); 130 sessionHolder.set(si); 131 allSessions.add(si); 132 if (log.isDebugEnabled()) { 133 log.debug("Adding thread " + Thread.currentThread().getName() + " for CoreSession: " + sessionId); 134 } 135 return si; 136 } 137 138 @Override 139 public boolean isLive(boolean onThread) { 140 if (!onThread) { 141 return !allSessions.isEmpty(); 142 } 143 return sessionHolder.get() != null; 144 } 145 146 @Override 147 public void close() { 148 CoreInstance.closeCoreSession(this); // calls back destroy() 149 } 150 151 @Override 152 public void beforeCompletion() { 153 // insure the connection is closed before commit 154 closeInThisThread(); 155 } 156 157 @Override 158 public void afterCompletion(int status) { 159 if (status == Status.STATUS_ROLLEDBACK) { 160 // insure the connection is closed on roll-back also 161 closeInThisThread(); 162 } 163 } 164 165 protected void closeInThisThread() { 166 SessionInfo si = sessionHolder.get(); 167 if (si == null) { 168 return; 169 } 170 if (log.isDebugEnabled()) { 171 log.debug("Removing thread " + Thread.currentThread().getName() + " for CoreSession: " + sessionId); 172 } 173 try { 174 si.session.close(); 175 } finally { 176 sessionHolder.remove(); 177 allSessions.remove(si); 178 } 179 } 180 181 // explicit close() 182 @Override 183 public void destroy() { 184 if (log.isDebugEnabled()) { 185 log.debug("Destroying CoreSession: " + sessionId); 186 } 187 closeInThisThread(); 188 } 189 190 @Override 191 public NuxeoPrincipal getPrincipal() { 192 return principal; 193 } 194 195 @Override 196 public boolean isStateSharedByAllThreadSessions() { 197 // by design we always share state when in the same thread (through the sessionHolder ThreadLocal) 198 return true; 199 } 200 201}