lunes, 18 de julio de 2016

Enviar documentos desde una aplicación web hacia la impresora sin pre visualización en el navegador

En este post explicare como imprimir un documento PDF directamente desde una aplicación web evitando la pre visualización por defecto que realizan los navegadores web o evitando también la descarga por defecto del documento en la maquina cliente.

Para llevar a cabo la solución, vamos a utilizar una implementación open source (Project Tyrus) de la especificación JSR 356 - Java API for WebSocket para establecer la comunicación entre la aplicación web y el servidor que deberá estar ejecutándose en la maquina cliente y que recibirá mediante WebSocket el documento a imprimir y enviar este a la impresora. A continuación los pasos a realizar utilizando el IDE IntelliJ IDEA:

PASO 1: Creación del proyecto Tyrus donde se definirá el WebSocket endpoint y el servidor




PASO 2: Agregar las dependencias maven necesarias para la creación de nuestro servidor Tyrus



    <dependencies>
        <dependency>
            <groupId>org.glassfish.tyrus</groupId>
            <artifactId>tyrus-server</artifactId>
            <version>1.13</version>
        </dependency>

        <dependency>
            <groupId>org.glassfish.tyrus</groupId>
            <artifactId>tyrus-container-grizzly-server</artifactId>
            <version>1.13</version>
        </dependency>
    </dependencies>



PASO 3: Implementación del endpoint server FileServerEndpoint

package ws.serverprinter.server;

import java.io.*;
import java.nio.ByteBuffer;

import javax.websocket.CloseReason;
import javax.websocket.EndpointConfig;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;

@ServerEndpoint("/receive/fileserver")
public class FileServerEndpoint {

    static ByteArrayOutputStream baos = null;

    @OnOpen
    public void open(Session session, EndpointConfig conf) {
        System.out.println("FileServerEndpoint is open");
    }

    @OnMessage
    public void processUpload(ByteBuffer msg, boolean last, Session session) {
        System.out.println("ByteBuffer message");
        while(msg.hasRemaining()) {
            baos.write(msg.get());
        }
    }

    @OnMessage
    public void message(Session session, String msg) {
        System.out.println("Message: " + msg);
        if(!msg.equals("end")) {
            baos = new ByteArrayOutputStream();
        }else {
            try {
                baos.flush();
                baos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    @OnClose
    public void close(Session session, CloseReason reason) {
        System.out.println("WebSocket closed: "+ reason.getReasonPhrase());
    }

    @OnError
    public void error(Session session, Throwable t) {
        t.printStackTrace();

    }
}

PASO 4: Implementación de la aplicación principal y el server Tyrus

package ws.serverprinter.server;

import org.glassfish.tyrus.server.Server;

import java.io.BufferedReader;
import java.io.InputStreamReader;

/**
 * Created by williams on 19-07-16.
 */
public class MainApp {

    public static void main(String[] args) {
        runServer();
    }

    private static void runServer() {
        Server server = new Server("localhost", 8025, "/websockets", null, FileServerEndpoint.class);
        try {
            server.start();
            BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
            System.out.print("Press a key to stop the server.");
            reader.readLine();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            server.stop();
        }
    }
}


PASO 5: Desarrollo de la aplicación cliente


<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Chat</title>
</head>
<body>
<h2>File Upload</h2>
Select file
<input type="file" id="filename" />
<br>
<input type="button" value="Connect" onclick="connect()" />
<br>
<input type="button" value="Upload" onclick="sendFile()" />
<script>
    var ws;

    function connect() {
        ws = new WebSocket("ws://localhost:8025/websockets/receive/fileserver");
        ws.binaryType = "arraybuffer";
        ws.onopen = function() {
            alert("Connected.")
        };

        ws.onmessage = function(evt) {
            alert(evt.msg);
        };

        ws.onclose = function() {
            alert("Connection closed...");
        };
        ws.onerror = function(e) {
            alert(e.msg);
        }

    }

    function sendFile() {
        var file = document.getElementById('filename').files[0];
        ws.send('filename:'+file.name);
        var reader = new FileReader();
        var rawData = new ArrayBuffer();
        reader.loadend = function() {}
        reader.onload = function(e) {
            rawData = e.target.result;
            ws.send(rawData);
            alert("file transferred.")
            ws.send('end');
        }
        reader.readAsArrayBuffer(file);
    }


</script>
</body>
</html>

PASO 6: Implementación del servicio de impresión


    @OnMessage
    public void message(Session session, String msg) {
        System.out.println("Message: " + msg);
        if(!msg.equals("end")) {
            baos = new ByteArrayOutputStream();
        }else {
            try {
                baos.flush();
                baos.close();
                print(baos);
            } catch (IOException e) {
                e.printStackTrace();
            } catch (PrintException e) {
                e.printStackTrace();
            }
        }
    }

    private void print(ByteArrayOutputStream baos) throws PrintException {
        PrintService[] services = PrintServiceLookup.lookupPrintServices(null, null);
        if (services.length > 0) {
            DocFlavor psInFormat = DocFlavor.INPUT_STREAM.AUTOSENSE;
            Doc myDoc = new SimpleDoc(new ByteArrayInputStream(baos.toByteArray()), psInFormat, null);
            DocPrintJob job = services[0].createPrintJob();
            job.print(myDoc, null);
        }
    }

PASO 7: Iniciamos el server Tyrus


PASO 8: Adjuntamos algún archivo PDF desde nuestra aplicación cliente y lo enviamos al server


Se puede observar como el archivo se envió directamente a la impresora.

Fuentes:
https://tyrus.java.net/documentation/1.13/user-guide.html#standalone-mode http://stackoverflow.com/questions/21769470/file-upload-using-java-websocket-api-and-javascript http://docs.oracle.com/javase/7/docs/api/javax/print/package-summary.html https://java.net/projects/websocket-spec

Github: https://github.com/ghwrivas/server-printer-ws

miércoles, 9 de abril de 2014

Spring Web Flow - Prevenir el doble click cuando enviamos un formulario html pero sin dejar de enviar el botón presionado

En la mayoría de aplicaciones muchas veces es necesario des-habilitar el botón tipo submit del un formulario html que se encarga de captura de datos para posteriormente persistir estos en la base de datos.

En mi caso particular desarrollando una aplicación utilizando spring web flow, me fue necesario des-habilitar el botón que enviaba el formulario al servidor pero resulta que al des-habilitar el botón, este no es enviado con el formulario y para mi caso, spring web flow necesita saber el nombre del botón presionado para saber por donde continuar su flujo en el archivo de configuración del flujo.

Extracto de mi archivo de configuración del flujo:

 
  
  
  
 

 
  
  
  
 

 
  
 

Pueden observar que en el estado de la vista form-datos-envio, tenemos una transicion de nombre save la cual ejecutara la accion de guardar.

Para hacer esto, create una funcion que valide el formulario al momento del submit y utilizare jquery en la vista conjuntamente con un input hidden en el cual guardare el nombre del botón presionado y así poder des-habilitar el botón pero enviar su nombre mediante un campo oculto.

Botones necesarios en la vista:

    
    

    
    

    
    

Campo oculto donde almacenare el valor del boton presionado:



Jquery:

 

Validacion del formulario en el evento onsubmit: