domenica 3 maggio 2015

Spring esternalizzare configurazioni datasource e cifrare password

Scenario: abbiamo una applicazione Java che si connettead un db utilizzando Spring, vogliamo fare in modo che i dati della connettività siano spostati dal file di contesto di Spring ad un normale file di properties esterno al jar, in modo che l'utente possa personalizzarlo a seconda dell'ambiente di esecuzione senza dover toccare il file specifico del context di Spring.
Nel file di configurazione di Spring è possibile definire la seguente proprietà:

<context:property-override location="file:conf/db.properties"/>

Il file è letto in un percorso esterno rispetto al jar di esecuzione, come specificato appunto dalla direttiva file.

Il file esterno avrà una struttura di questo tipo (quella tipica di un file di properties):


dataSource.url=jdbc:sqlserver://localhost:1433;dataBaseName=hibernatetest
dataSource.username=pippo
dataSource.password=m4dr+75TW7u0nWFUU52IaQ==


Quindi nell'applicationContext.xml avremo la seguente definizione del dataSource:


 <bean id="dataSource"
  class="it.dao.BasicDataSourceCifrato">
  <property name="driverClassName" value="com.microsoft.sqlserver.jdbc.SQLServerDriver" />
  <property name="url" value="${dataSource.url}" />
  <property name="username" value="${dataSource.username}" />
  <property name="password" value="${dataSource.password}" />
  <property name="initialSize" value="5"></property>
  <property name="maxActive" value="10"></property>
 </bean>


Il datasource è stato ridefinito estendendo  org.apache.commons.dbcp.BasicDataSource .
In questo modo possiamo effettuare la ridefinizione del metodo setPassword inserendo la decifratura della password:


package it.dao;
import it.oasi.cipher.Cifratura;
import org.apache.commons.dbcp.BasicDataSource;
public class BasicDataSourceCifrato extends BasicDataSource {
 public BasicDataSourceCifrato() {
 }
 public synchronized void setPassword(String password) {     
        super.setPassword(decryptPassword(password));
    }
 private String decryptPassword(String password) { 
          // qui si inserisce la logica di decifratura pwd
          ...... 
 }

}


Spring Jdbc Template

In alcuni casi particolari risulta molto utile utilizzare per l'accesso al DBMS JDBC invece degli ORM (Hibernate,EBatis etc.), soprattutto in ambienti legacy dove il database è già esitente e non ottimizzato per l'utilizzo degli ORM.
Il problema di JDBC è comunque l'eccessiva verbosità del codice che ci costringe a gestire l'apertura delle connessioni, la chiusura, i PreparedStatement etc.
Una soluzione ideale in questo ambito è Spring Jdbc, che con il modello dei template ci esenta dal dover gestire manualmente il "plumbing code" JDBC.

Vediamo i passi da seguire per realizzare una applicazione client che si connette ad un db.

LIBRERIE

Ho utilizzato in questo caso spring 3.0
Le dipendenze maven sono le seguenti:


<properties>
 <org.springframework.version>3.0.0.RELEASE</org.springframework.version>
</properties>
 <dependencies>

  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-core</artifactId>
   <version>${org.springframework.version}</version>
  </dependency>
  <dependency>

   <groupId>org.springframework</groupId>

   <artifactId>spring-jdbc</artifactId>

   <version>${org.springframework.version}</version>

  </dependency>
  <dependency>
   <groupId>commons-dbcp</groupId>
   <artifactId>commons-dbcp</artifactId>
   <version>1.2.2</version>
  </dependency>
  <dependency>
   <groupId>commons-pool</groupId>
   <artifactId>commons-pool</artifactId>
   <version>1.4</version>
  </dependency>

 </dependencies>








FILE DI CONFIGURAZIONE

Il file di configurazione del context di Spring:

<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
 xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context"
 xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx"
 xmlns:task="http://www.springframework.org/schema/task"
 xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.0.xsd">


 <bean id="adUserDao" class="it.dao.AdUserDaoImpl">
  <property name="dataSource" ref="dataSource" />
 </bean>

 <bean id="dataSource"
  class="org.apache.commons.dbcp.BasicDataSource">

  <property name="driverClassName" value="com.microsoft.sqlserver.jdbc.SQLServerDriver" />
  <property name="url" value="jdbc:sqlserver://localhost:1433;dataBaseName=hibernatetest" />
  <property name="username" value="pippo" />
  <property name="password" value="pippo" />
  <property name="initialSize" value="5"></property>
  <property name="maxActive" value="10"></property>
 </bean>
 
</beans>


TABELLA

La tabella su cui si scrive e si legge ha il seguente script di creazione (DBMS Sql server versione 2008):


