Eduard Lebedyuk · Jan 17, 2018 go to post

while i was using your code, i am getting this error in response, which says "read" method is unknown

You're getting this error because list of objects does not have this method. Check your list of objects for contents. Seems like json is already converted so you don't need to do that.

Eduard Lebedyuk · Jan 16, 2018 go to post

For SQL, use LAST_DAY function - it's a date function that returns the date of the last day of the month for a date expression:

SELECT LAST_DAY('2004-02-25')

It's also available in Caché ObjectScript:

Write $SYSTEM.SQL.LASTDAY("2004-02-25")
Eduard Lebedyuk · Jan 16, 2018 go to post

Convert json to proxy object (list of in your case) and iterate over it

set json = "[{""Name"":""bat""},{""Name"":""Cat""},{""Name"":""rat""},{""Name"":""mat""},{""Name"":""hat""},{""Name"":""chat""},{""Name"":""please""},{""Name"":""help""},{""Name"":""me""},{""Name"":""in""},{""Name"":""getting""},{""Name"":""the""},{""Name"":""value""},{""Name"":""of""},{""Name"":""nameFromObjectInsideArray""}]"
set sc = ##class(%ZEN.Auxiliary.jsonProvider).%ConvertJSONToObject(json,,.obj,1)
for i=1:1:obj.Count() { 
    write obj.GetAt(i).Name,!
}

To get json string from  request:

set json = %request.Content.Read($$$MaxStringLength)

You may also need to convert request into UTF8:

set json = $ZCVT(json,"I","UTF8")
Eduard Lebedyuk · Jan 16, 2018 go to post

Sorry I'm lost here. and not sure what you in 1,2 or 3 !?

It's an iterative process to pinpoint and remove errors. You start by establishing a channel which can send valid requests. For example using Postman. Next you compare valid request (which you can now consistently send) with your request which does not pass. Next you iteratively break valid request or fix failed request or send failed request via Postman. Sooner or later you understand what causes your request to fail and fix that.

the other difference is that Now they also require a Header, before they they only had a Body in their API.  

You need to add all required headers via SetHeader method.

I think my date is already like that!? do you mean removing the double quotes or changing it to YYYY-DD-MM?

You're sending:

"PaymentRecordTransactionD":"PaymentRecordTransactionD"

But API expects:

PaymentRecordTransactionD": "2014-12-31"

Looks different for me.

Eduard Lebedyuk · Jan 16, 2018 go to post

The error says parse error, but in this case I think it means either datatype validation (probably)  or fixed property order (less probable, but that can actually happen). My default advice in cases like this is as follows:

  1. Set up a way to send valid requests
  2. Send request via Caché/Ensemble
  3. Compare (DiffDog, etc) requests from 1 and 2, here you need to:
    • Modify valid request till it fails
    • Modify invalid request  till it succeeds

In your case the first modification I would do is replacing dates with real dates, for example with 2014-01-11.

Eduard Lebedyuk · Jan 15, 2018 go to post

Doubtful. Web browsers security policies probably forbid this kind of thing.

Why do you need that?

Eduard Lebedyuk · Jan 15, 2018 go to post

Well, yes, it returns the sender of the message. If you're sending message from BS to BO directly sender would be BS. If you're sending BS-BP-BO, for BO the sender would be BP because it is.

If you want to get BS from BO in BS-BP-BO you can implement it via several approaches:

  1. Add this info to BP-BO message.
  2. You have BP-BO header, use it, SessionId and BusinessProcessId to locate  BS-BP header in the header table.
  3. If you have unique identifier for BP-BO message and you know it in BS you can store this information during BS processing (to a temp holder class) and then retrieve it from BO by the identifier.

 

I'd like to add that second approach without careful planning and optimizing may negatively impact performance  if stored messages are in millions. Starting new SessionId for each new message sent from BS may help. (because BS MessageId would then be equal to SessionId in BO).

Eduard Lebedyuk · Jan 12, 2018 go to post

I think that's for services only. They store information about processed rows in: these 2 globals

$$$DoneRowTable(key)
^CacheTemp.Adapter.sqlrow(..BusinessHost.%ConfigName, ..%InstKey, key)

Where DoneRowTable macro is resolved into

^Ens.AppData(..BusinessHost.%ConfigName, "adapter.sqlrow", key)

You can check if there's anything for your BO.

The globals are set in OnTask method of inbound adapter.

Eduard Lebedyuk · Jan 12, 2018 go to post

What happens if you don't declare a Persistent value when you call ExecuteQuery()?

What is " Persistent value"?

What does Ensemble set as the key value for your query?

What is "key value"?

That said, if you execute the same query against external database, and the data in the external database does not change you should get the same results.

After you finish working with resultset the first time (and before creating it a second time) you can call:

Set sc = rs.Close()

Maybe that would help.

Eduard Lebedyuk · Jan 12, 2018 go to post

Some possible code improvements:

1. Instead of:

s sc = httprequest.Post("http://127.0.0.1:57772/api/v0/bwxpert/visiocheck/account")

it's enough to specify:

s sc = httprequest.Post("/api/v0/bwxpert/visiocheck/account")

as the rest of the address is defined beforehand.

2. Instead of:

s httprequest.Authorization = "Basic X1N5c3RlbTpTWVM="

you can specify:

s httprequest.Username = "_SYSTEM"
s httprequest.Password = "SYS"

Which is more readable

3. When specifying server, protocol in not required, incorrect even:

s httprequest.Server="127.0.0.1"

4. Finally, don't use short syntax (s, d) and use one naming convention consistently - in your example some commands are in lowercase and some start with a capital letter.

Eduard Lebedyuk · Jan 12, 2018 go to post

Private is not important in that case - it only affects from where method could be called. Private methods can be invoked only by methods of this class or its subclasses.

 

The difference between class methods and instance methods is that instance methods can access object context. For example you can access current object property from instance methods. Use instance methods only when you need access to object context. In all other cases use class methods.

Here's a sample code, highlighting the difference:

 

Class Test.Person Extends %RegisteredObject {

Property Name As %String;

Method PrintName() {
  Write ..Name
}

ClassMethod PrintText(text) {
  Write text
}

}
Eduard Lebedyuk · Jan 9, 2018 go to post
  1. Please ask this as a separate question.
  2. What, and from where do you want to kill? Why? Consider expanding your question.
Eduard Lebedyuk · Jan 8, 2018 go to post

and by the way, is Set Request.Https=1 correct given that Set Request.Https=$$$YES not supported in Ensemble 2014?

Yes, it's the same thing $$$YES macro resolves into 1.

but I get this error :  
{"Errors":["The request entity's media type 'text/html' is not supported for this resource."],"StatusCode":415}

Specify content type. For example:

Set Request.ContentType = "application/json"

or whatever content type the api requires.

Eduard Lebedyuk · Jan 8, 2018 go to post

They are various jobs required for Caché to function. They run tasks, write to database, etc.

Is there any specific reason you ask this question?

Eduard Lebedyuk · Jan 8, 2018 go to post

Not really. KeyFieldName only affects the tracking of processed records.  Example 5 shows configuration where each row is passed each time the service querying the database is ran.

SQL Inbound Adapter always passes rows one by one.

Your options are:

  1. Create a buffer and check if this is a last row. If it's a last row - call bulk API. If it's not a last row then add row to buffer.
  2. Call bulk API after every N (i.e. 100) of rows.
  3. Extend SQL  Inbound Adapter to pass a whole resultset or to pass batches of rows.
  4. Write changes locally row by row. Create a separate service that calls bulk API from local data.

What option to choose? Here are several questions, which may help you:

  1. What are the throughout rates (per second/minute/day/peaks?)?
    • New rows in the source system
    • How much rows Ensemble can process
    • How much requests can API process (does it matter if you pass several rows? For example you can pass 100 individual requests per second or 10 bulk requests with 1000 rows each)
  2. Administrative limits for the above-mentioned external systems?
  3. Can you normalize some of these rates? (For example you receive updates during the work hours, but send the 24/7) 
  4. What's the bulk error strategy (good to bad)?
    • You get an error list which you can easily match to individual records
    • You get an error list but you're unable to match it to the individual records
    • You get one (first/last/random) error
  5. Should you group your records before sending (can be useful for visual tracing, pinpointing error source)?
    • By some criteria (hospital, country, day, etc)
  6. What's the resend strategy in cause of errors?
    • Let's say you sent a batch of 1000 records: 999 ok, 1 failed. Can you easily resend only one record? Would sending a whole batch again break data consistency or just fail altogether? 
  7. Can you send the records individually?
Eduard Lebedyuk · Jan 8, 2018 go to post

so is there a way to get it working with SSLConfig

As I said:

Where  SSLConfiguration is the name of your ssl config.

You need to create a SSL config. Docs explain how.

 test 0 or 1 means as the doc doesn't mention it!?

Test values

  • 0 - send as usual
  • 1 - do not send, output request to the current device
  • 2- send the request and output response to the current device

location in Post method is optional given that we set Set Request.Location = "/STP_IF/rest/Employee/Create"

Yes, it's enough to specify Location property.

Eduard Lebedyuk · Jan 6, 2018 go to post

I'd move

Write !?(%lev*3)

into a separate method:

ClassMethod Log(text)
{
  Write !?(%lev*3), text
}
Eduard Lebedyuk · Jan 5, 2018 go to post

Google implies that your request is not complete.

Generally, when debugging web services you should follow these steps:

  1. Have a way to send valid requests (i.e. SoapUI)
  2. Send request via Caché/Ensemble
  3. Compare (DiffDog, etc) requests from 1 and 2, here you need to:
    • Modify valid request till it fails
    • Modify invalid request  till it succeeds

Also, don't forget to send new request after each modification. Web services often have GUID/ID by which they identify incoming requests. If incoming request has the same identifier they don't execute it again, but just return cached result. Identifier can be a header or inside message body.

Eduard Lebedyuk · Jan 5, 2018 go to post

Try to remove reNo altogether and get the root node from the xml document:

 do ..ShowNode(document.GetDocumentElement())

In GetDocumentElement method, you can see that for node to exist it needs 2 things:

  • Document
  • Pointer to the exact node

When you created the node manually,  you've done the first but not the second.

Eduard Lebedyuk · Jan 5, 2018 go to post

SET JArray=["somthing "] 

OR

Set Array1 = ##class(%Library.DynamicArray).%New()
 SET Array1=[]

so in both cases Array1 or JArray is not understanding [] so should be including or extending a classe(s) or ...etc 

That's available from version 2016.2. Refer to your local documentation or online documentation for 2014 version.

FYI Srever name & port are not required for this Wed Service. 

It's not server name - it's host and it's required. You also need to specify HTTPS and SSLConfig.

If I execute your code in test mode (  Set Status = Request.Post(,1) ) I receive:

POST /https%3A//devtest.altus.net.au/STP_IF/rest/Employee/ HTTP/1.1
User-Agent: Mozilla/4.0 (compatible; Cache;)
Host: localhost
Accept-Encoding: gzip
Content-Length: 239
Content-Type: text/html; charset=UTF-8
 
{
        "Id":123,
        "PayerAustralianBusinessNumbe":"PayerAustralianBusinessNumbe",
        "PayerBusinessHoursPhoneNumbe":"PayerBusinessHoursPhoneNumbe",
        "PayerEmailAddress":"email",
        "PayerWithholdingPayerNumber":"PayerWithholdingPayerNumber"
}

You should probably modify your code like this this:

ClassMethod Test(test As %String(VALUELIST="0,1,2") = 1)
{
 Set Body = ##class(%ZEN.proxyObject).%New()
 Set Body.Id = "123"
 Set Body.PayerEmailAddress = "email"
 Set Body.PayerBusinessHoursPhoneNumbe = "PayerBusinessHoursPhoneNumbe"
 Set Body.PayerAustralianBusinessNumbe = "PayerAustralianBusinessNumbe"
 Set Body.PayerWithholdingPayerNumber = "PayerWithholdingPayerNumber"

 Set Request= ##class(%Net.HttpRequest).%New()
 Set Request.Server = "devtest.altus.net.au"
 
 Set Request.Location = "STP_IF/rest/Employee/"
 Set Request.Https = $$$YES
 Set Request.SSLConfiguration = "YourSSLConfig"
 Set Status = ##class(%ZEN.Auxiliary.jsonProvider).%WriteJSONStreamFromObject(Request.EntityBody, Body)
 Set Status = Request.Post(,test)
}

Where  SSLConfiguration is the name of your ssl config. Afterwards with test=1 I get this:

POST /STP_IF/rest/Employee/ HTTP/1.1
User-Agent: Mozilla/4.0 (compatible; Cache;)
Host: devtest.altus.net.au
Accept-Encoding: gzip
Content-Length: 239
Content-Type: text/html; charset=UTF-8
 
{
        "Id":123,
        "PayerAustralianBusinessNumbe":"PayerAustralianBusinessNumbe",
        "PayerBusinessHoursPhoneNumbe":"PayerBusinessHoursPhoneNumbe",
        "PayerEmailAddress":"email",
        "PayerWithholdingPayerNumber":"PayerWithholdingPayerNumber"
}

And the response with test=2 is as follows:

HTTP/1.1 405 Method Not Allowed
ALLOW: GET
CACHE-CONTROL: no-cache
CONNECTION: keep-alive
CONTENT-LENGTH: 73
CONTENT-TYPE: application/json; charset=utf-8
DATE: Fri, 05 Jan 2018 08:16:20 GMT
EXPIRES: -1
PRAGMA: no-cache
SERVER: Microsoft-IIS/8.5
 
{"Message":"The requested resource does not support http method 'POST'."}

You should check the API docs for correct location (or HTTP verb)

I'm basically looking to handcraft the Body.%ToJSON() content to to be contained in []

Wrap Body in %ListOfObjects for that:

Set List = ##class(%ListOfObjects).%New()
Do List.Insert(Body)
Set Status = ##class(%ZEN.Auxiliary.jsonProvider).%WriteJSONStreamFromObject(Request.EntityBody, List)

And your request body would look like this:

[ {
                "Id":123,
                "PayerAustralianBusinessNumbe":"PayerAustralianBusinessNumbe",
                "PayerBusinessHoursPhoneNumbe":"PayerBusinessHoursPhoneNumbe",
                "PayerEmailAddress":"email",
                "PayerWithholdingPayerNumber":"PayerWithholdingPayerNumber"
        }
]
Eduard Lebedyuk · Jan 4, 2018 go to post

passing sbJSON by reference would make this recursive construct easier to understand

Object are always passed by reference.

Eduard Lebedyuk · Jan 4, 2018 go to post

replacement with %ToJSON()  does it as well.

Is there a way to call %ToJSON, so it would return formatted output?