001/*
002 * Copyright 2002-2020 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 java.util.Map;
020import java.util.function.Consumer;
021
022import org.apache.commons.logging.Log;
023import org.apache.commons.logging.LogFactory;
024import org.hibernate.resource.beans.container.spi.BeanContainer;
025import org.hibernate.resource.beans.container.spi.ContainedBean;
026import org.hibernate.resource.beans.spi.BeanInstanceProducer;
027
028import org.springframework.beans.BeansException;
029import org.springframework.beans.factory.BeanCreationException;
030import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
031import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
032import org.springframework.lang.Nullable;
033import org.springframework.util.Assert;
034import org.springframework.util.ConcurrentReferenceHashMap;
035
036/**
037 * Spring's implementation of Hibernate 5.3's {@link BeanContainer} SPI,
038 * delegating to a Spring {@link ConfigurableListableBeanFactory}.
039 *
040 * <p>Auto-configured by {@link LocalSessionFactoryBean#setBeanFactory},
041 * programmatically supported via {@link LocalSessionFactoryBuilder#setBeanContainer},
042 * and manually configurable through a "hibernate.resource.beans.container" entry
043 * in JPA properties, e.g.:
044 *
045 * <pre class="code">
046 * &lt;bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"&gt;
047 *   ...
048 *   &lt;property name="jpaPropertyMap"&gt;
049 *         &lt;map>
050 *       &lt;entry key="hibernate.resource.beans.container"&gt;
051 *             &lt;bean class="org.springframework.orm.hibernate5.SpringBeanContainer"/&gt;
052 *           &lt;/entry&gt;
053 *         &lt;/map&gt;
054 *   &lt;/property&gt;
055 * &lt;/bean&gt;</pre>
056 *
057 * Or in Java-based JPA configuration:
058 *
059 * <pre class="code">
060 * LocalContainerEntityManagerFactoryBean emfb = ...
061 * emfb.getJpaPropertyMap().put(AvailableSettings.BEAN_CONTAINER, new SpringBeanContainer(beanFactory));
062 * </pre>
063 *
064 * Please note that Spring's {@link LocalSessionFactoryBean} is an immediate alternative
065 * to {@link org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean} for common
066 * JPA purposes: In particular with Hibernate 5.3/5.4, the Hibernate {@code SessionFactory}
067 * will natively expose the JPA {@code EntityManagerFactory} interface as well, and
068 * Hibernate {@code BeanContainer} integration will be registered out of the box.
069 *
070 * @author Juergen Hoeller
071 * @since 5.1
072 * @see LocalSessionFactoryBean#setBeanFactory
073 * @see LocalSessionFactoryBuilder#setBeanContainer
074 * @see org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean#setJpaPropertyMap
075 * @see org.hibernate.cfg.AvailableSettings#BEAN_CONTAINER
076 */
077public final class SpringBeanContainer implements BeanContainer {
078
079        private static final Log logger = LogFactory.getLog(SpringBeanContainer.class);
080
081        private final ConfigurableListableBeanFactory beanFactory;
082
083        private final Map<Object, SpringContainedBean<?>> beanCache = new ConcurrentReferenceHashMap<>();
084
085
086        /**
087         * Instantiate a new SpringBeanContainer for the given bean factory.
088         * @param beanFactory the Spring bean factory to delegate to
089         */
090        public SpringBeanContainer(ConfigurableListableBeanFactory beanFactory) {
091                Assert.notNull(beanFactory, "ConfigurableListableBeanFactory is required");
092                this.beanFactory = beanFactory;
093        }
094
095
096        @Override
097        @SuppressWarnings("unchecked")
098        public <B> ContainedBean<B> getBean(
099                        Class<B> beanType, LifecycleOptions lifecycleOptions, BeanInstanceProducer fallbackProducer) {
100
101                SpringContainedBean<?> bean;
102                if (lifecycleOptions.canUseCachedReferences()) {
103                        bean = this.beanCache.get(beanType);
104                        if (bean == null) {
105                                bean = createBean(beanType, lifecycleOptions, fallbackProducer);
106                                this.beanCache.put(beanType, bean);
107                        }
108                }
109                else {
110                        bean = createBean(beanType, lifecycleOptions, fallbackProducer);
111                }
112                return (SpringContainedBean<B>) bean;
113        }
114
115        @Override
116        @SuppressWarnings("unchecked")
117        public <B> ContainedBean<B> getBean(
118                        String name, Class<B> beanType, LifecycleOptions lifecycleOptions, BeanInstanceProducer fallbackProducer) {
119
120                SpringContainedBean<?> bean;
121                if (lifecycleOptions.canUseCachedReferences()) {
122                        bean = this.beanCache.get(name);
123                        if (bean == null) {
124                                bean = createBean(name, beanType, lifecycleOptions, fallbackProducer);
125                                this.beanCache.put(name, bean);
126                        }
127                }
128                else {
129                        bean = createBean(name, beanType, lifecycleOptions, fallbackProducer);
130                }
131                return (SpringContainedBean<B>) bean;
132        }
133
134        @Override
135        public void stop() {
136                this.beanCache.values().forEach(SpringContainedBean::destroyIfNecessary);
137                this.beanCache.clear();
138        }
139
140
141        private SpringContainedBean<?> createBean(
142                        Class<?> beanType, LifecycleOptions lifecycleOptions, BeanInstanceProducer fallbackProducer) {
143
144                try {
145                        if (lifecycleOptions.useJpaCompliantCreation()) {
146                                return new SpringContainedBean<>(
147                                                this.beanFactory.createBean(beanType, AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR, false),
148                                                this.beanFactory::destroyBean);
149                        }
150                        else {
151                                return new SpringContainedBean<>(this.beanFactory.getBean(beanType));
152                        }
153                }
154                catch (BeansException ex) {
155                        if (logger.isDebugEnabled()) {
156                                logger.debug("Falling back to Hibernate's default producer after bean creation failure for " +
157                                                beanType + ": " + ex);
158                        }
159                        try {
160                                return new SpringContainedBean<>(fallbackProducer.produceBeanInstance(beanType));
161                        }
162                        catch (RuntimeException ex2) {
163                                if (ex instanceof BeanCreationException) {
164                                        if (logger.isDebugEnabled()) {
165                                                logger.debug("Fallback producer failed for " + beanType + ": " + ex2);
166                                        }
167                                        // Rethrow original Spring exception from first attempt.
168                                        throw ex;
169                                }
170                                else {
171                                        // Throw fallback producer exception since original was probably NoSuchBeanDefinitionException.
172                                        throw ex2;
173                                }
174                        }
175                }
176        }
177
178        private SpringContainedBean<?> createBean(
179                        String name, Class<?> beanType, LifecycleOptions lifecycleOptions, BeanInstanceProducer fallbackProducer) {
180
181                try {
182                        if (lifecycleOptions.useJpaCompliantCreation()) {
183                                Object bean = this.beanFactory.autowire(beanType, AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR, false);
184                                this.beanFactory.autowireBeanProperties(bean, AutowireCapableBeanFactory.AUTOWIRE_NO, false);
185                                this.beanFactory.applyBeanPropertyValues(bean, name);
186                                bean = this.beanFactory.initializeBean(bean, name);
187                                return new SpringContainedBean<>(bean, beanInstance -> this.beanFactory.destroyBean(name, beanInstance));
188                        }
189                        else {
190                                return new SpringContainedBean<>(this.beanFactory.getBean(name, beanType));
191                        }
192                }
193                catch (BeansException ex) {
194                        if (logger.isDebugEnabled()) {
195                                logger.debug("Falling back to Hibernate's default producer after bean creation failure for " +
196                                                beanType + " with name '" + name + "': " + ex);
197                        }
198                        try {
199                                return new SpringContainedBean<>(fallbackProducer.produceBeanInstance(name, beanType));
200                        }
201                        catch (RuntimeException ex2) {
202                                if (ex instanceof BeanCreationException) {
203                                        if (logger.isDebugEnabled()) {
204                                                logger.debug("Fallback producer failed for " + beanType + " with name '" + name + "': " + ex2);
205                                        }
206                                        // Rethrow original Spring exception from first attempt.
207                                        throw ex;
208                                }
209                                else {
210                                        // Throw fallback producer exception since original was probably NoSuchBeanDefinitionException.
211                                        throw ex2;
212                                }
213                        }
214                }
215        }
216
217
218        private static final class SpringContainedBean<B> implements ContainedBean<B> {
219
220                private final B beanInstance;
221
222                @Nullable
223                private Consumer<B> destructionCallback;
224
225                public SpringContainedBean(B beanInstance) {
226                        this.beanInstance = beanInstance;
227                }
228
229                public SpringContainedBean(B beanInstance, Consumer<B> destructionCallback) {
230                        this.beanInstance = beanInstance;
231                        this.destructionCallback = destructionCallback;
232                }
233
234                @Override
235                public B getBeanInstance() {
236                        return this.beanInstance;
237                }
238
239                public void destroyIfNecessary() {
240                        if (this.destructionCallback != null) {
241                                this.destructionCallback.accept(this.beanInstance);
242                        }
243                }
244        }
245
246}