Article
· May 3, 2023 10m read

Change Message Viewer trace to JSON instead of XML

Programming and languages

Being a programmer nowadays is basically the geek version of being a polyglot. Of course, most of us here, in the InterSystems Community, “speak ObjectScript”. Howeever, I believe this wasn’t the first language for many people. For instance, I had never heard about it prior to getting the appropriate training at Innovatium.

The most fascinating part of this is that even though we are able to learn any language and become fluent in it, we will always have our favorites – the ones we feel more comfortable and familiar with, and, as a rule, it has a lot to do with the first language we learned.

With that in mind, I have come across the idea to Make JSON representation of messages in Interoperability message viewer instead of XML from the InterSystems Ideas Portal, and I found out that @Guillaume Rongier has already solved this issue for us. My point in this article is to show you how to use his solution  and talk about some utilities.

 

The Context

For every product we build, the InterSystems IRIS Management Portal creates an interface that displays all the connected components and every message related to their behavior.

The picture above illustrates the Production Configuration Portal, where you can see all Business Services, Processes and Operations, divided by categories of your choice. For each one, you can check and/or change general and custom settings and perform such actions as changing the state, testing, exporting, checking the jobs, queues and logs related to it, etc. However, the most important thing for us now is the ability to check the messages.

By selecting Messages on the tab on the right, you get to see a table of all the messages related to this production. At the same time, if you click on Go To Message Viewer you’ll see a more detailed table with various options for filtering. Also, if you pick a business component before choosing the messages tab, you will see an already filtered selection of only those production messages that communicate with the designated component.

 

In this other picture, we can notice the Message Viewer and finally acquire the information we were aiming for. If you select a line in the table, you will open a section on the right with every possible detail about the chosen message. That means you will see a tab for Header, another one for the Body, and one more for Tracing with a diagram demonstrating where the message came from and the related components. The tab we are looking for is the Contents.

The contents tab shows us an XML version of the message, and today we are going to change it for a JSON version.

PS.: You could also click on the Session number in the table to obtain a focused view of the trace beside the Header, Body and Contents tab, as exemplified in the image below.

 

 

Detour!

This section is supposed to be a quick detour around the basics of building an API, a common case widely employing JSON, and get familiar with a context where the application could help us.

In order to give you a feeling of the changes this application brings, We are going to build a simple example of an API that receives some information in an HTTP Request in JSON format. If you are already familiar with APIs, feel free to jump to the next section. However, I recommend at least scanning this section. In this way, you will be able to understand the examples discussed ahead better.

I will follow Innovatium’s best practices, but remember that this is not the only way to do it.

 

First, we need to create a Dispatch service, extending %CSP.REST and Ens.BusinessService, with the adapter EnsLib.HTTP.InboundAdapter that handles incoming HTTP requests. We should specify if the service handles CORS requests. We can also optionally implement the OnHandleOptionsRequest class method and finally define the XData UrlMap with Map parameters, forwarding to the next class we will make.

 

Class Sample.Service.Dispatch Extends (%CSP.REST, Ens.BusinessService)
{

 Parameter HandleCorsRequest = 1;
 Parameter ADAPTER = "EnsLib.HTTP.InboundAdapter";
 XData UrlMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ]
 {
    <Routes>
        <Map Prefix="/send" Forward="Sample.Service.Send"/>
    </Routes>
 }

 ClassMethod GetService(Output tService As Ens.BusinessService) As %Status
 {
 	 Quit ##class(Ens.Director).CreateBusinessService(..%ClassName(1), .tService)
 }

 ClassMethod OnHandleOptionsRequest(url As %String) As %Status
 { 
     ; your code here
 }
}

 

Then, on the Management Portal we create a web application with REST enabled with the class we have just created as the dispatch class.

 

