I'd move
Write !?(%lev*3)
into a separate method:
ClassMethod Log(text)
{
Write !?(%lev*3), text
}- Log in to post comments
I'd move
Write !?(%lev*3)
into a separate method:
ClassMethod Log(text)
{
Write !?(%lev*3), text
}Google implies that your request is not complete.
Generally, when debugging web services you should follow these steps:
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.
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:
When you created the node manually, you've done the first but not the second.
Use static classmethods, unless you need them to be instance methods.
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"
}
]Why not?
It's an oref, you can even get the object by this string if it exists somewhere in process memory, here's an example.
passing sbJSON by reference would make this recursive construct easier to understand
Object are always passed by reference.
replacement with %ToJSON() does it as well.
Is there a way to call %ToJSON, so it would return formatted output?
Export it as XML. Store in VCS with code. Import with the rest of the code.
Other option is to store it as XData, here's sample code:
XData DisplayProperies
{
<Export generator="Cache">
<Global>
<Node><Sub>^Test</Sub>
<Node><Sub>1</Sub>
<Data>123</Data>
</Node>
<Node><Sub>Val2</Sub>
<Data>prop3</Data>
</Node>
<Node><Sub></Sub>
<Data>prop1</Data>
</Node>
</Node>
</Global>
</Export>
}
/// Set global
/// do ##class(Class.Installer).setGlobal()
ClassMethod setGlobal() As %Status
{
#dim sc As %Status = $$$OK
#dim className As %String = $classname()
// search for XData: DisplayProperies
#dim xdata As %Dictionary.CompiledXData = ##class(%Dictionary.CompiledXData).IDKEYOpen(className, "DisplayProperies",, .sc)
quit:$$$ISERR(sc) sc
set stream = xdata.Data
set sc = $system.OBJ.LoadStream(stream, "/displaylog=0 /displayerror=0")
quit sc
}s ^TMPPRD=reqObj
That won't work. Global can store object serialization or some property value.
sbJSON that actually get passed to XmlToJSONNode is not an object. Additionally try this modification to XmlToJSON (you need to specify file if you want to use file stream):
Method XmlToJSON(xml As %Stream) As %Stream
{
set xmlDoc =##class(%XML.TextReader).ParseStream(xml,.textreader)
set sbJSON =##class(%Stream.FileCharacter).%New()
do sbJSON.LinkToFile("somefile")
do sbJSON.WriteLine($C(123))
do ..XmlToJSONnode(sbJSON,textreader.Name,1)
do sbJSON.writeLine($C(125))
return sbJSON
}You can redefine these dynamic dispatch methods:
And they are available for all classes.
can't find properties Prop1, Prop2...etc in %ZEN.proxyObject documentation
They are dynamic (defined via %DispatchGetProperty/%DispatchSetProperty) so you can assign any properties to %ZEN.proxyObject.
CopyFrom in %Net.HttpRequest, you can find Write method but not the Copyfrom method so how do we suppose to know those properties and methods?
Unable to reproduce (on 2017.2 though).

