001/*
002 * Copyright 2012-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 *      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
017package org.springframework.boot.autoconfigure.jdbc;
018
019import javax.sql.DataSource;
020import javax.sql.XADataSource;
021import javax.transaction.TransactionManager;
022
023import org.springframework.beans.BeanUtils;
024import org.springframework.beans.factory.BeanClassLoaderAware;
025import org.springframework.beans.factory.ObjectProvider;
026import org.springframework.boot.autoconfigure.AutoConfigureBefore;
027import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
028import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
029import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
030import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
031import org.springframework.boot.context.properties.EnableConfigurationProperties;
032import org.springframework.boot.context.properties.bind.Bindable;
033import org.springframework.boot.context.properties.bind.Binder;
034import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
035import org.springframework.boot.context.properties.source.ConfigurationPropertyNameAliases;
036import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
037import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
038import org.springframework.boot.jdbc.DatabaseDriver;
039import org.springframework.boot.jdbc.XADataSourceWrapper;
040import org.springframework.context.annotation.Bean;
041import org.springframework.context.annotation.Configuration;
042import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
043import org.springframework.util.Assert;
044import org.springframework.util.ClassUtils;
045import org.springframework.util.StringUtils;
046
047/**
048 * {@link EnableAutoConfiguration Auto-configuration} for {@link DataSource} with XA.
049 *
050 * @author Phillip Webb
051 * @author Josh Long
052 * @author Madhura Bhave
053 * @since 1.2.0
054 */
055@Configuration
056@AutoConfigureBefore(DataSourceAutoConfiguration.class)
057@EnableConfigurationProperties(DataSourceProperties.class)
058@ConditionalOnClass({ DataSource.class, TransactionManager.class,
059                EmbeddedDatabaseType.class })
060@ConditionalOnBean(XADataSourceWrapper.class)
061@ConditionalOnMissingBean(DataSource.class)
062public class XADataSourceAutoConfiguration implements BeanClassLoaderAware {
063
064        private final XADataSourceWrapper wrapper;
065
066        private final DataSourceProperties properties;
067
068        private final XADataSource xaDataSource;
069
070        private ClassLoader classLoader;
071
072        public XADataSourceAutoConfiguration(XADataSourceWrapper wrapper,
073                        DataSourceProperties properties, ObjectProvider<XADataSource> xaDataSource) {
074                this.wrapper = wrapper;
075                this.properties = properties;
076                this.xaDataSource = xaDataSource.getIfAvailable();
077        }
078
079        @Bean
080        public DataSource dataSource() throws Exception {
081                XADataSource xaDataSource = this.xaDataSource;
082                if (xaDataSource == null) {
083                        xaDataSource = createXaDataSource();
084                }
085                return this.wrapper.wrapDataSource(xaDataSource);
086        }
087
088        @Override
089        public void setBeanClassLoader(ClassLoader classLoader) {
090                this.classLoader = classLoader;
091        }
092
093        private XADataSource createXaDataSource() {
094                String className = this.properties.getXa().getDataSourceClassName();
095                if (!StringUtils.hasLength(className)) {
096                        className = DatabaseDriver.fromJdbcUrl(this.properties.determineUrl())
097                                        .getXaDataSourceClassName();
098                }
099                Assert.state(StringUtils.hasLength(className),
100                                "No XA DataSource class name specified");
101                XADataSource dataSource = createXaDataSourceInstance(className);
102                bindXaProperties(dataSource, this.properties);
103                return dataSource;
104        }
105
106        private XADataSource createXaDataSourceInstance(String className) {
107                try {
108                        Class<?> dataSourceClass = ClassUtils.forName(className, this.classLoader);
109                        Object instance = BeanUtils.instantiateClass(dataSourceClass);
110                        Assert.isInstanceOf(XADataSource.class, instance);
111                        return (XADataSource) instance;
112                }
113                catch (Exception ex) {
114                        throw new IllegalStateException(
115                                        "Unable to create XADataSource instance from '" + className + "'");
116                }
117        }
118
119        private void bindXaProperties(XADataSource target,
120                        DataSourceProperties dataSourceProperties) {
121                Binder binder = new Binder(getBinderSource(dataSourceProperties));
122                binder.bind(ConfigurationPropertyName.EMPTY, Bindable.ofInstance(target));
123        }
124
125        private ConfigurationPropertySource getBinderSource(
126                        DataSourceProperties dataSourceProperties) {
127                MapConfigurationPropertySource source = new MapConfigurationPropertySource();
128                source.put("user", this.properties.determineUsername());
129                source.put("password", this.properties.determinePassword());
130                source.put("url", this.properties.determineUrl());
131                source.putAll(dataSourceProperties.getXa().getProperties());
132                ConfigurationPropertyNameAliases aliases = new ConfigurationPropertyNameAliases();
133                aliases.addAliases("user", "username");
134                return source.withAliases(aliases);
135        }
136
137}