Different JMS connection factories instantiation via driver name and URL - java

Spring JDBC allows to specify in properties file for PROD:
jdbc.driverClassName = oracle.jdbc.OracleDriver
jdbc.url = jdbc:oracle:thin:#...
and for tests
jdbc.driverClassName = org.h2.Driver
jdbc.url = jdbc:h2:mem:test;INIT=...
Thus it's possible to instantiate needed java.sql.DataSource instance depends of configuration settings with generic code
#Bean
public DataSource dataSource(
#Value("${jdbc.driverClassName}") String driverClass,
#Value("${jdbc.url}") String url,
#Value("${jdbc.username}") String username,
#Value("${jdbc.password}") String password
) {
DriverManagerDataSource dataSource = new DriverManagerDataSource(url, username, password);
dataSource.setDriverClassName(driverClass);
return dataSource;
}
Is it possible in Spring to configure specific type of java.jms.ConnectionFactory via driver and URL properties' strings like in Spring JDBC?
Actually, my goal is to use Tibco connection factory for PROD and ActiveMQ for tests.

You can use spring profiles to pull in a different Bean for tests or you can simply override the connection factory bean with a different one in the test case.
EDIT
#Bean
public FactoryBean<ConnectionFactory> connectionFactory(#Value("${jms.url}") final String urlProp) {
return new AbstractFactoryBean<ConnectionFactory>() {
#Override
public Class<?> getObjectType() {
if (urlProp.startsWith("activemq:")) {
return ActiveMQConnectionFactory.class;
}
// ...
throw new RuntimeException("bad url: " + urlProp);
}
#Override
protected ConnectionFactory createInstance() throws Exception {
if (urlProp.startsWith("activemq:")) {
URI uri = new URI(urlProp.substring(urlProp.indexOf(":") + 1));
return new ActiveMQConnectionFactory(uri);
}
// ...
throw new RuntimeException("bad url: " + urlProp);
}
};
}
and
jms.url=activemq:vm://localhost

Related

Spring use Computer name instead of supplied username for jdbc database access