Next, we develop a class extending the dispatch with an XData UrlMap with Route parameters, and construct the method that can actually handle the incoming request. This can also be done in the dispatch service. Using this method you can do anything you want with the request. However, we are going to focus on receiving a JSON, transforming it into an object and sending it to a business operation that will be able to handle the information in any way required.

Class Sample.Service.Send Extends Sample.Service.Dispatch
{

Parameter CONTENTTYPE = "application/json";
Parameter CHARSET = "utf-8";
XData UrlMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ]
{
<Routes>
<Route Method="POST" Url="/message" Call="SendMessage"/>
</Routes>
}

ClassMethod SendMessage() As %Status
{
    Set tSC = $$$OK
    Try
    {
        #Dim %request As %CSP.Request
        #Dim %response As %CSP.Response
        #Dim Service As Ens.BusinessService
        
        Set %response.ContentType = "application/json"
        Set %response.CharSet = "utf-8"
        ; code for authentication - this part was cut out for simplification
        ; in this part I set the variables tUsername and tBasicAuth, used to create the request below.
        ; implementing authentication is optional, this is just an example
        // creates service
        Set tSC = ..GetService(.tService)
        
        If $System.Status.IsError(tSC) Quit
        // gets request body
        Set tReceived = ##class(%Stream.GlobalCharacter).%New()
        Set tSC = tReceived.CopyFrom(%request.Content)
        
        If $System.Status.IsError(tSC) Quit
        Set tMessage = ##class(%DynamicObject).%FromJSON(tReceived)
        
        // creates request object for the business operation 
        Set tRequest = ##Class(Sample.Operation.SendMessage.Request).%New()
        Set tRequest.Message = tMessage.message
        Set tRequest.token = tBasicAuth
        Set tRequest.Username = tUsername
        
        Set tSC = tService.SendRequestSync("Sample.Operation",tRequest,.tResponse)
        
        If $System.Status.IsError(tSC) Quit
        // treats response
        If $IsObject(tResponse)
        {
            Set tSC = tResponse.CopyToObject(.tJSON)
            Quit:$$$ISERR(tSC)
        }
        
        Set tResult = ##class(%Stream.GlobalCharacter).%New()
        
        Do tResult.Write(tJSON.%ToJSON())
        Do tResult.OutputToDevice()
    } 
    Catch Ex
    {
        Set tSC = Ex.AsStatus()
    }	

    Quit tSC
}

}

 

It’s good to know, but why should I even bother?

In this section I will try to convince you that it is a good idea to have the option of showing your messages content in JSON format.

JSON is also human-readable, meaning you can open a file and see what's inside without running it through a parser. This makes debugging problems with your code more accessible and helps document the data you have received from other applications.

Of course, sometimes “feeling comfortable” with a language is not worth changing anything at your work routine because the change itself might not feel comfy. However, in the API we have just built, there are a few other things to consider apart from the convenience issue. We can notice that the code was not written in XML at this point, so viewing it in that language would add a new level of complexity.

Let me explain this complexity. Even though it doesn’t seem to make much of a difference for a developer to read a simple message with only string properties in tags ( <tags> ) or in JSON format, it is also important to make the message look familiar to non-developers that may have access to this portal and might need to understand the overall processing of the information, since JSON is way more “user friendly”. Besides, when the messages carry properties of other objects or types, they will only be sent in JSON properly if they have the appropriate adaptation for it. IRIS made it easy to solve by just extending the class %JSON.Adaptor. At the same time, messages will only appear on the Message Viewer if they have a suitable adaptation for XML, which IRIS also made painless to solve by extending the class %XML.Adaptor. Yet, if the Message Viewer showed it in JSON, this last problem wouldn’t exist. In practical terms, it means lowering the number of dependencies in a project, which, in human language, means decreasing the number of things that could go wrong while developing and when future updates of the project or even of IRIS come out. And on top of that, you will do longer need a team capable of understanding XML.