You may try to use #Dim directive:
#Dim Stream As %GlobalBinaryStream
Set Stream = ##class(%GlobalBinaryStream).%New()
Do Stream.Write("111")
Set Request.EntityBody = StreamAutocomplete works better for variables defined with #Dim (also it's useful for lists/arrays with objects).
what the Maximum Props we can have ?
Thank you, Robert. Missed that during copy&paste from terminal. Fixed.
Here's a sample request with JSON payload from %ZEN.proxyObject, with object->json serialization via %ZEN.Auxiliary.jsonProvider:
Set Body = ##class(%ZEN.proxyObject).%New() Set Body.Prop1 = "Val1" Set Body.Prop2 = "Val2" Set Array = ##class(%ListOfDataTypes).%New() Do Array.Insert(1) Do Array.Insert(2) Do Array.Insert(4) Set Body.Array = Array Set Request= ##class(%Net.HttpRequest).%New() Set Request.Server = "server" Set Request.Location = "location" Set Status = ##class(%ZEN.Auxiliary.jsonProvider).%WriteJSONStreamFromObject(Request.EntityBody, Body) Set Status = Request.Post()
Payload looks like this:
Do Body.%ToJSON()
{
"Array":[1,2,4
],
"Prop1":"Val1",
"Prop2":"Val2"
}
I encountered somewhat similar problem, here are the conditions:
To solve this, we used deferred responses and the class I posted above except it also has dependsOn property which contained a list of tokens which should be satisfied before current token (message corresponding to current token) can be returned.
It looked like this:
Property dependsOn As List Of %String(SQLPROJECTION = "table/column", STORAGEDEFAULT = "array"); // Index dependsOnIndex On dependsOn (ELEMENTS);
So our process was:
Projecting list property as a table helped. Also collection properties are indexable, so it all worked quite fast.
You'll need to develop the criteria yourself as it's heavily dependent upon your business logic. There's no common solution there. Generally you need to:
I usually have a Deferred token table where I keep track of tokens and objects to which they relate:
#Include Ensemble
/// Stored Deferred token.
/// Assumption: one BH generates only one type of Deferred token.
Class Production.DeferredToken Extends %Persistent
{
/// Token
Property token As %String;
Index IDKEY On token [ IdKey ];
/// Object's class anme
Property objectContext As %Dictionary.CacheClassname;
/// Object's primary key
Property objectId As %String;
Index objectIndex On (objectContext, objectId) [ Unique ];
/// Update ts (UTC)
Property updatedOn As %TimeStamp [ Required, SqlComputeCode = { set {*} = $$$ts}, SqlComputed, SqlComputeOnChange = (%%INSERT, %%UPDATE) ];
/// Ensemble session
Property sessionId As %Integer [ InitialExpression = {$get($$$JobSessionId)} ];
/// Ensemble BH
Property configName As %Integer [ InitialExpression = {$get($$$EnsJobLocal("ConfigName"))} ];
/// Ensemble BH class
Property ensembleContext As %Integer [ InitialExpression = {$get($$$ConfigClassName($get($$$EnsJobLocal("ConfigName")," ")))} ];
/// Tries to store a new token if possible
/// w ##class(Production.DeferredToken).add()
ClassMethod add(token As %String, class As %String = "", id As %Integer = "") As %Status
{
return:((class'="") && ('##class(%Dictionary.ClassDefinition).%ExistsId(class))) $$$ERROR($$$ClassDoesNotExist, class)
return:((class'="") && (id'="") && ('$classmethod(class, "%ExistsId", id))) $$$ERROR($$$GeneralError, $$$FormatText("Object '%2' from class '%1' does not exist", class, id))
return:..IDKEYExists(token) $$$ERROR($$$GeneralError, $$$FormatText("Token '%1' already exist", token))
set obj = ..%New()
set obj.objectContext = class
set obj.objectId = id
set obj.token = token
set sc = obj.%Save()
return sc
}Use %ExistsId, %DeleId, objectIndexExists, objectIndexOpen to manipulate stored tokens.
I think you need deferred response.
It works like this:

You can add Ens.ProductionMonitorService service to your production, which provides a monitor service for the production status
The default behavior of this service is to call UpdateProduction once it notices the production is not up-to-date. Users can override the OnProcessInput method to customize the behavior. It would be enough to automatically restart relevant items and keep production up to date.
Generally to access/manipulate info about Ensemble and it's hosts use Ens.Director class, i.e. UpdateProduction method.
Pool size 4 would be better.
Adding 4 same processes would only make production configuration more complicated.
Maybe %NOFLATTEN can help, if we're sure that inner query returns few results.
Also you can try to use IN.
Also possible to use $$$NULL macro:
do $$$AssertEquals(myObj, $$$NULL, "myObj is null")
For example, Java Gateway Wizard is at:
http:://host/csp/reg/%25ZEN.Template.ObjectGatewayWizard.JavaHome.cls
I usually use "find in files" and search for page title to find where it is.
[@Jeffrey Drumm]'s answer is way better.


in my sample, it should be like P.id = P1.id and P1.id = P2.id.
It would work like that too.
Also, you self join on the identical fields "ID", so I guess the engine could infer p1.id = p2.id by "P.id = P1.id and P.id = P2.id" , this would interfere my intention.
Please consider posting:
Yes.
SELECT p.id, p1.id, p2.id FROM Sample.Person p LEFT JOIN Sample.Person p1 ON p1.id=p.id INNER JOIN Sample.Person p2 ON p2.id=p.id
You can just open Addon url in browser.
SSH is more secure. Open telnet port can cause security problems.
Alternatively you can use WebTerminal.