Reading Pro Spring 5, an example that show Spring JDBC is using MySQL DB. I use the book source code. Here is the code
#Configuration
#PropertySource("classpath:db/jdbc2.properties")
#ComponentScan(basePackages = "com.apress.prospring5.ch6")
public class AppConfig {
private static Logger logger = LoggerFactory.getLogger(AppConfig.class);
#Value("${driverClassName}")
private String driverClassName;
#Value("${url}")
private String url;
#Value("${username}")
private String username;
#Value("${password}")
private String password;
#Bean(destroyMethod = "close")
public DataSource dataSource() {
try {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName(driverClassName);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
} catch (Exception e) {
logger.error("DBCP DataSource bean cannot be created!", e);
return null;
}
}
jdbc2.properties
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/musicdb?useSSL=true
username=prospring5
password=prospring5
The test
public class AnnotationJdbcTest {
private GenericApplicationContext ctx;
private SingerDao singerDao;
#Before
public void setUp() {
ctx = new AnnotationConfigApplicationContext(AppConfig.class);
singerDao = ctx.getBean(SingerDao.class);
assertNotNull(singerDao);
}
#Test
public void testFindAll() {
List<Singer> singers = singerDao.findAll();
assertTrue(singers.size() == 3);
listSingers(singers);
ctx.close();
}
My MySQL instance already have the user prospring5 and the schema created and populated
When I try to run AnnotationJdbcTest, I get this exception:
Failed to obtain JDBC Connection; nested exception is java.sql.SQLException: Cannot create PoolableConnectionFactory (Access denied for user 'Mehdi'#'localhost' (using password: YES))
org.springframework.jdbc.CannotGetJdbcConnectionException: Failed to obtain JDBC Connection; nested exception is java.sql.SQLException: Cannot create PoolableConnectionFactory (Access denied for user 'Mehdi'#'localhost' (using password: YES))
at org.springframework.jdbc.datasource.DataSourceUtils.getConnection(DataSourceUtils.java:82)
at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:612)
As you can see the project is using my computer name 'Mehdi' instead of the one in the .properties file 'prospring5' . Why is That? and how Can I fix it?
You can yourself download the source code and run it from here: https://github.com/Apress/pro-spring-5
the project is: chapter6/spring-jdbc-annotations
EDIT:
I printed the values as suggested by #STaefi and here is the output:
com.mysql.cj.jdbc.Driver
jdbc:mysql://localhost:3306/musicdb?useSSL=false
Mehdi
prospring5
Code
#Bean()
public DataSource dataSource() {
try {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName(driverClassName);
System.out.println(driverClassName);
dataSource.setUrl(url);
System.out.println(url);
dataSource.setUsername(username);
System.out.println(username);
dataSource.setPassword(password);
System.out.println(password);
return dataSource;
First I tried setting the values at initialization and that was no good. but after I used username = "prospring5"; dataSource.setUsername(username); it worked. so what does this mean. why Spring cannot load the username like it successfully loaded the url and the password.
Could you please try by giving a default value as :
#Value("${driverClassName:com.mysql.cj.jdbc.Driver}")
private String driverClassName;
#Value("${url:jdbc:mysql://localhost:3306/musicdb?useSSL=true}")
private String url;
#Value("${username:prospring5}")
private String username;
#Value("${password:prospring5}")
private String password;
And remove the #PropertySource("classpath:db/jdbc2.properties") at the top. If this works then you can try with the propertySource again
So I have fixed the problem by simply using a different String identifier in the .propreties file . I changed it to one=prospring5 and it worked. Apparently using a proprety named 'username' will be loaded with anything other than Your Computer name. I don't know if Spring provides a list of prohibited values for property names. if they don't, they certainly should consider.

What to do if I don't want to set the datasource in application.properties?

I have a spring boot application. And there is a postgres driver. But I don't want to set
spring.datasource.url=jdbc:postgresql://localhost:5432/app
spring.datasource.username=postgres
spring.datasource.password=qwerty
in application.properties, because I did it later in code.
If I delete this part I have problem:
Description:
Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.
Reason: Failed to determine suitable jdbc url
How to fix it?
UPD:
#RequestMapping(value = "setdb/{url}/{db}/{schema}/{login}/{password}")
public String setDB(#PathVariable(name = "url") String url,
#PathVariable(name = "db") String db,
#PathVariable(name = "schema") String schema,
#PathVariable(name = "login") String login,
#PathVariable(name = "password") String password) throws SQLException, ClassNotFoundException {
this.url = url;
this.db = db;
this.schema = schema;
this.login = login;
this.password = password;
Class.forName("org.postgresql.Driver");
url = "jdbc:postgresql://" + url + "/" + db + "?currentSchema=" + schema + "/";
connection = DriverManager.getConnection(url, login, password);
//anothercode
}
You have to define it at .properties file and then use it as source at java class, because it is a best practice to define properties at separate files.
As an example I use database properties in my Hibernate config class by annotation
#PropertySource(value = "classpath:db.properties")
Spring Boot auto configures the datasource: if you dont want that because you want to take care of it yourself or for other reasons (for example I disable it during unit testing) you can use an annotation
#SpringBootApplication(exclude = {
DataSourceAutoConfiguration.class
})

Need to change the database Name of datasource url dynamically(Multi tenant database )

I'm using Spring boot and Spring-data-Jpa. I am setting my data source URL, username, and password in the application.properties file. It works perfectly for one database connection,Now I am facing issue with my Database project structure that is based on the particular user his own database need to connect and get the result to a particular user database and I can achieve this using abstract data source, DataSourceBuilder at configuration level(it is one time can I able to change data source dynamically) but I need change in data source each time controller hits.
here is some code for application.properties and I have injected my datasource using autowire.
abstract datasource I have used and it is limited to the static client, But in my structure Clients Database keep on increasing so it's not useful for me
spring.datasource.url=jdbc:sqlserver://test-datbase:1433;dbName1
spring.datasource.username=userName
spring.datasource.password=Password
spring.datasource.driverClassName=com.microsoft.sqlserver.jdbc.SQLServerDriver
Need code or method I can change my database connection on every hit to the controller
Note: I just need to change my database, My dialect and everything else will be the same.
Yes, we can do it by using placeholder. Set -DdbName1=YOUR_DB_NAME in environment variables. For example:
spring.datasource.url=jdbc:sqlserver://test-datbase:1433;${dbName1}
spring.datasource.username=userName
spring.datasource.password=Password
spring.datasource.driverClassName=com.microsoft.sqlserver.jdbc.SQLServerDriver
Here is how I'd resolve such problem:
You can create 2 separate data sources. Create qualifiers for them and both inject to your controller.
Then in endpoint write logic which would select one of the sources to save info.
Here is how to add extra data source to you project:
https://medium.com/#joeclever/using-multiple-datasources-with-spring-boot-and-spring-data-6430b00c02e7
I think that it is a good idea to use Wildfly in this situation. At Wildfly, you can change the connected database using settings.
My solution:
enter link description here
and please wirte your own PersistenceConfiguration class when you choose database
enter link description here
if you would like to choose the base dynamically using methods in the java code
with the help of below link i can able to set my multiple datasource when server get start
https://fizzylogic.nl/2016/01/24/make-your-spring-boot-application-multi-tenant-aware-in-2-steps/
but i want to remove configuration annotation like below and set my tenant using method as below,but through this i am not able to connect to database.
public class MultitenantConfiguration {
#Bean
#ConfigurationProperties(
prefix = "spring.datasource"
)
public DataSource dataSource(ArrayList<String> names) {
Map<Object,Object> resolvedDataSources = new HashMap<>();
for(String dbName: names) {
DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create(this.getClass().getClassLoader());
dataSourceBuilder.driverClassName("com.microsoft.sqlserver.jdbc.SQLServerDriver")
.url("jdbc:sqlserver://abc.server;databaseName="+dbName+"")
.username("userName")
.password("Password");
resolvedDataSources.put(dbName, dataSourceBuilder.build());
}
MultitenantDataSource dataSource = new MultitenantDataSource();
dataSource.setDefaultTargetDataSource(defaultDataSource());
dataSource.setTargetDataSources(resolvedDataSources);
dataSource.afterPropertiesSet();
return dataSource;
}
/**
* Creates the default data source for the application
* #return
*/
private DataSource defaultDataSource() {
DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create(this.getClass().getClassLoader())
.driverClassName("com.microsoft.sqlserver.jdbc.SQLServerDriver")
.url("jdbc:abc.server;databaseName=test")
.username("UserName")
.password("Password");
return dataSourceBuilder.build();
}
}
I've done a project that I can create multiple dataSources with your specific changeSets, so if you need to add another dataSource, it would just change your application.yml, no longer needing to change the code.
But if not use, just remove the liquibase that works too!
On each hit for your controller, you need to get an X-TenantId header, which will change your ThreadLocal, which in turn changes the datasource according with tenant
Code complete: https://github.com/dijalmasilva/spring-boot-multitenancy-datasource-liquibase
application.yml
spring:
dataSources:
- tenantId: db1
url: jdbc:postgresql://localhost:5432/db1
username: postgres
password: 123456
driver-class-name: org.postgresql.Driver
liquibase:
enabled: true
default-schema: public
change-log: classpath:db/master/changelog/db.changelog-master.yaml
- tenantId: db2
url: jdbc:postgresql://localhost:5432/db2
username: postgres
password: 123456
driver-class-name: org.postgresql.Driver
- tenantId: db3
url: jdbc:postgresql://localhost:5432/db3
username: postgres
password: 123456
driver-class-name: org.postgresql.Driver
TenantContext
public class TenantContext {
private static ThreadLocal<String> currentTenant = new ThreadLocal<>();
static String getCurrentTenant() {
return currentTenant.get();
}
static void setCurrentTenant(String tenant) {
currentTenant.set(tenant);
}
static void clear() {
currentTenant.remove();
}
}
Filter to Controllers
public class TenantFilter extends GenericFilterBean {
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
final String X_TENANT_ID = "X-TenantID";
final HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
final String tenantId = httpServletRequest.getHeader(X_TENANT_ID);
if (tenantId == null) {
final HttpServletResponse response = (HttpServletResponse) servletResponse;
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.getWriter().write("{\"error\": \"No tenant header supplied\"}");
response.getWriter().flush();
TenantContext.clear();
return;
}
TenantContext.setCurrentTenant(tenantId);
filterChain.doFilter(servletRequest, servletResponse);
}
}
Configuration class if use liquibase
#Configuration
#ConditionalOnProperty(prefix = "spring.liquibase", name = "enabled", matchIfMissing = true)
#EnableConfigurationProperties(LiquibaseProperties.class)
#AllArgsConstructor
public class LiquibaseConfiguration {
private LiquibaseProperties properties;
private DataSourceProperties dataSourceProperties;
#Bean
#DependsOn("tenantRoutingDataSource")
public MultiTenantDataSourceSpringLiquibase liquibaseMultiTenancy(Map<Object, Object> dataSources,
#Qualifier("taskExecutor") TaskExecutor taskExecutor) {
// to run changeSets of the liquibase asynchronous
MultiTenantDataSourceSpringLiquibase liquibase = new MultiTenantDataSourceSpringLiquibase(taskExecutor);
dataSources.forEach((tenant, dataSource) -> liquibase.addDataSource((String) tenant, (DataSource) dataSource));
dataSourceProperties.getDataSources().forEach(dbProperty -> {
if (dbProperty.getLiquibase() != null) {
liquibase.addLiquibaseProperties(dbProperty.getTenantId(), dbProperty.getLiquibase());
}
});
liquibase.setContexts(properties.getContexts());
liquibase.setChangeLog(properties.getChangeLog());
liquibase.setDefaultSchema(properties.getDefaultSchema());
liquibase.setDropFirst(properties.isDropFirst());
liquibase.setShouldRun(properties.isEnabled());
return liquibase;
}
}
Code complete: https://github.com/dijalmasilva/spring-boot-multitenancy-datasource-liquibase

Connect JMS queue using JNDI with Spring Boot

I had a hard time figuring out how to implement a Spring Boot JMS Listener, listening to an ActiveMQ queue within a JBoss application server.
Therefore I choose to post a question and answer it with my final solution, hoping it could save some of you a few hours.
ActiveMQ is supported by Spring Boot autoconfiguration, but since it was inside the JBoss server Spring Boot was failing to connect ActiveMQ.
In fact you need to define connectionFactory and jmsListenerContainerFactory beans yourself by doing a lookup on the JNDI provider.
#Configuration
#EnableJms
public class ActiveMqConnectionFactoryConfig {
#Value("${broker.url}")
String brokerUrl;
#Value("${borker.username}")
String userName;
#Value("${borker.password}")
String password;
#Value("${queue}")
String queueName;
private static final String INITIAL_CONTEXT_FACTORY = "org.jboss.naming.remote.client.InitialContextFactory";
private static final String CONNECTION_FACTORY = "jms/RemoteConnectionFactory";
#Bean
public ConnectionFactory connectionFactory() {
try {
System.out.println("Retrieving JMS queue with JNDI name: " + CONNECTION_FACTORY);
JndiObjectFactoryBean jndiObjectFactoryBean = new JndiObjectFactoryBean();
jndiObjectFactoryBean.setJndiName(CONNECTION_FACTORY);
jndiObjectFactoryBean.setJndiEnvironment(getEnvProperties());
jndiObjectFactoryBean.afterPropertiesSet();
return (QueueConnectionFactory) jndiObjectFactoryBean.getObject();
} catch (NamingException e) {
System.out.println("Error while retrieving JMS queue with JNDI name: [" + CONNECTION_FACTORY + "]");
} catch (Exception ex) {
System.out.println("Error");
}
return null;
}
Properties getEnvProperties() {
Properties env = new Properties();
env.put(Context.INITIAL_CONTEXT_FACTORY, INITIAL_CONTEXT_FACTORY);
env.put(Context.PROVIDER_URL, brokerUrl);
env.put(Context.SECURITY_PRINCIPAL, userName);
env.put(Context.SECURITY_CREDENTIALS, password);
return env;
}
#Bean
public DefaultJmsListenerContainerFactory jmsListenerContainerFactory(ConnectionFactory connectionFactory) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
JndiDestinationResolver jndiDestinationResolver = new JndiDestinationResolver();
jndiDestinationResolver.setJndiEnvironment(getEnvProperties());
factory.setDestinationResolver(jndiDestinationResolver);
return factory;
}
Then if you want to consume the queue you just define your JMS consumer class with a method annotated with #JmsListener(destination = "${queue}")
#JmsListener(destination = "${queue}")
public void receive(Message message) {
System.out.println("Received Message: " + message);
}
Hope that helps save a few hours of research ;)
Cheers

