David Hockenbroch · Jun 29, 2023 go to post

What is the error message telling you? I'm not as familiar with working on a Mac as with Windows or Linux, but you need to make sure that the irisusr account has permission to create that folder at the operating system level.

David Hockenbroch · Jun 29, 2023 go to post

If you're developing in Studio, open the file that contains that TestAccountSearchWithoutAccount method, then click the "View Other Code" button so you're looking at the INT code. If it says no other code to view, that's fine; you're already where you need to be. There should be a small text field near the top of the window with a drop down arrow on its left end. In that text field, type TestAccountSearchWithoutAccount+6 and press enter. That should take you directly to the line where the error is occurring.

Something on that line is trying to reference an object that doesn't exist. That's what "INVALID OREF" means. Whatever objects are on that line, check how they were created and/or opened and see if there's a chance that the object doesn't exist.

David Hockenbroch · Jun 29, 2023 go to post

I've used it with Cache 2018, but not as far back as 2016. You'll have to manually add the driver to DBeaver first to try it. To do that, first get a copy of the JDBC driver, which should be in the installation directory for your Cache instance. Look for a folder called dev, then java, then lib, then JDK17 or JDK18 depending on which version of Java is installed on your machine, then there should be a jar file with a name like cache-jdbc-x.y.z.jar. Copy that somewhere easy to get to.

In DBeaver, click on the Database menu, then Driver Manager. Click New. Give the driver a name like Cache 2016 or something you'll recognize. Under driver files, click Add File and choose that .jar file. The class name for the driver should be com.intersys.jdbc.CacheDriver. In the sample URL field put a URL that would connect to your Cache instance, which would look like jdbc:Cache//(server):(port)/(namespace). Click on Test Driver to verify. If it works, click OK.

Now when you go to Database, New Connection, you should see that driver listed as an option.

David Hockenbroch · Jul 18, 2023 go to post

It looks to me like you might want to use %DynamicObject instead of %DynamicAbstractObject. %DynamicAbstractObject doesn't have a %Get method.

David Hockenbroch · Jul 25, 2023 go to post

Does the user you're using to access the API have proper permissions to use the API and the tables that it accesses?

David Hockenbroch · Jul 28, 2023 go to post

Phil, I'm not sure about VS Code, but you can do this in studio by creating a new CSP page with the following contents:

<csp:StudioSimpleTemplate name="CustomCommentHead" type="CLS" mode="new">/// Organization:/// Version 1.0/// Author/Co-author:/// Project: /// Date: /// Description: /// Change Log:/// Notes:

Then save and compile that file. It doesn't matter where. Then when you click File, New, Custom, you'll see your CustomCommentHead template. If you choose it, you'll get a .cls file with those lines already inserted.

David Hockenbroch · Aug 1, 2023 go to post

I'll second this! I'm not an Apple person either. I really like my Samsung Galaxy watch. For Android users, that may be a good alternative to an Apple watch.

David Hockenbroch · Aug 2, 2023 go to post

Just out of curiosity, what's the wireless charging mouse pad? All of the other prizes have a specific brand and model listed, but not that one.

David Hockenbroch · Aug 4, 2023 go to post

This, or if SecondMethod returns a value of some sort, you'd want to:

Method "this_MainMethod"()
{
    set somevalue = ..SecondMethod()
}

Method SecondMethod()
{
    // ...
}
David Hockenbroch · Aug 4, 2023 go to post

What was the query you were trying to run? This looks like an issue with the way the query was written.

David Hockenbroch · Aug 29, 2023 go to post

FYI, the reason this is so difficult is because an XLSX file is actually a ZIP archive with a bunch of XML files in it. You'd actually have to save the file as a .ZIP, extract it, navigate to the XML files for the worksheets (on the current version, that's inside the archive in \xl\worksheets, but that's changed between versions if I remember correctly) then parse the data out of the XML file and write it to your CSV file. That's why there are all these third-party tools people are recommending to handle this issue.

David Hockenbroch · Aug 31, 2023 go to post

Have you checked the timeout on the web application?

See this FAQ and look for the question "I closed my CSP session, but Caché still reports that I am using a license. Why?" I don't know much about your specific application, but that may be what you're running into. CSP sessions have a grace period that, frankly, doesn't make much sense at all. That FAQ explains how long the CSP session grace period lasts, and the question after that is about how to set the timeout for your web applications. Doing so appropriately will minimize the grace period.

