Jenna Makin · Sep 23, 2019 go to post

I would recommend getting your journal drive fixed, taking a full backup of your databases and then start journaling from there.

Whether or not you need the journals depends on how you might be using them 

Obviously they are used to recover your databases since the last backup .

They are also used for shadowing and mirroring 

Jenna Makin · Sep 22, 2019 go to post

What kind of error do you get when you try from a browser.

From a browser URL the method is Get.   Is your service Method a Get?

Could also be licensing.

Jenna Makin · Sep 4, 2019 go to post

You could throw them out into a file and then use an external diff tool.

Jenna

Jenna Makin · Aug 23, 2019 go to post

When you installed Cache, what type of security did you specify?

Minimal

Normal

Locked Down

Jenna Makin · Aug 23, 2019 go to post

The following will count the number of data nodes under a given ^Location(country)

USER>S G1=$NA(^Locations("Canada"))
 USER>S G=$E(G1,1,$L(G1)-1)
USER>W G
^Locations("Canada"
USER>W G1
^Locations("Canada")
USER>F  S G1=$Q(@G1) Q:G1=""!($E(G1,1,$L(G))'=G)  S CT=$I(CT)
USER>W CT
2

Jenna Makin · Aug 8, 2019 go to post

Hi-

Heres something I did on a Windows system at one point to purge files in a directory that were older than a given time frame

ClassMethod PurgeFiles(Path As %String, OlderThan As %Integer)
{
    set Date=$zd($h-OlderThan)
    set cmd="forfiles /P "_Path_" /D -"_Date_" /C ""cmd /c del @path"""
    set sc=$zf(-1,cmd)
}

I'm pretty sure there is also ways to do this using something in %File or one of those classes also. I will poke around for an example there as well, but this should get you started.

Jen

Jenna Makin · Aug 8, 2019 go to post

Hi Salma

What do you mean by "subfolders" . Is this a CSP project?

Jenna

Jenna Makin · Aug 8, 2019 go to post

is it possible that "par1" and "par2" don't contain the values you think they do.  I would start there and confirm that par1 = "2019" and par2 = "0912"

Jenna

Jenna Makin · Aug 6, 2019 go to post

Hi Thomas

Can I clarify your question, what you are really looking to do is each day start a process that will delete files in a given directory that are older than some period of time?

If so I may be able to help there.

Jenna

Jenna Makin · Aug 6, 2019 go to post

Hi Jimmy

The short answer is that the EnsLib.File.PassthroughOperation is a special operation that can take a stream and write it out to a file.  The operation expects you to pass in an instance of Ens.StreamContainer and you need to populate the Stream property which is what the PassthroughOperation is looking for.

An example might be:

set sc=##class(Ens.StreamContainer).%New()

set stream = ##class(%Stream.GlobalCharacter).%New()

do stream.Write("This is my text to go into the file")

set sc.Stream=stream

Once you have done this, you can send sc as the input to your operation using ..SendRequestAsync or ..SendRequestSync, or from a BP if that is where the message is coming from

My question is given your request message you created, what are you hoping the output in the file will look like?

Thanks

Jenna

Jenna Makin · Apr 25, 2019 go to post

Chris, 

Great tutorial.  Was able to get this all working fairly easy against an InterSystems IRIS for Health environment.  Looking forward to the next section.

Ken

Jenna Makin · May 3, 2018 go to post

Okay, looked at the docs more and my eyes were playing tricks on me.

Looks like you cant call a routine at a tag in another namespace, but can call a routine at the top in another namespace.

The syntax is:

do ^|"namespace"|routine

so:

do ^|"SAMPLES"|YJM

Jenna Makin · May 3, 2018 go to post

Interesting, I dont see anywhere in the docs where it would indicate that D ["SAMPLES"]YJM^YJM will work

The docs would indicate that you can do this:

DO ^["SAMPLES"]Y2K

However, that throws a SYNTX error too

Jenna Makin · May 2, 2018 go to post

Yes, that is true, however, that doesnt allow you to execute a class method from namespace A in namespace B.

When you :

do ["b"]tag^routine

Cache actually executes tag^routine in namespace b vs the namespace you are currently in.

Creating a package map for namespace a that maps a particular package from namespace b only makes that class available to namespace a.  When I execute methods within the mapped class those methods are executed in namespace a and not b.

Jenna Makin · May 2, 2018 go to post

Im pretty sure that class methods cant be called in another namespace without wrapping the method call in a call that actually switches namespaces, calls them method and then switches back.

do ["namespace"]obj.method 

isn't supported where

do ["namespace"]tag+offset^routine

is

Jenna Makin · Apr 28, 2018 go to post

I added an export of the ISC.JSONGlobalProcessor class which contains the Export method to the original post

Jenna Makin · Apr 28, 2018 go to post

So a customer asked me a question about this and I decided to actually implement a mechanism to encode a global as JSON.  I haven't done the opposite which would be to take the encoded json and turn it back into a global, but the reverse is pretty simple

Here's the way I encode the Global as json  There is a top-level object with two properties, globalName and nodes.  globalName is a string and represents the actual global name, nodes is an array and contains an object for each node of the global which contains data.  To ensure that non-printable characters are handled properly,  I am using HTML Escaping to escape both the data and the subscript values

{

     "globalName":"^ISCSOAP",
     "nodes":[{
               "data":"io",
               "subscripts":[
                             ""Log""
                            ]
              },
              {
               "data":"c:\\temp\\SOAP.log",
               "subscripts":[
                             ""Log"",
                             ""LogFile""
                            ]
              }]
} 

Here is an example of the class method that generates this output

Class ISC.JSONGlobalProcessor [ Not ProcedureBlock ]
{

ClassMethod Export(GlobalRoot As %String, Output JSON As %DynamicObject) As %Status [ ProcedureBlock = 0 ]
{
    if '$d(@GlobalRoot) quit $System.Status.Error(5001, "Nothing to export; "_GlobalRoot_" <undefined>")
    set root=$p(GlobalRoot,")",1,$l(GlobalRoot,")")-1),node=GlobalRoot s:root="" root=GlobalRoot
    set JSON=##class(%DynamicObject).%New()
    set JSON.globalName=$p(GlobalRoot,"(",1)
    set JSON.nodes=##class(%DynamicArray).%New()
    while $e(node,1,$l(root))=root {
        if $d(@node)#10 do ..addNode(node,.JSON)
        set node=$q(@node)
    }
    quit 1
}

ClassMethod addNode(node As %String, ByRef JSON As %DynamicObject) As %Status
{
    set nodeJSON=##class(%DynamicObject).%New()
    set data=@node,nodeJSON.data=##class(%CSP.Page).EscapeHTML(data)
    set subscripts=$p(node,"(",2,999),subscripts=$p(subscripts,")",1,$l(subscripts,")")-1)
    if ""'=subscripts {
        set nodeJSON.subscripts=##class(%DynamicArray).%New()
        set cp=1
        for {
            q:cp>$l(subscripts,",")
            set subscript=$p(subscripts,",",cp)
            f  {
                q:$l(subscript,"""")#2
                set cp=cp+1,subscript=subscript_","_$p(subscripts,",",cp)
            }
            set subArray=$i(subArray),subArray(subArray)=subscript
            set cp=cp+1
        }
        for i=1:1:subArray do nodeJSON.subscripts.%Push(##class(%CSP.Page).EscapeHTML(subArray(i)))
    }    
    do JSON.nodes.%Push(nodeJSON)
}

}

To call this code you can do the following

set sc=##class(ISC.JSONGlobalProcessor).Export($na(^SAMPLE.PersonD),.json)

Once you have the global encoded in a JSON object, you can output that JSON by:

do json.%ToJSON()

Jenna Makin · Apr 25, 2018 go to post

There are extra commands executed in order to call the class method, however the impact is negligible and the ultimate code executed is really identical assuming the method and routine code is the same 

Yes, Not extending %RegisteredObject will produce less code however the code you are executing will be identical.

Jenna Makin · Apr 25, 2018 go to post

Another reason to opt for Objects over Routines is that Objects provide an automated documentation mechanism that Routines dont.  All elements of a class are documented in the class documentation and the developer can add their own text documentation as well

Jenna Makin · Apr 23, 2018 go to post

I think it really depends on your coding preferences. There are benefits to writing using Objects (CLS) and benefits to writing in (MAC) and which you use may simply depend on what your coding preferences are.
Do you like to code using objects, then CLS is the way to go. Are you a procedural programmer, then (MAC) is the way to go.

Using CLS, you program using objects with properties and methods. You gain the benefit of being able to access your object data via three different mechanisms, direct global access, object access, or sql. You implement methods and typically your methods are logically organized into the classes that they pertain to. You gain inheritance, XML support, JSON support, and so much more.

All that said, if you come from a background of procedural programming and don't have a need or desire to work in objects, there's nothing wrong with using MAC routines.

Jenna Makin · Jan 8, 2018 go to post

Hello-

First, admittedly I'm not sure the cause to your error as I do not have access to your generated SOAP client classes, your SSL Configuration, etc.

That said, I was able to implement the UPS API for the Tracking API and was able to execute without issue.  To get it to work you will need to do the following.

1.   Create a SSL client configuration (test it against the UPS server) to be used for SSL encryption.  it does not appear that UPS provides a non encrypted connection for testing.

2.   Obtain all of the wsdl files (including the additional xsd documentts that define the different types that UPS API supports. 

3.  Use the SOAP wizard to create a client.  I used the package UPS.   In the wizard, on the Step 3 page, select the option "Use unwrapped message format for document style web methods "  This is critical because the SOAP wizard will not create the correct client without it checked.

Once created, the following class method can be used to test your service.  I just added this to my SOAP client class UPS.TrackPort

ClassMethod Test(InquiryNumber As %String) As UPS.common.ResponseType
{
    ; Setup Web Service Client and Security
    s ws=##class(UPS.TrackPort).%New()
     s ws.SSLConfiguration="SSLClient"
     s sechdr=##class(UPS.upss.UPSSecurity).%New()
     s usertoken=##class(UPS.upss.UsernameToken).%New()
     s usertoken.Username="myusername"
     s usertoken.Password="mypassword"
     s sechdr.UsernameToken=usertoken
     s acctoken=##class(UPS.upss.ServiceAccessToken).%New()
     s acctoken.AccessLicenseNumber="myaccessLicenseNumber"
     s sechdr.ServiceAccessToken=acctoken
     do ws.HeadersOut.SetAt(sechdr,"UPSSecurity")
    ;
    ; Setup Request
    set trakRequest=##class(UPS.trk.TrackRequest).%New()
    set trakRequest.Request=##class(UPS.common.RequestType).%New()
    do trakRequest.Request.RequestOption.Insert(1)
    set transactionReference=##class(UPS.common.TransactionReferenceType).%New()
    set transactionReference.CustomerContext="My Ensemble Process "_$j
    set trakRequest.Request.TransactionReference=transactionReference
    set trakRequest.InquiryNumber=InquiryNumber
    ;
    quit ws.ProcessTrack(trakRequest)
}

Once this is done, you can test as follows

USER>s resp=##class(UPS.TrackPort).Test("1Z12345E0205271688")
 
USER>w resp
20@UPS.trk.TrackResponse
USER>zw resp
resp=<OBJECT REFERENCE>[20@UPS.trk.TrackResponse]
+----------------- general information ---------------
|      oref value: 20
|      class name: UPS.trk.TrackResponse
| reference count: 2
+----------------- attribute values ------------------
|           (none)
+----------------- swizzled references ---------------
|       i%Disclaimer = ""
|    i%Disclaimer(1) = "You are using UPS tracking service on customer integration test environment, please switch to UPS production environment once you finish the test. The URL is https://onlinetools.ups.com/webservices/Track"
|       r%Disclaimer = "49@%Collection.ListOfDT"  <Set>
|         i%Response = ""
|         r%Response = "21@UPS.common.ResponseType"
|         i%Shipment = ""
|      i%Shipment(1) = ""
|         r%Shipment = "48@%Collection.ListOfObj"
|      r%Shipment(1) = "24@UPS.trk.ShipmentType"
+-----------------------------------------------------

Hope this helps

Jenna Makin · Jan 6, 2018 go to post

Curious as to whether you are trying to implement their REST api or their XML (SOAP) api?

The mechanisms to interface with each are quite different.

Kenneth

Jenna Makin · Oct 2, 2017 go to post

Definately agree.  Pretty sure my UTILITY global is mapped to CACHETEMP.

Jenna Makin · Sep 30, 2017 go to post

Thanks Robert-

I dont think I have a choice here.  the All method of the %SYSTEM.OBJ.FM2Class expects the array to be passed by reference.  I did come up with a solution though....

/// Run FM2Class in background fo rnewly created namespace

ClassMethod ConfigFM2Class(Namespace As %String, LocalPath As %String) As %String
{
new $namespace set curnsp=$namespace,$namespace=Namespace
write !,"Starting FM2Class for Namespace ",Namespace," in background"
; Build Up Parameters
set params("childTableNameFormat")="SUB_<FILENAME>,<FILENUMBER>"
    set params("compile")=1
    set params("compileQSpec")="/display=all/lock=0"
    set params("dateType")="%Library.FilemanDate"
    set params("datetimeType")="%Library.FilemanTimeStamp"
    set params("deleteQSpec")="/display=all"
    set params("display")=0
    set params("expandPointers")=0
    set params("expandSetOfCodes")=0
    set params("extendedMapping")=""
    set params("fieldNameFormat")="Exact"
    set params("ienFieldName")="IEN"
    set params("logFile")=LocalPath_"fm2class_"_Namespace_".log"
    set params("nameLength")=180
    set params("owner")="_SYSTEM"
    set params("package")="VISTA"
    set params("readonly")=0
    set params("recursion")=2
    set params("requiredType")=0
    set params("retainClass")=1
    set params("setOfCodesEnum")=1
    set params("strictData")=0
    set params("superClasses")=""
    set params("tableNameFormat")="<FILENAME>,<FILENUMBER>"
    set params("variablePointerValueField")=0
    set params("wpIsList")=0
    kill ^UTILITY(Namespace,$j,"ISC.HealthConnect.Installer") merge ^UTILITY($j,"ISC.HealthConnect.Installer")=params
    set $namespace=curnsp
    job ##class(ISC.HealthConnect.Installer).jobfm(Namespace,$j)::5
set zSC=1
if '$t set zSC=0
quit zSC
}
ClassMethod jobfm(Namespace,job)
{
;Startup FM2Class
new $namespace set $namespace=Namespace
merge params=^UTILITY(job,"ISC.HealthConnect.Installer")
kill ^UTILITY(job,"ISC.HealthConnect.Installer")
do ##class(%SYSTEM.OBJ.FM2Class).All(.params)
quit

}

Jenna Makin · Sep 30, 2017 go to post

Eduard, there are dozens.  params contains any number of values, not all required.

Thinking the easiest way may be to just save params to a subscripted global, job off a wrapper, passing the subscript.  Grab the array out of the global and then start the class method, killing the global.

Jenna Makin · Sep 30, 2017 go to post

Looking further, it would seem that we can not pass an array as an argument to a JOB command so I need to come up with a new way to get this array of parameters to the JOBed process.