jueves, 16 de mayo de 2019

How to remove namespaces from XML using XSLT

Sometimes, XML documents you receive could contain unneeded or incorrect namespace information. You can use XSLT stylesheets to remove the namespace information in these documents.

XML Input
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns1:reportRequest xmlns:ns1="http://v1.dto.reports.rolandopalermo.com">
    <ns1:metadata>
        <ns1:account>12345678</ns1:account>
        <ns1:reportName>Current_Payment</ns1:reportName>
    </ns1:metadata>
    <ns1:report>
        <ns1:reportId>1</ns1:reportId>
        <ns1:header>
            <ns1:id>999999999</ns1:id>
            <ns1:name>John Smith</ns1:name>
        </ns1:header>
        <ns1:invoices>
            <ns1:invoice>
                <ns1:invoiceNumber>0000000000</ns1:invoiceNumber>
                <ns1:dueDate>07/01/18</ns1:dueDate>
                <ns1:invoiceAmount>14.00</ns1:invoiceAmount>
                <ns1:amountPaid>14.00</ns1:amountPaid>
            </ns1:invoice>
        </ns1:invoices>
        <ns1:paymentMethodInformation>
            <ns1:cardType>VISA</ns1:cardType>
            <ns1:cardName>************1111</ns1:cardName>
            <ns1:cardHolder>Juan Pérez</ns1:cardHolder>
            <ns1:authorizationCode>dd92c0f864ea9ff1016501e880d03451</ns1:authorizationCode>
        </ns1:paymentMethodInformation>
    </ns1:report>
</ns1:reportRequest>
XSLT
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
    xmlns:ns1="http://v1.dto.reports.rolandopalermo.com">
    <xsl:template match="/">
        <xsl:apply-templates
            select="ns1:reportRequest/ns1:report" />
    </xsl:template>
    <xsl:template match="*">
        <xsl:element name="{local-name(.)}">
            <xsl:apply-templates />
        </xsl:element>
    </xsl:template>
    <xsl:template match="@*">
        <xsl:copy />
    </xsl:template>
</xsl:stylesheet>
XML Output
<?xml version="1.0" encoding="UTF-8"?>
<report>
    <reportId>1</reportId>
    <header>
        <id>999999999</id>
        <name>John Smith</name>
    </header>
    <invoices>
        <invoice>
            <invoiceNumber>0000000000</invoiceNumber>
            <dueDate>07/01/18</dueDate>
            <invoiceAmount>14.00</invoiceAmount>
            <amountPaid>14.00</amountPaid>
        </invoice>
    </invoices>
    <paymentMethodInformation>
        <cardType>VISA</cardType>
        <cardName>************1111</cardName>
        <cardHolder>Juan Pérez</cardHolder>
        <authorizationCode>dd92c0f864ea9ff1016501e880d03451</authorizationCode>
    </paymentMethodInformation>
</report>

miércoles, 15 de mayo de 2019

Cliente para consumo de servicios Web de la DIAN con Java

Hace ya algunos meses se viene implementando el tema de facturación electrónica en Colombia, lo cual ha conllevado a muchos desarrolladores de software a una intensa búsqueda de recursos en internet que le permitan completar el objetivo de llevar a sus respectivas empresas a la emisión exitosa de comprobantes electrónicos. 

Sin embargo, el camino definitivamente no será fácil debido a la pobre documentación que la DIAN pone a disposición. Este escenario es muy común en países de Latinoamérica; ya hace algunos años tuve la oportunidad de conocer el modelo de facturación electrónica de Ecuador, el cual adolecía de los mismos problemas de documentación. 

Ahora bien, para alegría de nuestros lectores, el día de hoy pongo a su alcance las URL's de los WSDL's de los servicios web de la DIAN, tanto para ambientes de Habilitación como de Operación. En la siguiente tabla muestro un listado de las tres operaciones que la DIAN pone a nuestra disposición: 

Consulta de documentos
Habilitación: https://facturaelectronica.dian.gov.co/habilitacion/B2BIntegrationEngine/FacturaElectronica/consultaDocumentos.wsdl
Operación: https://facturaelectronica.dian.gov.co/operacion/B2BIntegrationEngine/FacturaElectronica/consultaDocumentos.wsdl

Consulta de resoluciones
Habilitación: https://facturaelectronica.dian.gov.co/servicios/B2BIntegrationEngine-servicios/FacturaElectronica/consultaResolucionesFacturacion.wsdl
Operación: https://facturaelectronica.dian.gov.co/servicios/B2BIntegrationEngine-servicios/FacturaElectronica/consultaResolucionesFacturacion.wsdl

Emisión de comprobantes electrónicos
Habilitación: https://facturaelectronica.dian.gov.co/habilitacion/B2BIntegrationEngine/FacturaElectronica/facturaElectronica.wsdl
Operación: https://facturaelectronica.dian.gov.co/operacion/B2BIntegrationEngine/FacturaElectronica/facturaElectronica.wsdl

Y si ya logramos generar los clientes para el consumo de estos servicios Web, ahora es necesario configurar las cabeceras de seguridad que permitirán un apropiado consumo de estos servicios ¡Bastante trabajo el que nos esperar!

Pero no todas son malas noticias - ¿Se imaginan que al ejecutar el siguiente código Java podamos, por ejemplo, consultar un documento electrónico enviado a la DIAN?
public static void main(String[] args) throws MalformedURLException, DatatypeConfigurationException, ParseException {
    String identificadorSoftware = "";
    String password = "";
    SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    EnvioConsultaDocumento request = new EnvioConsultaDocumento();
    //Métodos set omitidos
    DianSoapWSClient client = new DianSoapWSClient(identificadorSoftware, password, TipoAmbiente.HABILITACION);
    DocumentosRecibidos response = client.consultaResultadoValidacionDocumentos(request);
}
Luego de muchas vueltas a la documentación de la DIAN y apoyado en experiencias pasadas, se ha logrado la implementación de un cliente Jar que nos brinda una capa de abstración a toda la complejidad que exige la DIAN como parte del consumo de sus servicios.

Detalles del producto
Versión Java JDK 8
Modo de integración Artefacto Maven
Costo USD 75
Servicios soportados Emisión de facturas, consulta de documentos, consulta de resoluciones
Licenciamiento No posee restricciones de licencia

Datos de contacto
Skype rolandopalermo
Email rolando.roc@gmail.com
Facebook fb.com/rolandopalermo

jueves, 25 de octubre de 2018

How to reset a remote git repository to remove all commits

Delete the .git directory locally and recreate the git repostory:
$ cd (project-directory)
$ git init
$ (add some files)
$ git add .
$ git commit -m 'Initial commit'
Push to remote server, overwriting. Remember you're going to mess everyone else up doing this so you better be the only client.
$ git remote add origin <url>
$ git push --force --set-upstream origin master

domingo, 21 de octubre de 2018

How to Create an Executable JAR with Maven

In this quick article we will focus on packaging a Maven project into an executable Jar file. In order to create an executable jar, we don’t need any additional dependencies. We just need to create Maven Java project, and have at least one class with the main(…) method.

We also need to make sure that our pom.xml contains the the following elements:
<build>
    <finalName>anjus-invoicing-${project.version}</finalName>
    <plugins>
        <plugin>
            <artifactId>maven-assembly-plugin</artifactId>
            <configuration>
                <descriptorRefs>
                    <descriptorRef>jar-with-dependencies</descriptorRef>
                </descriptorRefs>
                <appendAssemblyId>false</appendAssemblyId>
                <archive>
                    <manifest>
                        <mainClass>com.rolandopalermo.example.Main</mainClass>
                    </manifest>
                </archive>
            </configuration>
            <executions>
                <execution>
                    <id>make-assembly</id>
                    <phase>package</phase>
                    <goals>
                        <goal>assembly</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

viernes, 19 de octubre de 2018

Generación de número CUFE con Java

Siguiendo con la serie de artículos relacionados con Facturación Electrónica en Colombia, les comparto una función para la generación del número CUFE en Java.
public static String calculateInvoiceCUFE(String numFac, String fechaFac, String horaFac, String valFac,
   String valImp1, String valImp2, String valImp3, String valPag, String nitOFE, String tipAdq, String numAdq,
   String clTec) {
  StringBuilder sb = new StringBuilder("");
  sb.append(numFac);
  sb.append(fechaFac);
  sb.append(horaFac);
  sb.append(valFac);
  sb.append(CodImp1);
  if (StringUtils.isEmpty(valImp1)) {
   sb.append("0.00");
  } else {
   sb.append(valImp1);
  }
  sb.append(CodImp2);
  if (StringUtils.isEmpty(valImp2)) {
   sb.append("0.00");
  } else {
   sb.append(valImp2);
  }
  sb.append(CodImp3);
  if (StringUtils.isEmpty(valImp3)) {
   sb.append("0.00");
  } else {
   sb.append(valImp3);
  }
  sb.append(valPag);
  sb.append(nitOFE);
  sb.append(tipAdq);
  sb.append(numAdq);
  sb.append(clTec);
  return DigestUtils.sha1Hex(sb.toString());
 }
Es importante agregar la siguiente dependencia:
<dependency>
 <groupId>commons-codec</groupId>
 <artifactId>commons-codec</artifactId>
 <version>1.11</version>
</dependency>

jueves, 18 de octubre de 2018

Facturacion Electrónica en Colombia

Conocido es por todos que hoy en día la facturación electrónica es ya una realidad en Colombia. Se han realizado planes piloto con diversas empresas y en la actualidad ya es una necesidad acogerse a esta nueva tecnología tributaria.

En este artículo, no voy a entrar en detalle en las resoluciones y leyes emitidas por la entidad regulatoria en Colombia, la DIAN, sin embargo como desarrollador de software, me causa un profundo malestar la pésima documentación que acompaña a las normas que obligan a las empresas a facturar de manera electrónica.

Y es que no es una novedad que instituciones como el SRI en Ecuador, la SUNAT en Perú o la DIAN en Colombia pongan trabas con pésimos canales de resolución de dudas y mala documentación. A veces me planteo la interrogante de cómo es que planifican la puesta en marcha a nivel nacional de proyectos tan grandes con tan poca o nula documentación.

En mi experiencia, leyendo un manual técnico y entendido el esquema funcional del proceso, se debería estar apto para emitir comprobantes electrónicos en no más de un mes, sin embargo nos tapamos con trabas como el tener que leer un PDF que nos habla de un servicio web de tipo SOAP para consultas de facturas y terminar dándonos cuenta que en ningún momento nos indicaron cuál es el WSDL (Si bien son términos muy técnicos, los lectores que desarrollarán software entenderán la gravedad de esto).

Podría disponer de líneas y líneas en mi blog para hablar de la mala experiencia que puede resultar implementar facturación electrónica en Sudamérica pues la diversidad de manuales no versionados sumado a la poca organización de la información y la indisponibilidad de enlaces y URL's puede, a veces, hacernos arrepentir de haber emprendido este tipo de desarrollos. Sin embargo, el propósito de este artículo es clarificar de cierta manera cuales son los pasos que se debe seguir en Colombia.

El siguiente esquema funcional representa el flujo que se sigue para autorizar o en otras palabras, dar validez tributaria, a una factura electrónica en Colombia.


Básicamente, son dos los servicios Web que nos permiten autorizar una factura, el de recepción y el de consulta. Cada uno tiene un WSDL diferente además de estar configurados para ser consumidos mediante WS-Security. Los enlaces los muestro a continuación:

https://facturaelectronica.dian.gov.co/habilitacion/B2BIntegrationEngine/FacturaElectronica/facturaElectronica.wsdl

https://facturaelectronica.dian.gov.co/habilitacion/B2BIntegrationEngine/FacturaElectronica/consultaDocumentos.wsdl

Los pasos a seguir son:

1. Generar XML
Para generar los archivos XML, a diferencia de Ecuador, debemos utilizar el esquema UBL 2.0. En este punto recomiendo no utilizar librerías externas y mejor generar los documentos XML de manera manual utilizando las clases del paquete org.w3c.dom.

2. Firmar XML
Para firmar el documento XML, una excelente librería para Java es xades4j. La dependencia de maven es la siguiente:
<dependency>
 <groupId>com.googlecode.xades4j</groupId>
 <artifactId>xades4j</artifactId>
 <version>1.5.0</version>
</dependency>
En la sección de issues en su página de github podemos encontrar diversas consultas en donde también sugieren cómo implementar la firma electrónica según los estándares de Colombia.

3. Enviar XML firmado a la DIAN
Este paso es nuestra primera interacción con la DIAN. En este servicio se valida la estructura del documento XML así como la firma digital. Es importante recordar que el SOAP request deberá tener como cabecera el objeto de WS-Security. A continuación muestro un ejemplo de request:
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
    <SOAP-ENV:Header>
        <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" S:mustUnderstand="1">
            <wsse:UsernameToken>
                <wsse:Username><!--- Proporcionado por la DIAN --></wsse:Username>
                <wsse:Password><!--- Proporcionado por la DIAN --></wsse:Password>
                <wsu:Created xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
                <!--- Hora en que se realiza la petición en formato yyyy-MM-ddTHH:mm:ssZ -->
                </wsu:Created>
                <wsse:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis200401-wss-soap-message-security1.0#Base64Binary">
                <!--- Secuencia aleatoria codificada en base 64 -->
                </wsse:Nonce>
            </wsse:UsernameToken>
        </wsse:Security>
    </SOAP-ENV:Header>
    <S:Body>
        <EnvioFacturaElectronicaPeticion xmlns="http://www.dian.gov.co/servicios/facturaelectronica/ReportarFactura">
            <NIT><!--- NIT del facturador --></NIT>
            <InvoiceNumber><!--- Número secuencial de la factura --></InvoiceNumber>
            <IssueDate><!--- Fecha de emisión de la factura en formato yyyy-MM-ddTHH:mm:ss --></IssueDate>
            <Document><!--- Archivo ZIP codificado en base 64 --></Document>
        </EnvioFacturaElectronicaPeticion>
    </S:Body>
</S:Envelope>

4. Validar Factura
En caso el código de respuesta del servicio anterior sea 200, el siguiente paso es comprobar que la factura tenga validez tributaria. Al igual que la llamada anterior, la estructura de la petición para este servicio también considera la cabecera de tipo wS-Security.
Este paso es nuestra primera interacción con la DIAN. Es importante recordar que el SOAP request deberá tener como cabecera el objeto de WS-Security. A continuación muestro un ejemplo de request:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:con="http://www.dian.gov.co/servicios/facturaelectronica/ConsultaDocumentos">
   <soapenv:Header>
        <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
            <wsse:UsernameToken>
                <wsse:Username><!--- Proporcionado por la DIAN --></wsse:Username>
                <wsse:Password><!--- Proporcionado por la DIAN --></wsse:Password>
                <wsu:Created xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
                <!--- Hora en que se realiza la petición en formato yyyy-MM-ddTHH:mm:ssZ -->
                </wsu:Created>
                <wsse:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis200401-wss-soap-message-security1.0#Base64Binary">
                <!--- Secuencia aleatoria codificada en base 64 -->
                </wsse:Nonce>
            </wsse:UsernameToken>
        </wsse:Security>
    </soapenv:Header>
    <soapenv:Body>
        <con:ConsultaResultadoValidacionDocumentosPeticion>
            <con:TipoDocumento>
            <!--- Parámetros enviados con error, el tipo de documento debe ser 
            1 = Factura, 
            2 = Nota Débito, 
            3 = Nota Crédito -->
            </con:TipoDocumento>
            <con:NumeroDocumento>
            <!--- Número secuencial de la factura -->
            </con:NumeroDocumento>
            <con:NitEmisor><!--- NIT del facturador --></con:NitEmisor>
            <con:FechaGeneracion>
            <!--- Fecha de emisión de la factura en formato yyyy-MM-ddTHH:mm:ss -->
            </con:FechaGeneracion>
            <con:IdentificadorSoftware>
            <!--- Proporcionado por la DIAN -->
            </con:IdentificadorSoftware>
            <con:CUFE>
            <!--- El valor del CUFE es obligatorio cuando el tipo de documento es 1 = 'INVOICE' -->
            </con:CUFE>
        </con:ConsultaResultadoValidacionDocumentosPeticion>
    </soapenv:Body>
</soapenv:Envelope>

5. Comunicar factura
Como paso final, en caso de tener como código de transacción 200, ya podemos enviar la factura en formato ZIP junto con el PDF correspondiente al receptor.

Finalmente les comparto la información de un taller que estamos impartiendo para capacitar a desarrolladores Java en la implementación de este tipo de aplicaciones.

Espero esta información les sea de mucha utilidad y quedo presto a resolver interrogantes adicionales que podrán dejar en la caja de comentarios.

viernes, 20 de abril de 2018

Verónica: Facturación Electrónica de código abierto

En el contexto de la computación, el código fuente se define como un conjunto de líneas de texto, que son las instrucciones que debe seguir la computadora para ejecutar dicho programa; por lo que es en el código fuente, donde se encuentra escrito el funcionamiento de un determinado producto de software. Ahora bien, el código fuente de un programa está escrito en uno o más lenguajes de programación específicos, sin embargo este tipo de lenguajes no pueden ser ejecutado directamente por una computadora sino que debe ser traducidos a otro lenguaje que el ordenador pueda ejecutar más fácilmente.

Acceder al código fuente de un programa, consiste en tener acceso a los algoritmos desarrollados por sus creadores. Es la única forma de transformar de manera eficiente un programa. Al tener acceso a las fuentes, es fácil determinar las falencias de un producto sin embargo, desde un punto de vista más optimista, esto no es tan contraproducente como se pensaría puesto que el encontrar errores de programación podría ser de gran ayuda para poder subsanar de manera rápida dichos fallos o incluso, mejorar el rendimiento de un software.

Y es esto último justamente la razón por la cual, luego de haber trabajado por 6 años con más de 40 empresas en Ecuador, he decidido liberar el código fuente de la plataforma para la emisión de comprobantes electrónicos según la normativa impuesta por el Servicio de Rentas Internas (SRI) desde el año 2012. 

Veronica, que es como bauticé al proyecto, reúne todos los requerimientos para generar, firmar, autorizar y almacenar un comprobante electrónico según las leyes del SRI, la autoridad tributaria Ecuatoriana. La idea es formar una comunidad que permita hacer crecer el proyecto de tal manera que pueda ser utilizado por empresas desde la más pequeña hasta la que posea altos volúmenes de facturación. La arquitectura sobre la que está implementada Veronica justamente fue pensada para eso, la escalabilidad.

Evidentemente el fin no es lucrar con un aspecto tan importante como es la cultura tributaria y la declaración de impuestos, sino es poder apoyar a difundir el uso de la facturación electrónica dados los diversos beneficios que está traería consigo no solo para Ecuador sino también para la región.

El proyecto ha sido liberado en la plataforma de control de versiones Github y se encuentra disponible para su rápida descarga, compilación y despliegue, brindando una potente API Rest (Tendencia en el mercado actual de software) que cubre todas las especificaciones del SRI.

Involúcrate en el proyecto y hagamos que se convierta en un estándar.
https://github.com/rolandopalermo/Veronica

martes, 20 de marzo de 2018

Deserialización JSON de tipos genéricos usando Jackson

Como parte de las buenas prácticas en la reutilización de clases, es común encontrar el uso de tipos genéricos en Java. Sin embargo, cuando estas forman parte de una respuesta JSON, pueden ser un dolor de cabeza. Imaginemos que tenemos como respuestas de un API Rest los dos siguientes escenarios:

PersonaResponse
{
   "codigo":1,
   "mensaje":"ok",
   "data":{
      "nombres":"Rolando Palermo",
      "edad":29
   }
}
ProductoResponse
{
   "codigo":1,
   "mensaje":"ok",
   "data":{
      "codigo":"FX-536",
      "precio":56.98,
      "moneda":"PEN",
   }
}
Ambos Json tienen propiedades comunes, sin embargo sólo el objeto data es diferente. Para deserializar estos jsons sin tipos genéricos, tendríamos que crear 2 envoltorios con 2 clases de datos diferentes. Pero con los tipos genéricos solo necesitamos crear una clase genérica para las propiedades de la raíz y una clase específica para cada objeto en la clave de data.

GenericResponse.java
public class GenericResponse<T> {

    private String codigo;
    private String mensaje;
    T data;

    public String getCodigo() {
        return codigo;
    }

    public void setCodigo(String codigo) {
        this.codigo = codigo;
    }

    public String getMensaje() {
        return mensaje;
    }

    public void setMensaje(String mensaje) {
        this.mensaje = mensaje;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }
    
}
PersonaResponse
public class PersonaResponse {

    private String nombres;
    private int edad;

    public String getNombres() {
        return nombres;
    }

    public void setNombres(String nombres) {
        this.nombres = nombres;
    }

    public int getEdad() {
        return edad;
    }

    public void setEdad(int edad) {
        this.edad = edad;
    }
}
ProductoResponse
public class ProductoResponse {

    private String codigo;
    private double precio;
    private String moneda;

    public String getCodigo() {
        return codigo;
    }

    public void setCodigo(String codigo) {
        this.codigo = codigo;
    }

    public double getPrecio() {
        return precio;
    }

    public void setPrecio(double precio) {
        this.precio = precio;
    }

    public String getMoneda() {
        return moneda;
    }

    public void setMoneda(String moneda) {
        this.moneda = moneda;
    }

}
Ahora, la deserialización JSON nos quedaría de la siguiente manera:
ObjectMapper mapper = new ObjectMapper();
//Para persona
GenericResponse<PersonaResponse> response1 = mapper.readValue(jsonString, new TypeReference<GenericResponse<PersonaResponse>>() {});
//Para producto
GenericResponse<ProductoResponse> response2 = mapper.readValue(jsonString, new TypeReference<GenericResponse<ProductoResponse>>() {});

sábado, 24 de febrero de 2018

Bloqueo de recursos en SQL Server

En los últimos años hemos sido testigos de un crecimiento exponencial de aplicaciones distribuidas, con infinidad de componentes concurrentes de propósito específico interactuando unos con otros en tiempo real y a grandes velocidades. Estos nuevos enfoques de arquitecturas asíncronas han traído consigo antiguos problemas que nosotros, los desarrolladores, debemos resolver en nuestro día a día; uno de ellos, el mítico bloqueo mutuo.

Escenarios tales como accesos a interfaces de hardware, aplicaciones IOT o hasta la simple generación de números de secuencia de tickets son casos donde el interbloqueo o bloqueo mutuo es un problema que de no ser resuelto, podría comprometer el éxito de un proyecto de software. Si bien es cierto, existen soluciones que van desde complejas arquitecturas distribuidas hasta increíbles artimañanas inventadas por algún programador adicto al café, en este artículo nos centraremos en una propuesta que manejará el interbloqueo desde base de datos haciendo uso de un procedimiento almacenado de sistema, el sp-getapplock.

El problema se planteará de la siguiente manera: "Debemos desarrollar un procedimiento almacenado que garantice la inserción de registros en una tabla determinada, cada uno de los cuales estará identificado por un id que deberá generarse de manera secuencial sin dejar ningún vacío, esto quiere decir que de presentarse algún error, se deberá ejecutar una instrucción de rollback y la siguiente inserción deberá hacer uso de esta secuencia".

El primer paso será crear nuestra tabla, para lo cual haremos ejecutaremos la siguiente consulta en SQL Server.
CREATE TABLE dbo.Registros  
   (Registro_Seq_ID int PRIMARY KEY NOT NULL,  
    Registro_Descripcion varchar(25) NOT NULL)  
