001/*
002 * Copyright 2002-2018 the original author or authors.
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 *      https://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
017package org.springframework.orm.hibernate5;
018
019import javax.transaction.Status;
020import javax.transaction.SystemException;
021import javax.transaction.TransactionManager;
022
023import org.apache.commons.logging.LogFactory;
024import org.hibernate.FlushMode;
025import org.hibernate.HibernateException;
026import org.hibernate.Session;
027import org.hibernate.context.spi.CurrentSessionContext;
028import org.hibernate.engine.spi.SessionFactoryImplementor;
029import org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform;
030
031import org.springframework.lang.Nullable;
032import org.springframework.orm.jpa.EntityManagerHolder;
033import org.springframework.transaction.support.TransactionSynchronizationManager;
034
035/**
036 * Implementation of Hibernate 3.1's {@link CurrentSessionContext} interface
037 * that delegates to Spring's {@link SessionFactoryUtils} for providing a
038 * Spring-managed current {@link Session}.
039 *
040 * <p>This CurrentSessionContext implementation can also be specified in custom
041 * SessionFactory setup through the "hibernate.current_session_context_class"
042 * property, with the fully qualified name of this class as value.
043 *
044 * @author Juergen Hoeller
045 * @since 4.2
046 */
047@SuppressWarnings("serial")
048public class SpringSessionContext implements CurrentSessionContext {
049
050        private final SessionFactoryImplementor sessionFactory;
051
052        @Nullable
053        private TransactionManager transactionManager;
054
055        @Nullable
056        private CurrentSessionContext jtaSessionContext;
057
058
059        /**
060         * Create a new SpringSessionContext for the given Hibernate SessionFactory.
061         * @param sessionFactory the SessionFactory to provide current Sessions for
062         */
063        public SpringSessionContext(SessionFactoryImplementor sessionFactory) {
064                this.sessionFactory = sessionFactory;
065                try {
066                        JtaPlatform jtaPlatform = sessionFactory.getServiceRegistry().getService(JtaPlatform.class);
067                        this.transactionManager = jtaPlatform.retrieveTransactionManager();
068                        if (this.transactionManager != null) {
069                                this.jtaSessionContext = new SpringJtaSessionContext(sessionFactory);
070                        }
071                }
072                catch (Exception ex) {
073                        LogFactory.getLog(SpringSessionContext.class).warn(
074                                        "Could not introspect Hibernate JtaPlatform for SpringJtaSessionContext", ex);
075                }
076        }
077
078
079        /**
080         * Retrieve the Spring-managed Session for the current thread, if any.
081         */
082        @Override
083        @SuppressWarnings("deprecation")
084        public Session currentSession() throws HibernateException {
085                Object value = TransactionSynchronizationManager.getResource(this.sessionFactory);
086                if (value instanceof Session) {
087                        return (Session) value;
088                }
089                else if (value instanceof SessionHolder) {
090                        // HibernateTransactionManager
091                        SessionHolder sessionHolder = (SessionHolder) value;
092                        Session session = sessionHolder.getSession();
093                        if (!sessionHolder.isSynchronizedWithTransaction() &&
094                                        TransactionSynchronizationManager.isSynchronizationActive()) {
095                                TransactionSynchronizationManager.registerSynchronization(
096                                                new SpringSessionSynchronization(sessionHolder, this.sessionFactory, false));
097                                sessionHolder.setSynchronizedWithTransaction(true);
098                                // Switch to FlushMode.AUTO, as we have to assume a thread-bound Session
099                                // with FlushMode.MANUAL, which needs to allow flushing within the transaction.
100                                FlushMode flushMode = SessionFactoryUtils.getFlushMode(session);
101                                if (flushMode.equals(FlushMode.MANUAL) &&
102                                                !TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
103                                        session.setFlushMode(FlushMode.AUTO);
104                                        sessionHolder.setPreviousFlushMode(flushMode);
105                                }
106                        }
107                        return session;
108                }
109                else if (value instanceof EntityManagerHolder) {
110                        // JpaTransactionManager
111                        return ((EntityManagerHolder) value).getEntityManager().unwrap(Session.class);
112                }
113
114                if (this.transactionManager != null && this.jtaSessionContext != null) {
115                        try {
116                                if (this.transactionManager.getStatus() == Status.STATUS_ACTIVE) {
117                                        Session session = this.jtaSessionContext.currentSession();
118                                        if (TransactionSynchronizationManager.isSynchronizationActive()) {
119                                                TransactionSynchronizationManager.registerSynchronization(
120                                                                new SpringFlushSynchronization(session));
121                                        }
122                                        return session;
123                                }
124                        }
125                        catch (SystemException ex) {
126                                throw new HibernateException("JTA TransactionManager found but status check failed", ex);
127                        }
128                }
129
130                if (TransactionSynchronizationManager.isSynchronizationActive()) {
131                        Session session = this.sessionFactory.openSession();
132                        if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
133                                session.setFlushMode(FlushMode.MANUAL);
134                        }
135                        SessionHolder sessionHolder = new SessionHolder(session);
136                        TransactionSynchronizationManager.registerSynchronization(
137                                        new SpringSessionSynchronization(sessionHolder, this.sessionFactory, true));
138                        TransactionSynchronizationManager.bindResource(this.sessionFactory, sessionHolder);
139                        sessionHolder.setSynchronizedWithTransaction(true);
140                        return session;
141                }
142                else {
143                        throw new HibernateException("Could not obtain transaction-synchronized Session for current thread");
144                }
145        }
146
147}