Browse Source
Update DataSource and JPA auto-configuration to consider XA datasources. See gh-947pull/1323/merge
12 changed files with 528 additions and 34 deletions
@ -0,0 +1,137 @@
@@ -0,0 +1,137 @@
|
||||
/* |
||||
* Copyright 2012-2014 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.boot.autoconfigure.jdbc; |
||||
|
||||
import org.springframework.util.Assert; |
||||
import org.springframework.util.StringUtils; |
||||
|
||||
/** |
||||
* Enumeration of common database drivers. |
||||
* |
||||
* @author Phillip Webb |
||||
* @author Maciej Walkowiak |
||||
* @since 1.2.0 |
||||
*/ |
||||
enum DatabaseDriver { |
||||
|
||||
/** |
||||
* Unknown type. |
||||
*/ |
||||
UNKNOWN(null), |
||||
|
||||
/** |
||||
* Apache Derby. |
||||
*/ |
||||
DERBY("org.apache.derby.jdbc.EmbeddedDriver"), |
||||
|
||||
/** |
||||
* H2. |
||||
*/ |
||||
H2("org.h2.Driver", "org.h2.jdbcx.JdbcDataSource"), |
||||
|
||||
/** |
||||
* HyperSQL DataBase. |
||||
*/ |
||||
HSQLDB("org.hsqldb.jdbc.JDBCDriver", "org.hsqldb.jdbc.pool.JDBCXADataSource"), |
||||
|
||||
/** |
||||
* SQL Lite. |
||||
*/ |
||||
SQLITE("org.sqlite.JDBC"), |
||||
|
||||
/** |
||||
* MySQL. |
||||
*/ |
||||
MYSQL("com.mysql.jdbc.Driver", "org.mysql.jdbc.MySQLDataSource"), |
||||
|
||||
/** |
||||
* Maria DB. |
||||
*/ |
||||
MARIADB("org.mariadb.jdbc.Driver", "org.mariadb.jdbc.MySQLDataSource"), |
||||
|
||||
/** |
||||
* Google App Engine. |
||||
*/ |
||||
GOOGLE("com.google.appengine.api.rdbms.AppEngineDriver"), |
||||
|
||||
/** |
||||
* Oracle |
||||
*/ |
||||
ORACLE("oracle.jdbc.OracleDriver", "oracle.jdbc.xa.OracleXADataSource"), |
||||
|
||||
/** |
||||
* Postres |
||||
*/ |
||||
POSTGRESQL("org.postgresql.Driver", "org.postgresql.xa.PGXADataSource"), |
||||
|
||||
/** |
||||
* JTDS |
||||
*/ |
||||
JTDS("net.sourceforge.jtds.jdbc.Driver"), |
||||
|
||||
/** |
||||
* SQL Server |
||||
*/ |
||||
SQLSERVER("com.microsoft.sqlserver.jdbc.SQLServerDriver"); |
||||
|
||||
private final String driverClassName; |
||||
|
||||
private final String xaDataSourceClassName; |
||||
|
||||
private DatabaseDriver(String driverClassName) { |
||||
this(driverClassName, null); |
||||
} |
||||
|
||||
private DatabaseDriver(String driverClassName, String xaDataSourceClassName) { |
||||
this.driverClassName = driverClassName; |
||||
this.xaDataSourceClassName = xaDataSourceClassName; |
||||
} |
||||
|
||||
/** |
||||
* @return the driverClassName or {@code null} |
||||
*/ |
||||
public String getDriverClassName() { |
||||
return this.driverClassName; |
||||
} |
||||
|
||||
/** |
||||
* @return the xaDataSourceClassName or {@code null} |
||||
*/ |
||||
public String getXaDataSourceClassName() { |
||||
return this.xaDataSourceClassName; |
||||
} |
||||
|
||||
/** |
||||
* Find a {@link DatabaseDriver} for the given URL. |
||||
* @param url JDBC URL |
||||
* @return driver class name or {@link #UNKNOWN} if not found |
||||
*/ |
||||
public static DatabaseDriver fromJdbcUrl(String url) { |
||||
if (StringUtils.hasLength(url)) { |
||||
Assert.isTrue(url.startsWith("jdbc"), "URL must start with 'jdbc'"); |
||||
String urlWithoutPrefix = url.substring("jdbc".length()).toLowerCase(); |
||||
for (DatabaseDriver driver : values()) { |
||||
String prefix = ":" + driver.name().toLowerCase() + ":"; |
||||
if (driver != UNKNOWN && urlWithoutPrefix.startsWith(prefix)) { |
||||
return driver; |
||||
} |
||||
} |
||||
} |
||||
return UNKNOWN; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,119 @@
@@ -0,0 +1,119 @@
|
||||
/* |
||||
* Copyright 2012-2014 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.boot.autoconfigure.jdbc; |
||||
|
||||
import javax.sql.DataSource; |
||||
import javax.sql.XADataSource; |
||||
import javax.transaction.TransactionManager; |
||||
|
||||
import org.springframework.beans.BeanUtils; |
||||
import org.springframework.beans.MutablePropertyValues; |
||||
import org.springframework.beans.factory.BeanClassLoaderAware; |
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.boot.autoconfigure.AutoConfigureAfter; |
||||
import org.springframework.boot.autoconfigure.AutoConfigureBefore; |
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; |
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; |
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; |
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; |
||||
import org.springframework.boot.autoconfigure.jta.JtaAutoConfiguration; |
||||
import org.springframework.boot.bind.RelaxedDataBinder; |
||||
import org.springframework.boot.context.properties.ConfigurationProperties; |
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties; |
||||
import org.springframework.boot.jta.XADataSourceWrapper; |
||||
import org.springframework.context.annotation.Bean; |
||||
import org.springframework.util.Assert; |
||||
import org.springframework.util.ClassUtils; |
||||
import org.springframework.util.StringUtils; |
||||
|
||||
/** |
||||
* {@link EnableAutoConfiguration Auto-configuration} for {@link DataSource} with XA. |
||||
* |
||||
* @author Phillip Webb |
||||
* @author Josh Long |
||||
* @since 1.2.0 |
||||
*/ |
||||
@AutoConfigureBefore(DataSourceAutoConfiguration.class) |
||||
@AutoConfigureAfter(JtaAutoConfiguration.class) |
||||
@EnableConfigurationProperties(DataSourceProperties.class) |
||||
@ConditionalOnClass({ DataSource.class, TransactionManager.class }) |
||||
@ConditionalOnBean(XADataSourceWrapper.class) |
||||
@ConditionalOnMissingBean(DataSource.class) |
||||
public class XADataSourceAutoConfiguration implements BeanClassLoaderAware { |
||||
|
||||
@Autowired |
||||
private XADataSourceWrapper wrapper; |
||||
|
||||
@Autowired |
||||
private DataSourceProperties properties; |
||||
|
||||
@Autowired(required = false) |
||||
private XADataSource xaDataSource; |
||||
|
||||
private ClassLoader classLoader; |
||||
|
||||
@Bean |
||||
@ConfigurationProperties(prefix = DataSourceProperties.PREFIX) |
||||
public DataSource dataSource() throws Exception { |
||||
XADataSource xaDataSource = this.xaDataSource; |
||||
if (xaDataSource == null) { |
||||
xaDataSource = createXaDataSource(); |
||||
} |
||||
return this.wrapper.wrapDataSource(xaDataSource); |
||||
} |
||||
|
||||
@Override |
||||
public void setBeanClassLoader(ClassLoader classLoader) { |
||||
this.classLoader = classLoader; |
||||
} |
||||
|
||||
private XADataSource createXaDataSource() { |
||||
String className = this.properties.getXa().getDataSourceClassName(); |
||||
if (!StringUtils.hasLength(className)) { |
||||
className = DatabaseDriver.fromJdbcUrl(this.properties.getUrl()) |
||||
.getXaDataSourceClassName(); |
||||
} |
||||
Assert.state(StringUtils.hasLength(className), |
||||
"No XA DataSource class name specified"); |
||||
XADataSource dataSource = createXaDataSourceInstance(className); |
||||
bindXaProperties(dataSource, this.properties); |
||||
return dataSource; |
||||
} |
||||
|
||||
private XADataSource createXaDataSourceInstance(String className) { |
||||
try { |
||||
Class<?> dataSourceClass = ClassUtils.forName(className, this.classLoader); |
||||
Object instance = BeanUtils.instantiate(dataSourceClass); |
||||
Assert.isInstanceOf(XADataSource.class, instance); |
||||
return (XADataSource) instance; |
||||
} |
||||
catch (Exception ex) { |
||||
throw new IllegalStateException( |
||||
"Unable to create XADataSource instance from '" + className + "'"); |
||||
} |
||||
} |
||||
|
||||
private void bindXaProperties(XADataSource target, DataSourceProperties properties) { |
||||
MutablePropertyValues values = new MutablePropertyValues(); |
||||
values.add("user", this.properties.getUsername()); |
||||
values.add("password", this.properties.getPassword()); |
||||
values.add("url", this.properties.getUrl()); |
||||
values.addPropertyValues(properties.getXa().getProperties()); |
||||
new RelaxedDataBinder(target).withAlias("user", "username").bind(values); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,128 @@
@@ -0,0 +1,128 @@
|
||||
/* |
||||
* Copyright 2012-2014 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.boot.autoconfigure.jdbc; |
||||
|
||||
import javax.sql.DataSource; |
||||
import javax.sql.XADataSource; |
||||
|
||||
import org.hsqldb.jdbc.pool.JDBCXADataSource; |
||||
import org.junit.Test; |
||||
import org.springframework.boot.jta.XADataSourceWrapper; |
||||
import org.springframework.boot.test.EnvironmentTestUtils; |
||||
import org.springframework.context.ApplicationContext; |
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext; |
||||
import org.springframework.context.annotation.Bean; |
||||
import org.springframework.context.annotation.Configuration; |
||||
|
||||
import static org.hamcrest.Matchers.equalTo; |
||||
import static org.junit.Assert.assertNotNull; |
||||
import static org.junit.Assert.assertThat; |
||||
import static org.mockito.Mockito.mock; |
||||
|
||||
/** |
||||
* Tests for {@link XADataSourceAutoConfiguration}. |
||||
* |
||||
* @author Phillip Webb |
||||
*/ |
||||
public class XADataSourceAutoConfigurationTests { |
||||
|
||||
@Test |
||||
public void wrapExistingXaDataSource() throws Exception { |
||||
ApplicationContext context = createContext(WrapExisting.class); |
||||
context.getBean(DataSource.class); |
||||
XADataSource source = context.getBean(XADataSource.class); |
||||
MockXADataSourceWrapper wrapper = context.getBean(MockXADataSourceWrapper.class); |
||||
assertThat(wrapper.getXaDataSource(), equalTo(source)); |
||||
} |
||||
|
||||
@Test |
||||
public void createFromUrl() throws Exception { |
||||
ApplicationContext context = createContext(FromProperties.class, |
||||
"spring.datasource.url:jdbc:hsqldb:mem:test", |
||||
"spring.datasource.username:un"); |
||||
context.getBean(DataSource.class); |
||||
MockXADataSourceWrapper wrapper = context.getBean(MockXADataSourceWrapper.class); |
||||
JDBCXADataSource dataSource = (JDBCXADataSource) wrapper.getXaDataSource(); |
||||
assertNotNull(dataSource); |
||||
assertThat(dataSource.getUrl(), equalTo("jdbc:hsqldb:mem:test")); |
||||
assertThat(dataSource.getUser(), equalTo("un")); |
||||
} |
||||
|
||||
@Test |
||||
public void createFromClass() throws Exception { |
||||
ApplicationContext context = createContext( |
||||
FromProperties.class, |
||||
"spring.datasource.xa.data-source-class:org.hsqldb.jdbc.pool.JDBCXADataSource", |
||||
"spring.datasource.xa.properties.database-name:test"); |
||||
context.getBean(DataSource.class); |
||||
MockXADataSourceWrapper wrapper = context.getBean(MockXADataSourceWrapper.class); |
||||
JDBCXADataSource dataSource = (JDBCXADataSource) wrapper.getXaDataSource(); |
||||
assertNotNull(dataSource); |
||||
assertThat(dataSource.getDatabaseName(), equalTo("test")); |
||||
|
||||
} |
||||
|
||||
private ApplicationContext createContext(Class<?> configuration, String... env) { |
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); |
||||
EnvironmentTestUtils.addEnvironment(context, env); |
||||
context.register(configuration, XADataSourceAutoConfiguration.class); |
||||
context.refresh(); |
||||
return context; |
||||
} |
||||
|
||||
@Configuration |
||||
static class WrapExisting { |
||||
|
||||
@Bean |
||||
public MockXADataSourceWrapper wrapper() { |
||||
return new MockXADataSourceWrapper(); |
||||
} |
||||
|
||||
@Bean |
||||
public XADataSource xaDataSource() { |
||||
return mock(XADataSource.class); |
||||
} |
||||
|
||||
} |
||||
|
||||
@Configuration |
||||
static class FromProperties { |
||||
|
||||
@Bean |
||||
public MockXADataSourceWrapper wrapper() { |
||||
return new MockXADataSourceWrapper(); |
||||
} |
||||
|
||||
} |
||||
|
||||
private static class MockXADataSourceWrapper implements XADataSourceWrapper { |
||||
|
||||
private XADataSource dataSource; |
||||
|
||||
@Override |
||||
public DataSource wrapDataSource(XADataSource dataSource) { |
||||
this.dataSource = dataSource; |
||||
return mock(DataSource.class); |
||||
} |
||||
|
||||
public XADataSource getXaDataSource() { |
||||
return this.dataSource; |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue