mercoledì 17 aprile 2013

Salvare un file in percorso temporaneo

Per utilizzare un percorso temporaneo è molto utile la variabile java.io.tmpdir, definita così

"java.io.tmpdir is a standard Java system property which is used by the disk-based storage policies. It determines where the JVM writes temporary files, including those written by these storage policies (see Section 4 and Appendix A.6). The default value is typically "/tmp" on Unix-like platforms."

 L'unica piccola seccatura è che a seconda del sistema operativo o anche della singola distribuzione nel mondo Linux il percorso può avere o non avere lo slash finale.

Di solito uso questo metodo per ottenere il percorso di un file temporaneo:


private static String getFileTempName(String nomeFile){
 StringBuffer sb=new StringBuffer();
 String td=System.getProperty("java.io.tmpdir");
 sb.append(System.getProperty("java.io.tmpdir"));
 if ( !(td.endsWith("/") || td.endsWith("\\")) ){
  sb.append(File.separator);
 }
 sb.append(nomeFile);
 log.debug("File temporaneo di salvataggio: "+sb.toString());
 return sb.toString();
 }

venerdì 12 aprile 2013

Web Service gestire zip file

Sotto il codice di realizzazione di un Web Service con un operation che, data in input una directory torna lo zip di tutti i file presenti dentro la directory stessa.
Tralasciando il codice di definizione del Web Service (si tratta di annotare una classe con @WebService e utilizzare @MTOM per ottimizzare il passaggio dati) il metodo è il seguente:

public DataHandler provaDownloadAllegati(String dir) throws Exception
 {
  
 File f=new File(dir);
 File filezip=new File(dir+File.separator+"result.zip");
 ZipOutputStream out = new ZipOutputStream(new FileOutputStream(filezip));
 if(f.isDirectory()){
   String[] fileNames=f.list();
   for(String s: fileNames)
   {
    log.debug("Zippo file "+s);
    if("result.zip".equals(s)){
     continue;
    }
    byte[] b=FileUtil.load(dir+File.separator+s);
    ZipEntry ze=new ZipEntry(s);
    out.putNextEntry(ze);
    out.write(b);
    out.closeEntry();
    
   }
   out.close();
   DataHandler retVal= new DataHandler(new FileDataSource(new File(dir+File.separator+"result.zip")));
   return retVal;
 }
 else
 {
   throw new Exception("Specificare path corretto directory");
 }
  
 }



Ho provato anche ad evitarmi il salvataggio su File System, tornando quindi solo lo stream, ma, pur tornandomi il dato con la size corretta, purtroppo lo stream risulta sempre corrotto, quindi non riesco ad aprirlo.
Posto il codice, non si sa mai se qualcuno ci sia riuscito


......
 ByteArrayOutputStream bos = new ByteArrayOutputStream();
 ZipOutputStream out = new ZipOutputStream(bos);
for(String s:fileNames){
         byte[] allegatoBytes=getBytesFromFileName(dir+file.separator+s);
         out.putNextEntry(new ZipEntry(s));
         out.write(allegatoBytes);
            out.closeEntry();
         }
  return bos.toByteArray();
......

Il metodo getBytesFromFileName è il seguente:


.....
 FileInputStream fin=new FileInputStream(new File(name));
 byte readBuf[] = new byte[512*1024];
   
 try { 
 ByteArrayOutputStream bout = new ByteArrayOutputStream();    
 int readCnt = fin.read(readBuf);
 while (0 < readCnt) {
         bout.write(readBuf, 0, readCnt);
         readCnt = fin.read(readBuf);
 }     
 fin.close();
 return bout.toByteArray();
}
catch(Exception ex ){
.....gestire eccezione......
}


mercoledì 3 aprile 2013

Ant errore "com.sun.tools.javac.Main is not on the classpath. Perhaps JAVA_HOME does not point to the JDK."

Sono incappato in questo errore configurando un nuovo pc.
L'errore avveniva in fase di compilazione del build.xml di ant.
Per risolvere il problema da Eclipse  (trattasi di encoding non riconosciuto) occorre inserire l'opzione
-Dfile.encoding=Cp1252
Nel tab JRE , sezione VM arguments dell' "External Tool Configurations" dell'ant.


venerdì 29 marzo 2013

JSF definire e utilizzare PhaseListener

L'interfaccia javax.faces.event.PhaseListener se implementata consente di scrivere codice in ogni punto del ciclo di vita della pagina JSF.
I cicli di vita della pagina sono:
  • RESTORE VIEW
  • APPLY REQUEST VALUES
  • PROCESS VALIDATION
  • UPDATE MODEL VALUE
  • INVOKE APPLICATION
  • RENDER RESPONSE
Si può definire o a livello globale, dichiarandola nel faces-config.xml, oppure sulla singola view in cui è dichiarata (solo dalla versione 1.2 in poi).

Nel primo caso, una volta scritta la classe, è sufficiente dichiarare nel faces-config.xml


<lifecycle>
 <phase-listener>it.events.lifecycleJsf.RegistroEventiPagina</phase-listener>
 </lifecycle>

Nel secondo caso invece dobbiamo:
  1. Definire la classe come proprietà del Managed Bean;
  2. Inserire nella pagina di interesse il tag
    <f:phaseListener binding="#{workflowTabManager.eventListener}" type="it.events.lifecycleJsf.RegistroEventiPagina"/>

La classe RegistroEventiPagina è la seguente:

public class RegistroEventiPagina implements PhaseListener {
 private static final long serialVersionUID = 1L;
 private static Logger log=Logger.getLogger(Costanti.LOG_APPENDER);
 @Override
 public void afterPhase(PhaseEvent pe) {
  if(pe.getPhaseId()==PhaseId.RESTORE_VIEW){
   log.debug("Esco dalla fase di RESTORE_VIEW: "+new Date()+" phaseId-"+pe.getPhaseId().toString());
  }
  else if(pe.getPhaseId()==PhaseId.APPLY_REQUEST_VALUES)
  {
   log.debug("Esco dalla fase di APPLY_REQUEST_VALUES: "+new Date()+" phaseId-"+pe.getPhaseId().toString());
  }
  else if(pe.getPhaseId()==PhaseId.PROCESS_VALIDATIONS)
  {
   log.debug("Esco dalla fase di PROCESS_VALIDATIONS: "+new Date()+" phaseId-"+pe.getPhaseId().toString());
  }
  else if(pe.getPhaseId()==PhaseId.UPDATE_MODEL_VALUES)
  {
   log.debug("Esco dalla fase di UPDATE_MODEL_VALUES: "+new Date()+" phaseId-"+pe.getPhaseId().toString());
  }
  else if(pe.getPhaseId()==PhaseId.INVOKE_APPLICATION)
  {
   log.debug("Esco dalla fase di INVOKE_APPLICATION: "+new Date()+" phaseId-"+pe.getPhaseId().toString());
  }
  else if(pe.getPhaseId()==PhaseId.RENDER_RESPONSE)
  {
   log.debug("Esco dalla fase di RENDER_RESPONSE: "+new Date()+" phaseId-"+pe.getPhaseId().toString());
  }
  
 }
 @Override
 public void beforePhase(PhaseEvent pe) {
  
  if(pe.getPhaseId()==PhaseId.RESTORE_VIEW){
   log.debug("Entro nella fase di RESTORE_VIEW: "+new Date()+" phaseId-"+pe.getPhaseId().toString());
  }
  else if(pe.getPhaseId()==PhaseId.APPLY_REQUEST_VALUES)
  {
   log.debug("Entro nella fase di APPLY_REQUEST_VALUES: "+new Date()+" phaseId-"+pe.getPhaseId().toString());
  }
  else if(pe.getPhaseId()==PhaseId.PROCESS_VALIDATIONS)
  {
   log.debug("Entro nella fase di PROCESS_VALIDATIONS: "+new Date()+" phaseId-"+pe.getPhaseId().toString());
  }
  else if(pe.getPhaseId()==PhaseId.UPDATE_MODEL_VALUES)
  {
   log.debug("Entro nella fase di UPDATE_MODEL_VALUES: "+new Date()+" phaseId-"+pe.getPhaseId().toString());
  }
  else if(pe.getPhaseId()==PhaseId.INVOKE_APPLICATION)
  {
   log.debug("Entro nella fase di INVOKE_APPLICATION: "+new Date()+" phaseId-"+pe.getPhaseId().toString());
  }
  else if(pe.getPhaseId()==PhaseId.RENDER_RESPONSE)
  {
   log.debug("Entro nella fase di RENDER_RESPONSE: "+new Date()+" phaseId-"+pe.getPhaseId().toString());
  }
  
 }

 @Override
 public PhaseId getPhaseId() {
  
   return PhaseId.ANY_PHASE;
 }

}

PrimeFaces dataTable paginazione e ritorno a pagina selezionata

