miércoles, 24 de junio de 2015

Trocear parámetros de la línea de comando (comando xargs)

Hoy os voy a contar la solución a un pequeño problema que se me ha presentado hace unos días, y así aprovecho para contaros una cosa que no sabía y que he acabado utilizando del comando xargs.

El caso es que tenemos un script que procesa cadenas de texto y compone con ellas una cadena más grande. En concreto, las cadenas son una serie de valores con los que el script compone la condición de una instrucción SQL (una select) del estilo

select campo1, campo2...
from tabla
where dato in ('dato1', 'dato2', 'dato3'...)

Esos dato1, dato2... son los parámetros que se le pasan al script.

Queda más claro viendo la Fig. 1.

Fig. 1. Salida del script que genera instrucciones SQL con cláusula "in"


Como es fácil imaginar, el script no está pensado para ejecutarlo manualmente, sino que las cadenas a incluir proceden de una lista existente en un fichero de texto plano. He sustituido los datos reales por basura, pero más o menos, algo así:

Fig. 2. La lista de valores a procesar. Imaginativa, ¿a que sí? Lo que yo decía...

Hay que aclarar que el script no procesa la entrada estándar, en cuyo caso podríamos haber utilizado los pipes (tuberías), sino que espera que le llegue la lista de cadenas (datos) como parámetros. Es decir, los extrae de la variable $argv (es un script PHP).

Una forma fácil de pasarle los valores al script podría ser utilizando las comillas simples invertidas para ejecutar el comando cat y luego la salida de este comando que se sustituya en la línea de comandos de la llamada a nuestro script. No os compliquéis leyendo de nuevo lo que he dicho, queda más claro leyendo el código de la Fig. 3. Vamos, algo así:


Fig. 3. Uso de las comillas simples invertidas para pasar los parámetros al script

Otra solución podría ser utilizar el programa xargs. ¿Y qué hace este programa? Básicamente, recoge todo lo que haya en su entrada estándar y se lo pasa como parámetros al programa que le digamos. Se entenderá mejor con un ejemplo: vamos a ver en la Fig. 4 cómo conseguir lo que hemos hecho antes utilizando xargs en vez de las comillas invertidas.


Fig. 4. Uso de xargs para conseguir el mismo resultado que antes

Hasta aquí vemos dos formas en apariencia más o menos equivalentes (de momento, no entraremos a hablar de rendimiento) de hacer lo mismo. Sin embargo, el problema que estamos resolviendo tiene una peculiaridad más, y aquí ya veremos cómo ambas formas de hacerlo ya no nos valen por igual.


TROCEAR LA ENTRADA

La lista a procesar tiene muuuuuuuuuuchos valores, más de diez mil. En vez de que se nos genere una cláusula "in" monstruosamente grande, lo que queremos es trocear la consulta en diferentes consultas que iremos dosificando poco a poco a la base de datos, a fin de no saturarla y poder ir obteniendo resultados parciales. O por los motivos que sea, que ahora mismo dan igual.

Y aquí es donde se revela una ventaja muy importante de xargs: la capacidad de generar no una, sino N llamadas al comando indicado, pero dándole "bloques" de argumentos del tamaño que queramos. Es decir, en vez de llamar una sola vez al script, y en esa llamada pasarle todos los parámetros de golpe, puede que queramos ir pasándole los argumentos de 10 en 10, pongamos por caso, y que nos devuelva las diferentes SQL con los parámetros de cada bloque.

¿Y cómo se consigue esto: simplemente con la opción -nX del comando xargs, donde debemos sustituir X por el número que indique cuántos parámetros se le pasarán al comando en cada llamada. Así, por ejemplo, si queremos pasarle los argumentos de 10 en 10, pondríamos lo siguiente:


Fig. 5. Pasando los parámetros al script en bloques de 10

En este caso, la lista que he utilizado tiene sólo 29 elementos, así que podemos ver que en la última SELECT se han utilizado los 9 últimos que quedaban, y no 10 argumentos, como en las dos llamadas anteriores. Es decir, que si el script está pensado para recibir un número fijo de parámetros, hay que tener en cuenta que al trocearlos, es posible que la última invocación no incluya todos los necesarios.

Una cosa interesante es que si invocáis a xargs con la opción -t, podéis ver qué se ejecutará sin llegar a ejecutarlo de verdad, sólo se muestra la línea de comando que se ejecutaría. Esto viene bien para hacer pruebas antes de lanzar la llamada definitiva.

No me extiendo más, pero os sugiero que le dediquéis un rato a la página del manual del comando xargs, que posiblemente amortizaréis el tiempo empleado en ello.

Que ustedes tengan un buen tecleo.

PARA SABER MÁS

1. En español: http://systemadmin.es/2009/04/uso-de-xargs-herramientas-unix-ii#

2. En inglés: https://en.wikipedia.org/wiki/Xargs

viernes, 12 de junio de 2015

Segundo portlet con Liferay: listar una tabla (parte 2 de 2)

