jueves, 7 de mayo de 2015

Stored Procedure SQL-XML como External Object

En esta ocasión les dejo una pequeña guía para ejecutar un query sql-xml en SQL Server, mediante un sencillo stored procedure que en GX usaremos definido como External Object. Para que tengan de referencia, este ejemplo lo hice usando GXXEv2u6, generador C# y MS Sql Server 2008 R2 sp2.

Creación del Stored Procedure

Acá les dejo el script:
CREATE PROCEDURE [dbo].[sp_dynQuery]
 @query nvarchar(2048) ,
 @outNvar nvarchar(max) OUT
AS
BEGIN
    DECLARE @parmDef nvarchar(512);
    DECLARE @queryTmp nvarchar(2048)
    DECLARE @outTmp nvarchar(max);
    SET NOCOUNT ON;
    SET @parmDef = '@out nvarchar(max) OUTPUT';
    SET @queryTmp = 'SELECT @out = CAST((' + @query + ') AS NVARCHAR(max))';
    EXECUTE sp_executesql @queryTmp, @parmDef, @out=@outTmp OUTPUT; 
    SELECT @outNvar = @outTmp;
    SET NOCOUNT OFF
    RETURN(0)
END
Verán que básicamente lo que hace el SP es ejecutar la query que le daremos por parámetros, pasando el resultado a una variable de tipo nvarchar y retornando en el parámetro de salida. Como se trata de un proc que ejecuta queries dinámicas, que eventualmente puede representar un problema de seguridad, debemos asegurar dejarlo asociado a un usuario con restricciones. Específicamente, sólo permiso para ejecutar el SP y de lectura en tablas. Para estos seteos podemos pedir ayuda a nuestro buen amigo Google, al DBA o algún crack del sql que tengamos cerca.

