001/* 002 * (C) Copyright 2016 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 * Florent Guillaume 018 */ 019package org.nuxeo.ecm.core.api; 020 021import java.util.ArrayList; 022import java.util.List; 023import java.util.Map; 024import java.util.concurrent.ConcurrentHashMap; 025 026import org.apache.logging.log4j.LogManager; 027import org.apache.logging.log4j.Logger; 028import org.nuxeo.ecm.core.api.local.LocalSession; 029import org.nuxeo.runtime.model.DefaultComponent; 030 031import com.google.common.cache.Cache; 032import com.google.common.cache.CacheBuilder; 033 034/** 035 * Implementation for the service managing the acquisition/release of {@link CoreSession} instances. 036 * 037 * @since 8.4 038 */ 039public class CoreSessionServiceImpl extends DefaultComponent implements CoreSessionService { 040 041 private static final Logger log = LogManager.getLogger(CoreSessionServiceImpl.class); 042 043 /** 044 * All open {@link CoreSessionRegistrationInfo}, keyed by session id. 045 */ 046 private final Map<String, CoreSessionRegistrationInfo> sessions = new ConcurrentHashMap<String, CoreSessionRegistrationInfo>(); 047 048 /** 049 * Most recently closed sessions. 050 */ 051 protected final Cache<String, CoreSessionRegistrationInfo> recentlyClosedSessions = // 052 CacheBuilder.newBuilder().maximumSize(100).build(); 053 054 @Override 055 public CloseableCoreSession createCoreSession(String repositoryName, NuxeoPrincipal principal) { 056 LocalSession session = new LocalSession(repositoryName, principal); 057 sessions.put(session.getSessionId(), new CoreSessionRegistrationInfo(session)); 058 return session; 059 } 060 061 @Override 062 public void releaseCoreSession(CloseableCoreSession session) { 063 String sessionId = session.getSessionId(); 064 CoreSessionRegistrationInfo info = sessions.remove(sessionId); 065 String debug = "closing stacktrace, sessionId=" + sessionId + ", thread=" + Thread.currentThread().getName(); 066 if (info == null) { 067 CoreSessionRegistrationInfo closed = recentlyClosedSessions.getIfPresent(sessionId); 068 if (closed == null) { 069 // no knowledge of this sessionId, log the current stacktrace 070 Exception e = new Exception("DEBUG: " + debug); 071 log.warn("Closing unknown CoreSession", e); 072 } else { 073 // this sessionId was recently closed and we kept info about it 074 // log the current stacktrace with the original opening and closing as suppressed exceptions 075 Exception e = new Exception("DEBUG: spurious " + debug); 076 e.addSuppressed(closed); 077 log.warn("Closing already closed CoreSession", e); 078 } 079 } else { 080 // regular closing, record a stacktrace 081 info.addSuppressed(new Exception("DEBUG: " + debug)); 082 recentlyClosedSessions.put(sessionId, info); 083 // don't keep the session around, all we want is the stacktrace objects 084 info.session = null; 085 } 086 session.destroy(); 087 } 088 089 @Override 090 public CoreSession getCoreSession(String sessionId) { 091 if (sessionId == null) { 092 throw new NullPointerException("null sessionId"); 093 } 094 CoreSessionRegistrationInfo info = sessions.get(sessionId); 095 return info == null ? null : info.getCoreSession(); 096 } 097 098 @Override 099 public int getNumberOfOpenCoreSessions() { 100 return sessions.size(); 101 } 102 103 @Override 104 public List<CoreSessionRegistrationInfo> getCoreSessionRegistrationInfos() { 105 return new ArrayList<>(sessions.values()); 106 } 107 108}