CREATE TABLE [dbo].[aduser](
 [id] [bigint] IDENTITY(1,1) NOT NULL,
 [name] [varchar](255) NOT NULL,
 [password] [varchar](255) NOT NULL,
 CONSTRAINT [PK_aduser] PRIMARY KEY CLUSTERED 
(
 [id] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]



INTERFACCIA ADUSERDAO


package it.dao;
import java.util.List;
import it.objects.AdUser;
public interface AdUserDao {
 
 public boolean insert(AdUser obj) throws DaoException ;
 
 public AdUser getUserById(int id) throws DaoException;
 
 public List<AdUser> getListaUtenti() throws DaoException;

}


CLASSE ADUSER 

package it.objects;
public class AdUser {
 private static final String A_CAPO = "\r\n";
 private int id;
 private String nome;
 private String password;
 public int getId() {
  return id;
 }
 public void setId(int id) {
  this.id = id;
 }
 public String getNome() {
  return nome;
 }
 public void setNome(String nome) {
  this.nome = nome;
 }
 public String getPassword() {
  return password;
 }
 public void setPassword(String password) {
  this.password = password;
 }
 public AdUser(){
  
 }
 public AdUser(String nome,String password){
  this.nome=nome;
  this.password=password;
  
 }
 public String toString(){
  StringBuffer sb=new StringBuffer();
  sb.append("ID: ");
  sb.append(this.id);
  sb.append(A_CAPO);
  sb.append("NOME: ");
  sb.append(this.nome);
  sb.append(A_CAPO);
  sb.append("PWD: ");
  sb.append(this.password);
  sb.append(A_CAPO);
  return sb.toString();
 }
}



IMPLEMENTAZIONE DAO 
package it.dao;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.sql.DataSource;
import org.springframework.jdbc.core.JdbcTemplate;
import it.objects.AdUser;
public class AdUserDaoImpl implements AdUserDao {
 private DataSource dataSource;
 private JdbcTemplate jdbcTemplate;
 public DataSource getDataSource() {
  return dataSource;
 }

 public void setDataSource(DataSource dataSource) {
  this.dataSource = dataSource;
 }

 public AdUserDaoImpl() {
  // TODO Auto-generated constructor stub
 }

 @Override
 public boolean insert(AdUser obj) throws DaoException {
  try
  {
   boolean retVal=false;
   jdbcTemplate=new JdbcTemplate(dataSource);
   String insert="insert into aduser(name,password) values (?,?)";
   int esito=jdbcTemplate.update(insert,new Object[]{obj.getNome(),obj.getPassword()});
   if(esito>0) retVal= true;
   return retVal;
  }
  catch(Exception ex){
   throw new DaoException("Errore nell'insert dettaglio "+ex.getMessage());
  }
 }

 @Override
 public AdUser getUserById(int id) throws DaoException {
  try
  {
   jdbcTemplate=new JdbcTemplate(getDataSource());
   String get="select id,name,password from aduser where id=?";
   AdUser retVal=jdbcTemplate.queryForObject(get,new Object[]{id}, new AdUserRowMapper());
   return retVal;
  }
  catch(Exception ex){
   throw new DaoException("Errore nel recupero dettaglio "+ex.getMessage());
  }
 }

 @Override
 public List<AdUser> getListaUtenti() throws DaoException {
  try
  {
   jdbcTemplate=new JdbcTemplate(getDataSource());
   String get="select id,name,password from aduser";
   List<AdUser> listaUtenti=new ArrayList<AdUser>();
   List<Map<String, Object>> rows =jdbcTemplate.queryForList(get);
    for (Map row : rows) {
     AdUser u=new AdUser();
     u.setId(Integer.parseInt(String.valueOf(row.get("ID"))));
     u.setNome((String)row.get("name"));
     u.setPassword((String)row.get("password"));
     listaUtenti.add(u);
    }
   return listaUtenti;
  }
  catch(Exception ex){
   throw new DaoException("Errore nel recupero dettaglio "+ex.getMessage());
  }
 }

}



ROWMAPPER 

Il RowMapper è una classe che implementa l'interfaccia di Spring org.springframework.jdbc.core.RowMapper e che serve a mappare il ResultSet con l'oggetto.

package it.dao;
import java.sql.ResultSet;
import java.sql.SQLException;
import it.objects.AdUser;
import org.springframework.jdbc.core.RowMapper;
public class AdUserRowMapper implements RowMapper<AdUser> {
 public AdUserRowMapper() {
  // TODO Auto-generated constructor stub
 }
 @Override
 public AdUser mapRow(ResultSet rs, int arg1) throws SQLException {
  // TODO Auto-generated method stub
  AdUser a=new AdUser();
  a.setId(rs.getInt("ID"));
  a.setNome(rs.getString("name"));
  a.setPassword(rs.getString("password"));
  return a;
 }

}



ESECUZIONE PROGRAMMA

 ConfigurableApplicationContext context=new ClassPathXmlApplicationContext("conf/applicationContext.xml");
   AdUserDao ad=(AdUserDao)context.getBean("adUserDao");
        AdUser user= ad.getUserById(1);
        System.out.println(user);
        List<AdUser> utenti=ad.getListaUtenti();
        for(AdUser a : utenti){
         System.out.println(a);
        }
        ad.insert(new AdUser("qweqw", "rrrrrrr"));
        context.close(); 

NOTE 

In questo caso abbiamo utilizzato la connessione da un pool gestito dalle librerie :

commons-dbcp-1.4.jar
commons-pool-1.4.jar

Si può anche decidere di utilizzare il DriverManagerDataSource di Spring che ci ritorna una connessione ogni volta che viene richiesta. Oppure il SingleConnectionDataSource che torna ogni volta la stessa connessione già utilizzata, come se si trattase di un pool di connessioni con size pari a 1.
Di seguito la configurazione per l'implementazione del DriverManagerDataSource:


<bean id="dataSource"
  class="org.springframework.jdbc.datasource.DriverManagerDataSource">

  <property name="driverClassName" value="com.microsoft.sqlserver.jdbc.SQLServerDriver" />
  <property name="url" value="jdbc:sqlserver://localhost:1433;dataBaseName=hibernatetest" />
  <property name="username" value="pippo" />
  <property name="password" value="pippo" />
 </bean>