Para que todo resulte más cómodo, les dejo scripts para crear la tabla del ejemplo y cargarle algunos datos.
CREATE TABLE [dbo].[Developer](
 [DeveloperId] [smallint] NOT NULL,
 [DeveloperName] [varchar](64) NOT NULL,
 [DeveloperLastName] [varchar](64) NOT NULL,
 [DeveloperMail] [varchar](128) NOT NULL,
PRIMARY KEY([DeveloperId])
INSERT INTO [Developer]
([DeveloperId],[DeveloperName],[DeveloperLastName],[DeveloperMail])
VALUES
    (1,'ZERR','Angelo','angelo.zerr@gmail.com')
   ,(2,'Leclercq','Pascal','pascal.leclercq@gmail.com')

A continuación la query que pasaremos al SP desde GX:
SELECT [DeveloperName]     as devname 
      ,[DeveloperLastName] as devlastname
      ,[DeveloperMail]     as devmail
FROM [Developer]
WHERE [DeveloperId] < 3
FOR XML PATH ('developer'), ROOT ('devs')
Este ejemplo es bastante sencillo, consulta algunos campos en una tabla, pero se pueden construir cosas bastante más complejas. Aquí hay un buen post sobre el tema.


Creación de External Object

Lo siguiente es la creación del External Object que en nuestra KB representará al stored procedure. Como con cualquier objeto, lo podemos iniciar con Ctrl-N, asignando el nombre que deseamos, en este caso "dynQuery". Desde antes debemos tener preparado un Data Store, que llamaremos "StoredProcs", especial para ejecutar nuestro Stored Procedure, seteado con una cuenta de usuario que mencioné antes, la cual debe tener ciertas restricciones.
En las propiedades seleccionamos el tipo "Stored Procedure" y asignamos el datastore creado previamente.
El único método a definir será "dynQuery" y en External Name ponemos el nombre del Stored Procedure, en este caso "sp_dynQuery" (se puede ver en el script de creación del SP).

En la definición de cada parámetro debemos considerar las propiedades del Stored Procedure en cuanto a Tipo de Acceso, Tipo de Dato y Nombre.



Uso del External Object

Finalmente, en algún objeto de prueba, podemos poner la invocación al EO de la siguiente forma:
&query = 'SELECT [DeveloperName]     as devname ' +
         '      ,[DeveloperLastName] as devlastname ' +
         '      ,[DeveloperMail]     as devmail ' +
         'FROM [Developer] ' +
         'WHERE [DeveloperId] < 3 ' +
         "FOR XML PATH ('developer'), ROOT ('devs')"
csharp try{
    &result = &dynQuery.dynQuery(&query,&return)
csharp }
csharp catch ( Exception e ){
csharp     [!&error!] = e.ToString();
csharp }

La ejecución de esto, si no hay problemas, debiera darnos en la variable &return el siguiente xml:
<devs>
    <developer>
        <devname>ZERR</devname>
        <devlastname>Angelo</devlastname>
        <devmail>angelo.zerr@gmail.com</devmail>
    </developer>           
    <developer>
        <devname>Leclercq</devname>
        <devlastname>Pascal</devlastname>
        <devmail>pascal.leclercq@gmail.com</devmail>
    </developer>           
</devs>           
Nota: La "indentacion" y los saltos de linea los puse para efectos de mejor visualización.

Este tipo de solución se puede usar en innumerables situaciones. Por mencionar algo, están los reportes dinámicos que les mencioné en el post anterior.
También podemos convertir a JSON si tenemos algún User Control que lo requiera, etc., etc..

martes, 21 de abril de 2015

Ideas de reporting con xDocReport y Gx-Java

Algo que me motiva es hacer aplicaciones que tengan opciones de configuración potentes, que den a los usuarios buenas posibilidades de ajustarse a diferentes escenarios, a cambiantes contextos de negocios. O sea, a lo que tenemos por estos tiempos, cada día.
Tengo la idea de que cada vez es más común y aceptado que usuarios de configuración dispongan de más recursos en este sentido, de tal forma que los sistemas se puedan ajustar y adaptar rápido a determinados cambios. Por ejemplo, cuando se necesita definir un producto nuevo o cuando cambian reglas de cálculo de una nómina.
Otra veta a explotar en este camino es el reporting. En este post les cuento un poco sobre xDocReport, una api Java que nos permite trabajar con templates diseñados por cualquier usuario, usando MS Word, insertando campos de "combinación de correspondencia" (mergefields) que asumirán los valores de un xml al momento de crear el reporte. Usaremos la sintaxis de FreeMarker para los campos en el template docx.

Bueno, lo primero: Descargar xDocReport desde aquí.
Luego crear una KB GX-Java (yo usé la XEv2) con un webpanel que llame a un procedure, copiar todos los Jar a la carpeta web y setear la Path List de la siguiente forma:


A continuación preparar el template con MS Word.
Debe quedar más o menos así:


Dejé resaltados los campos para que se aprecie bien, así que no esperen que el archivo les quede exactamente igual. Los campos se crean usando Ctrl-F9 y se completan con la opción "Editar Campo" que aparece con el click derecho. Ahí deben dejarlo de tipo MergeField y en el nombre poner el identificador o directiva que corresponda (por ejemplo ${doc.project.name}).


El xml lo podemos generar con un sdt y el método ToXml, pero en este ejemplo lo pondremos en un archivo:
<devs>
    <developer>
        <devname>ZERR</devname>
        <devlastname>Angelo</devlastname>
        <devmail>angelo.zerr@gmail.com</devmail>
    </developer>           
    <developer>
        <devname>Leclercq</devname>
        <devlastname>Pascal</devlastname>
        <devmail>pascal.leclercq@gmail.com</devmail>
    </developer>           
</devs>           
El siguiente sería el código del procedure:

&tmpString = !"C:" + chr(92) + "xDocReportFiles" + chr(92)
&fileIN  = &tmpString + !"xDocRptTemplate.docx"
&fileXml = &tmpString + !"xDocRptData.xml"
&fileOUT = &tmpString + !"xDocRptOut.docx"
java try {
// 1) Load Docx file by filling  template engine and cache it to the registry
java     java.io.InputStream in = new java.io.FileInputStream([!&fileIN!]);
java     fr.opensagres.xdocreport.document.IXDocReport report;
java     fr.opensagres.xdocreport.template.TemplateEngineKind kind = fr.opensagres.xdocreport.template.TemplateEngineKind.Freemarker;
java     report = fr.opensagres.xdocreport.document.registry.XDocReportRegistry.getRegistry().loadReport(in,kind);
// 2) Create context Java model
java     fr.opensagres.xdocreport.template.IContext context = report.createContext();
java     java.io.InputStream projectInputStream = new java.io.FileInputStream([!&fileXml!]);
java     org.xml.sax.InputSource projectInputSource = new org.xml.sax.InputSource( projectInputStream );
java     freemarker.ext.dom.NodeModel project = freemarker.ext.dom.NodeModel.parse( projectInputSource );
java     context.put( "doc", project );
// 3) Generate report by merging Java model with the Docx
java     java.io.OutputStream out = new java.io.FileOutputStream(new java.io.File([!&fileOUT!]));
java     report.process(context, out);
java     } catch (Exception e) {
java         System.out.println("Exception thrown: " + e);
java     e.printStackTrace();
java }
RESULTADO
Para ejecutar, asegurarse de que los archivos estén en el directorio que corresponda, especialmente los Jar que deben estar en la "lib" de nuestra aplicación en el Tomcat.
Si todo sale bien, aparecerá nuestro report con los datos provistos por el xml y debiera verse así:



Eso, por ahora.

Queda una lista interesante de cosas por hacer: generar data con SDTs/DataProviders, queries dinámicas con "For Xml" (usando un store proc en Sql Server), convertir a Pdf, convertir las librerías para poder usarlas con Gx-Csharp, etc..

salu2!!!

martes, 20 de enero de 2015

Scripting GX C# con Jint 2.4


Amigos, en esta ocasión hago un "update" del tema de scripting con Jint, uno de los engines javascript que podemos usar con C#, que desde un tiempo pasó a la versión 2.0 con importantes mejoras.

En esta pasada aprovecho de mostrarles cómo hacer para que desde el javascript puedan llamar a procedures gx que estén en la misma KB, lo que favorece de buena forma las posibilidades de este tipo de integración y su aplicación en casos reales.

Pondremos en un procedure jintEngine el código de más abajo, en el que se puede apreciar en las primeras líneas el truco para "cortar" el C# generado por GX y meter en medio una definición de "delegate" y un método que usaremos luego para llamar a un procedure gx desde el javascript. Nótese que el método recibe un string y retorna otro string, tal como el "delegate".

 parm(&jsVarchar,&returnVarchar,&isOK);
 csharp     executePrivate2();
 csharp }
 csharp public delegate string getObjectDeleg(string inStr);
 csharp private string getObject(string inStr)
 csharp {
         csharp [!&in!] = inStr;
         //acá la lamada a otro procedure de nuestra kb
         jintGetObjJsonString.call(&in,&ret)
         csharp return [!&ret!];
 csharp }
 csharp void executePrivate2( )
 csharp {
 
 // código para instanciar el engine Jint
 &returnVarchar = ""
 &isOK = true
 csharp try {
         csharp var engine = new Jint.Engine(cfg => cfg.AllowClr());
         //acá hacemos una instancia del delegate
         csharp getObjectDeleg getObjectDelegInstance = new getObjectDeleg(getObject);
         //y se lo pasamos al engine
         csharp engine.SetValue("getObjJsonStr",getObjectDelegInstance);
         //ejecutar el javascript que viene en &jsVarchar
         csharp engine.Execute([!&jsVarchar!]);
          //el resultado lo dejamos en &returnVarchar
         csharp [!&returnVarchar!] = engine.GetCompletionValue().ToString();
 csharp }
 csharp catch ( Exception e ){
         csharp [!&returnVarchar!] = e.ToString();
         &isOK = false
 csharp }  
El procedure jintGetObjJsonString podrá ser invocado desde el javascript con getObjJsonStr, de acuerdo a cómo se lo pasamos al engine con SetValue.
Para este procedure podríamos tener el siguiente código:
 parm(&in,&ret); //parms de tipo varchar
 //asumimos que en &in viene un json con la estructura de &sdtEntrada
 &sdtEntrada.fromjson(&in)
 ...
 // aquí lo que sea que se necesite para cargar el &sdtSalida
 ...
 &ret = &sdtSalida.tojson() 

Un ejemplo de javascript que podríamos ejecutar:
 //objeto a usar como parm (con la misma estructura de &sdtEntrada)
 var objSdtEntrada = {Id:1,............};
 //llamada al proc gx usando delegate
 var objDesdeGX = getObjJsonStr(JSON.stringify(objSdtEntrada));
 function mainFunction(){
     ...
     var otroDesdeGX = getObjJsonStr(JSON.stringify(objSdtEntrada));
     ...
     //el retorno puede ser un json o cualquier cosa string
     return JSON.stringify(otroDesdeGX);
 }
 //finalizar con la invocación a la función "main"
 mainFunction();
Lo importante de este ejemplo es mostrar cómo se invoca el método delegate mediante la función getObjJsonStr, que finalmente llama al procedure jintGetObjJsonString, y que debemos usar una función para hacer el return que Jint atrapa con GetCompletionValue.

Un abrazo y buen 2015 a todos.
salu2!

P.D.: para este año me gustaría que Artech nos anunciara un generador web basado en AngularJS.