mercoledì 29 febbraio 2012

Configurare log4j per una Web Application

Una pratica comune quando si configura una Web Application per utilizzare log4j è la seguente.
Definiamo innanzitutto un appender per il log della nostra applicazione e poi tramite una servlet che si carica all'attivazione dell'applicazione configuriamo il log4j.
Nel nostro caso  la servlet cerca un parametro definito a livello di Web-xml (un context parameter) e, se lo trova valorizzato allora cerca il log4j.properties nella directory specificata, altrimenti utilizza l'impostazione di default.
Ricordiamo che i livelli di log previsti dal frqamework log4j sono i seguenti:

  •  TRACE;
  •  DEBUG;
  •  INFO;
  •  WARN;
  •  ERROR;
  •  FATAL.

log4j.properties

##############################################################
################### DEFINIZIONE DEI LOGGER ###################
##############################################################


log4j.rootLogger=INFO,ApplicationConsoleLog

#Gestione log4j generale per l'Applicazione

log4j.logger.WebTest.MyLogger=DEBUG, ApplicationLog



###############################################################
########## DEFINIZIONE DEGLI APPENDER DEFINITI SOPRA ##########
                        ###### CONSOLE ########
                        #######################
log4j.appender.ApplicationConsoleLog=org.apache.log4j.ConsoleAppender
log4j.appender.ApplicationConsoleLog.layout=org.apache.log4j.PatternLayout

                        ###### EXCEPTION ######
                        #######################
log4j.appender.ApplicationLog=org.apache.log4j.RollingFileAppender
log4j.appender.ApplicationLog.File=
log4j.appender.ApplicationLog.MaxFileSize=2000KB
log4j.appender.ApplicationLog.MaxBackupIndex=10
log4j.appender.ApplicationLog.layout=org.apache.log4j.PatternLayout
log4j.appender.ApplicationLog.layout.ConversionPattern=%d{dd-MM-yyyy HH\:mm\:ss} -%p- %m%n

Sono definiti 2 logger, un ApplicationConsoleLog   che opera solo a livello di console dell'application server e poi un ApplicationLog che si occupa di loggare su File.
Il codice della Servlet è il seguente:

InitServlet

public class InitServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
    private static final String LOG4J_FILE_PROP="log4j.appender.ApplicationLog.File";
      
    /**
     * @see HttpServlet#HttpServlet()
     */
    public InitServlet() {
        super();
        // TODO Auto-generated constructor stub
    }

    /**
     * @see Servlet#init(ServletConfig)
     */
    public void init(ServletConfig config) throws ServletException {
        super.init(config);
        ServletContext ctx=config.getServletContext();
        StringBuffer log4jConf=new StringBuffer();
        log4jConf.append(getConfigPath(ctx));
        log4jConf.append(File.separator);
        log4jConf.append("log4j.properties");
        try
        {
        File log4JFile=new File(log4jConf.toString());
        if(log4JFile.exists()){
            InputStream stream=new FileInputStream(log4JFile);
            Properties log4jProp = new Properties();
            log4jProp.load(stream);
            
            if(log4jProp.getProperty(LOG4J_FILE_PROP).trim().equals("")){
                String logDir=getLogFileDefault(ctx);
                log4jProp.setProperty(LOG4J_FILE_PROP, logDir);
                
                
            }
            PropertyConfigurator.configure(log4jProp);
            Logger.getLogger("WebTest.MyLogger").warn("###########################\nLog4j CARICATO CON SUCCESSO");
        }
        }
        catch(Exception ex){
            ex.printStackTrace();
        }
        
    }
    private String getLogFileDefault(ServletContext ctx){
        StringBuffer sb=new StringBuffer();
        sb.append(ctx.getRealPath(""));
        sb.append(File.separator);
        sb.append("logs");
        sb.append(File.separator);
        sb.append("log.log");
        return sb.toString();
    }
    /**
     * Cerco la directory contenente il file  di configurazione di log4j <br>
     * nella directory specificata nel context parameter"locationConfig"<br>
     * se il percorso non è specificato allora lo cerco dentro la directory config della web-app
     * @param ctx
     * @return
     */
    private String getConfigPath(ServletContext ctx) {
        String path="";
        String nome="locationConfig";    
        path = ctx.getInitParameter(nome);
        if(path==null || path.trim().equals("")){
            // non � stato configurato via Web Xml
            path=ctx.getRealPath("")+File.separator+"config";
        }
        return path;
    }

}
 
