Announcement Announcement Module
Collapse
No announcement yet.
Setting up the contextSource bean using info from Database instead of XML file Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

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

    I'm currently running spring-ldap using a XML file to store the LDAP information I need (password, username, etc.) as is:

    Code:
    <bean id="contextSource"
    		class="org.springframework.ldap.core.support.LdapContextSource">
    		<property name="url" value="ldaps://xxx:636" />
    		<property name="base" value="c=fr" />
    		<property name="userDn" value="xxx" />
    		<property name="password" value="secret" />
    	</bean>
    Those information are stored on a Database, and I would like to retrieve them and to use them... but I don't know how to map these information in order to set up the contextSource bean when the application is started (using the class StartupListener)!

    I already have getter methods to retrieve the information I need; I just don't know how to set up the bean...

    Any ideas?

    I'm currently running spring-ldap 1.2.1 with AppFuse 1.9.4.

    Thanks in advance!

  • #2
    Now we're into Spring core container functionality. Personally, I think it's a bad idea to store configuration properties in a database, but nevertheless, perhaps this thread can give you some ideas.

    Comment


    • #3
      Hey,

      thanks for your reply. I tried to post a similar topic in the Core Container section yesterday but apparently it was not published...

      I have to say I don't really understand the link you gave... I'm quite of a beginner at Java Beans and I didn't understand how to reach a value stored in a specific table on a specific database...

      The thing is I already have a proper manager for this table (called PARAM), so I can reach any parameter with a getter. The only thing I don't understand is how to set the bean dynamically, in a way...

      And yes, I know it's not a good idea for security purpose, but it's the easiest way when you have to deal with several developer, validation and production environment instances (cause of course, the properties on the development database is not the same as the ones on the production database)...

      Comment


      • #4
        I would probably create a custom FactoryBean for creating and setting up the ContextSource. The FactoryBean would subclass AbstractFactoryBean be responsible for retrieving the required data from DB (or the manager you're referring to) and creating and setting the properties on a LdapContextSource. Please note that if you are creating your ContextSource manually you need to explicitly call afterPropertiesSet() after setting the configuration parameters.

        Comment


        • #5
          Yes, this is an excellent example of the power of FactoryBean: a simple "new" is not enough; instead you need to perform some logic in order to be able to initialize your bean.

          Comment


          • #6
            Originally posted by rasky View Post
            I would probably create a custom FactoryBean for creating and setting up the ContextSource. The FactoryBean would subclass AbstractFactoryBean be responsible for retrieving the required data from DB (or the manager you're referring to) and creating and setting the properties on a LdapContextSource. Please note that if you are creating your ContextSource manually you need to explicitly call afterPropertiesSet() after setting the configuration parameters.
            Thanks for these clues... but as I said, I'm a beginner, and althought I learned many things from your message, I still lack the very basic to understand what to do (sorry...).

            My Web application is currently working with this piece of XML code to define the beans:

            Code:
            <bean id="contextSource" class="org.springframework.ldap.core.support.LdapContextSource">
            		<property name="url" value="ldaps://xxx:636" />
            		<property name="base" value="c=fr" />
            		<property name="userDn" value="xxx" />
            		<property name="password" value="secret" />
            	</bean>
            
            	<bean id="ldapTemplate"
            		class="org.springframework.ldap.core.LdapTemplate">
            		<constructor-arg ref="contextSource" />
            	</bean>
            
            	<bean id="utilisateurDao"
            		class="com.xxx.admin.dao.ldap.UtilisateurDaoLdap">
            		<property name="ldapTemplate" ref="ldapTemplate" />
            	</bean>
            I looked at LdapContextSource source code, and it extends AbstractContextSource. What I don't understand is what is called with the above XML piece of code? There is no constructor in LdapContextSource, and no constructor in AbstractContextSource. So what exactly happens when my server is launched and falls on the XML code?

            I suppose I shall replace the current contextSourcebean with something like

            Code:
            <bean id="contextSource" class="com.xxx.ldap.MyContextSourceFactory">
            	</bean>
            Where MyContextSourceFactory will be the piece of Java code that will use my ParamManager to retrieve the info from the DB.

            Since this bean will be used by the ldapTemplate bean, and ldapTemplate is expecting a ContextSource as a parameter, how to implement such a bean in Java? I honestly have no idea, and I don't know where to look for examples... and the source code is definitely obscure to me

            I tried to write MyContextSource.java, but since I have no idea what to do (I don't know how to return the ContextSource the LdapTemplate is waiting for since I don't know how a class called from a bean works), I did crap (I don't even dare showing you what I did !) and got this while running my application:

            Code:
            org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'contextSource' defined in ServletContext resource [/WEB-INF/applicationContext-hibernate.xml]: 1 constructor arguments specified but no matching constructor found in bean 'contextSource' (hint: specify index and/or type arguments for simple parameters to avoid type ambiguities)
            I would love to understand your previous message, but unfortunately it's Korean to me (I don't say Chinese because I'm studying it )

            Could you give me a little bit more details?

            Thanks for your help!
            Last edited by Pierrre; Aug 12th, 2008, 09:00 AM.

            Comment


            • #7
              I would love to help you out in more detail, but I'm sitting a little tight at the moment. Please have a look at Chapter 3 of the Spring manual, and in particular the section on FactoryBean. Experiment with Spring without involving Spring LDAP, to get the concepts clear. Then get back here and we'll see what we can do.

              Comment


              • #8
                Thanks for those links, I'll dig this out...

                I also found a blog article summarizing the FactoryBean concept:

                http://blog.arendsen.net/index.php/2...e-uncreatable/

                Comment


                • #9
                  Yes, that's a good description of the concept.

                  I noted a small error when reading through it quickly. In section "Objects obtained from ’strange’ locations", Alef writes:

                  We can just ‘new’ a DataSource because we want to have the DataSource configured in JNDI.
                  What he most likely meant was that we can not just 'new' a DataSource. The paragraph should read:

                  Consider objects obtained from a JNDI context like DataSources. We can not just ‘new’ a DataSource because we want to have the DataSource configured in JNDI. So it has to be explicitly retrieved from the JNDI tree by creating a new InitialContext and using its lookup() method. Not exactly JavaBean-friendly. Because we still want to wire DataSources in our Spring context for example we need some level of indirection. The FactoryBean provides a solution.

                  Comment


                  • #10
                    I'm getting a little bit more comfortable with all these...

                    I found a way of achieving what I needed,

                    Code:
                    	<bean id="myContextSource" class="com.xxx.ldap.MyContextSourceFactory" />
                     
                    	<bean id="ldapTemplate"
                    		class="org.springframework.ldap.core.LdapTemplate">
                    		<constructor-arg ref="myContextSource" />
                    	</bean>
                    MyContextSource looks like this:

                    Code:
                    public class MyContextSourceFactory implements FactoryBean {
                    	private String url;
                    	private String base;
                    	private String userDn;
                    	private String password;
                    	
                    	public Class getObjectType() {
                    		return LdapContextSource.class;
                    	}
                    
                    	public boolean isSingleton() {
                    		return false;
                    	}
                    
                    	public Object getObject() throws Exception {
                    
                    		this.url = "ldaps://xxx:636";
                    		this.base = "c=fr";
                    		this.userDn = "xxx";
                    		this.password = "secret";
                    		
                    		LdapContextSource contextSource = new LdapContextSource();
                    		contextSource.setUrl(url);
                    		contextSource.setBase(base);
                    		contextSource.setUserDn(userDn);
                    		contextSource.setPassword(password);
                    		contextSource.afterPropertiesSet();
                    		
                    		return contextSource;
                    	}
                    }
                    For now, I'm just testing it with direct values, and it works fine, but obviously does nothing more than the previous XML definition.

                    So instead of

                    Code:
                    this.url = "ldaps://xxx:636";
                    I would like something like

                    Code:
                    this.url = paramManager.getUrl();
                    Where the paramManager is the Manager I'm using to get all the info I need from the DB.

                    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?

                    There is probably something to do in the StartupListener (I'm using AppFuse, and StartupListener is a class defined to initialize the needed info at startup), but setting up in the StartupListeneer would be too late since ldapTemplate is initialized before the call to StartupListener... I don't know what to do and am stuck, again =_=

                    Comment


                    • #11
                      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.

                      Comment


                      • #12
                        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.

                        Comment


                        • #13
                          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.

                          Comment


                          • #14
                            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!

                            Comment

                            Working...
                            X