Finally, when the team is looking for errors and testing the API, you have the exact format of the incoming request logged on the Message Viewer, so it’s just a matter of copy-paste to test it in such platforms as Postman and others and to understand exactly what the system is reading and writing.

 

If you want to know even more about the pros and cons of XML and JSON, I highly recommend checking json-vs-xml, which provides a great discussion on situations they are both used. The quote above was taken from that website.

 

Ok, this is great! Show me how to use it!

Installing

You can install with ZPM by typing on your terminal in the desired namespace:

zpm "install objectscript-json-trace-viewer"

Another option is to enter ZPM mode by typing zpm and then typing install objectscript-json-trace-viewer

 

If you don't have an IRIS instance on your machine, another option to test it is to clone the repository and run docker-compose build and then docker-compose up -d. Once it finishes starting the container, follow the link http://localhost:52777/csp/sys/UtilHome.csp?$NAMESPACE=DEMO and log in with username _SYSTEM and password SYS to see how it works. You can also check out the codes from the GitHub repository for implementation examples.

 

Using

For every class you want to trace as a JSON and not as an XML, you should add "Grongier.JsonTraceViewer.Message" to the Extensions list or any of its subclasses. This is my Request class from the example in the last section:

Class Sample.Operation.SendMessage.Request Extends (Grongier.JsonTraceViewer.Request, Ens.Request)
{

 Parameter XMLTYPE = "RequestSample";
 Parameter RESPONSECLASSNAME = "Sample.Operation.SendMessage.Response";
 Property Message As %String;
 Property Username As %String;
 Property token As %String;
}

 

Now, when calling the API again, the Message Viewer displays the content as follows:

 

 

Errors you may run into

  • Error when trying to run docker-compose build: 
failed to solve: rpc error: code = Unknown desc = failed to solve with frontend dockerfile.v0: failed to create LLB definition: docker.io/store/intersystems/iris-community:2020.2.0.196.0: not found

SOLVED by opening a Dockerfile and changing ARG IMAGE value to an image available on your docker. Another option is to use the InterSystems extension by CaretDev and pull the latest iris-community image. In my case, the first line of a Dockerfile became:

ARG IMAGE=containers.intersystems.com/intersystems/iris-community:2023.1.0.229.0

 

 

  • Error when trying to run a container:
#12 19.05 Error: ERROR #5001: Could not start SuperServer on port 1972, may be in use by another instance - Shutting down the system : $zu(56,2)=$Id: //iris/2023.1.0/kernel/common/src/journal.c#3 $ 10906 0Starting IRIS
failed to solve: executor failed running [/irissession.sh do $SYSTEM.OBJ.Load("Installer.cls", "ck")   set sc = ##class(App.Installer).setup()   do $system.OBJ.Load("/tmp/deps/zpm.xml", "ck")   zn "DEMO"]: exit code: 225

SOLVED by stopping any existing IRIS instances on the machine and trying to run the container again

 

  • Error when trying to run a container:
Error response from daemon: Ports are not available: exposing port TCP 0.0.0.0:52777 -> 0.0.0.0:0: listen tcp 0.0.0.0:52777: bind: An attempt was made to access a socket in a way forbidden by its access permissions.

SOLVED on Windows by executing the system's terminal as administrator and running the following command. This error may occur if the chosen port is already taken. Just change it in docker-compose.yml or  just stop the process which is using it.

net stop hns
net start hns

 

  • Error when compiling custom classes using the JsonTraceViewer classes:
Error #6281: XMLTYPE of Sample.Operation.SendMessage.Request class must be able to differentiate child classes of Grongier.JsonTraceViewer.Request.
> Error #5090: An error occurred while creating projection Grongier.JsonTraceViewer.Request:XMLEnabled.

​​​​​​SOLVED by adding "Parameter XMLTYPE = 'type'" to every class extending Grongier.JsonTraceViewer.Message, and changing "type" for unique types for each class.

Discussion (8)3
Log in or sign up to continue