web.xml

....

 <servlet>
    <description></description>
    <display-name>InitServlet</display-name>
    <servlet-name>InitServlet</servlet-name>
    <servlet-class>it.servlet.InitServlet</servlet-class>
    <load-on-startup>0</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>InitServlet</servlet-name>
    <url-pattern>/InitServlet</url-pattern>
  </servlet-mapping>
<context-param>
<param-name>locationConfig</param-name>
<param-value></param-value>
</context-param>

.....


Per richiamare il nostro log all'interno del codice Java sarà sufficiente scrivere:

.......

private static Logger log=Logger.getLogger("WebTest.MyLogger");

.......

domenica 26 febbraio 2012

Query JPQL e query native, comportamento apparentemente strano

Ho notato un comportamento "strano" utilizzando in JPA le query JPQL rispetto a quelle Native.

Abbiamo la seguente situazione di partenza su un db:




In un Ejb stateless abbiamo i due seguenti metodi:

@Override
    public void updateForTestJPQL() {
        Persona cust = em.find(Persona.class, 1);
        cust.setCognome("bianchi");
        Query q = em.createQuery( "UPDATE Persona p SET p.cognome = 'gialli'");
       
        q.executeUpdate();
        System.out.println(cust.getCognome());
       
    }
    @Override
    public void updateForTestNative(){
        Persona cust = em.find(Persona.class, 1);
        cust.setCognome("bianchi");
        Query q=em.createNativeQuery("UPDATE persona p SET p.cognome = 'gialli'");
        q.executeUpdate();
        System.out.println(cust.getCognome());
       
    }

Il primo metodo utilizza le query JPQL, il secondo invece la query native (sql standard).

Il risultato delle due esecuzioni però differisce.
In entrambi i casi in output trovo la scritta "bianchi".
Ma sul db nel caso di query JPQL ho la seguente situazione






quindi tutti i record della tabella sono stati aggiornati con il valore "gialli".

Invece nel secondo caso (query nativa):





Il primo record, quello che era stato recuperato tramite il metodo find dell'Entity Manager risulta con cognome "bianchi" e solo gli altri due record sono stati aggiornati.
Il motivo di questo apparente comportamento è che la query nativa viene eseguita in un suo contesto transazionale, quindi :
  1. e' effettuata prima l'update su tutto il db;
  2. alla fine del metodo avviene la sincronizzazione dell'entity manager con il db e quindi modificato il cognome del primo record.
Viveversa usando jpql tutto è eseguito alla fine del metodo, quindi "prevale" l'ultima operazione, quella dell'aggiornamento

venerdì 24 febbraio 2012

Trovare tutte le query in esecuzione sul DB

Eseguendo la seguente query sullo schema MASTER si possono tirare fuori tutte le query in esecuzione :

SELECT st.text, r.session_id, r.status, r.command, r.cpu_time, r.total_elapsed_time
FROM sys.dm_exec_requests r
CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) AS st
order by total_elapsed_time desc

sys.dm_exec_sql_text(sql_handle | plan_handle)
"Restituisce il testo del batch SQL identificato dall'argomento sql_handle specificato" (MSDN)

Può essere utilizzato con le seguenti tabelle:

  • sys.dm_exec_query_stats
  • sys.dm_exec_requests
  • sys.dm_exec_cursors
  • sys.dm_exec_xml_handles
  • sys.dm_exec_query_memory_grants
  • sys.dm_exec_connections

giovedì 23 febbraio 2012

Strategie mappature PK con gli EJB 3.0

Ogni entity bean deve essere identificato univicamente.
Sono possibili 3 strategie:

  • Usando l'annotation @Id;
  • Usando l'annotation @IdClass;
  • Usando l'annotation @EmbeddedId.
@Id

Questa annotation è applicata al campo che mappa la primary key nel database, quando nel database la chiave primaria è composta da un singolo campo.
I campi supportati sono campi primitivi, e oggetti che implementano Serializable come java.lang.String java.util.Date e java.sql.Date.
Non è raccomandato utilizzare campi di tipo float o double, per via di possibili arrotondamenti e imprecisioni

@IdClass

Nel caso di composite key sul db si crea una classe con le chiavi e in questa classe si effettua l'override dei due metodi di object equals e hashCode.

Quindi nell'entity bean si utilizza a livello di classe l'annotation @IdClass facendo riferimento alla classe appena creata e quindi sui singoli campi che fanno parte dell'id si utilizza l'annotation @Id.

Esempio:

Supponiamo di avere una classe Persona con chiave composta formata dall'accoppiata cognome e nome + altri campi.

Definiamo quindi l'IdClass

public class ChiavePersona implements Serializable
{
    String nome;
    String cognome;
   public ChiavePersona(){}
   public boolean equals(Object other) {
   if(other instanceof ChiavePersona){
   final ChiavePersona otherPK=(ChiavePersona)other;
   return (other.name.equals(this.nome) && other.cognome.equals(this.cognome));

   }
   }
   public int hashCode(){
    return super.hashCode();
    }
 
}

L' entity è definita così:

@Entity
@IdClass(ChiavePersona.class)
public class Persona{
 @Id
  protected String nome;
 @Id
  protected String cognome;

   ......
}



@EmbeddedId

In questo caso si crea come nel caso precedente la classe con la primary key ma annotandola con @Embeddable e quindi referenziando poi la classe stessa con l'annotation @EmbeddedId
Nell'Entity quindi

@Entity
public class Persona{
 public Persona(){}
 @EmbeddedId
 protected ChiavePersona personaId;
}
.....

}


venerdì 17 febbraio 2012

Installare SVN sotto Windows

SVN nasce in ambiente Linux e quindi di solito sulle guide si trovano spesso le modalità di installazione per le distribuzioni del pinguino.
Per installare su Windows il tutto comunque non ci sono particolari difficoltà.
I passi che ho seguito (ovviamente non essendo un sistemista ho effettuato una installazione basilare e non so come effettivamente poi personalizzare il tutto) sono:

SCARICARE IL SW

Dal sito http://svn1clicksetup.tigris.org/   si può scaricare l'ultima versione disponibile. L'eseguibile si chiama Svn1ClickSetup-1.3.3.exe e l'installazione è guidata.
Al termine dell'installazione sarà startato un processo sul S.O.denominato SVNService.
Di default è utilizzata la porta 3690.
Inoltre saranno create 2 cartelle sul S.O.:

  • C:\Programmi\Subversion con le librerie di installazione e le guide di utilizzo;
  • C:\svnrepos dove è presente il "DB" del sistema (nella directory db).

NOTE INSTALLAZIONE

E' richiesta durante l'installazione la userId e pwd da amministratore. Le utenze saranno salvate nel file c:\svnrepos\conf\passwd . Sono salvate in chiaro nella forma username=password (anche in questo caso presumo sia possibile effettuare installazioni più serie con password cifrate ecc. ecc.). Per censire nuovi utenti bisogna editare questo file.

Una volta installato il pacchetto automaticamente la distribuzione ha scaricato anche Tortoise, il comodo client che può essere utilizzato nel menu contestuale che appare alla pressione del tasto destro del mouse.