GO 
El siguiente paso será crear un procedimiento almacenado que se encargue de la inserción, esto con el fin de tener un punto de entrada dónde poder aplicar nuestro control contra los interbloqueos.
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO

ALTER PROCEDURE spInsertarRegistro
 @Descripcion VARCHAR(25),
 @RegistroSeq INT OUT
AS
BEGIN
 SET NOCOUNT ON;
 SELECT @RegistroSeq = ISNULL(MAX(Registro_Seq_ID), 0) + 1  from Registros;
 INSERT INTO Registros(Registro_Seq_ID, Registro_Descripcion) VALUES (@RegistroSeq, @Descripcion);
END
GO
Y ahora utilizaremos Java para probar nuestro procedimiento almacenado.
package com.rolandopalermo.sql;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class App {
    
 public String driver = "com.microsoft.sqlserver.jdbc.SQLServerDriver";
 public void insertRegistro(String descripcion) throws SQLException,
 ClassNotFoundException {
  String connectionUrl = "jdbc:sqlserver://localhost:55418;databaseName=BD_TEST;user=user_test_app;password=12345678";
  try (
  Connection connection = DriverManager.getConnection(connectionUrl);
  CallableStatement cstmt = connection.prepareCall("{call spInsertarRegistro(?,?)}")) {
   Class.forName(driver);
   cstmt.setString(1, descripcion);
   cstmt.registerOutParameter(2, java.sql.Types.INTEGER);
   cstmt.execute();
   System.out.println("Se creó el registro [" + cstmt.getInt(2) + "]");
  }
 }
 
 public static void main(String[] args) throws Exception {
  App app = new App();
  app.insertRegistro("Registro sin interbloqueo");
 }
 
}
Al ejecutar dicha clase, veremos que nuestro registro se insertó correctamente, sin embargo ahora realizaremos una pequeña mofidicación al método main para que inserte registros de manera continua, estableciendo una pausa de 200 milisegundos entre cada inserción. Al ejecutar un sólo proceso, el resultado será el siguiente:



Sin embargo, al ejecutar tres procesos, la historia cambia:



Es aquí donde sp-getapplock viene a salvarnos el día. Nuestro procedimiento sufrirá una ligera modifación, la cual evitará que más de una petición intente ejecutarlo de manera concurrente. El recurso de bloqueo creado por sp_getapplock se crea en la base de datos actual para la sesión.
SET ANSI_NULLS ON  
 GO  
 SET QUOTED_IDENTIFIER ON  
 GO  
 ALTER PROCEDURE spInsertarRegistro2  
      @Descripcion VARCHAR(25),  
      @RegistroSeq INT OUT  
 AS  
 DECLARE  
      @rc int = 0,  
      @msg varchar(2000)  
 SET @msg = CONVERT(varchar, GETDATE(), 114) + ' Se incia el segmento a sincronizar'  
 RAISERROR (@msg, 0, 1) WITH NOWAIT  
 BEGIN TRY  
      BEGIN TRAN  
           SET @msg = CONVERT(varchar, GETDATE(), 114) + ' Tratando de obtener el candado'  
           RAISERROR (@msg, 0, 1) WITH NOWAIT  
           -- Se solicita el candado  
           EXEC @rc = sp_getapplock   
                @Resource = 'spInsertarRegistro2', -- Nombre del recurso a bloquear  
                @LockMode = 'Exclusive', -- Tipo de candado  
                @LockOwner = 'Transaction', -- Transaction or Session  
                @LockTimeout = 15000 -- Tiempo de espera máximo, 15 seconds  
           -- Se verifica si se pudo obtener el candado  
           SET @msg =   
                CONVERT(varchar, GETDATE(), 114) +   
                ' sp_getapplock retornó ' +   
                CONVERT(varchar(30), @rc) +   
                ' -- ' +   
                CASE  
                     WHEN @rc < 0 THEN 'No se pudo obtener el candado'  
                     ELSE 'Candado obtenido'  
                END  
           RAISERROR (@msg, 0, 1) WITH NOWAIT  
           IF @rc >= 0  
                BEGIN  
                     SET @msg = CONVERT(varchar, GETDATE(), 114) + ' Se iniciará con la sección crítica '  
                     RAISERROR (@msg, 0, 1) WITH NOWAIT  
                     -- Inserciones protegias por código  
                     SET NOCOUNT ON;  
                     SELECT @RegistroSeq = ISNULL(MAX(Registro_Seq_ID), 0) + 1 from Registros;  
                     INSERT INTO Registros(Registro_Seq_ID, Registro_Descripcion) VALUES (@RegistroSeq, @Descripcion);  
                     --   
                     COMMIT TRAN -- Se libera el candado  
                     SET @msg = CONVERT(varchar, GETDATE(), 114) + ' Trabajo completado, se libera el candado'  
                     RAISERROR (@msg, 0, 1) WITH NOWAIT  
                END  
           ELSE  
                BEGIN  
                ROLLBACK TRAN  
                SET @rc = 50000  
           END  
 END TRY  
 BEGIN CATCH  
      set @msg = 'ERROR: ' + ERROR_MESSAGE() + ' en '   
       + coalesce(ERROR_PROCEDURE(), '')  
       + coalesce (' la línea:' + convert(varchar(30), ERROR_LINE()), '')  
      IF @@Trancount > 1  
           ROLLBACK TRAN;  
      RAISERROR (@msg, 0, 1)  
 END CATCH  
Ahora realizaremos una modificación a la clase Java para que inserte de manera concurrente registros sin ninguna pausa. Al ser la columna Registro_Seq_ID una clave primaria, si en algún momento llegase a duplicarse, se deberá lanzar un error. El siguiente video evidencia que, a pesar de la concurrencia, todos los registros se están insertando de manera correcta.


Espero que este post les sea de utilidad y cualquier consulta adicional, no duden en comentarla.

martes, 5 de diciembre de 2017

API Rest con PHP y MySQL para el manejo de imágenes

Las cargas de imágenes son una cosa que siempre se torna bastante complicada y manejar esto a través de una API Rest es aún más retador. En este artículo vamos a ver cómo insertar imágenes y demás datos en una tabla de MySQL a través de servicios web de tipo REST que permitirán ejecutar las diferentes operaciones sobre la base de datos. Como consideraciones iniciales, es importante mencionar que debamos revisar antes el siguiente artículo:
http://blog.rolandopalermo.com/2017/12/slim-composer-php-rest-api-install.html.

También tendremos que configurar nuestro servidor Apache para poder tener URL más amigables. Para esto modificar los archivos:

C:\xampp\apache\conf\extra\httpd-vhosts.conf
C:\Windows\System32\drivers\etc\hosts

Los cuales deberán quedarnos con el siguiente contenido:

Archivo httpd-vhosts.conf
NameVirtualHost *:80

<VirtualHost *:80>
    DocumentRoot "C:/xampp/htdocs/rest-api/public"
    ServerName rest-api
</VirtualHost>

<VirtualHost *:80>
    DocumentRoot "C:/xampp/htdocs/rest-api-demo/public"
    ServerName rest-api-demo
</VirtualHost>
Archivo hosts
127.0.0.1 rest-api-demo

Recuerden que a lo largo de todo el artículo haré referencia a la ruta C:/xampp que es donde tengo instalado a mi servidor Xampp.

Como primera parte, tendremos una base de datos con una tabla para gestionar usuarios. El script es el siguiente:
CREATE DATABASE IF NOT EXISTS rest_api_base;
USE rest_api_base;

--
-- Definition of table `user`
--

DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `id_user` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(100) NOT NULL,
  `avatar` blob NOT NULL,
  PRIMARY KEY (`id_user`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
Ahora la clase PHP para conectarnos a la base de datos:
<?php

class DB {

    private $db_host = 'localhost';
    private $db_user = 'root';
    private $db_pass = '123456';
    private $db_name = 'rest_api_base';

    public function connect() {
        $mysql_connect_str = "mysql:host=$this->db_host;dbname=$this->db_name";
        $dbConnection = new PDO($mysql_connect_str, $this->db_user, $this->db_pass);
        $dbConnection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
        return $dbConnection;
    }

}
Y la clase que mapea a la tabla users de la base de datos.
<?php

class User {

    public $idUser;
    public $name;
    public $avatar;

    function __construct($idUser, $name, $avatar) {
        $this->idUser = $idUser;
        $this->name = $name;
        $this->avatar = $avatar;
    }

}
También las clase DAO donde se implementarán las operaciones sobre la base de datos.
<?php

require '../src/model/user.php';

class UserDAO {

    public function findAll() {
        $sql = "SELECT * FROM user;";
        try {
            $db = new DB();
            $db = $db->connect();
            if (is_null($db)) {
                echo '{"error": true, "message": "text": "No se pudo conectar a la base de datos."}';
            } else {
                $stmt = $db->query($sql);
                $usuarios = $stmt->fetchAll();
                $arr_usuarios = array();  //array to parse jason from
                $db = null;
                foreach ($usuarios as $row) {
                    $usr = new User($row[0], $row[1], base64_encode($row[2]));
                    $arr_usuarios[] = $usr;
                }
                echo '{"error": false, "data": ' . json_encode($arr_usuarios) . '}';
            }
        } catch (PDOException $ex) {
            echo '{"error": true, "message": "' . $ex->getMessage() . '"}}';
        }
    }

    public function findOne($idUser) {
        $sql = "SELECT * FROM user WHERE id_user=?;";
        try {
            $db = new DB();
            $db = $db->connect();
            if (is_null($db)) {
                echo '{"error": true, "message": "text": "No se pudo conectar a la base de datos."}';
            } else {
                $stmt = $db->prepare($sql);
                $array = array($idUser);
                $stmt->execute($array);
                $usuarios = $stmt->fetchAll();
                $arr_usuarios = array();  //array to parse jason from
                foreach ($usuarios as $row) {
                    $usr = new User($row[0], $row[1], base64_encode($row[2]));
                    $arr_usuarios[] = $usr;
                }
                echo '{"error": false, "data": ' . json_encode($arr_usuarios) . '}';
            }
        } catch (PDOException $ex) {
            echo '{"error": true, "message": "' . $ex->getMessage() . '"}}';
        }
    }

    public function put($user) {
        $sql = "INSERT INTO user(name, avatar) VALUES (?,?)";
        try {
            $db = new DB();
            $db = $db->connect();
            if (is_null($db)) {
                echo '{"error": true, "message": "text": "No se pudo conectar a la base de datos."}';
            } else {
                $stmt = $db->prepare($sql);
                $array = array($user->name, base64_decode($user->avatar));
                $stmt->execute($array);
                $id_producto_insertado = $db->lastInsertId();
                $db = null;
                echo '{"error": false, "data": ' . $id_producto_insertado . '}';
            }
        } catch (PDOException $ex) {
            echo '{"error": true, "message": "' . $ex->getMessage() . '"}}';
        }
    }
    
    public function update($user) {
        $sql = "UPDATE user SET name=?, avatar=? WHERE id_user=?";
        try {
            $db = new DB();
            $db = $db->connect();
            if (is_null($db)) {
                echo '{"error": true, "message": "text": "No se pudo conectar a la base de datos."}';
            } else {
                $stmt = $db->prepare($sql);
                $array = array($user->name, base64_decode($user->avatar),$user->idUser);
                $stmt->execute($array);
//                $id_producto_insertado = $db->lastInsertId();
                $db = null;
                echo '{"error": false, "data": "Usuario actualizado"}';
            }
        } catch (PDOException $ex) {
            echo '{"error": true, "message": "' . $ex->getMessage() . '"}}';
        }
    }

}
Ahora, como mencioné al inicio del artículo, utilizaremos el Framework Slim cuya instalación la expliqué en el post del cual hice referencia al inicio de este artículo. En esta clase se definirán las rutas y los métodos a través de los cuales se accederá a las distintas opciones de nuestra API.
<?php

use PsrHttpMessageServerRequestInterface as Request;
use PsrHttpMessageResponseInterface as Response;

//$app_productos = new SlimApp;

require '../src/dao/user_dao.php';

$app_dapp->get('/api/users/findall', function(Request $request, Response $response) {
    $dao = new UserDAO();
    $dao->findAll();
});

$app_dapp->get('/api/users/find/{idUser}', function(Request $request, Response $response) {
    $dao = new UserDAO();
    $dao->findOne($request->getAttribute('idUser'));
});

$app_dapp->post('/api/users/put', function(Request $request, Response $response) {
    $dao = new UserDAO();
    $user = new User(null, $request->getParam('name'), $request->getParam('avatar'));
    $dao->put($user);
});

$app_dapp->post('/api/users/update', function(Request $request, Response $response) {
    $dao = new UserDAO();
    $user = new User($request->getParam('idUser'), $request->getParam('name'), $request->getParam('avatar'));
    $dao->update($user);
});
Si todo ha marchado bien, podremos acceder desde cualquier cliente y visualizar la información que se solicite. Por ejemplo, lanzado una petición a través de POSTMAN a la opción de listar los usuarios, tendremos lo siguiente:



En este caso, las imágenes fueron tratas en su codificación de Base64. Ahora, si queremos más de cerca nuestra API en acción, podemos utilizar POSTMAN para insertar una imagen:



También podemos escribir un sencillo cliente en PHP. El código sería el siguiente:
<?php

$url = "http://rest-api-demo/api/users/findall";
$client = curl_init($url);
curl_setopt($client, CURLOPT_RETURNTRANSFER, 1);
$curl_response = curl_exec($client);
//echo $curl_response;
$obj_response = json_decode($curl_response);
$array_data = $obj_response->data;
//header("Content-type: image/gif");
foreach($array_data as $item) {
 echo $item->name . '<br>';
 echo '<img width="150" src="data:image/gif;base64,' . $item->avatar . '" />';
 echo '<br>';
}
Y deberíamos tener algo similar a esto:



El proyecto completo lo pueden encontrar en Github y lo pueden descargar del siguiente enlace:
https://github.com/rolandopalermo/rest-api-demo

Espero que este artículo les sea de utilidad y no olviden seguirme a través de Facebook.