Yone Moreno · Nov 28, 2022 go to post

🧐 Additionally we have read 💭💭 carefully:

https://docs.intersystems.com/healthconnectlatest/csp/docbook/DocBook.U…

https://docs.intersystems.com/healthconnectlatest/csp/docbook/DocBook.U…

https://docs.intersystems.com/healthconnectlatest/csp/docbook/DocBook.U…

https://docs.intersystems.com/healthconnectlatest/csp/docbook/DocBook.U…

https://docs.intersystems.com/irisforhealthlatest/csp/docbook/DocBook.U…

https://community.intersystems.com/post/making-jwtoauth20

However, we do still have the same question:

What way is there to "call", "invoke", "communicate" from the other environments ( for example INTEGRATION ) with the PREPRODUCTION Resource Server in order to Validate the Token?

That is to say, the question in another way would be:

What mechanism exists to communicate from an Environment A (Integration) with an Environment B (Preproduction) with the mission to Validate the Token from Environment A using the centralized Resource Server available in Environment B?

👀 Thank you very much for your attention, and thank you for your answers.

Best regards

Yone Moreno · Sep 19, 2022 go to post

As a reference a the Java solution:

Could be:

import java.util.*;

public class DnaStrand {

  static final Map<String,String> letters = Map.ofEntries(Map.entry("A","T"),Map.entry("T","A"),Map.entry("G","C"),Map.entry("C","G"));

  public static String makeComplement /*🧬🔁🧬*/ (String dna) {
    StringBuilder result = new StringBuilder();
    
    for(String letter:dna.split("")){
      result.append(letters.get(letter));
    }
    return result.toString();
  }
}

Would you like to improve this in ObjectScript?

Are you able?

Yone Moreno · Sep 18, 2022 go to post

Example solutions:

dinglemouseChungGorrainrainEAnochlunacjulialebowAndrew P.Kuzmanov_MarioIvanKotsovskiZasho (+ 13)'s solutions

public class Kata {
  public static String declareWinner(Fighter fighter1, Fighter fighter2, String firstAttacker) {
    Fighter a=fighter1, b=fighter2;
    if (firstAttacker.equals(fighter2.name)) {
      a = fighter2; b = fighter1;
    }    
    while (true) {      
      if ((b.health -= a.damagePerAttack) <= 0) return a.name;  // a wins
      if ((a.health -= b.damagePerAttack) <= 0) return b.name;  // b wins
    }
  }
}

My own:

public class Kata {
  public static String declareWinner(Fighter fighter1, Fighter fighter2,
                String firstAttacker) {
    
    String nextAttacker = firstAttacker;
    do{
        if(nextAttacker.equals(fighter1.name)){  
          fighter2.health -= fighter1.damagePerAttack;
          if(fighter2.health > 0){
            nextAttacker = fighter2.name;
          }
        }else{
          fighter1.health -= fighter2.damagePerAttack;
          if(fighter1.health > 0){
            nextAttacker = fighter1.name;
          }
        }
    
    }while(fighter1.health > 0 && fighter2.health > 0);
    return nextAttacker;
  }
}

marko-bekhtaKutayBSVarakutarosedhivyaShreyasRanimeshp's solution
 

public class Kata {
  public static String declareWinner(Fighter fighter1, Fighter fighter2, String firstAttacker) {
        int moves1 = (int) Math.ceil( (double)fighter2.health / fighter1.damagePerAttack);
        int moves2 = (int) Math.ceil( (double)fighter1.health / fighter2.damagePerAttack);
        if (moves1 > moves2) {
            return fighter2.name;
        } else if (moves1 < moves2) {
            return fighter1.name;
        } else {
            return firstAttacker;
        }
  }
}

I hope you feel challenged with this exercise

Yone Moreno · Sep 14, 2022 go to post

Hello Dewey Hunt, thanks for your replies:

1) I am looking to develop current skills and/or generate new ones. But it would be prefered to deep into ObjectScript or into some skills related to interoperability.

2) It is interesting what you write about state of flow

Greetings

Yone Moreno · Sep 13, 2022 go to post

As a reference you could observe my following solution in Java:


 

import java.util.Arrays;

public class Kata {
    public static int findShort(String s) {
        String[] words = s.split(" ");
        int shortestLength = 0;
        for(String word : words){
          if(shortestLength == 0){
            shortestLength = word.length();
          }else{
            if(shortestLength > word.length()){
              shortestLength = word.length();
            }
          }  
        }
        return shortestLength;
    }
}
Yone Moreno · Apr 1, 2022 go to post

Thanks Pasi Leino for your reply and help

We would be very grateful if you could help us with the following:

Could you explain us how could we download, install and start to use a free version od the Dicom Dvtk Ris-Emulator, please?

Yone Moreno · Oct 15, 2021 go to post

We may need to add the Content-Disposition header in the request and there specify the file name.

In the example we had originally made with SoapUI, that header had this value:

Content-Disposition: attachment; name="application.zip"

Fine Tuning a WebClient tells you how you can specify headers in your web client using the SetHttpHeader() method that inherits from %SOAP.WebClient.

Following the explanation in the documentation, we have added:

do ..SetHttpHeader("Content-Disposition","application.zip")

We have written the line before:

Quit ..WebMethod("cargarFichero","CargarFicheroVacuRequest").Invoke($this,"http://ws.regvacuWs.ms.es/FicheroVacu/cargarFichero",.fichero,.ccaaId,.tipoFichero)

When capturing the LOGSOAP we notice that it is left without including the Content-Disposition header and the name application.zip in what we send:

Why could it be that when writing the line, it is left without including the new Content-Disposition header and the application.zip file name? 💭🤔
 

Also, in other tests, we have written 3 additional ways, to try to solve it:

set ..ContentType="application/octet-stream; name=nombre"

Using the above line, we don't see that it adds the "name" parameter.

We tried the following line and it would generate an Ensemble exception:

set ..HttpRequest.ContentType="application/octet-stream; name=nombre"

ERROR #5001: <INVALID OREF>zcargarFichero+16^WSCLIENTE.HistoriaClinica.FicheroVacuServiceSOAP.1

and using the next line:

do ..HttpRequest.SetHeader("name","nombre")

It gives us exception as well:

<INVALID OREF>zcargarFichero+16^WSCLIENTE.HistoriaClinica.FicheroVacuServiceSOAP.1

The complete class we have written of the Web Service is:

Class WSCLIENTE.HistoriaClinica.FicheroVacuServiceSOAP Extends %SOAP.WebClient [ ProcedureBlock ]
{

// Parameter LOCATION = "https://regvacube.sns.gob.es/regvacu/ws/FicheroVacuService";

/// This is the URL used to access the web service.
/// This is the namespace used by the Service
Parameter NAMESPACE = "http://ws.regvacuWs.ms.es/regvacu/ws/FicheroVacuService";

///  20/09/21 Cambiamos a 0, con el objetivo de quitar el xsi:type
Parameter OUTPUTTYPEATTRIBUTE = 0;

/// Determines handling of Security header.
Parameter SECURITYIN = "ALLOW";

/// This is the name of the Service
Parameter SERVICENAME = "FicheroVacuService";

// Parameter SOAPVERSION = 1.2;

Parameter SOAPVERSION = 1.1;

/// This is the SOAP version supported by the service.
Parameter MTOMREQUIRED = 1;

// Method cargarFichero(fichero As %xsd.base64Binary(REQUIRED=1), ccaaId As EsquemasDatos.HistoriaClinica.tns.CCAAIdType(REQUIRED=1), tipoFichero As EsquemasDatos.HistoriaClinica.tns.TipoFicheroType(REQUIRED=1)) As EsquemasDatos.HistoriaClinica.tns.InfoFicheroType(XMLNAME="responseFichero") [ Final, ProcedureBlock = 1, SoapBindingStyle = document, SoapBodyUse = literal, WebMethod ]

// Method cargarFichero(fichero As %GlobalBinaryStream, ccaaId As EsquemasDatos.HistoriaClinica.tns.CCAAIdType(REQUIRED=1), tipoFichero As EsquemasDatos.HistoriaClinica.tns.TipoFicheroType(REQUIRED=1)) As EsquemasDatos.HistoriaClinica.tns.InfoFicheroType(XMLNAME="responseFichero") [ Final, ProcedureBlock = 1, SoapBindingStyle = document, SoapBodyUse = literal, WebMethod ]

/// 15 10 21 Edu explica que el fichero, el .zip con un .csv necesitamos enviarlo SIN CODIFICAR
/// quitamos %GlobalBinaryStream y ponemos %GlobalCharacterStream
///
Method cargarFichero(fichero As %GlobalCharacterStream, ccaaId As EsquemasDatos.HistoriaClinica.tns.CCAAIdType(REQUIRED=1), tipoFichero As EsquemasDatos.HistoriaClinica.tns.TipoFicheroType(REQUIRED=1)) As EsquemasDatos.HistoriaClinica.tns.InfoFicheroType(XMLNAME="responseFichero") [ Final, ProcedureBlock = 1, SoapBindingStyle = document, SoapBodyUse = literal, WebMethod ]
{
  //Header - Addresing
 set addressing = ..crearAddressing()
 
 set addressing.Action = "cargarFichero"
 
 set ..AddressingOut                = addressing
 set ..AddressingOut.mustUnderstand = "1"

 //Firma el XML (mensaje SOAP)
 //do ..crearSignature()
 
 set ..MTOMRequired=1
 
 //24 09 21 para añadir parametro name en cabecera content type
 //set ..ContentType="application/octet-stream; name=nombre"
 
 //28 09 21 probamos a ajustarlo
 //set ..ContentType="application/octet-stream; charset=latin1"
 //set ..ContentType="application/xhtml+xml; charset=latin1"
 
 /*
     27 09 21 con el objetivo de poner parametro name en cabecera content type
     Genera excepcion:      ERROR #5001: <INVALID OREF>zcargarFichero+16^WSCLIENTE.HistoriaClinica.FicheroVacuServiceSOAP.1
 */
 //set ..HttpRequest.ContentType="application/octet-stream; name=nombre"
 //do ..HttpRequest.SetHeader("name","nombre")
 
 
 /*
 15 10 21 seguimos la indicacion de Alberto Fuentes:
     ➕ añadir la cabecera Content-Disposition en la petición y ahí especificar el nombre del archivo.
     https://es.community.intersystems.com/post/%C2%BFc%C3%B3mo-podr%C3%ADamos-usar-mtom-para-enviar-un-zip-con-un-csv-adentro#comment-169536
     https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GSOAP_cli_details#GSOAP_cli_details_http_headers
 */
 do ..SetHttpHeader("Content-Disposition","application.zip")
 
 Quit ..WebMethod("cargarFichero","CargarFicheroVacuRequest").Invoke($this,"http://ws.regvacuWs.ms.es/FicheroVacu/cargarFichero",.fichero,.ccaaId,.tipoFichero)
}

Method infoFichero(ccaaId As EsquemasDatos.HistoriaClinica.tns.CCAAIdType(REQUIRED=1), infoFichero As EsquemasDatos.HistoriaClinica.tns.InfoFicheroType(REQUIRED=1), Output estado As EsquemasDatos.HistoriaClinica.tns.EstadoFicheroType(REQUIRED=1)) As %xsd.base64Binary(XMLNAME="fichero") [ Final, ProcedureBlock = 1, SoapBindingStyle = document, SoapBodyUse = literal, WebMethod ]
{
 set ..MTOMRequired=1    
 Quit ..WebMethod("infoFichero","InfoFicheroVacuRequest").Invoke($this,"http://ws.regvacuWs.ms.es/FicheroVacu/infoFichero",.ccaaId,.infoFichero,.estado)
}

Method crearAddressing() As %SOAP.Addressing.Properties
{
    set IPRedSanitaria = ##class(Util.TablasMaestras).getValorMaestra("PARAMETROS","IPRedSanitaria")
     set puertoRespuestas = ##class(Util.TablasMaestras).getValorMaestra("PARAMETROS","PuertoRespuestasSSL")
     set ReplyTo = ##class(%SOAP.Addressing.EndpointReference).%New()
     set ReplyTo.Address = "http://www.w3.org/2005/08/addressing/anonymous"
     //set ReplyTo.Address = "https://"_IPRedSanitaria_":"_puertoRespuestas_"/csp/SNS/Servicios.ProgramasAsistenciales.SIFCOv02r00.cls"
     set MessageId = ##class(Util.FuncionesComunes).getUID()
     
     set addressing = ##class(%SOAP.Addressing.Properties).%New()
     set addressing.MessageId = MessageId
     set addressing.Destination = ..Location
     set addressing.ReplyEndpoint = ReplyTo    
          
     Quit addressing
}

}

How could we understand, debug, tune, and resolve together, this situation?

If you could give us some guidance, we would appreciate it 🙇🙏🙏🙏🙏🙏

How would you recommend us to follow?

What documentation do we need to study, read, understand, to complete this part?

Thanks for your answers and time reading this question

Yone Moreno · Oct 15, 2021 go to post

We have been developing the Web Service following the official documentation:

Using MTOM for Attachments. Webclient

Specifically we have followed an example of a web service client where MTOMRequired property has been activated, and we have also changed the data types to %Stream.

We have written the following WebService:

Class WSCLIENTE.HistoriaClinica.FicheroVacuServiceSOAP Extends %SOAP.WebClient [ ProcedureBlock ]
{

// Parameter LOCATION = "https://regvacube.sns.gob.es/regvacu/ws/FicheroVacuService";

/// This is the URL used to access the web service.
/// This is the namespace used by the Service
Parameter NAMESPACE = "http://ws.regvacuWs.ms.es/regvacu/ws/FicheroVacuService";

///  20/09/21 Cambiamos a 0, con el objetivo de quitar el xsi:type
Parameter OUTPUTTYPEATTRIBUTE = 0;

/// Determines handling of Security header.
Parameter SECURITYIN = "ALLOW";

/// This is the name of the Service
Parameter SERVICENAME = "FicheroVacuService";

// Parameter SOAPVERSION = 1.2;

Parameter SOAPVERSION = 1.1;

/// This is the SOAP version supported by the service.
Parameter MTOMREQUIRED = 1;

// Method cargarFichero(fichero As %xsd.base64Binary(REQUIRED=1), ccaaId As EsquemasDatos.HistoriaClinica.tns.CCAAIdType(REQUIRED=1), tipoFichero As EsquemasDatos.HistoriaClinica.tns.TipoFicheroType(REQUIRED=1)) As EsquemasDatos.HistoriaClinica.tns.InfoFicheroType(XMLNAME="responseFichero") [ Final, ProcedureBlock = 1, SoapBindingStyle = document, SoapBodyUse = literal, WebMethod ]

// Method cargarFichero(fichero As %GlobalBinaryStream, ccaaId As EsquemasDatos.HistoriaClinica.tns.CCAAIdType(REQUIRED=1), tipoFichero As EsquemasDatos.HistoriaClinica.tns.TipoFicheroType(REQUIRED=1)) As EsquemasDatos.HistoriaClinica.tns.InfoFicheroType(XMLNAME="responseFichero") [ Final, ProcedureBlock = 1, SoapBindingStyle = document, SoapBodyUse = literal, WebMethod ]

/// 15 10 21 Edu explica que el fichero, el .zip con un .csv necesitamos enviarlo SIN CODIFICAR
/// quitamos %GlobalBinaryStream y ponemos %GlobalCharacterStream
///
Method cargarFichero(fichero As %GlobalCharacterStream, ccaaId As EsquemasDatos.HistoriaClinica.tns.CCAAIdType(REQUIRED=1), tipoFichero As EsquemasDatos.HistoriaClinica.tns.TipoFicheroType(REQUIRED=1)) As EsquemasDatos.HistoriaClinica.tns.InfoFicheroType(XMLNAME="responseFichero") [ Final, ProcedureBlock = 1, SoapBindingStyle = document, SoapBodyUse = literal, WebMethod ]
{
  //Header - Addresing
 set addressing = ..crearAddressing()
 
 set addressing.Action = "cargarFichero"
 
 set ..AddressingOut                = addressing
 set ..AddressingOut.mustUnderstand = "1"

 //Firma el XML (mensaje SOAP)
 //do ..crearSignature()
 
 set ..MTOMRequired=1
 
 //24 09 21 para añadir parametro name en cabecera content type
 //set ..ContentType="application/octet-stream; name=nombre"
 
 //28 09 21 probamos a ajustarlo
 //set ..ContentType="application/octet-stream; charset=latin1"
 set ..ContentType="application/xhtml+xml; charset=latin1"
 
 /*
     27 09 21 con el objetivo de poner parametro name en cabecera content type
     Genera excepcion:      ERROR #5001: <INVALID OREF>zcargarFichero+16^WSCLIENTE.HistoriaClinica.FicheroVacuServiceSOAP.1
 */
 //set ..HttpRequest.ContentType="application/octet-stream; name=nombre"
 //do ..HttpRequest.SetHeader("name","nombre")
 
 Quit ..WebMethod("cargarFichero","CargarFicheroVacuRequest").Invoke($this,"http://ws.regvacuWs.ms.es/FicheroVacu/cargarFichero",.fichero,.ccaaId,.tipoFichero)
}

Method infoFichero(ccaaId As EsquemasDatos.HistoriaClinica.tns.CCAAIdType(REQUIRED=1), infoFichero As EsquemasDatos.HistoriaClinica.tns.InfoFicheroType(REQUIRED=1), Output estado As EsquemasDatos.HistoriaClinica.tns.EstadoFicheroType(REQUIRED=1)) As %xsd.base64Binary(XMLNAME="fichero") [ Final, ProcedureBlock = 1, SoapBindingStyle = document, SoapBodyUse = literal, WebMethod ]
{
 set ..MTOMRequired=1    
 Quit ..WebMethod("infoFichero","InfoFicheroVacuRequest").Invoke($this,"http://ws.regvacuWs.ms.es/FicheroVacu/infoFichero",.ccaaId,.infoFichero,.estado)
}

Method crearAddressing() As %SOAP.Addressing.Properties
{
    set IPRedSanitaria = ##class(Util.TablasMaestras).getValorMaestra("PARAMETROS","IPRedSanitaria")
     set puertoRespuestas = ##class(Util.TablasMaestras).getValorMaestra("PARAMETROS","PuertoRespuestasSSL")
     set ReplyTo = ##class(%SOAP.Addressing.EndpointReference).%New()
     set ReplyTo.Address = "http://www.w3.org/2005/08/addressing/anonymous"
     //set ReplyTo.Address = "https://"_IPRedSanitaria_":"_puertoRespuestas_"/csp/SNS/Servicios.ProgramasAsistenciales.SIFCOv02r00.cls"
     set MessageId = ##class(Util.FuncionesComunes).getUID()
     
     set addressing = ##class(%SOAP.Addressing.Properties).%New()
     set addressing.MessageId = MessageId
     set addressing.Destination = ..Location
     set addressing.ReplyEndpoint = ReplyTo    
          
     Quit addressing
}

Method crearSignature() As %XML.Security.Signature
{
       //Generamos el Binary Security Token a partir del mcertificado
     set x509alias = ##class(Util.TablasMaestras).getValorMaestra("PARAMETROS","aliasCertMSSSI")
    set pwd = ##class(Util.TablasMaestras).getValorMaestra("PARAMETROS","pwdCertMSSSI")
    set cred = ##class(%SYS.X509Credentials).GetByAlias(x509alias,pwd)
    set token = ##class(%SOAP.Security.BinarySecurityToken).CreateX509Token(cred)
    
    //Creamos la firma
    //set sig1=##class(%XML.Security.Signature).CreateX509(token,,$$$KeyInfoX509IssuerSerial)
    //set sig2=##class(%XML.Security.Signature).CreateX509(token,$$$SOAPWSIncludeSoapBody,$$$SOAPWSReferenceDirect)
    set sig2=##class(%XML.Security.Signature).CreateX509(token,$$$SOAPWSIncludeDefault,$$$SOAPWSReferenceDirect)
    
    //do sig1.SetSignatureMethod($$$SOAPWSrsasha1)
    do sig2.SetSignatureMethod($$$SOAPWSrsasha1)
    //do sig1.SetDigestMethod($$$SOAPWSsha1)
    do sig2.SetDigestMethod($$$SOAPWSsha1)
    
    //Creamos la referencia al id del token generado a partir de la firma
    //set algorithm=$$$SOAPWSEnvelopedSignature_","_$$$SOAPWSc14n
    set reference=##class(%XML.Security.Reference).Create(token.Id)
    do sig2.AddReference(reference)
    
    //Crear TimeStamp
    Set timestamp=##class(%SOAP.Security.Timestamp).Create()
    
    //Se une
    //do ..SecurityOut.AddElement(sig1)
    do ..SecurityOut.AddToken(token)
    do ..SecurityOut.AddElement(sig2)    
    Do ..SecurityOut.AddToken(timestamp)
}

}

The target system, asks us to implement a WebService with MTOM to send the zip with a csv inside, as follows:

When importing the WSDL of the target system, the header of the "cargarFichero" (loadFile) method was generated with "fichero" (file) as a "%xsd.base64Binary".

We changed file to "%GlobalCharacterStream".

This way the file, the csv, is sent unencoded, inside a CDATA

Being the response of the target system:

10/15/2021 08:57:24 *********************
Input to Web client with SOAP action = http://ws.regvacuWs.ms.es/FicheroVacu/cargarFichero
--MIME_Boundary
Content-ID: <root.message@cxf.apache.org>
Content-Type: application/xop+xml; type="text/xml"; charset=utf-8
Content-Transfer-Encoding: 8bit

        <?xml version="1.0" encoding="UTF-8"?>
        <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
            <soap:Body>
                <soap:Fault>
                    <faultcode>soap:Server</faultcode>
                    <faultstring>Name must not be null</faultstring>
                </soap:Fault>
            </soap:Body>
        </soap:Envelope>

--MIME_Boundary--

---------------
Validate Security header: action=http://ws.regvacuWs.ms.es/FicheroVacu/cargarFichero, MethodName=cargarFichero
**** SOAP client return error. method=cargarFichero, action=http://ws.regvacuWs.ms.es/FicheroVacu/cargarFichero
     ERROR #6248: La respuesta de SOAP es un error de SOAP: faultcode=Server
faultstring=Name must not be null
faultactor=
detail=

However, as we can see in the first image, the target system would need, would require, that we send the file encoded in binary, since it says:

Content-Transfer-Encoding: binary

When we adapt the file to be "%GlobalBinaryStream" we see the following trace, where it is encoded in binary:

When sending binary encoded, the target system also responds:

09/28/2021 16:56:43 *********************
Input to Web client with SOAP action = http://ws.regvacuWs.ms.es/FicheroVacu/cargarFichero
--MIME_Boundary
Content-ID: <root.message@cxf.apache.org>
Content-Type: application/xop+xml; type="text/xml"; charset=utf-8
Content-Transfer-Encoding: 8bit

            <?xml version="1.0" encoding="UTF-8"?>
            <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
                <soap:Body>
                    <soap:Fault>
                        <faultcode>soap:Server</faultcode>
                        <faultstring>Name must not be null</faultstring>
                    </soap:Fault>
                </soap:Body>
            </soap:Envelope>

--MIME_Boundary--

---------------
Validate Security header: action=http://ws.regvacuWs.ms.es/FicheroVacu/cargarFichero, MethodName=cargarFichero
**** SOAP client return error. method=cargarFichero, action=http://ws.regvacuWs.ms.es/FicheroVacu/cargarFichero
     ERROR #6248: La respuesta de SOAP es un error de SOAP: faultcode=Server
faultstring=Name must not be null
faultactor=
detail=

In short, the target system needs from us:

"add the "name" parameter inside the "Content-Type" header when attaching the file. Your client should generate this parameter to avoid getting this error."

Being the full comparison between what is sent by ensemble (without the "name" parameter, therefore incorrect) on the left; and what is generated by the SoapUI (with the name parameter, correct), on the right:

➡️ Please could you point us to examples, documentation, projects, code, that we can use as a reference to investigate, research and complete the development? 💭

We have also investigated the following answers:

https://community.intersystems.com/post/add-parameter-name-inside-conte…

Thank you very much for your time, reading and responding.

Thanks for your help.

Yone Moreno · Oct 8, 2021 go to post

Thanks Jeffrey Drumm for your help, because the answer you have proposed works as expected

In addition you have helped us to learn about the square bracket syntax [] to find a value in a field in all repeating segments

Plus you have shared with us how does it outputs the values of the repeating segments: inside a string where each field's value is inside "<...>"

Thanks

Yone Moreno · Oct 7, 2021 go to post

Thanks Jeffrey Drumm for your reply

We have observed that our Ensemble's version is:

Cache for UNIX (Red Hat Enterprise Linux for x86-64) 2017.2.1 (Build 801_3_18358U) Tue Jul 24 2018 16:36:10 EDT

It does not contain "RegexMatch" function in Ens.Util.FunctionSet

We have tried to develop a rule using Length and Contains functions

We would need to detect the following:

if at least one OBX3.1 is "24642" or at least one OBX3.5 = "24642"

What we have developed does not work, and we do not know why

Could you help us, please?

We have been testing, developing and debugging some hours, and we would really appreciate your time, replies, effort, and examples, please

The rule which does not work is:

Being the code:

<rule name="22 09 21 ADTs Antigenos DragoAE -&gt; DRAGOAP" disabled="true">
<constraint name="source" value="GestionPacientesFromSelenev02"></constraint>
<constraint name="msgClass" value="EnsLib.HL7.Message"></constraint>
<constraint name="docCategory" value="2.5"></constraint>
<when condition="(Document.{MSH:MessageType.TriggerEvent}=&quot;A08&quot;)&amp;&amp;(((Length(HL7.[OBX:3.1])=&quot;5&quot;)&amp;&amp;(HL7.[OBX:3.1] Contains &quot;24642&quot;))||((Length(HL7.[OBX:3.5])=&quot;5&quot;)&amp;&amp;(HL7.[OBX:3.5] Contains &quot;24642&quot;)))&amp;&amp;((Document.{OBX(1):ObservationResultStatus}=&quot;R&quot;)||(Document.{OBX(1):ObservationResultStatus}=&quot;C&quot;))">
<trace value="&quot;Se permiten ADTs con código de Antígenos en OBX3.5 o OBX3.1 y con OBX.11 = &apos;R&apos; Creaciones o &apos;C&apos; Modificaciones&quot;"></trace>
<send transform="" target="EnrutadorTestAntigenos"></send>
<return></return>
</when>
</rule>

Could you help us?

Would you know why it does not detect that the OBX3.1 and OBX3.5 are "24642"?

Thanks for your time

Yone Moreno · Sep 27, 2021 go to post

Thanks David Hockenbroch for your help

We have written:

set ..HttpRequest.ContentType="application/octet-stream; name=nombre"
 do ..HttpRequest.SetHeader("name","nombre")

Being the full method as follows:

Method cargarFichero(fichero As %GlobalBinaryStream, ccaaId As EsquemasDatos.HistoriaClinica.tns.CCAAIdType(REQUIRED=1), tipoFichero As EsquemasDatos.HistoriaClinica.tns.TipoFicheroType(REQUIRED=1)) As EsquemasDatos.HistoriaClinica.tns.InfoFicheroType(XMLNAME="responseFichero") [ Final, ProcedureBlock = 1, SoapBindingStyle = document, SoapBodyUse = literal, WebMethod ]
{
  //Header - Addresing
 set addressing = ..crearAddressing()
 
 set addressing.Action = "cargarFichero"
 
 set ..AddressingOut                = addressing
 set ..AddressingOut.mustUnderstand = "1"

 //Firma el XML (mensaje SOAP)
 //do ..crearSignature()
 
 set ..MTOMRequired=1
 
 //24 09 21 para añadir parametro name en cabecera content type
 set ..ContentType="application/octet-stream; name=nombre"
 
 /*
     27 09 21 con el objetivo de poner parametro name en cabecera content type
 */
 set ..HttpRequest.ContentType="application/octet-stream; name=nombre"
 do ..HttpRequest.SetHeader("name","nombre")
 
 Quit ..WebMethod("cargarFichero","CargarFicheroVacuRequest").Invoke($this,"http://ws.regvacuWs.ms.es/FicheroVacu/cargarFichero",.fichero,.ccaaId,.tipoFichero)
}

When we execute it, Ensemble throws an exception in the message viewer:

➡️ ERROR #5001: <INVALID OREF>zcargarFichero+16^WSCLIENTE.HistoriaClinica.FicheroVacuServiceSOAP.1

We think it means that the variable "HttpRequest" is an invalid oref

How could we continue?

What steps would you recommend us to add the parameter "name" inside "Content-Type" header when we send a MTOM attachment using a SOAP request?

Thanks for your time, answers and help 💭

Yone Moreno · Sep 24, 2021 go to post

Thanks Sean Connelly for your time and help answering to us

We have written:

Method cargarFichero(fichero As %GlobalBinaryStream, ccaaId As EsquemasDatos.HistoriaClinica.tns.CCAAIdType(REQUIRED=1), tipoFichero As EsquemasDatos.HistoriaClinica.tns.TipoFicheroType(REQUIRED=1)) As EsquemasDatos.HistoriaClinica.tns.InfoFicheroType(XMLNAME="responseFichero") [ Final, ProcedureBlock = 1, SoapBindingStyle = document, SoapBodyUse = literal, WebMethod ]
{
  //Header - Addresing
 set addressing = ..crearAddressing()
 
 set addressing.Action = "cargarFichero"
 
 set ..AddressingOut                = addressing
 set ..AddressingOut.mustUnderstand = "1"

 //Firma el XML (mensaje SOAP)
 //do ..crearSignature()
 
 set ..MTOMRequired=1
 
 //24 09 21 para añadir parametro name en cabecera content type
 set ..ContentType="application/octet-stream; name=nombre"
 
 Quit ..WebMethod("cargarFichero","CargarFicheroVacuRequest").Invoke($this,"http://ws.regvacuWs.ms.es/FicheroVacu/cargarFichero",.fichero,.ccaaId,.tipoFichero)
}

However when we output the LOGSOAP we observe:

Output from Web client with SOAP action =http:// [endpoint]/cargarFichero
----boundary2247.8235294117647062276.235294117647059--
Content-Type: application/xop+xml; type="text/xml"; charset="UTF-8"
Content-Transfer-Encoding: 8bit
Content-Id: <0.E238359C.1D35.11EC.923C.005056B672A4>

    <?xml version="1.0" encoding="UTF-8" ?>
    <SOAP-ENV:Envelope xmlns:SOAP-ENV='http://schemas.xmlsoap.org/soap/envelope/' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns:s='http://www.w3.org/2001/XMLSchema' xmlns:wsa='http://www.w3.org/2005/08/addressing'>
        <SOAP-ENV:Header>
            <wsa:Action>cargarFichero</wsa:Action>
            <wsa:MessageID>e236513c1d3511ec923c005056b672a4</wsa:MessageID>
            <wsa:ReplyTo>
                <wsa:Address>http://www.w3.org/2005/08/addressing/anonymous</wsa:Address>
            </wsa:ReplyTo>
            <wsa:To>https://[endpoint]?wsdl</wsa:To>
        </SOAP-ENV:Header>
        <SOAP-ENV:Body>
            <CargarFicheroVacuRequest xmlns="http://[endpoint]">
                <fichero>
                    <xop:Include href="cid:1.E238359C.1D35.11EC.923C.005056B672A4" xmlns:xop="http://www.w3.org/2004/08/xop/include"/>
                </fichero>
                <ccaaId>01</ccaaId>
                <tipoFichero>2</tipoFichero>
            </CargarFicheroVacuRequest>
        </SOAP-ENV:Body>
    </SOAP-ENV:Envelope>
----boundary2247.8235294117647062276.235294117647059--
Content-Id: <1.E238359C.1D35.11EC.923C.005056B672A4>
Content-Transfer-Encoding: binary
CONTENT-TYPE: application/octet-stream

;;;47B7F6BF1C6D [... csv file content ...]

As you would observe in the LOGSOAP, the request is sending the headers as: "CONTENT-TYPE: application/octet-stream"

We would need to send:

Content-Type: application/octet-stream; name=1.E238359C.1D35.11EC.923C.005056B672A4

Content-Disposition: attachment; name="1.E238359C.1D35.11EC.923C.005056B672A4"; filename="1.E238359C.1D35.11EC.923C.005056B672A4"

Because it is being expected by the receiver system, to include the parameter "name" inside the header "Content-Type", as is shown in the next image:

How could we achieve this feature?

Would you recommend us some guide or documentation, to read about this topic?

Are there any examples that could help us?

Thanks in advance

Yone Moreno · Aug 9, 2021 go to post

Thanks Julius Kavay for your reply

Your contribution has helped us a lot, thanks, sincerely thanks.

Specially we thank you for your explanation it was very helpful!

Yone Moreno · Aug 5, 2021 go to post

Thanks

Yone Moreno · Jul 1, 2021 go to post

Hello Eduard thanks for your reply

msg is a %GlobalCharacterStream

it is defined before, here is the code:

                            set msg=##class(%GlobalCharacterStream).%New()
                            if ('tSC) || (response.error '= "")||(response.informacion.mensaje '= "") {
                                do msg.Write("<br></br><H1>No existe el informe solicitado</H1>")
                            } else {
                                do ..Adapter.AssignOneSetting("Pdf","1","")
                                Do msg.Write($SYSTEM.Encryption.Base64Decode(response.datos.pdf))
                            }
Yone Moreno · Jun 30, 2021 go to post

Hello, thanks for your time reading our questions and doubts,

We have studied how to use the following data types: %Stream.TmpCharacter and %Stream.TmpBinary

We have used both as follows:

              

               set pOutput = ##class(%Stream.TmpBinary).%New()
                //set pOutput = msg
                while(msg.AtEnd=0){
                    do pOutput.Write(msg.Read())
                }
                do msg.Clear()
                Quit $$$OK
 
                do pOutput.Clear()
                $$$LOGWARNING("Despues de borrar en el Servicio: pOutput.Clear()")

However we still observing a .stream being created at: /opt/ensemble/ESBHCDSNS/stream

How could we delete a .stream which represents a pdf being returned from a REST Service to POSTMAN?

Thanks for your help 🙇‍♂️  and time reading our questions and code ⌚️

➡️ We would need some help if you could explain to us how would you recommend to delete a .stream in a REST Service? , please.

We have also read:

https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cl…

https://docs.intersystems.com/irislatest/csp/documatic/%25CSP.Documatic…

https://docs.intersystems.com/irislatest/csp/documatic/%25CSP.Documatic…

https://docs.intersystems.com/latest/csp/documatic/%25CSP.Documatic.cls…

Yone Moreno · May 31, 2021 go to post

Thanks Jeffrey for your kind help, and thanks for your time.

We have wrote your method in a class called "Util.FuncionesComunes"

Namespace "ESBSSCC"

We have wrote in the terminal:

ESBSSCC 2e1>Set hosts=##class(Util.FuncionesComunes).GetHostsByAdapter("Producion.ESBSSCC","EnsLib.HTTP.InboundAdapter")
 

We observe:


SET hosts=##class(Util.FuncionesComunes).GetHostsByAdapter("Produccion.ESBSSCC",
^
"EnsLib.HTTP.InboundAdapter")
<COMMAND>^Util.FuncionesComunes.1

ESBSSCC 2e1>

Production's name is: Producion.ESBSSCC

How could we debug this to be able to execute the code and list all REST services in the current namespace?

Thanks for your help, time and replies Jeffrey

Yone Moreno · May 28, 2021 go to post

Thanks Jeffrey

We have placed your function in a class called "Util.FuncionesComunes"

We have tried to execute it as follows:

Set hosts=##class(Util.FuncionesComunes).GetHostsByAdapter("Produccion.ESBSSCC","EnsLib.HTTP.InboundAdapter")

It shows:

SET hosts=##class(Util.FuncionesComunes).GetHostsByAdapter("Produccion.ESBSSCC",
^
"EnsLib.HTTP.InboundAdapter")
<COMMAND>^Util.FuncionesComunes.1

When we write: "w $ZERROR" it outputs:

SET hosts=##class(Util.FuncionesComunes).GetHostsByAdapter("Produccion.ESBSSCC",
^
"EnsLib.HTTP.InboundAdapter")
<COMMAND>^Util.FuncionesComunes.1

How could we solve this?

Yone Moreno · Apr 15, 2021 go to post

Hello Marc Mundt,

ITB.HL7.BS.XMLService code is the following:

/// HL7 XML services common class
Class ITB.HL7.BS.XMLService Extends (Ens.BusinessService, ITB.HL7.XMLHost)
{

/// Location and Revision of this file in Perforce (Auto-updating)
Parameter SrcVer = "$Id$";

Property UseAckCommitCodes As %Boolean [ InitialExpression = 1 ];

Property BadMessageHandler As %String(MAXLEN = 1000);

/// Name of the element to send the incoming XML stream received by this Service if message is processed OK
Property XMLInputHandler As %String(MAXLEN = 1000);

/// Colon-separated LocalFacility:LocalApplication:MessageStructure codes representing this (receiving) facility, application, returning MessageStructure, AcceptAcknowledgmentType and ApplicationAcknowledgmentType<br/>
/// These are used in constructing reply ACK message headers as SendingFacility, SendApplication and MessageStructure. <br/>
/// The '@' symbol represents using the corresponding field from the incoming message. <br/>
/// If your ID must contain a literal @ symbol, escape it with backslash: '\@'
Property LocalFacilityApplication As %String [ InitialExpression = "ISC:EnsembleHL7:ACK:NE:NE" ];

/// Strip namespace in HL7 XML (ACK message).
Property StripNamespace As %Boolean [ InitialExpression = 1 ];

/// Control of ACK handling; options: <br/>
/// - Never : Do not send back any ACK <br/>
/// - Immediate : Send back (commit) ACK reply message immediately upon receipt of the inbound message <br/>
/// - Application : If message passes validation, wait for ACK from target config item and forward it back when it arrives <br/>
Property AckMode As %String(DISPLAYLIST = ",Never,Immediate,Application", VALUELIST = ",Never,Immed,App") [ InitialExpression = "Immed", Required ];

/// Names the target(s) from which an ACK response should be forwarded back to the caller, if the AckMode="Application".
Property ResponseFrom As %String(MAXLEN = 1000);

Parameter SETTINGS = "StripNamespace,LocalFacilityApplication,AckMode,ResponseFrom::selector?context={Ens.ContextSearch/ProductionItems?targets=1&productionName=@productionId},UseAckCommitCodes,TargetConfigNames:Basic:selector?multiSelect=1&context={Ens.ContextSearch/ProductionItems?targets=1&productionName=@productionId},BadMessageHandler:Basic:selector?context={Ens.ContextSearch/ProductionItems?targets=1&productionName=@productionId},XMLInputHandler:Basic:selector?context={Ens.ContextSearch/ProductionItems?targets=1&productionName=@productionId},SearchTableClass::selector?context={Ens.ContextSearch/SearchTableClasses?host=EnsLib.HL7.Service.Standard},MessageSchemaCategory:Basic:selector?context={Ens.ContextSearch/SchemaCategories?host=EnsLib.HL7.Service.Standard},AlertGracePeriod:Alerting";

/// HL7 XML (Stream) process input
Method StreamProcessInput(pInput As %Stream.Object, Output pOutput As %Stream.Object, pSendAck As %Boolean = 0, pCallTargets As %Boolean = 1, Output pER7 As EnsLib.HL7.Message) As %Status
{
    set ret = $$$OK
    
    try {
        // convert XML input to ER7
        set tER7 = ##class(ITB.HL7.Util.Convert).XMLToER7(pInput,.tSC,..MessageSchemaCategory)
        if $$$ISERR(tSC) $$$ThrowStatus(tSC)
        set pER7 = tER7
        
        // send ACK
        if pSendAck,..AckMode="Immed" {
            set tAckCode = $case(..UseAckCommitCodes, 1:"CA", 0:"AA")
            set tAckER7 = ..GetAck(tER7, tAckCode)
            set tAckXML = ##class(ITB.HL7.Util.Convert).ER7ToXML(tAckER7,.tSC,,,,..StripNamespace)
            if $$$ISERR(tSC) $$$ThrowStatus(tSC)
            set pOutput = tAckXML
        }
        
        // send EnsLib.HL7.Message to targets
        if pCallTargets {
            for i=1:1:$l(..TargetConfigNames, ",") {
                set tTarget=$zstrip($p(..TargetConfigNames,",",i),"<>W")
                if pSendAck,..AckMode="App",..ResponseFrom=tTarget {
                    $$$THROWONERROR(tSC,..SendRequestSync(tTarget, tER7, .tAckER7))
                    set tAckXML = ##class(ITB.HL7.Util.Convert).ER7ToXML(tAckER7,.tSC,,,,..StripNamespace)
                    if $$$ISERR(tSC) $$$ThrowStatus(tSC)
                    set pOutput = tAckXML
                } else {
                    $$$THROWONERROR(tSC,..SendRequestAsync(tTarget, tER7))
                }
            }
        }
        
        // index HL7 in SearchTable
        if ..SearchTableClass'="" {
            set tSC = $zobjclassmethod(..SearchTableClass,"IndexDoc",tER7)
            if $$$ISERR(tSC) $$$LOGERROR("SearchTableClass Error: "_##class(%SYSTEM.Status).GetErrorText(tSC))
        }
        
        // ok. send XML input to XMLInputHandler if any
        do:..XMLInputHandler'="" ..SendStreamToTarget(..XMLInputHandler,pInput)
        
    } catch ex {
        set ret = ex.AsStatus()
        $$$LOGERROR($$$StatusDisplayString(ret))
        
        // error occured. send service input to BadMessageHandler if any
        do:..BadMessageHandler'="" ..SendStreamToTarget(..BadMessageHandler,pInput)
        
        // send alert when HL7 XML has not been processed correctly
        do:..AlertOnError ..SendAlert(##class(Ens.AlertRequest).%New($LB(..%ConfigName,$$$StatusDisplayString(ret))))
    }
    
    quit ret
}

/// Get ACK message for a given HL7 message
Method GetAck(pMsg As EnsLib.HL7.Message, pReplyCode As %String) As EnsLib.HL7.Message
{
    // create ACK and copy the control id to the ack control id
    set tReply = pMsg.NewReplyDocument(,..LocalFacilityApplication)
    set tReply.Source = pMsg.%Id()
    do tReply.SetValueAt(pMsg.GetValueAt("1:10"),"1:10")
    do tReply.SetValueAt($p(..LocalFacilityApplication,":",3),"1:9.3")
    do tReply.SetValueAt($p(..LocalFacilityApplication,":",4),"1:15")
    do tReply.SetValueAt($p(..LocalFacilityApplication,":",5),"1:16")
    
    // MSA segment
    set tMSA=##class(EnsLib.HL7.Segment).%New($LB("",1))
    set tMSA.Separators=tReply.Separators
    do tMSA.SetValueAt("MSA",0)
    do tMSA.SetValueAt(pReplyCode,1)
    do tMSA.SetValueAt(pMsg.GetValueAt("1:10"),2)
    do tReply.AppendSegment(tMSA)
    
    quit tReply
}

/// Send pInput stream to a production target
Method SendStreamToTarget(pTarget As %String, pInput As %Stream.Object) As %Status
{
    set tMsg = ##class(Ens.StreamContainer).%New(pInput)
    set tSC = ..SendRequestAsync(pTarget, tMsg)
    if $$$ISERR(tSC) $$$LOGERROR(##class(%SYSTEM.Status).GetOneErrorText((tSC)))
    quit tSC
}

/// Return an array of connections for drawing lines on the config diagram
ClassMethod OnGetConnections(Output pArray As %String, pItem As Ens.Config.Item)
{
    do ##super(.pArray,pItem)
    
    if pItem.GetModifiedSetting("TargetConfigNames",.tValue) {
        
        set:pItem.GetModifiedSetting("BadMessageHandler",.tBadMessageHandler) tValue=tValue_","_tBadMessageHandler
        set:pItem.GetModifiedSetting("XMLInputHandler",.tXMLInputHandler) tValue=tValue_","_tXMLInputHandler
        
        for i=1:1:$L(tValue,",") {
            set tOne=$zstrip($p(tValue,",",i),"<>W")
            continue:""=tOne
            set pArray(tOne)=""
        }
    }
}

}

GetAck is a "Method"

How should we call it using method syntax, Marc Mundt?

Thanks for your replies

Yone Moreno · Mar 9, 2021 go to post

Thanks Eduard for sharing this interesting piece of advice

Why would be better or recommended to use %CSP.REST directly, instead of EnsLib.REST.Service?

Are there any improvements if we use %CSP.REST?

Thanks for your reply

Yone Moreno · Mar 9, 2021 go to post

We are grateful Marc for your help,

Thanks for explaining how did you find the cause which was preventing to get the response in POSTMAN

Yone Moreno · Mar 8, 2021 go to post

Thanks Marc Mundt for your help

You are right

When we removed that line, the response is shown in POSTMAN:

Thanks four your help Marc

How did you know we should remove that line?

Yone Moreno · Mar 8, 2021 go to post

Thanks Marc Mundt for your attention, and your helpful reply

 

Our current code is:

Class Servicios.REST.miSCS.Pruebas Extends EnsLib.REST.Service
{

Parameter ADAPTER = "EnsLib.HTTP.InboundAdapter";

Parameter EnsServicePrefix = "/aplicaciones/scs/test/miscs";

XData UrlMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ]
{
<Routes>

<Route Url="/:personType/:keyfield/:keyval/:getfield" Method="GET" Call="retrievePerson"/>
<Route Url="/consultarImagen" Method="POST" Call="consultarImagen"/>

</Routes>
}

/// Retrieve
Method retrievePerson(pInput As %Library.AbstractStream, Output pOutput As %Stream.Object, pPersonType As %String, pKeyField As %String, pKeyVal As %String, pGetField As %String = "") As %Status
{
    Set tType=$ZConvert(pPersonType,"L")  Quit:$Case(tType,"employee":0, "person":0, :1) $$$ERROR($$$EnsErrGeneral,"Directory type "_..#EnsServicePrefix_"/"_tType_"/ not supported.")
    Set $E(tType)=$ZConvert($E(tType),"U")
    Set tKeyIn=pKeyField, tKey=$ZConvert(tKeyIn,"L")  Quit:$Case(tKey,"name":0, "ssn":0, :1) $$$ERROR($$$EnsErrGeneral,"Directory key "_..#EnsServicePrefix_"/"_tType_"/"_tKey_" not supported.")
    Set tKeyVal=$Replace($ZConvert(pKeyVal,"I","URL"),"'","''")
    Set tField=pGetField  Set:""=tField tField="*"  Quit:tField["," $$$ERROR($$$EnsErrGeneral,"Commas not allowed in selection field; found: .../"_tField)
    Set tNS=$Namespace

    Set tKeyWild=$Translate(pKeyVal,"*?","%_")
    Do:tKeyWild'=pKeyVal pOutput.Write("[")
    ZNSpace "SAMPLES"
    try {
        Set tSel=$S("*"=tField:"ID",1:tField)
        Set tSQL="SELECT "_tSel_$S("*"=tField||(tKey=tSel):"", 1:","_tKey)_$Case("ID",tKey:"",tSel:"",:",ID")_" FROM Sample."_tType_" WHERE "_tKey_" LIKE '"_tKeyWild_"'"
        //$$$LOGINFO("tSQL: "_tSQL)
        Set tRS=##class(%ResultSet).%New()
        Set tSC=tRS.Prepare(tSQL)  Quit:$$$ISERR(tSC)
        Set tSC=tRS.Execute()  Quit:$$$ISERR(tSC)
        Set tFirst=1
        Set tOut=##class(%IO.StringStream).%New()
        While tRS.Next(.tSC) && $$$ISOK(tSC) {
            #; first normalize the case of the key and sel property names
            If tFirst {
                Set k="" For { Set k=$O(tRS.Data(k))  Quit:""=k
                    If $ZConvert(k,"L")=$Zconvert(tSel,"L") Set tSelN=k
                    If $ZConvert(k,"L")=$Zconvert(tKey,"L") Set tKeyN=k
                }
            }
            If $Case(tSelN, "Company":1, "Notes":1, "Home":1, "Office":1, :0) {
                Set tVal=tRS.Data("ID")
                Set tObj=$classmethod("Sample."_tType,"%OpenId",tVal,,.tSC)  Quit:$$$ISERR(tSC)
                Set tVal=$property(tObj,tSelN)
                Set tSelX = $Case(tSelN, "Home":"Addr", "Office":"Addr", :tSelN)
                Set tVal=$Case(tSelX, "Company":tVal.Name, "Notes":tVal.Read(), "Addr":tVal.Street_", "_tVal.City_" "_tVal.State_" "_tVal.Zip, :tVal)
            } Else {
                Set tVal=tRS.Data(tSelN)
            }
            If "*"=tField {
                Set tObj=$classmethod("Sample."_tType,"%OpenId",tVal,,.tSC)  Quit:$$$ISERR(tSC)
                Set tProxyObj=..buildProxyObj(tObj)
                Do tOut.Write($S(tFirst:"",1:","))
                Set tSC=..ObjectToJSONStream(tProxyObj,.tOut)
            } Else {
                Set:tKeyN'=tSelN tKeyFound=tRS.Data(tKeyN)
                Do tOut.Write($S(tFirst:"",1:",")_"{"_$S(tKeyN=tSelN:"",1:""""_tKeyIn_""":"""_tKeyFound_""", ")_""""_tSel_""":"""_tVal_"""}"_$C(13,10))
            }
            Set tFirst=0
            ZNSpace tNS
            Do tOut.Rewind()  Set tSC1=pOutput.Write(tOut.Read())  Do tOut.Clear()  Set:$$$ISOK(tSC) tSC=tSC1  Quit:$$$ISERR(tSC)
            ZNSpace "SAMPLES"
        } Quit:$$$ISERR(tSC)
        Do:tKeyWild'=tKeyVal pOutput.Write("]"_$C(13,10))
    } catch {
        Kill tRS
        ZNSpace tNS
        Set tSC=$$$SystemError
    }
    Kill tRS
    ZNSpace tNS
    $$$LOGINFO("tSQL: "_tSQL)
    Do:$$$ISOK(tSC) pOutput.SetAttribute("Content-Type","application/json")
    
    while (pOutput.AtEnd = 0){
        set respuestaFinal = pOutput.Read()
    }
    do pOutput.Rewind()
    $$$LOGINFO("respuestaFinal: "_respuestaFinal)
    
    Quit tSC
}

/// Normalize the Person or Employee info by copying its properties to a proxy object in a selective way
ClassMethod buildProxyObj(pObj As %Persistent) [ Internal ]
{
    Set tProxy = ##class(%ZEN.proxyObject).%New()
    Set tProxy.ID=pObj.%Id()
    Set tProxy.Name=pObj.Name
    Set tProxy.Age=pObj.Age
    Set tProxy.DOB=$ZDateTime(pObj.DOB,3)
    Set tProxy.SSN=pObj.SSN
    Set tProxy.FavoriteColors=pObj.FavoriteColors
    Set tProxy.Spouse=pObj.Spouse.Name
    Set tProxy.Home=..buildProxyAddr(pObj.Home)
    Set tProxy.Office=..buildProxyAddr(pObj.Office)
    If pObj.%IsA("Sample.Employee") {
        Set tProxy.Company=pObj.Company.Name
        Set tProxy.Notes=$S($IsObject(pObj.Notes):pObj.Notes.Read(),1:"")
    }
    Quit tProxy
}

ClassMethod buildProxyAddr(pObj As %SerialObject) [ Internal ]
{
    Set tProxy = ##class(%ZEN.proxyObject).%New()
    Set tProxy.Street=pObj.Street
    Set tProxy.City=pObj.City
    Set tProxy.State=pObj.State
    Set tProxy.Zip=pObj.Zip
    Quit tProxy
}

/// Control the type and content of error returned to the REST caller
ClassMethod OnErrorStream(pStatus As %Status)
{
     Set tStream = ##class(%GlobalBinaryStream).%New()  $$$ASSERT($IsObject(tStream))
    Do tStream.Write($ZConvert($$$StatusDisplayString(pStatus)_$C(13,10),"O","UTF8"))
    Set tStream.Attributes("Content-Type")=" text/plain; charset=""UTF-8"""
    Set tStream.Attributes("ResponseCode")="500 Internal Server Error"
     Quit tStream
}

/// Obtener la imagen guardada en TSI 📥📥📥📥
Method consultarImagen(pInput As %Library.AbstractStream, Output pOutput As %Stream.Object) As %Status
{
    Set pOutput=##class(%GlobalBinaryStream).%New()
    set claseAux = ##class(%ZEN.Auxiliary.jsonProvider).%New()
    
    //Convertir el body del JSON a objeto de Ensemble
    set body = pInput.Read()
    $$$LOGINFO("miSCS: body: "_body)
    do pInput.Rewind()
    
    //El mensaje esta en el body
    set tSC= claseAux.%ConvertJSONToObject(.body,"Mensajes.Request.miSCS.ConsultarImagen",.objetoEntrada,1)
    

    //Enviamos al Proceso
    set tSC = ..SendRequestSync("miSCS",objetoEntrada,.objetoSalida)

    //Convertimos el OBJETO devuelto por el Proceso en JSON
    //set tSC = claseAux.%WriteJSONStreamFromObject(.pOutput,.objetoSalida,,,,"aloqtuw")
    
    //Esta linea hace esta en el metodo de ejemplo "retrievePerson" y convierte el mensaje response en JSON
    Set tSC=..ObjectToJSONStream(objetoSalida,.pOutput)
    
    while (pOutput.AtEnd = 0){
        set respuesta = pOutput.Read()
    }
    do pOutput.Rewind()
    $$$LOGINFO("respuesta: "_respuesta)
    
    $$$LOGINFO("tSC: "_tSC)
    $$$LOGINFO("$$$ISOK(tSC): "_$$$ISOK(tSC))
    
    //Enviamos el JSON con cabeceras
    Do:$$$ISOK(tSC) pOutput.SetAttribute("Content-Type","application/json")
    do pOutput.SetAttribute("Access-Control-Allow-Origin","*")
    do pOutput.SetAttribute("Access-Control-Allow-Credentials","true")
    do pOutput.SetAttribute("Access-Control-Allow-Methods","GET")
    do pOutput.SetAttribute("Access-Control-Allow-Headers","request,Access-Control-Allow-Headers, Origin,Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers")
    
    while (pOutput.AtEnd = 0){
        set respuestaFinal = pOutput.Read()
    }
    do pOutput.Rewind()
    $$$LOGINFO("respuestaFinal: "_respuestaFinal)
    Quit tSC
}

}

When we dig deeper using Whireshark:

First, we find that the method in the example that uses GET, the retrievePerson, does answers with a JSON to POSTMAN, so it works:

GET request:

GET response:

In POSTMAN:

However,

Our custom method: consultarImagen which is a POST

Shows nothing in POSTMAN

Why?

POST request:

POST response:

In POSTMAN:

Could you help us?

Thanks for your replies

Yone Moreno · Mar 8, 2021 go to post

Thanks Marc Mundt for your reply

Yes you are right, we should use POST

We have changed it:

<Route Url="/consultarImagen" Method="POST" Call="consultarImagen"/>

However we do not see the response in POSTMAN:

And the headers are:

Content-Type: text/html

Content-Length: 0

CACHE-CONTROL: no-cache

PRAGAM: no-cache

We send the POST to the following URL:

http://localhost:19622/aplicaciones/scs/test/miscs/consultarImagen
 

Besides we observe the response message being converted from Ensemble object to JSON in the service:

We do see the Response Message from the Operation to the Service

Why we do not see the JSON being replied from the Service in POSTMAN?

How could we debug this behaviour?

Thanks for your replies

Yone Moreno · Jan 29, 2021 go to post

Thanks Marc Mundt for your help,

We have tried to make objetoSalida.binario as %Binary

Exactly we do the following:

1) We read the image as binary directly from the external system in the REST Operation:

set linea=""
    if (tResponse.Data.AtEnd = 0) {
        set linea = tResponse.Data.Read()
    }
    
    set pResponse.binario = linea
    
    $$$LOGINFO("pResponse.binario: "_pResponse.binario)

We observe the binary correctly written in the LOGINFO:

2) In the Service, we have a LOGINFO  before writing it to JSON

 $$$LOGINFO("objetoSalida.binario: "_objetoSalida.binario)        

 set tSC = claseAux.%WriteJSONStreamFromObject(.pOutput,.objetoSalida,,,,"aeloqtuw")

We see it correctly:

3) However when we convert it to JSON we see that there are strange characters like "\x00"

set tSC = claseAux.%WriteJSONStreamFromObject(.pOutput,.objetoSalida,,,,"aeloqtuw")

$$$LOGINFO("pOutput.Read(): "_pOutput.Read())

How would you recommend us to continue?

What documents or code examples would you study or write to handle this issue?

Thanks for your help