David Hockenbroch · Aug 31, 2023 go to post

Assuming that's a typo and the last one is supposed to be Class BZ.Test3, this is all correct and as expected. Since it extends Test2, Test3's onloadHandler function is identical to Test2's onloadHandler, which runs its superclass's onloadHandler and prints test2. Since its superclass is Test2, it's superclass's onloadHandler is also running its onloadHandler, then printing test2. Since Test2's superclass is Test1, Test2 is running its superclass's onloadHandler, printing test1.

Remember, removing the onloadHandler method from Test3 doesn't mean that it doesn't have an onloadHandler; it just means its onloadHandler is the same as the one in its superclass, Test2.

David Hockenbroch · Sep 6, 2023 go to post

As for telling whether it's a production or test system, you might want to consider $SYSTEM.Version.SystemMode(). Calling that function with no argument will return the current system mode, which can be DEVELOPMENT, LIVE, TEST, or FAILOVER. It's usually set in the System Management Portal, but you can also call that function passing any of those strings are an argument to set it programmatically.

David Hockenbroch · Sep 13, 2023 go to post

Do so at your own risk, but if you use _PUBLIC as the role/user argument, then all users will have the granted permissions as soon as they are created.

David Hockenbroch · Sep 14, 2023 go to post

What browsers are you using to do this? Many have restricted the use of window.close to only work on windows that were also opened by javascript using window.open.

David Hockenbroch · Sep 22, 2023 go to post

Just to clarify, Tony, I'm not looking for a notification that a failover has occurred. I'm looking for a notification that the async mirror has gone offline.

David Hockenbroch · Sep 29, 2023 go to post

Where you have your query defined, does it work if you change the definition to include the SELECTMODE as RUNTIME?

Query GetAddressQuery() As%SQLQuery(ROWSPEC = "Address",SELECTMODE = "RUNTIME") [ SqlProc ]
David Hockenbroch · Oct 10, 2023 go to post

I don't typically use Ensemble for my REST services, but I do see a couple of things.

First, in the documentation you linked to, pOutput is a %DynamicObject, not a %RegisteredObject. %RegisteredObject does not have a %ToJSON() method.

Second, in your PostMessage method, the line "return pResponse" shouldn't be there.

David Hockenbroch · Oct 24, 2023 go to post

I was actually pondering this question myself for an article I'm working on. I ended up in a very different place than you did, though. I created an Abstract class that extends %CSP.REST and overrides the XData schema, the DispatchMap method, and the DispatchRequest method. If I extend this class - which I've called REST.Resourceful - I can include a resource in the URL map as a resource:permission pair. For example:

<Route Url="/securetest" Method="GET" Call="securetest" Resource="MyResource:U" />

Will only allow you to access the /securetest endpoint if the user has Use permission on the resource "MyResource". If you don't, you get a 401 error. If I leave out the Resource attribute on that node, it doesn't check for any additional resources.

Source code is below. If you search the text for my initials, DLH, you'll see comments where I've made changes in the method, plus I added the attribute "Resource" to the XData schema.