Bueno, por diversos motivos he tenido que estar parado un tiempo sin poder actualizar el blog, lo siento mucho. Así que voy a ver si ahora puedo poco a poco volver a la normalidad y hacer las entradas de forma más frecuente.

Continuamos con la creación de un portlet que consulta a una BD Oracle y nos muestra los datos en una simple tabla HTML.

Enlace a la primera parte.

Recordemos que lo último que habíamos hecho era incluir el driver de Oracle en nuestro proyecto, así que ya podemos pasar a establecer la conexión con la BD.

5. Establecer la conexión

Para crear la conexión, necesitamos un objeto de la clase Connection. Esta clase se encuentra en el paquete java.sql, así que añadiremos estas líneas al código:

    import java.sql.Connection;
    ...
   
    //conectar
    Connection conOracle = null;
    conOracle = this.getConexionOracle();


También necesitaremos otras clases de este paquete, así que las añadiremos:

    import java.sql.DriverManager;
    import java.sql.SQLException;
    import java.sql.Statement;
    import java.sql.ResultSet;


Una opción más cómoda sería haber importado todo el paquete java.sql con una única instrucción, así:

    import java.sql.*;
   
Pero yo prefiero especificar individualmente cada clase (si fueran muchísimas no me complicaría la vida e importaría todo el paquete, pero en este caso son pocas). De esta manera, hacemos explícito el conocimiento de las clases que estamos importando.

Para conectar con un servidor Oracle, necesitamos los siguientes datos: servidor (nombre o dirección IP), puerto (por defecto, 1521), nombre del esquema, usuario y la clave. Si tenéis claro todo esto, el método para obtener la conexión quedaría algo así (he borrado los datos específicos de mi instalación, naturalmente):

    private Connection getConexionOracle() {
        String
            servidor = "xx",
            puerto   = "1521",
            esquema  = "xx",
            passBD = "xx",
            usuario = "xx";
        String cadenaBD = "jdbc:oracle:thin:@" + servidor + ":" + puerto + ":" + esquema;
        Connection conOracle = null;
        try {
            DriverManager.registerDriver (new oracle.jdbc.driver.OracleDriver());
                conOracle = java.sql.DriverManager.getConnection(cadenaBD, usuario, passBD);           
        } catch (SQLException e) {
            e.printStackTrace();
            System.out.println("Error en getConexionOracle() : " + e.getMessage());
        }
        return conOracle;
    }


OJO: esto de poner en el propio código Java los datos de la conexión probablemente no es la forma más adecuada de hacerlo. De momento lo dejaremos así por simplicidad de comprensión, pero no es lo más recomendable. Sería mejor configurar la conexión a nivel del contenedor de portlets (Tomcat en nuestro caso, Javaboss... ) y desde el código Java obtener la conexión.

6. Realizar la consulta

Una vez obtenida la conexión, ya está hecho lo más difícil. Ahora tan sólo nos queda realizar la consulta, lo cual podemos hacer con el código que se ve a continuación

    //recuperar datos
    Statement orclStmt = conOracle.createStatement();
    String sql = "select campo1, campo2 from tabla order by campo1 ";
    orclResult = orclStmt.executeQuery(sql);


Como vemos, se utiliza el método createStatement del objeto conOracle y el objeto Statement devuelto nos permite ejecutar la consulta correspondiente.

El objeto oraclResult es de tipo java.sql.ResultSet. Como véis, no lo he declarado en estas líneas, y eso se debe a que voy a poner todo este código dentro de un bucle try/catch, ya que los métodos createStatement y executeQuery pueden lanzar algunas excepciones y debemos atraparlas. Por ello, declararé las variables orclResult y la variable conOracle antes de este bloque, así:

public String recuperarTablaHTML() {
    String res = "<table border=1>";
   
    Connection conOracle = null;
    ResultSet orclResult = null;
    try {
        //conectar
        conOracle = this.getConexionOracle();

        //recuperar datos
        Statement orclStmt = conOracle.createStatement();
        String sql = "select campo1, campo2 from tabla1 order by campo1 ";
        orclResult = orclStmt.executeQuery(sql);


Nuestro código va tomando forma.

7. El bucle de procesamiento

Una vez obtenido el resultado, ya sólo nos queda ir recorriendo el conjunto de registros del resultado. Algo que podemos hacer así:

    while (orclResult.next()) {
        String campo1 = orclResult.getString("campo1");
        String campo2 = orclResult.getString("campo2");
        res += "<tr>";
        res += "<td>" + campo1 + "</td>";
        res += "<td>" + campo2 + "</td>";
        res += "</tr>";
        this.numResultados++;
    }


También hemos introducido en el bucle un contador del número de registros procesados, como puede verse en la última línea dentro del bucle.


8. Mostrar los resultados

Bueno, pues hecho todo lo anterior, sólo nos queda regresar al fichero view.jsp y añadir el código para mostrar el resultado.

Fig. 17. Mostrando los resultados en la vista del portlet

Ahora nos vamos al portal y comprobamos si todo funciona como debería:


Fig. 18. Ejecutando el portlet en nuestro portal


Bueno, parece que sí. Así que ya podemos dar por terminada esta entrada.