Utilizzando il componente p:dataTable di primefaces ho avuto necessità di implementare la paginazione (cosa banale) e poi soprattutto, una volta selezionato un elemento della lista, al ritorno sulla lista stessa tornare sulla pagina selezionata e non , come avviene con il comportamento di default, sulla prima pagina.
Stiamo parlando di un caricamento dati classico, non di un LazyLoading.
Per fare questa attività mi è stato molto utile questo link sul forum di primefaces (http://forum.primefaces.org/viewtopic.php?f=3&t=25399 ).

Le attività da compiere sono le seguenti:

1) Definire il bean della tabella come SessionScoped

2) Definire sul bean una proprietà di tipo int (io seguendo l'esempio del forum l'ho denominata first)

3) Inserire sugli attributi del p:dataTable l'attributo first="#{tabellaClienti.first}" dove tabellaClienti è il nome del mio bean di tabella e first la proprietà appena inserita.

4) All'interno della tabella inserire un evento ajax che scatta al cambio di paginazione



<p:ajax event="page" listener="#{tabellaClienti.onPageChange}"/>


Il codice dell'evento è il seguente

public void onPageChange(PageEvent event) {
 log.debug("Clicco sulla pagina "+event.getPage());
 this.setFirst(event.getPage());
}


PrimeFaces aggiungere parametri da processare via ajax

In un form avevo la seguente problematica.
Dopo aver settato un valore di un inplace editor (quindi un inplace associato ad un inputText) viene scatenata una chiamata Ajax che recupera dei dati dalla sessione , fa alcune elaborazioni e quindi rieffettua l'update di un pannello sul form.
Il problema segnalato era che modificando una textarea interna del pannello con l'in place ancora aperto (senza aver cliccato sulla casella di spunta) una volta sottomessa la chiamata Ajax il valore della textArea andava perduto.
Quindi avevo la necessità di portarmi dietro anche il valore della textArea nella chiamata Ajax, in modo che fosse recuperato lato server e non perso.
Con il tag p:ajax questo è molto semplice, basta settare l'attributo "process".
Dalla javadoc di primefaces la descrizione dell'attributo è la seguente:

Component(s) to process in partial request. Defaults to @this.

Quindi basta scrivere nell'atributo process l'id del componente da processare.

Esempio:


<p:inplace id="val_sog" editor="true" 
rendered="#{verificaComplessiva.daModificare 
and verificaComplessiva.stato!='K'}" >  
<p:inputText value="#{verificaComplessiva.punteggio_soggettivo_ope}" immediate="true" />  
<p:ajax event="save" listener="#{workflowTabManager.updateVerificaComplessiva}" 
 process="val_sog txtNote" update="verificaComplessivaPanel"/> 
</p:inplace>                                           

Nel metodo "updateVerificaComplessiva", recuperando dalla sessione il bean, si trova anche il valore della text area, invece prima invece il campo testo era vuoto.


HttpServletRequest request = (HttpServletRequest)FacesContext.getCurrentInstance().getExternalContext().getRequest();
HttpSession session = (HttpSession) request.getSession();
VerificaComplessiva vc = (VerificaComplessiva) session.getAttribute("verificaComplessiva");





lunedì 25 marzo 2013

Configurare web app isolate in JBOSS (versione 5.1)

Un problema che spesso si riscontra con JBOSS è che la politica di default nel caricamento delle librerie è quella del PARENT FIRST.
Quindi JBOSS carica nel classpath prima i suoi jar e solo in seguito passa ad analizzare le librerie presenti nella WEB-INF/lib dell'applicazione.
Nel caricamento dei jar nel classpath  vige la regola che il primo caricato è poi quello effettivamente utilizzato.
Da qui tutti gli innumerevoli problemi tra versioni diverse dello stesso jar, per cui il nostro war una volta deployato smette di funzionare per incompatibilità nelle librerie.
In questi casi la soluzione migliore è quella di isolare la nostra applicazione, ossia fare in modo che siano caricati prima i jar della web app e dopo quelli dell'application server.
Su JBOSS 5.1 per ottenere questo risultato bisogna aggiungere un file xml dentro la directory WEB-INF della nostra applicazione, denominato jboss-classloading.xml.

<?xml version="1.0" encoding="UTF-8"?>

         <classloading xmlns="urn:jboss:classloading:1.0"

                       parent-first="false"              

                       domain="DefaultDomain"

                       top-level-classloader="true"

                       parent-domain="Ignored"

                       export-all="NON_EMPTY"

                       import-all="true">

        </classloading>