/// Extends the %CSP.REST class to include securing of endpoints by resource in the URL map
Class REST.Resourceful Extends %CSP.REST [ Abstract ]
{XData Schema [ Internal ]
{
<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema><xs:element name="Routes">
<xs:complexType>
<xs:choice  minOccurs="0" maxOccurs="unbounded">
<xs:element name="Route">
<xs:complexType>
<xs:attribute name="Url"    type="string" use="required"/>
<xs:attribute name="Method" type="string" use="required"/>
<xs:attribute name="Call" type="call" use="required"/>
<xs:attribute name="Cors" type="xs:boolean" use="optional" default="false"/>
<xs:attribute name="Resource" type="string" use="optional" default=" "/>
</xs:complexType>
</xs:element>
<xs:element name="Map">
<xs:complexType>
<xs:attribute name="Prefix" type="string" use="required"/>
<xs:attribute name="Forward" type="forward" use="required"/>
</xs:complexType>
</xs:element>
</xs:choice>
</xs:complexType>
</xs:element><xs:simpleType name="call">
<xs:restriction base="xs:string">
<xs:pattern value="([%]?[a-zA-Z][a-zA-Z0-9]*(\.[a-zA-Z][a-zA-Z0-9]*)*:)?[%]?[a-zA-Z][a-zA-Z0-9]*"/>
</xs:restriction>
</xs:simpleType><xs:simpleType name="forward">
<xs:restriction base="xs:string">
<xs:pattern value="([%]?[a-zA-Z][a-zA-Z0-9]*(\.[a-zA-Z][a-zA-Z0-9]*)*)"/>
</xs:restriction>
</xs:simpleType><xs:simpleType name="string">
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
</xs:restriction>
</xs:simpleType></xs:schema>
}/// This generator creates the DispatchMap Method used to dispatch the Url and Method to the associated target method
ClassMethod DispatchMap(pIndex As %Integer) As %String [ CodeMode = generator ]
{
#dim tSC As %Status = $$$OK
    #dim As %Exception.AbstractException
    
    #dim tStream As %Stream.TmpCharacter
    #dim tHandler As %XML.ImportHandler
    #dim tCompiledClass As %Dictionary.CompiledClass
    
    #dim tArgCount,tIndex,tI,tCounter As %Integer
    #dim tArgs,tChild,tClassName,tCall,tCors,tForward,tError,tMap,tMethod,tPattern,tPiece,tPrefix,tType,tUrl,tResource As %String
    
    Try {
        
        Set tClassName=%classname
        
        #; Don't run on base class
        If tClassName="%CSP.REST" Quit
       
        #; Find named XDATA block
        If ##class(%Dictionary.CompiledXData).%ExistsId(tClassName_"||UrlMap") {
            
            Set tCompiledClass=##class(%Dictionary.CompiledClass).%OpenId(tClassName,,.tSC)
            If '$IsObject(tCompiledClass)||$$$ISERR(tSC) Quit
            
            Set tIndex = tCompiledClass.XDatas.FindObjectId(tClassName_"||UrlMap")
            If tIndex="" Set tSC=$$$ERROR($$$XDataBlockMissing,tClassName,"UrlMap") Quit
        
            #; Get XDATA as stream
            Set tStream = tCompiledClass.XDatas.GetAt(tIndex).Data
            Do tStream.Rewind()
            
            #; Create an XML import handler ( use the internal handler )
            Set tHandler=##class(%XML.ImportHandler).%New("CacheTemp",$$$IntHandler)
        
            #; Create the Entity Resolver
            Set tResolver=##class(%XML.SAX.XDataEntityResolver).%New(tClassName)
        
            #; Parse the XML data in the specfied stream
            Set tSC=##Class(%XML.SAX.Parser).ParseStream(tStream,tHandler,tResolver,,,"Schema")
            If $$$ISERR(tSC) Quit       
        
            #; Copy tree because handler will delete its copy when it goes out of scope
            Merge tMap=@tHandler.DOMName@(tHandler.Tree)
    
            If $Data(tMap("error"))||$Data(tMap("warning")) {
                
                Set tSC=$$$ERROR($$$InvalidDispatchMap)
                For tType="error","warning" {       
                    Set tIndex = "" For {
                        Set tIndex=$Order(tMap(tType,tIndex),1,tError) If tIndex="" Quit
                        Set tSC=$$$ADDSC(tSC,$$$ERROR($$$GeneralError,tError))
                    }
                }
                Quit
            }
            
            #; Walk the xml and generate the routing map
            Set tChild="",tCounter=0 For {
                
                Set tChild=$Order(tMap(1,"c",tChild)) If tChild="" Quit
                
                If tMap(tChild)="Route" {
                    
                #; Need to substitute capture groups for arguments
                #; Added setting of tResource based on URL map - DLH
                Set tPattern="",tArgCount=0,tUrl=tMap(tChild,"a","Url"),tCors=tMap(tChild,"a","Cors"),tResource=tMap(tChild,"a","Resource")
                        
                #; Substitute variable placeholders for capture group
                For tI=1:1:$Length(tUrl,"/") {
                    Set tPiece=$Piece(tUrl,"/",tI)
                    If $Extract(tPiece)=":" {
                        Set $Piece(tPattern,"/",tI)="([^"_$Char(0)_"]+)"
                    else {
                        Set $Piece(tPattern,"/",tI)=tPiece                  }
                }
                Set tPattern=$Translate(tPattern,$Char(0),"/")                 Set tCounter=$Increment(tCounter),tMethod=tMap(tChild,"a","Method"),tCall=$Get(tMap(tChild,"a","Call"))
                #; Added getting resource from the URL Map here. - DLH
                $$$GENERATE(" If pIndex="_tCounter_" Quit $ListBuild(""R"","""_tPattern_""","""_tMethod_""","""_tCall_""","""_tCors_""","""_tResource_""")")
                
            else {
                
                Set tCounter=$Increment(tCounter),tPrefix=tMap(tChild,"a","Prefix"),tForward=$Get(tMap(tChild,"a","Forward"))                 #; Need to substitute capture groups for arguments
                Set tPattern=""
                For tI=2:1:$Length(tPrefix,"/") {
                    Set tPiece=$Piece(tPrefix,"/",tI)
                    If $Extract(tPiece)=":" {
                        Set tPattern=tPattern_"/[^/]+"
                    else {
                        Set tPattern=tPattern_"/"_tPiece
                    }
                }
                
                Set tPattern = "("_ tPattern _ ")/.*"
                
                $$$GENERATE(" If pIndex="_tCounter_" Quit $ListBuild(""M"","""_tPattern_""","""_tForward_""")")
            }
            }
            $$$GENERATE(" Quit """"")
                
        else {
            
            #; The specified class must have an XDATA Block named UrlMap
            Set tSC=$$$ERROR($$$XDataBlockMissing,tClassName,"UrlMap")
        }
        
    Catch (e) {
        Set tSC=e.AsStatus()
    }
    
    Quit tSC
}/// Dispatch a REST request according to URL and Method
ClassMethod DispatchRequest(pUrl As %String, pMethod As %String, pForwarded As %Boolean = 0) As %Status
{
    #dim tSC As %Status = $$$OK
    #dim As %Exception.AbstractException
    
    #dim tMatcher As %Regex.Matcher
    
    #dim tArgs,tClass,tMatchUrl,tMapEntry,tRegEx,tCall,tForward,tAccess,tSupportedVerbs,tTarget,tType As %String
    #dim tI,tIndex As %Integer
    #dim tResourceMatched,tContinue As %Boolean
    #dim tMethodMatched As %Boolean
    
    Try {
        
        Set (tResourceMatched,tMethodMatched)=0
        #; Initializing tSecurityResourceMatched - DLH
        Set tSecurityResourceMatched = 1
        #; Extract the match url from the application name
        If (0=pForwarded) {
            Set tMatchUrl="/"_$Extract(pUrl,$Length(%request.Application)+1,*)
        else {
            Set tMatchUrl=pUrl
        }
      
        #; Uppercase the method
        Set pMethod=$ZCVT(pMethod,"U")
          
        #; Pre-Dispatch
        Set tContinue=1,tSC=..OnPreDispatch(tMatchUrl,pMethod,.tContinue)
        If $$$ISERR(tSC) Quit
        
        #; It's the users responsibility to return the response in OnPreDispatch() if Continue = 0
        If tContinue=0 Quit
            
        #; Walk the dispatch map in collation order of defintion
        For tIndex=1:1 {
            
            #; Get the next map entry
            Set tMapEntry=..DispatchMap(tIndex) If tMapEntry="" Quit
             
            #; Pick out the RegEx
            Set tRegEx=$List(tMapEntry,2)
            
            #; Create a matcher
            Set tMatcher=##class(%Regex.Matcher).%New(tRegEx)
            
            #; Test each regular expression in turn, extracting the arguments,
            #; dispatching to the named method
            If tMatcher.Match(tMatchUrl) {
#; We have matched the resource
                Set tResourceMatched=1
                #; Logic to check the resource from the URL map
                set tResource = $List(tMapEntry,6)
                If tResource '= " "{
                If $SYSTEM.Security.Check($P(tResource,":",1),$P(tResource,":",2))=0{
         Set tSecurityResourceMatched=0
         }
                }
  #; Added an if so the method only gets dispatched if we have the resource permission
                If tSecurityResourceMatched = 1{
                
                Set tType=$List(tMapEntry,1)
                 
                 #; If we are a simple route
                 If tType="R" {
                    
                     #; Support OPTIONS VERB (cannot be overriden)
                    If pMethod="OPTIONS" {
                         
                         Set tMethodMatched=1
                        
                         Set tSC=..OnHandleOptionsRequest(tMatchUrl)
                         If $$$ISERR(tSC) Quit
                        
                         #; Dispatch CORS
                        Set tSC=..ProcessCorsRequest(pUrl,$list(tMapEntry,5))
                         If $$$ISERR(tSC) Quit
                        
                         Quit
                     }
                     
                     #; comparison is case-insensitive now
                    If pMethod'=$ZCVT($List(tMapEntry,3),"U") Continue
                     
                     Set tTarget=$List(tMapEntry,4)
                     
                     #; We have matched a method
                     Set tMethodMatched=1
                    
                    #; Dispatch CORS
                     Set tSC=..ProcessCorsRequest(pUrl,$list(tMapEntry,5))
                     If $$$ISERR(tSC) Quit
                   
                     #; Got a match, marshall the arguments can call directly
                     If tMatcher.GroupCount {
                         For tI=1:1:tMatcher.GroupCount Set tArgs(tI)=tMatcher.Group(tI)
                         Set tArgs=tI
                     else {
                         Set tArgs=0
                     }
                    
                     #; Check for optional ClassName prefix
                     Set tClass=$classname()
                     If tTarget[":" Set tClass=$Piece(tTarget,":"),tTarget=$Piece(tTarget,":",2)
                    
                     #; Dispatch
                     Set tSC=$zobjclassmethod(tClass,tTarget,tArgs...)
                       
                 else {
                    
                     #; We are a map, massage the URL and forward the request
                     Set tMatchUrl=$piece(tMatchUrl,tMatcher.Group(1),"2",*),tForward=$ListGet(tMapEntry,3)
                     Set (tResourceMatched,tMethodMatched)=1
                   
                     #; Dispatch with modified URL
                     Set tSC=$zobjclassmethod(tForward,"DispatchRequest",tMatchUrl,pMethod,1)
                 }
                
                 If $$$ISERR(tSC) Quit
                
                 #; Don't want multiple matches
                 Quit
                }
            }
        }
        
        #; Didn't have permission for the resource required by this enpoint; return 401 - DLH
        If tSecurityResourceMatched = 0 Set tSC=..ReportHttpStatusCode(..#HTTP401UNAUTHORIZED) Quit
        
        #; Didn't have a match for the resource, report not found
        If tResourceMatched=0 Set tSC=..ReportHttpStatusCode(..#HTTP404NOTFOUND) Quit
                  
        #; Had a match for resource but method not matched
        If tMethodMatched=0 {
            
            Set tSC=..SupportedVerbs(tMatchUrl,.tSupportedVerbs)
            If $$$ISERR(tSC) Quit
            
            Set tSC=..Http405(tSupportedVerbs) Quit
        }
            
    Catch (e) {
        
        Set tSC=e.AsStatus()
    }
    
    Quit tSC
}}
 
David Hockenbroch · Oct 25, 2023 go to post

I didn't check the credentials because this all takes place after the user has already been through the authentication process. If they aren't valid, they wouldn't get this far anyway. My approach is dealing with just the authorization, not the authentication. I still use $SYSTEM.Security.Check to see if the process has the right permissions.

As you said, it comes down to personal preference. One of my preferences is not messing with the authentication processes if I don't have to. That way I don't have to account for all of the different authentication options, and they could all still be used.

David Hockenbroch · Oct 26, 2023 go to post

I haven't touched PHP in a bit, but are you binding your columns? I seem to remember PHP things getting a little finicky about making sure you do that before you fetch if you were specifying columns in the query, but not if you were doing a select *.

David Hockenbroch · Oct 26, 2023 go to post

Yes, security settings can get down to a columnar level. Also, if what you're querying is a view rather than a table, or if the ID column is defined to be something other than ID, that column just might not exist.

Also for clarification, when you use the select *, are you getting an Id column in the resulting data?

David Hockenbroch · Nov 8, 2023 go to post

That's an issue with the SSL certificate on the server. It's the kind of error you'd see if the domains on the SSL certificate don't match the domain being used to access the website. SSL certificates are a little out of my areas of expertise, so I'm not sure how you determine what you need to do to fix that.