UTILIZZO

Con Tortoise è già possibile utilizzare il repository, per linkarlo bisogna digitare l'indirizzo svn://[NOME_SERVER].

Per utilizzarlo su eclipse invece bisogna scaricare il plugin.
Da Eclipse  Indigo Help/Install New Software l'url del repository da inserire è  http://subclipse.tigris.org/update_1.6.x/

L'utilizzo del Plug In è semplice ed intuitivo come CVS.
Esistono poi numerose guide on line che spiegano in dettaglio l'utilizzo del sistema di versioning.



mercoledì 15 febbraio 2012

EJB Timer esempio di utilizzo


Una delle caratteristiche avanzate degli Ejb 3.0 è la possibilità di definire dei Timer sull'AS.

TEORIA

I Timer consentono di effettuare operazioni schedulate.
Essendo asincroni e stateless possono essere implementati soltanto con i Session Beans e con i Message Driven Bean.
I Timers sono anche transazionali, quindi se il metodo che li utilizza va in eccezione anche il Timer viene distrutto.
Altro vantaggio dei Timer è che, una volta creati, sopravvivono allo spegnimento dell'AS, quindi al riavvio saranno di nuovo attivi.
Per crearli si utilizza l'interfaccia TimerService, che va iniettata a livello di bean.
L'interfaccia javax.ejb.TimerService è così definita:

public interface javax.ejb.TimerService {
public Timer createTimer(long duration,
java.io.Serializable info);
public Timer createTimer(long initialDuration,
long intervalDuration, java.io.Serializable info);
public Timer createTimer(java.util.Date expiration,
java.io.Serializable info);
public Timer createTimer(java.util.Date initialExpiration,
long intervalDuration, java.io.Serializable info);
public Collection getTimers();
}

E' possibile quindi definire delle schedulazioni base, dando la data iniziale e specificando ogni quanto saranno ripetuti oppure facendoli partire una singola volta soltanto. Non c'è ovviamente la completezza di un cron scheduler o di tool come Quartz, ma in molti casi risultano utili.
Il "cosa fare" alla scadenza del timer è indicato nell'ejb annotando un metodo con l'annotazione @TimeOut.
Il metodo deve avere una definizione di questo tipo:

void <METHOD>(Timer timer)

L'interfaccia javax.ejb.Timer è così definita:

 public interface javax.ejb.Timer {
public void cancel();
public long getTimeRemaining();
public java.util.Date getNextTimeout();
public javax.ejb.TimerHandle getHandle();
public java.io.Serializable getInfo();
}
Presenta una serie di metodi di utilità ; molto importante è il metodo cancel() che consente di rimuovere il Timer dall'AS.

ESEMPIO DI UTILIZZO

Vediamo un semplice esempio in cui abbiamo un Ejb remoto che in un suo metodo crea il Timer.

Questo Timer nel metodo Timeout effettua delle insert su una tabella di DataBase, ripetendo questi inserimenti ogni minuto.

Il db server di riferimento è MySql 5.1., l'AS JBoss 5.1 la jdk utilizzata la java 1.6.x.
Come ambiente di sviluppo ho usato Eclipse Indigo, creando un semplice progetto Java e aggiungendo nel classpath le librerie dell'Application Server. 
Per deployare l'Ejb è sufficiente effettuare l'export del jar dentro la directory deploy di Jboss (si noti che a aprtire dalla versione 3.0  il file ejb-jar.xml non è più necessario)
Essendoci la connettività verso MySql è necessario scaricare dal sito il driver JDBC e copiarlo dentro la directory server/default/lib.

La tabella creata ha la seguente struttura:

CREATE TABLE  `test`.`logaccessi` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `descrizione` varchar(45) NOT NULL,
  `ts` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1;

L'Entity Bean utilizzato è il seguente:

@Entity
@Table(name="logaccessi")
public class LogInfo implements Serializable{
   
