Page 2 of 2 FirstFirst 12
Results 11 to 14 of 14

Thread: Setting up the contextSource bean using info from Database instead of XML file

  1. #11
    Join Date
    Jul 2005
    Location
    Helsingborg, Sweden
    Posts
    504

    Default

    To summarize: the problem is that we want to set the ContextSource properties using values retrieved from a database rather than from a properties file. The context configuration file looks something like this:

    Code:
    <beans ...>
       <bean id="contextSource"
             class="org.springframework.ldap.core.support.LdapContextSource">
          <property name="url" value="${ldap.url}" />
          <property name="base" value="${ldap.base}" />
          <property name="userDn" value="${ldap.userDn}" />
          <property name="password" value="${ldap.password}" />
       </bean>
    
       <bean id="ldapTemplate"
             class="org.springframework.ldap.core.LdapTemplate">
          <constructor-arg ref="contextSource" />
       </bean>
    </beans>
    I skipped the ContextSourceFactoryBean approach and went back to look at the post about using Apache Commons Configuration from my colleague Alin Dreghiciu. I got it working. At the moment, the database must be running before the context is loaded. We will assume it contains this information:

    Code:
    CREATE TABLE myconfig (
         key   VARCHAR NOT NULL PRIMARY KEY,
         value VARCHAR
     );
    
    INSERT INTO myconfig (key, value) VALUES ('ldap.url', 'ldap://localhost:3900');
    INSERT INTO myconfig (key, value) VALUES ('ldap.base', 'dc=jayway,dc=se');
    INSERT INTO myconfig (key, value) VALUES ('ldap.userDn', 'uid=admin,ou=system');
    INSERT INTO myconfig (key, value) VALUES ('ldap.password', 'secret');
    Table and column names are all configurable. The db.properties file contains this:

    Code:
    jdbc.driver=org.hsqldb.jdbcDriver
    jdbc.url=jdbc:hsqldb:hsql://localhost/aname
    jdbc.username=sa
    jdbc.password=
    config.table=myconfig
    config.keyColumn=key
    config.valueColumn=value
    The key points in this solution are:

    • the propertiesArray property of PropertyPlaceholderConfigurer (Spring)
    • the DatabaseConfiguration (Commons Configuration)
    • the MethodInvokingFactoryBean (Spring)

    The propertiesArray gives us the ability to merge properties from several sources; in our case a file (for the DataSource properties) and a database (for the ContextSource properties).

    Code:
    <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
       <property name="propertiesArray">
          <list>
             <ref bean="myPropsFromFile" />
             <ref bean="myPropsFromDb" />
          </list>
       </property>
    </bean>
    <bean id="myPropsFromFile"
          class="org.springframework.beans.factory.config.PropertiesFactoryBean">
       <property name="location" value="classpath:/conf/db.properties" />
    </bean>
    The DatabaseConfiguration performs all the work necessary to read properties from a database table. The internal configuration format is converted into standard Properties by the ConfigurationConverter:

    Code:
    <bean id="myPropsFromDb"
          class="org.apache.commons.configuration.ConfigurationConverter"
          factory-method="getProperties">
       <constructor-arg>
          <bean class="org.apache.commons.configuration.DatabaseConfiguration">
             <constructor-arg ref="dataSource" />
             <constructor-arg ref="prop.config.table" />
             <constructor-arg ref="prop.config.keyColumn" />
             <constructor-arg ref="prop.config.valueColumn" />
          </bean>
       </constructor-arg>
    </bean>
    The MethodInvokingFactoryBean is used to wire up String beans with property values retrieved from the file configuration source:

    Code:
    <bean id="prop.jdbc.driver"
          class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
       <property name="targetObject" ref="myPropsFromFile" />
       <property name="targetMethod" value="getProperty" />
       <property name="arguments">
          <list>
             <value>jdbc.driver</value>
          </list>
       </property>
    </bean>
    ...
    These values are used in the DatabaseConfiguration bean, as well as in the DataSource bean.

    Code:
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
          destroy-method="close">
       <property name="driverClassName" ref="prop.jdbc.driver" />
       <property name="url" ref="prop.jdbc.url" />
       <property name="username" ref="prop.jdbc.username" />
       <property name="password" ref="prop.jdbc.password" />
    </bean>
    Note that we're using normal bean dependencies and no custom Java code. Spring will make sure that the sequence is correct. It goes like this:

    1. The PropertyPlaceholderConfigurer is a BeanFactoryPostProcessor, so before any beans are created, it will be called on to replace property placeholders from its set of properties. However, in order to be instantiated, it does need a few beans.
    2. First of all, the configurer needs the myPropsFromFile bean, which will be created and load the db.properties (containing JDBC driver, url and such).
    3. Having the myPropsFromFile bean allows the MethodInvokingFactoryBeans to get the database properties mentioned above.
    4. This in turn allows the DataSource and the DatabaseConfiguration to be created.
    5. The myPropsFromDb bean is created using a somewhat advanced version of the factory-method concept. The bean is actually the result of a call to the static method ConfigurationConverter.getProperties that takes the DatabaseConfiguration bean as argument and returns a Properties object containing all the properties from the database. Confusing? Read more here and here.
    6. The PropertyPlaceholderConfigurer does now have all its required beans. It will go through the context and replace all ${} placeholders with the corresponding property value.
    Ulrik Sandberg
    Jayway (www.jayway.com)
    Spring LDAP project member

  2. #12
    Join Date
    Mar 2005
    Location
    Landskrona, Sweden
    Posts
    505

    Default

    Quote Originally Posted by Pierrre View Post
    The problem is this Manager is defined during the startup, and the only way to get it is to get the Application Context method getBean("paramManager"). But at the time MyContextSource is called, paramManager is not accessible... Well, maybe it is, but I don't know how to access it! Any idea?
    If you inject "paramManager" to the FactoryBean using a setter (or a constructor argument) you should be OK I think. Also, you should not implement FactoryBean directly, but subclass from AbstractFactoryBean and implement createInstance instead.
    Mattias Arthursson
    Jayway AB (www.jayway.se)
    Spring-LDAP project member

  3. #13
    Join Date
    Jul 2005
    Location
    Helsingborg, Sweden
    Posts
    504

    Default

    So, let's try the ContextSourceFactoryBean approach as well then. I have that working too.

    I assume that the ParamManager looks something like this:

    Code:
    public class ParamManager {
       private JdbcTemplate jdbcTemplate;
       private String table;
       private String keyColumn;
       private String valueColumn;
       private Map<String, String> properties = new HashMap<String, String>();
    
       public ParamManager(JdbcTemplate jdbcTemplate, String table,
             String keyColumn, String valueColumn) {
          this.jdbcTemplate = jdbcTemplate;
          this.keyColumn = keyColumn;
          this.table = table;
          this.valueColumn = valueColumn;
       }
    
       public void loadProperties() {
          RowCallbackHandler callbackHandler = new RowCallbackHandler() {
             public void processRow(ResultSet rs) throws SQLException {
                String key = rs.getString(1);
                String value = rs.getString(2);
                properties.put(key, value);
             }
          };
          String sql = "select " + keyColumn + ", " + valueColumn + " from "
                + table;
          jdbcTemplate.query(sql, callbackHandler);
       }
    
       public String getProperty(String key) {
          return properties.get(key);
       }
    }
    When loadProperties is called, ParamManager connects to the database and fills a Map with the keys and values from the given configuration table. Note the generic getProperty method.

    Time for the ContextSourceFactoryBean. As Mattias noted, it's good practice to extend from AbstractFactoryBean. We give it a ParamManager property, which we later will configure Spring to inject for us.

    Code:
    public class ContextSourceFactoryBean extends AbstractFactoryBean {
    
       private ParamManager paramManager;
    
       public void setParamManager(ParamManager paramManager) {
          this.paramManager = paramManager;
       }
    
       @Override
       protected Object createInstance() throws Exception {
          LdapContextSource contextSource = new LdapContextSource();
          contextSource.setUrl(paramManager.getProperty("ldap.url"));
          contextSource.setBase(paramManager.getProperty("ldap.base"));
          contextSource.setUserDn(paramManager.getProperty("ldap.userDn"));
          contextSource.setPassword(paramManager.getProperty("ldap.password"));
          contextSource.afterPropertiesSet();
    
          return contextSource;
       }
    
       @Override
       public Class getObjectType() {
          return LdapContextSource.class;
       }
    }
    This is all the custom Java code required. Now we will wire everything together. Here is the db.properties file:

    Code:
    jdbc.driver=org.hsqldb.jdbcDriver
    jdbc.url=jdbc:hsqldb:hsql://localhost/aname
    jdbc.username=sa
    jdbc.password=
    config.table=myconfig
    config.keyColumn=key
    config.valueColumn=value
    As in the previous solution, we assume that the database is running and that it looks like this:

    Code:
    CREATE TABLE myconfig (
         `key`   VARCHAR NOT NULL PRIMARY KEY,
         `value` VARCHAR
     );
    
    INSERT INTO myconfig (key, value) VALUES ('ldap.url', 'ldap://localhost:3900');
    INSERT INTO myconfig (key, value) VALUES ('ldap.base', 'dc=jayway,dc=se');
    INSERT INTO myconfig (key, value) VALUES ('ldap.userDn', 'uid=admin,ou=system');
    INSERT INTO myconfig (key, value) VALUES ('ldap.password', 'secret');
    And here is the context configuration file:

    Code:
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                            http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
       <bean id="dbConfigurer"
          class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
          <property name="location" value="classpath:/conf/db.properties" />
       </bean>
       <bean id="contextSource"
          class="se.jayway.demo.spring.ldap.util.ContextSourceFactoryBean">
          <property name="paramManager" ref="paramManager" />
       </bean>
       <bean id="ldapTemplate" class="org.springframework.ldap.core.LdapTemplate">
          <constructor-arg ref="contextSource" />
       </bean>
       <bean id="personDao" class="se.jayway.demo.spring.ldap.dao.PersonDaoImpl">
          <property name="ldapTemplate" ref="ldapTemplate" />
       </bean>
       <bean id="paramManager" class="se.jayway.demo.spring.ldap.util.ParamManager"
          init-method="loadProperties">
          <constructor-arg ref="jdbcTemplate" />
          <constructor-arg value="${config.table}" />
          <constructor-arg value="${config.keyColumn}" />
          <constructor-arg value="${config.valueColumn}" />
       </bean>
       <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
          <constructor-arg ref="dataSource" />
       </bean>
       <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
          destroy-method="close">
          <property name="driverClassName" value="${jdbc.driver}" />
          <property name="url" value="${jdbc.url}" />
          <property name="username" value="${jdbc.username}" />
          <property name="password" value="${jdbc.password}" />
       </bean>
    </beans>
    Note the 'init-method' attribute on the paramManager bean. Spring will call loadProperties for us once the bean has been created.
    Ulrik Sandberg
    Jayway (www.jayway.com)
    Spring LDAP project member

  4. #14
    Join Date
    Jul 2008
    Location
    Paris, France
    Posts
    15

    Default

    Whaaaaa!

    Thank you guys so much for your help! Ulsa, your explanations are very detailed and precise, it's a great help for me to understand the concepts you show.

    Rasky, thanks to ulsa comments, I understood your last message and tried it... and it worked!

    So I'm posting my method, it might help.

    First of all, I initialized used beans in my applicationContext-resource.xml file:

    Code:
    	<bean id="myContextSource"
    		class="com.xxx.ldap.MyContextSourceFactory">
    		<constructor-arg ref="paramManager" />
    	</bean>
    
    	<bean id="ldapTemplate"
    		class="org.springframework.ldap.core.LdapTemplate">
    		<constructor-arg ref="myContextSource" />
    	</bean>
    	<bean id="utilisateurDao"
    		class="com.xxx.admin.dao.ldap.UtilisateurDaoLdap">
    		<property name="ldapTemplate" ref="ldapTemplate" />
    	</bean>
    I don't know why but it didn't come to my mind I could use the paramManager bean as a constructor argument... thanks, rasky!

    paramManager is following the AppFuse case on how Managers are implemented.

    Then I implement MyContextSourceFactory as a subclass of AbstractFactoryBean:

    Code:
    package com.steria.mireor.ldap;
    
    import org.springframework.beans.factory.config.AbstractFactoryBean;
    import org.springframework.ldap.core.support.LdapContextSource;
    
    import com.xxx.service.ParamManager;
    
    public class MyContextSourceFactory extends AbstractFactoryBean {
    
    	private String url;
    	private String base;
    	private String userDn;
    	private String password;
    
    	public void setBase(String base) {
    		this.base = base;
    	}
    
    	public void setPassword(String password) {
    		this.password = password;
    	}
    
    	public void setUrl(String url) {
    		this.url = url;
    	}
    
    	public void setUserDn(String userDn) {
    		this.userDn = userDn;
    	}
    
    	public Class getObjectType() {
    		return LdapContextSource.class;
    	}
    
    	public boolean isSingleton() {
    		return true;
    	}
    
    	public MyContextSourceFactory(ParamManager paramManager) {
    		url = paramManager.getLdapUrl();
    		base = paramManager.getBase();
    		userDn = paramManager.getManagerDn();
    		password = paramManager.getManagerPassword();
    	}
    
    	protected Object createInstance() throws Exception {
    		LdapContextSource contextSource = new LdapContextSource();
    		contextSource.setUrl(url);
    		contextSource.setBase(base);
    		contextSource.setUserDn(userDn);
    		contextSource.setPassword(password);
    		contextSource.afterPropertiesSet();
    
    		return contextSource;
    	}
    }
    Et voilą... since paramManager can get all the info I need, the job is done.

    1. the bean myContextSource is initialized using the existing paramManager bean as its constructor argument, allowing it to get the info needed from the DataBase;
    2. createInstance is called in order to return the LdapContextSource the ldapTemplate Bean is asking for.

    Once again, thank you very much; I really learnt many things those past days thanks to you!

Tags for this Thread

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •