Upload de ficheros en JSF

2
52207

CÓMO HACER UPLOAD DE FICHEROS CON
JSF

Introducción

El objetivo de este tutorial es ver como se hacen upload de
ficheros utilizando JSF.

 

Para hacer este ejemplo voy a utilizar:

  • Sistema
    operativo: Windows XP Professional.
  • Servidor
    Web: Apache TomCat 5.5.9
  • Base
    de datos: mysql-5.0.18-win32
  • Driver jdbc para mysql:
    mysql-connector-java-3.1.12-bin-g.jar

  • Entorno
    de desarrollo: Eclipse 3.1.1 con ExadelStudio-3[1].0.5

Primero,  preparamos la base de datos

Lo único que tenemos que preparar es una tabla para almacenar
el fichero. Para ello, me he valido de MySQL
Table Editor
que se instala automáticamente junto con la base de datos MySQL. La pinta que tiene la tabla es la
siguiente:

 

 

Como podemos ver, he creado los siguientes campos:

  • documento_id: clave primaria del
    documento, que será un autonumérico
  • documento_doc: campo tipo blob
    donde se almacenará el documento.
  • documento_desc:
    descripción del documento. Puede resultar útil
  • documento_contenttype:
    content-type del documento. Útil para saber qué tipo de documento es; si
    se lo indicamos al navegador a la hora de visualizar el documento, si
    tiene dicho tipo entre los que reconoce, lo incrustará en el navegador.

 

Una vez introducidos los campos, seleccionamos Apply Changes y ya tenemos tabla para
hacer la prueba.

Segundo, vamos a lo difícil (o quizá no tanto)

Vamos a seguir los siguientes pasos:

 

1.     
Creamos
un nuevo proyecto en Eclipse para el ejemplo.

 

Utilizo el plugin de Exadel Studio para crear un nuevo
proyecto tipo MyFaces (en el tutorial Creando JSF ¿Exadel Studio,
Sun Studio Creador o nada?
Se detalla este proceso de creación), al que
llamo PruebaUpload. Una vez creado el
proyecto, sigo los siguientes pasos (esto también se detalla en el anterior
tutorial):

·       
Cambio el contenido del fichero /ant/build.xml porque no funciona bien.

·       
Cambio el nombre del fichero descriptor de /WEB-INF/examples-config.xml que es el
nombre que me impone Exadel a /WEB-INF/upload-config.xml
que es el nombre que a mí me gusta.

·       
Actualizo este último cambio en el fichero /WEB-INF/web.xml, y al hacer este cambio
descubro que hay un parámetro del web.xml que hace referencia al tamaño máximo
que pueden tener los ficheros al hacer el upload. Esto me sirve para que, una
vez tenga resuelto el problema, ver qué pasa si intento hacer un upload de
ficheros que superen el tamaño que aquí se indica:

 

 

2.     
Primer
acercamiento al problema. Creamos la página de alta del documento para hacer un
upload sin almacenar el documento en base de datos

 

A esta página la llamaré alta_documento.jsp
y para que sirva para hacer upload de ficheros debe cumplir los siguientes
requisitos:

  • El
    control JSF que representa el fichero binario está implementado por
    MyFaces, y se llama InputFileUpload.
    Por lo tanto, la página debe tener inicialmente esta pinta:

 

  • El
    formulario debe ser de tipo multipart/form-data:

 

La etiqueta x:inputFileUpload
representa un input type=”file” de
HTML.
La clase de MyFaces de este componente es HtmlInputFileUpload, que a su vez extiende de la clase estándar de
JSF HtmlInputText. La clase HtmlInputFileUpload se apoya en la clase
HtmlFileUploadRenderer  para generar las marcas HTML una vez la
página JSF ha sido procesada. MyFaces facilita el acceso al contenido,
content-type, nombre y tamaño del fichero a través de su interfaz UploadedFile. Esta interfaz tiene dos
implementaciones: UploadedFileDefaultMemoryImpl
y UploadedFileDefaultFileImpl. Aún
con conocimientos limitados de inglés más o menos nos hacemos una idea de lo
que hace cada implementación: una almacena y mantiene el fichero en memoria (UploadedFileDefaultMemoryImpl) en la
correspondiente variable de tipo UploadedFile
de nuestro bean; la otra
implementación mantiene en memoria sólo la referencia del fichero leído, y
según va leyendo el fichero, lo va almacenando como fichero temporal del
sistema operativo. Esta solución por supuesto es mucho más eficiente. Estas dos
formas de manejar los ficheros se especifican con el atributo storage.

  • Completamos
    el formulario para que contenga la descripción del documento. La página va
    quedando como sigue:

 

<%@ taglib
uri=»http://java.sun.com/jsf/core» prefix=»f»%>

<%@ taglib
uri=»http://java.sun.com/jsf/html» prefix=»h»%>

<%@ taglib
uri=»http://myfaces.apache.org/extensions» prefix=»x»%>

 

 

<html>

<head>

      <title>ADICTOS
AL TRABAJO – UPLOAD DE FICHEROS CON JSF</title>

                  <link rel=»stylesheet» type=»text/css»
href=»css/estilos.css»>

</head>

<body>

      <f:loadBundle

         
basename=»com.autentia.pruebaUpload.recursos.mensajes»

          var=»mensajes»/>

      <f:view>

      <h:form id=»formularioUpload»
enctype=»multipart/form-data»>

        
<h:messages id=»messageList» styleClass=»error»
showSummary=»true»

                    
showDetail=»true» />

                  <h:panelGrid
columns=»2″

                                         styleClass=»altaUpload»

                                         footerClass=»pieUpload»

                                         headerClass=»cabeceraUpload»

                                         columnClasses=»etiquetaUpload,inputUpload»>

                             <f:facet
name=»header»>

                                         <h:outputText
id=»lHeader»

                                  
value=»#{mensajes[‘aDocumento_introduzca’]}»/>

                             </f:facet>

                             <f:facet
name=»footer»>

                                   <h:outputText value=» «/>

                             </f:facet>

                             <!– DOCUMENTO –>

                             <h:outputLabel
for=»docDocumento»

                             
value=»#{mensajes[‘aDocumento_documento’]}»/>

                             <h:panelGroup>

                                         <x:inputFileUpload
id=»docDocumento»

                                   value=»#{uploadBean.docDocumento}»

                                   storage=»file»

                                  
styleClass=»cajaTexto»

                                  
required=»true»/> 

                             </h:panelGroup>

                             <!– DESCRIPCIÓN DEL DOCUMENTO
–>

                             <h:outputLabel
for=»descripcion»

                             
value=»#{mensajes[‘aDocumento_descripcion’]}»/>

                             <h:panelGroup styleClass=»cajaTexto»
id=»descripcion»>

                                 <h:inputTextarea
value=»#{uploadBean.descripcion}» cols=»50″

                                   
rows=»4″ styleClass=»cajaTexto»

                                   
id=»idDescripcion» required=”true” >

                                          <f:validateLength minimum=»5″
maximum=»1000″ />

                                 </h:inputTextarea>

                             </h:panelGroup>

                             <!–
BOTÓN QUE SUBMITE EL FORMULARIO –>

                             <h:panelGroup>

                                         <h:commandButton
id=»boton1″

                                     
value=»#{mensajes[‘bot_aceptar’]}»

                                     
action=»#{uploadBean.upload}»/>

                             </h:panelGroup>

                             </h:panelGrid>

         
</h:form>

          </f:view>

      </body>                                  

</html> 

 

 

3.     
Creamos
el bean que represente y maneje los componentes de la página

Seguimos los siguientes pasos:

  • Creamos
    el nuevo bean, que inicialmente tendrá la siguiente pinta:

 

package com.autentia.pruebaUpload.beans;

import javax.faces.application.FacesMessage;

import javax.faces.context.FacesContext;

import
org.apache.myfaces.custom.fileupload.UploadedFile;

/**

 * Esta clase representa la página de alta de
documentos

 * @author Bea

 *

 */

public class UploadBean

{

      /** Fichero binario */

      private UploadedFile docDocumento;

      /** Descripción del documento */

      private String descripcion;

      /**
Content-type del
documento */

      private
String contentType;       

      public
UploadBean(){}

      public
String getContentType() {

                  return
contentType;

      }

      public void
setContentType(String contentType) {

                  this.contentType
= contentType;

      }

      public
String getDescripcion() {

                  return
descripcion;

      }

      public void
setDescripcion(String descripcion) {

                  this.descripcion = descripcion;

      }

      public UploadedFile getDocDocumento() {

                  return docDocumento;

      }

      public void setDocDocumento(UploadedFile
docDocumento) {

                  this.docDocumento =
docDocumento;

      }

/**

       * 
Efectúa el upload del fichero

       */

      public String upload()

      {

        try

       {

           FacesContext facesContext =
FacesContext.getCurrentInstance();

          
facesContext.getExternalContext().getApplicationMap().put(«fileupload_bytes»,

                                                                                                   
       docDocumento.getBytes());

         
facesContext.getExternalContext().getApplicationMap().put(«fileupload_type»,     

                                                                                                        
docDocumento.getContentType());

         
facesContext.getExternalContext().getApplicationMap().put(«fileupload_name»,

                                                                                                        
docDocumento.getName());

         return «ver_documento»;

      }

      catch(Exception
x)

      {

          FacesMessage message = new
FacesMessage(FacesMessage.SEVERITY_FATAL,

                                                                                     x.getClass().getName(),

                                                                                     x.getMessage());

         
FacesContext.getCurrentInstance().addMessage(null, message);

         return
null;

                            

      }

    }

}

 

El método upload
recupera los bytes, el content-type y el nombre del documento y los deja
visibles a nivel de aplicación.

 

 

  • Editamos
    el fichero /WEB-INF/upload-config.xml
    y añadimos la referencia al nuevo bean insertando las siguientes líneas:

 

<?xml
version=»1.0″ encoding=»UTF-8″?>

<managed-bean>

 
<managed-bean-name>uploadBean</managed-bean-name>

  <managed-bean-class>

     
com.autentia.pruebaUpload.beans.UploadBean

  </managed-bean-class>

  <managed-bean-scope>request</managed-bean-scope>

 </managed-bean>

 

4.     
Añadimos
la navegabilidad entre páginas

 

  • Especificamos
    la página a la que irá el ejemplo cuando el usuario pulse el botón Aceptar

…..

<navigation-rule>

 
<display-name>Redirige a la página donde se visualiza el
documento</display-name>

  <from-view-id>/alta_documento.jsp</from-view-id>

  <navigation-case>

  
<from-outcome>ver_documento</from-outcome>

  
<to-view-id>/ver_documento.jsp</to-view-id>

   <redirect/>

  </navigation-case>

 </navigation-rule>

…..

 

  • Creamos
    la nueva página, que se encarga de escupir por su response el contenido
    del fichero. Si no seleccionamos un tipo raro que no reconozca el
    navegador, incrustará dicho documento en el propio navegador:

 

<%@
page import=»java.io.File,

                 java.io.InputStream,

                 java.io.FileInputStream,

                
java.io.OutputStream»%>

<%@
page session=»false» %>

<%

    String contentType =
(String)application.getAttribute(«fileupload_type»);

    byte[] bytes = (byte[])application.getAttribute(«fileupload_bytes»);

    if (bytes != null)

    {

        response.setContentType(contentType);

        response.getOutputStream().write(bytes);

   
}

%>

 

5.     
Probamos
lo que llevamos hasta ahora

Desplegamos la aplicación en Tomcat y este es el resultado….

 

 

Seleccionamos Aceptar sin introducir nada y….

 

Y si seleccionamos una imagen y
una descripción:

 

6.     
Lo
mismo pero guardando el documento en base de datos

Creamos la clase DocumentoDAO.java cuya responsabilidad
sea la de insertar y recuperar un documento de base de datos. Lo suyo sería
utilizar un pool de conexiones, en lugar de acceder a base de datos a pelo
utilizar una herramienta como hibernate pero… eso ya lo haremos en el próximo
tutorial. Para insertar un documento, hemos creado el siguiente método:

 

package com.autentia.pruebaUpload.data;

 

import java.io.ByteArrayInputStream;

import java.sql.*;

import
com.autentia.pruebaUpload.utilidades.MySqlUtilidades;

 

/**

 * Inserta y recupera documentos de base de
datos

 * @author Bea

 *

 */

public class DocumentoDAO

{

      /**

       *
Guarda un documento en la tabla DOCUMENTO y devuelve la clave primaria

       *
@param contenido contenido del documento

       *
@param contentType tipo de documento

       *
@param descripcion descripción del documento

       *
@throws SQLException posible error que se pueda producir al realizar la
inserción

       */

      public static int  salvaDocumento(byte[] contenido,

                                                        String contentType,

                                                         String descripcion) throws SQLException