    /**
     *
     */
    private static final long serialVersionUID = 1L;

    private int id;
   
    private String descrizione;
   
    private Timestamp ts;
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    @Column(name="descrizione")
    public String getDescrizione() {
        return descrizione;
    }
    public void setDescrizione(String descrizione) {
        this.descrizione = descrizione;
    }
    @Column(name="ts")
    public Timestamp getTs() {
        return ts;
    }
    public void setTs(Timestamp ts) {
        this.ts = ts;
    }
   
   

}


Il file persistence.xml definito dentro la directory META-INF del progetto java è il seguente:

<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
version="1.0">

    <persistence-unit name="timer" transaction-type="JTA">
        <provider>org.hibernate.ejb.HibernatePersistence</provider>
        <jta-data-source>java:jdbc/logDS</jta-data-source>
        <properties>
         <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLInnoDBDialect"/>
        <property name="hibernate.show_sql" value="true"/>
        <property name="hibernate.format_sql" value="true"/>
        </properties>
    </persistence-unit>
   
</persistence>



Il datasource sotto Jboss:

<datasources>
  <local-tx-datasource>
 
    <jndi-name>jdbc/logDS</jndi-name>
 
    <connection-url>jdbc:mysql://localhost:3306/test</connection-url>
    <driver-class>com.mysql.jdbc.Driver</driver-class>
 
    <user-name>root</user-name>
    <password>admin</password>
 
    <connection-property name="autoReconnect">true</connection-property>
 
    <!-- Typemapping for JBoss 4.0 -->
    <metadata>
      <type-mapping>mySQL</type-mapping>
    </metadata>
 
  </local-tx-datasource>
</datasources>

L'interfaccia Remota dell'Ejb:

@Remote
public interface LocalTimer {
           //starta il timer
    public void start();
           // rimuove tutti i Timer presenti nell'AS
    public void removeTimers();
     
}

L'Ejb Stateless:

@Stateless
@TransactionManagement(TransactionManagementType.CONTAINER)
public class LocalTimerEjb implements LocalTimer {

    @PersistenceContext(unitName = "timer")
    private EntityManager entityManager;
    @Resource
    TimerService  timer;
   
    public void start() {
       
        timer.createTimer(new Date(), 1*60*1000,new TimerInfo());
        System.out.println("Timer creato.....");
    }
    @Remove
    public void stop(){
        removeTimers();
    }
    @Timeout
    public void lavora(Timer timer){
       
        System.out.println("In esecuzione il timer creato il "+((TimerInfo)timer.getInfo()).getDataCreazione());
        LogInfo l=new LogInfo();
        l.setDescrizione(generaTestoCasuale(16));
        l.setTs(new java.sql.Timestamp(System.currentTimeMillis()));
        entityManager.persist(l);
        System.out.println("Finito!!!");
    }
    private String generaTestoCasuale(int lunghezza){
        .......
    }
   
    @Override
    public void removeTimers() {
        Collection<Timer> lista=timer.getTimers();
        int numero=lista.size();
        for(Timer t:lista){
            t.cancel();
        }
        System.out.println("Timer cancellati: "+numero);
       
    }
}

L'oggetto TimerInfo() passato al metodo create Timer da' solo informazioni sul Timer creato (in questo esempio volutamente banale soltanto la data creazione).

public class TimerInfo implements Serializable {
    public TimerInfo(){
        this.dataCreazione=new Date();
    }
    private static final long serialVersionUID = 1L;
   
    private Date dataCreazione;

    public Date getDataCreazione() {
        return dataCreazione;
    }

    public void setDataCreazione(Date dataCreazione) {
        this.dataCreazione = dataCreazione;
    }
   
}




Per Invocare l'Ejb, in un progetto client:

public class StartEjbTimer {

   
    public static void main(String[] args) throws Exception {
       
       
        LocalTimer lt=(LocalTimer)getContesto().lookup("LocalTimerEjb/remote");
        lt.removeTimers();
        lt.start();
      

    }
    public static Context getContesto() throws NamingException{
        Properties p=new Properties();
        p.put(Context.INITIAL_CONTEXT_FACTORY, "org.jnp.interfaces.NamingContextFactory");
        p.put(Context.URL_PKG_PREFIXES, "org.jboss.naming:org.jnp.interfaces");
        p.put(Context.PROVIDER_URL, "jnp://127.0.0.1:1099");
        Context ctx=new InitialContext(p);
        return ctx;
    }
}


martedì 14 febbraio 2012

Ant esempio utilizzo per Java Project

In questo post vediamo un semplice script Ant che provvede al deploy di un PGM Java in un jar file, stampando a video le proprietà generiche del sistema (JVM,ANT_HOME,Sistema Operativo) e chiedendo prima all'utente se vuole effettuare il deploy o meno; in caso di risposta negativa ant scrive un messaggio di saluto e non procede al deploy (questo solo per vedere un esempio di "if" lato ant).
Il pgm Java è volutamente banale (il classico Hello Word).
Il pgm si trova in un package it.test e si chiama Test.java. Il sorgente è inserito nella nostra base dir dentro la directory src.
Il jar file sarà creato al livello della nostra directory, mentre il .class sarà inserito nella directory build sempre presente a livello della nostra base dir.

Le particolarità di questo script ant sono:

1)  Accesso a variabili di ambiente ( inserendo il nodo <property environment="env"/>) che ci consente poi  di   accedere alla proprietà env direttamente;

2) Utilizzo di proprietà native di ant (disponibili sempre negli script) come ${os.name} per ottenere informazioni sul S.O.  ${ant.java.version} per ottenere la versione di JVM usata, ${user.home} per ottenere la directory user dove di default ant va a cercare ulteriori jar da inserire al classpath oltre a quelli della JVM;

3) Aggiunta di un attributo nel manifest del JAR (in questo caso l'attributo Main-Class) che ci consente di far partire il jar direttamente con java -jar Nome.jar senza specificare l'entry point della classe col main;

4) Gestione della logica condizionale (nel target Opzioni) con l'impostazione di due variabili di uscita (isDeploy e isNotDeploy) che condizionano l'esecuzione dei target di compilazione del sorgente e di creazione del jar file;

Di seguito lo script ant:

<?xml version="1.0" ?>
<project default="main">
<property environment="env"/>
<target name="main" depends="compile,compress,exit">
<echo>Sistema operativo : ${os.name}</echo>
<echo>Versione Java utilizzata: ${ant.java.version}</echo>
<echo>Directory user ${user.home}</echo>
<echo>Ant HOME ${env.ANT_HOME}</echo>
</target>
<target name="compile" depends="Opzioni" if="isDeploy" >
<javac destdir="${basedir}/build" srcdir="${basedir}/src"/>
</target>
<target name="compress" depends="Opzioni" if="isDeploy">
<jar jarfile="Project.jar" basedir="${basedir}/build" includes="**/*.class">
<manifest>
                <attribute name="Main-Class" value="it.test.Test"/>
</manifest>
</jar>
</target>
<target name="Opzioni">
       <input
      message="Si desidera effettuare il deploy ? "
      validargs="y,n"
      addproperty="do.check"
       />
      <condition property="isDeploy">
   <equals arg1="${do.check}" arg2 = "y" />
   </condition>
    <condition property="isNotDeploy">
   <equals arg1="${do.check}" arg2 = "n" />
   </condition>
     </target>

<target name="exit" depends="Opzioni" if="isNotDeploy">
<echo> si e ' scelto di non fare nulla.....</echo>
</target>
</project>


L'output prodotto è il seguente:

C:\proveAnt>ant
Buildfile: C:\proveAnt\build.xml

Opzioni:
    [input] Si desidera effettuare il deploy ?  (y, n)
y