How to read connection pool settings from resource adapter?

I am working on a new resource adapter for Glassfish.
It uses a connection pool that has a property set in the admin console.
Connector Connection Pools -> Additional Properties -> name=url, value=127.0.0.1
I would like to read this property from the resource adapter.
(from my managed connection implementation class for example)
I tried checking the documentation and online examples but did not find out how to do it.
This is the common way for almost all web apps on j2ee containers with connection pools.
InitialContext ctx = new InitialContext();
//The JDBC Data source that we just created
DataSource ds = (DataSource) ctx.lookup("url here");
Connection connection = ds.getConnection();
#Connector(reauthenticationSupport = false, transactionSupport = TransactionSupport.TransactionSupportLevel.NoTransaction)
public class SocketResourceAdapter implements ResourceAdapter {
/** The logger */
private static Logger log = Logger.getLogger("SocketResourceAdapter");
/** Name property */
#ConfigProperty(defaultValue = "DefaultMessage", supportsDynamicUpdates = true)
private String name;
#ConfigProperty(defaultValue = "---", type = String.class)
private String url;
#ConfigProperty(type = Integer.class)
private Integer port;
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public Integer getPort() {
return port;
}
public void setPort(Integer port) {
this.port = port;
}
And then I can just use getUrl() in the resource adapter.
At first it did not work because I was setting the properties for the connection factory and not the resource adapter.

Categories

Resources