      {

                  PreparedStatement
ps = null;

                  Connection conn = null;

int clavePrimaria = -1;

                  ResultSet rs = null;

                  try

                  {

                    // Recuperamos una conexión

                    conn =
MySqlUtilidades.getConexion(«localhost»,»root»,»12345678″,»prueba»);

                    // Establecemos el stream para el blob

                    ByteArrayInputStream inStream = new ByteArrayInputStream(contenido);

                    String query =
«INSERT INTO DOCUMENTO(DOCUMENTO_DOC,»+                                                                                                                             «DOCUMENTO_DESC,»+                                                                                                                           «DOCUMENTO_CONTENTTYPE) «+                                                                  
                                                                
«VALUES(?,?,?)»;

                   ps = conn.prepareStatement(query);

                   ps.setBinaryStream(1,inStream,inStream.available());

                   ps.setString(2,descripcion);

                   ps.setString(3,contentType);

                   ps.executeUpdate();

 rs =
ps.getGeneratedKeys();

                   if(rs.next())

                       clavePrimaria = rs.getInt(1);

                  return
clavePrimaria;

                            

                  }

                  finally

                  {

                             MySqlUtilidades.cerrar(rs,ps,conn);

                  }

      }

}

 

Esta clase se apoya en otra clase
de utilidades JDBC que se llama MySqlUtilidades.java

 

package
com.autentia.pruebaUpload.utilidades;

 

import java.sql.*;

 

/**

 * Utilidades para acceder a una base de datos
mySQL

 * @author Bea

 *

 */

public
class MySqlUtilidades

{

      /**

       *
Devuelve una conexión de mySQL

       */

      public
static Connection getConexion(String maquina,

                                                      String usuario,

                                                      String password,

                                                      String nombreBD)

      {

                  Connection
con = null;

       
try

       
{

           
Driver d =
(Driver)Class.forName(«com.mysql.jdbc.Driver»).newInstance();

           
con = DriverManager.getConnection(«jdbc:mysql://»+ maquina
+»/»+nombreBD,usuario,password);

        }

        catch(Exception e)

        {

            System.out.println(«Error al
recuperar conexion » + e.toString());

            return null;

        }

 

        return con;

      }

      /**

       *
Cierra conexiones

       */

      public
static void cerrar(ResultSet rs,Statement stmt,Connection conn)

      {

                  try

                  {

                             if(rst
!= null)

                             {

                                         rs.close();

                                         rs=null;

                             }

 

                             if(stmt
!= null)

                             {

                                         stmt.close();

                                         stmt=null;

                             }

                             if(conn
!= null)

                             {

                                         conn.close();

                                         conn=null;

                             }

                  }

                  catch(Exception
ex)

                  {

                             System.out.println(«MySqlUtilidades.cerrar:
Error cerrando conexiones:» +

                                                           
ex.getMessage());

                  }

      }

}

 

NOTA: Comentaros que he tenido un par de problemas que se detallan en
la parte final de este tutorial a la hora de insertar documentos en mySql.

 

Y ahora modificamos el método upload de UploadBean para que se comunique con la clase DocumentoDAO:

 

….

import
com.autentia.pruebaUpload.data.DocumentoDAO;

….

public String upload()