compile:
    [javac] C:\proveAnt\build.xml:11: warning: 'includeantruntime' was not set,
defaulting to build.sysclasspath=last; set to false for repeatable builds

compress:
exit:
main:
     [echo] Sistema operativo : Windows XP
     [echo] Versione Java utilizzata: 1.6
     [echo] Directory user C:\Documents and Settings\sciardone
     [echo] Ant HOME C:\Programmi\apache-ant-1.8.2

BUILD SUCCESSFUL
Total time: 2 seconds

lunedì 13 febbraio 2012

Rimuovere spazi doppi da Stringa

Con le Regular expression basta invocare il metodo replaceAll indicando di effettuare il replace di tutte le sottostringhe composte da uno o più spazi con lo spazio singolo (la regular expression è \\s+ )

String s="test       prova";
System.out.printnl(s.replaceAll("\\s+"," ");

In output avremo "test prova" con un singolo spazio.

Lo stesso risultato si può ottenere (ma è molto meglio usare le Reg Expression) anche con un metodo Java ad hoc tipo questo:

public static String removeSpacesMan(String s){
     StringBuffer sb=new StringBuffer();
     char[] l=s.toCharArray();
     int contaSpazi=0;
     for(int i=0;i<l.length;i++){
     
                   if(l[i]==' '){
      
                                        contaSpazi++;
                                  }
                  else
                                {
                                       contaSpazi=0;
                                }
       if(contaSpazi<=1){
           sb.append(l[i]);
        }
     }
   
     return sb.toString();
    }

lunedì 6 febbraio 2012

Javascript scorporo Iva

Funzioncina per calcolare scorporo iva in javascript, ci sono anche 2 metodi di utilità roundNumber per l'arrotondamento  e parseDouble per effettuare il parsing a Double:

<script language="javascript">
function calcolaScorporo(){

 var iva=parseDouble(document.getElementById("txtIva").value);
  var importoTotale=parseDouble(document.getElementById("txtImporto").value);
 if(!isNaN(iva) && !isNaN(importoTotale)){
 iva=iva/100;
 iva+=1;
 var sc=importoTotale/iva;
 sc=roundNumber(sc,2);
 document.getElementById("txtImportoOriginale").value=sc;
 document.getElementById("txtIvaScorporata").value=roundNumber(importoTotale-sc,2);
 }
 else
 {
    alert('Inserire valori numerici!!');
   }
}


function parseDouble(value){
  if(typeof value == "string") {
    value = value.match(/^-?\d*/)[0];
  }
  return !isNaN(parseInt(value)) ? value * 1 : NaN;
}


function roundNumber(number, digits) {
            var multiple = Math.pow(10, digits);
            var rndedNum = Math.round(number * multiple) / multiple;
            return rndedNum;
        }

</script>


Di seguito vediamo un form che richiama il javascript:



IMPORTO TOTALE PAGATO:
IVA (%):
IVA PAGATA:
IMPORTO ORIGINARIO:

Java encoding URL

Nella costruzione di URL o di link dinamici tramite concatenazione di Stringhe è facile incappare in errori di encoding dovuti a caratteri particolari, i cosiddetti caratteri "unsafe".
Per ovviare a questo problema la JDK ci viene in soccorso con la comoda classe java.net.URLEncoder.
Un esempio di utilizzo all'interno di un proprio metodo  è incollato sotto.
Il metodo encode di URLEncoder lancia una checked Exception, UnsupportedEncodingException, che è necessario gestire .
Per verificare se la propria JDK supporta UTF-8 come dovrebbe basta eseguire il seguente codice:

java.nio.charset.Charset.forName("UTF-8");

Se la chiamata al metodo va in eccezione allora l'encoding non è supportato dalla JDK.

Esempio metodo:

public static String encode(String s) {
        String retVal="";
        boolean supportUTF8=true;
        try
        {
           
            retVal=URLEncoder.encode(s, "UTF-8");
        }
        catch(UnsupportedEncodingException ex){
            // da specifica
            //The World Wide Web Consortium Recommendation states that UTF-8 should be used.
            // Not doing so may introduce incompatibilites.
            log.error("Attenzione, il runtime Java non supporta la codifica UTF-8 per l'url encoding, tentiamo la replace manuale dei caratteri particolari");
            log.error("Verificare il tipo di JDK usata, UTF-8 dovrebbe sempre essere supportato");
            supportUTF8=false;
        }
        if(!supportUTF8){
            retVal=provaAMano();// implementarsi questo metodo tentando di fare qualcosa manualmente, altrimenti tira su un'eccezione
        }
        return retVal;
    }

venerdì 3 febbraio 2012

Java Sql dinamico immune da SQL Injection

Uno dei problemi frequenti con l'Sql dinamico è il rischio di SQL Injection.
Infatti spesso si tende a costruire le query dinamiche semplicemente concatenando le stringhe con i parametri.
Vediamo come realizzare l'SQL dinamico utilizzando una web app.
L'esempio è realizzato usando semplicemente JDBC, ma si può in maniera analoga estendere a JPA.
Il concetto base è che per evitare SQL Injection bisogna utilizzare i PreparedStatement, quindi in generale non concatenando stringhe ma utilizzando i placeholder (in JDBC e in JPA si usa il ?), rendendo quindi la query dinamica e non statica come con la semplice concatenazione dei parametri.

Vediamo un esempio molto semplice, abbiamo un form con 3 campi di ricerca, Nome Cognome ed Età e vogliamo costruire la nostra query dinamica.
La stringa della Query in questo semplice caso è una costante presente in una classe, ed è così definita:

public static String GET_ANAGRAFICA="select * from persona where 1=1 /*1 " +
            "and nome like ? 1*/ /*2 and cognome like ?2*/ " +
            " /*3 and eta=? 3*/";


Come possiamo notare i parametri sono commentati (/*1....1*/).

Lato Web sono impostati i parametri nelle form e quindi si effettua l'invio ad una Servlet che si occupa di evadere la richiesta (non posto il codice che è banale).

Quello che avviene a questo punto nel metodo che si occupa nel Back End di ritornare i dati all'utente è il seguente:


public List<Persona> getListaCompletaPersone(Persona p){
        List<Persona> lista=new ArrayList<Persona>();
        try
        {
   
        PreparedStatement   pst=getDs().getConnection().prepareStatement(lavoraQuery(p));
        pst.setString(1, "%"+p.getNome()+"%");
        pst.setString(2,"%"+ p.getCognome()+"%");
        pst.setInt(3, p.getEta());
       
        ResultSet rs=pst.executeQuery();
        while(rs.next()){
            Persona pp=new Persona();
            pp.setCognome(rs.getString("cognome"));
            pp.setEta(rs.getInt("eta"));
            pp.setNome(rs.getString("nome"));
            lista.add(pp);
        }
        pst.close();
        rs.close();
       
        }
        catch(Exception ex){
            ex.printStackTrace();
        }
        return lista;
    }


Il metodo chiave per la creazione della stringa è il lavoraQuery che  nel caso in cui sia presente uno o più dei parametri toglie i commenti relativi, rendendo quindi la selezione effettiva.


private String lavoraQuery(Persona p){
        String qry=Query.GET_ANAGRAFICA;
        if(p.getCognome()!=null && !p.getCognome().trim().equals("")){
            qry=qry.replace("/*2", "");
            qry=qry.replace("2*/", "");
           
        }
        if(p.getNome()!=null && !p.getNome().trim().equals("")){
            qry=qry.replace("/*1", "");
            qry=qry.replace("1*/", "");
           
        }
        if(p.getEta()!=0){
            qry=qry.replace("/*3", "");
            qry=qry.replace("3*/", "");
        }
       
        return qry;
    }