{

  try

 {

   
 ……….             

      Int clavePrimaria = DocumentoDAO.salvaDocumento(docDocumento.getBytes(),

                                                                                          
docDocumento.getContentType(),

                                                                                           descripcion);

      ..

7.     
Recuperando
el documento de base de datos

Añadimos un nuevo método a la
clase DocumentoDAO.java para que
recupere un documento de base de datos.

 

public
static Documento recuperaDocumento(int clavePrimaria) throws Exception

{

      Statement stmt = null;

      Connection conn = null;

      Documento resultado = null;

      ResultSet rs = null;

      try

      {

                  //
Recuperamos una conexión

                  conn
=
MySqlUtilidades.getConexion(«localhost»,»root»,»12345678″,»prueba»);

                  stmt = conn.createStatement();

                  String query = «SELECT

                                              
DOCUMENTO_DOC,

                                              DOCUMENTO_DESC,

                                             DOCUMENTO_CONTENTTYPE

                                            FROM DOCUMENTO
WHERE

                                           
DOCUMENTO_ID=»+clavePrimaria;

                    rs =
stmt.executeQuery(query);

                    if(rs.next())

                   {

                        resultado
= new Documento(clavePrimaria);

                        String
path = guardaBlobEnFicheroTemporal(rs.getBlob(«DOCUMENTO_DOC»));

                        resultado.setPathFichero(path);

                        resultado.setDescripcion(rs.getString(«DOCUMENTO_DESC»));

                        resultado.setTipoDocumento(rs.getString(«DOCUMENTO_CONTENTTYPE»));

                   }

           

                  return
resultado;

      }

      finally

      {

                             MySqlUtilidades.cerrar(rs,stmt,conn);

      }

}

 

El método guardaBlobEnFicheroTemporal se hace para no tener que almacenar en
memoria el contenido del fichero, puede que sea grande y tumbemos al servidor
Web. Este método devuelve el path absoluto del fichero temporal creado.

 

public
static String guardaBlobEnFicheroTemporal(Blob blob) throws Exception

{

  
// Dejamos al sistema operativo que decida en qué directorio dejar el
fichero temporal

  File f =
File.createTempFile(«adictostrabajo_»,»_tmp»);

  InputStream in = blob.getBinaryStream();

  FileOutputStream out = new
FileOutputStream(f.getAbsolutePath());

  int c = 0;

  while (
(c = in.read()) >= 0 )

        out.write(c);

  out.flush();

  out.close();

  return f.getAbsolutePath();

}

 

Nos creamos una clase entidad que
representa un documento de base de datos:

 

package com.autentia.pruebaUpload.data;

 

import java.io.*;

import java.io.File;

 

/**

 * Clase que representa un documento de base de
datos

 * @author Bea

 *

 */

 

public class
Documento

{

      /** Clave primaria del documento*/

      private int clavePrimaria;

      /** Tipo de documento */

      private String tipoDocumento;

      /** Descripción del documento */

      private String descripcion;

      /** Path del fichero temporal donde está
el contenido del fichero */

      private String pathFichero;        

      ////////// CONSTRUCTORES

      public Documento(){}

      public Documento(int clavePrimaria)

      {

                  this.clavePrimaria=clavePrimaria;

      }

      public int getClavePrimaria() {

                  return clavePrimaria;

      }

      public void setClavePrimaria(int
clavePrimaria) {

                  this.clavePrimaria =
clavePrimaria;

      }

      public String getDescripcion() {

                  return descripcion;

      }

      public void setDescripcion(String descripcion)
{

                  this.descripcion =
descripcion;

      }

      public String getTipoDocumento() {

                  return tipoDocumento;

      }

      public void setTipoDocumento(String
tipoDocumento) {

                  this.tipoDocumento =
tipoDocumento;

      }

      public
String getPathFichero() {

                  return
pathFichero;

      }

      public void
setPathFichero(String pathFichero) {

                  this.pathFichero
= pathFichero;

      }

     

      public
static byte[] getDocumento(String path) throws Exception

      {

                  byte[]
resultado = null;

                  if(path
!= null)

                  {

                             File
f = new File(path);

                             FileInputStream
in = new FileInputStream(f);

                             resultado
= new byte[in.available()];

                             int
len = 0;

                             while
( (len = in.read( resultado )) > 0 )

                                         ;                                 

                  }

                  return resultado;

      }

}

 

Y volvemos a modificar el método upload de UploadBean para que proporcione al JSF los bytes que necesita para
poder pintar el fichero:

 

import com.autentia.pruebaUpload.data.DocumentoDAO;

import com.autentia.pruebaUpload.data.Documento;

 

….

public
String upload()

{

  try

 {

     

      int clavePrimaria = DocumentoDAO.salvaDocumento(docDocumento.getBytes(),

                                                                                          
docDocumento.getContentType(),

                                                           
                               descripcion);

Documento doc =
DocumentoDAO.recuperaDocumento(clavePrimariaDocumento);

                                   FacesContext facesContext =
FacesContext.getCurrentInstance();

                                   facesContext.getExternalContext().getApplicationMap().put(«fileupload_bytes»,
Documento.getDocumento(doc.getPathFichero()));

                                   facesContext.getExternalContext().getApplicationMap().put(«fileupload_type»,
doc.getTipoDocumento());

                                   facesContext.getExternalContext().getApplicationMap().put(«fileupload_name»,
docDocumento.getName());

return «ver_documento»;

      }

    …….

 

Obtendremos el mismo resultado que con la prueba anterior.

 

8.     
Algunos
problemas con MySQL

 

  • Cuando
    intentaba instanciar el Driver a la hora de recuperar una conexión de base
    de datos, me daba el siguiente error:

java.lang.NoClassDefFoundError:
org/aspectj/lang/Signature

at
java.lang.Class.forName0(Native Method)

at
java.lang.Class.forName(Class.java:141)

 

He añadido el fichero aspectjrt.jar
al classpath de la aplicación y funcionó.

 

  • El
    tamaño de los BLOB lo limita a 64 kB que es muy pobre. He tenido que cambiar
    el tipo BLOB por MEDIUMBLOB:

 

 

 

 

 

2 Comentarios

  1. Hola, una duda que quizás sea muy fácil. ¿es posible cambiar el botón por defecto \\\»Examinar\\\» por un icono u otra imagen?

    Gracias y un saludo.

Dejar respuesta

Please enter your comment!
Please enter your name here