Creating ABL Client to Consume a REST Web Service - progress-4gl

I'm not finding any documentation on connecting to a REST web service from progress. Can someone please point me in the direction of documentation or provide a connection example.
Progress: 11.3

Starting with 11.5.1 there's built in support for consuming REST webservices in a controlled way. But since you're not on that version I will leave that out for now.
Unless upgrading is an option you can do several things:
Call a OS program for HTTP like curl or wget
How to do this exactly will depend of utility of choice, os, version etc. Also be advised that os updates might change the behavior.
Also you should look into error redirecting etc before doing this. Calling out to the OS will create a second process so it might impact system resources. Use only if you take those things in account.
On the other side it's easy and fast. Curl is also highly flexible and will help with whatever headers or other things you might want to use.
You could also look into calling the curl library directly instead. Be advised that you will heavily rely on mempointers and other c-like structures in that case!
PROCEDURE curl:
DEFINE INPUT PARAMETER pcUrl AS CHARACTER NO-UNDO.
DEFINE OUTPUT PARAMETER pcResponse AS LONGCHAR NO-UNDO.
DEFINE VARIABLE cCommand AS CHARACTER NO-UNDO.
/* -s is for silent use - show no errors or feedback on the screen */
cCommand = "curl -s " + pcUrl + " > /tmp/curl.txt".
OS-COMMAND NO-CONSOLE VALUE(cCommand).
COPY-LOB FROM FILE "/tmp/curl.txt" TO pcResponse .
END.
DEFINE VARIABLE lc AS LONGCHAR NO-UNDO.
run curl("http://www.google.com/", OUTPUT lc).
MESSAGE STRING(SUBSTRING(lc,1,100)) VIEW-AS ALERT-BOX.
Use .net classes
I have no knowledge in .net so I can't help you with code for this. You will have to research how to call webservices in .Net to start with and then "translate" into ABL. This will only work on Windows - Progress have no support for Mono or other ways of doing .net in other OSes.
Roll your own using sockets
Shamelessly stolen example from: Progress Knowledgebase.
DEFINE VARIABLE vcHost AS CHARACTER INITIAL "localhost" NO-UNDO.
DEFINE VARIABLE vcPort AS CHARACTER INITIAL "8080" NO-UNDO.
DEFINE VARIABLE vhSocket AS HANDLE NO-UNDO.
CREATE SOCKET vhSocket.
vhSocket:CONNECT('-H ' + vcHost + ' -S ' + vcPort) NO-ERROR.
IF vhSocket:CONNECTED() = FALSE THEN
DO:
MESSAGE "Connection failure" VIEW-AS ALERT-BOX.
MESSAGE ERROR-STATUS:GET-MESSAGE(1) VIEW-AS ALERT-BOX.
RETURN.
END.
ELSE
MESSAGE "Connect"
VIEW-AS ALERT-BOX.
vhSocket:SET-READ-RESPONSE-PROCEDURE('getResponse').
/* supposes there is an webspeed app called yourapp.w that receives param1, param2, param3 */
RUN PostRequest (
INPUT '/scripts/cgiip.exe/WService=wsbroker1/yourApp.w',
INPUT 'param1=value&param2=value&param3=value'
).
WAIT-FOR READ-RESPONSE OF vhSocket.
vhSocket:DISCONNECT() NO-ERROR.
DELETE OBJECT vhSocket.
QUIT.
PROCEDURE getResponse:
DEFINE VARIABLE vcWebResp AS CHARACTER NO-UNDO.
DEFINE VARIABLE lSucess AS LOGICAL NO-UNDO.
DEFINE VARIABLE mResponse AS MEMPTR NO-UNDO.
IF vhSocket:CONNECTED() = FALSE THEN do:
MESSAGE 'Not Connected' VIEW-AS ALERT-BOX.
RETURN.
END.
lSucess = TRUE.
DO WHILE vhSocket:GET-BYTES-AVAILABLE() > 0:
SET-SIZE(mResponse) = vhSocket:GET-BYTES-AVAILABLE() + 1.
SET-BYTE-ORDER(mResponse) = BIG-ENDIAN.
vhSocket:READ(mResponse,1,1,vhSocket:GET-BYTES-AVAILABLE()).
vcWebResp = vcWebResp + GET-STRING(mResponse,1).
END.
/*
*PUT HERE THE CODE TO MANIPULATE THE ANSWER
*/
END.
PROCEDURE PostRequest:
DEFINE VARIABLE vcRequest AS CHARACTER.
DEFINE VARIABLE mRequest AS MEMPTR.
DEFINE INPUT PARAMETER postUrl AS CHAR.
/* URL that will send the data. It must be all the path after the server. IE:/scripts/cgiip.exe/WService=wsbroker1/myApp.htm */
DEFINE INPUT PARAMETER postData AS CHAR.
/* Parameters to be sent in the format paramName=value&paramName=value&paramName=value */
vcRequest =
'POST ' +
postUrl +
' HTTP/1.0~r~n' +
'Content-Type: application/x-www-form-urlencoded~r~n' +
'Content-Length:' + string(LENGTH(postData)) +
'~r~n' + '~r~n' +
postData + '~r~n'.
MESSAGE vcREquest
VIEW-AS ALERT-BOX.
SET-SIZE(mRequest) = 0.
SET-SIZE(mRequest) = LENGTH(vcRequest) + 1.
SET-BYTE-ORDER(mRequest) = BIG-ENDIAN.
PUT-STRING(mRequest,1) = vcRequest .
vhSocket:WRITE(mRequest, 1, LENGTH(vcRequest)).
END PROCEDURE.

Related

Are Ada function arguments evaluated if the body is empty?

According to this statement :
Trace.Debug("My String" & Integer'Image(x) & "is evaluated" & "or not" & "if my logger is disabled ?" & Boolean'Image(YesOrNo) );
And this implementation of Trace.Debug:
procedure Debug (Message : in String) is
begin
if Logger.Enabled then -- This boolean is defined during runtime by reading a value in a file
Put_Line(Message);
else
null; -- Do nothing
end if;
end Debug;
I have a software which can manage several levels of logs, and I would like to know what is the behavior in case of Logger.Enabled equals False.
I have a lot of logs calls, with sometimes complex strings to evaluate and I'm on a real time system, so I don't want to lost time to evaluate a string which will not printed.
I would like to know if compiler optimize the code in order to not evaluate the string in parameter of Trace.Debug while Logger.enabled is False, knowing that this boolean is set at the begging of runtime by reading a value in a file.
I am using gnat 7.3.2.
You can ensure that evaluation doesn't happen by providing a callback:
procedure Debug (Message : access function return String) is
begin
if Logger.Enabled then
Put_Line(Message.all);
end if;
end Debug;
Then to call it, do
declare
function Msg return String is
("My String" & Integer'Image(x) & "is evaluated" & "or not" & "if my logger is disabled ?" & Boolean'Image(YesOrNo));
begin
Debug (Msg'Access);
end;
In your original code, the only way that the compiler could skip the evaluation is when it inlines the Debug procedure and re-arranges the code so that the Message object is only assigned inside the if-block. You cannot force this; even pragma Inline is only a hint for the compiler.

Perl: Parameters to msgsnd

I am maintaining some existing code
I see this fragment:
msgsnd( $mQueue, pack("l! a*", length($msg), $msg), 0)
|| ... error handling ...
I would like to understand the call to pack() as the second argument to msgsnd.
I find the following documentation for msgsend
Calls the System V IPC function msgsnd to send the message MSG to the
message queue ID. MSG must begin with the native long integer message
type, be followed by the length of the actual message, and then
finally the message itself. This kind of packing can be achieved with
pack("l! a*", $type, $message) . Returns true if successful, false on
error. See also SysV IPC in perlipc and the documentation for
IPC::SysV and IPC::Msg .
This gives the second parameter to pack as $type, but does not explain what $type is. The code I'm trying to understand instead passes the message length.
What's going on? So far as I can tell the existing code is working reliably.
The application in question decided to use the type field to store the length of the message.
(This is rather odd, since the size of the message is already available to the reader.)
When the receiver request a message from the system, they may constrain the request to specific message types.
If msgtyp is 0, then the first message in the queue is read.
If msgtyp is greater than 0, then the first message in the queue of type msgtyp is read, unless MSG_EXCEPT was specified in msgflg, in which case the first message in the queue of type not equal to msgtyp will be read.
If msgtyp is less than 0, then the first message in the queue with the lowest type less than or equal to the absolute value of msgtyp will be read.
If the receiver specifies 0 for msgtyp, then the message type provided by the sender isn't used by the system, and can therefore be used to carry other information.
The man page for msgsnd says "The mtext field is an array (or other structure) whose size is specified by msgsz, a nonnegative integer value. Messages of zero length (i.e., no mtext field) are permitted. The mtype field must have a strictly positive integer value. This value can be used by the receiving process for message selection (see the description of msgrcv() below)."
Thus type is not used by sndmsg itself and the length that appears in the type field might or might not be used on the receiving side.
it creates a binary representation of the msg: len msg. Check it by:
perl -e '$a= "abcde"; print(pack("l! a*", length($a), $a))' | od -c
Gives:
0000000 005 \0 \0 \0 \0 \0 \0 \0 a b c d e
0000015

Opening a STREAM in a Persistent Procedure Function

I have a persistent procedure, in which I am trying to open, write and close a stream.
I have in the main area of the procedure
DEFINE STREAM sOutFile.
OPEN STREAM sOutFile TO VALUE( outFileName ).
MESSAGE SEEK( sOutFile ).
and subsequently a function within the persistent procedure
FUNCTION example RETURN LOGICAL:
MESSAGE SEEK( sOutFile ).
PUT STREAM sOutFile UNFORMATTED someData.
END.
When the persistent procedure is instantiated, the message displays "0" so the stream has been opened. However, when example is called, the message displays "?" and I get a message about attempting to write to a closed stream.
I've tried declaring the stream NEW SHARED but that didn't make any difference.
Am I doing something wrong, or is it impossible to define streams within persistent procedures?
It is early and my coffee hasn't kicked in yet but I think that you have to open the stream outside the body of the PP.
This works:
/* ppstream.p
*
*/
define stream logStream.
session:add-super-procedure( this-procedure ).
/* end of PP init */
function initLog returns logical ( input lgFile as character ):
output stream logStream to value( lgFile ) unbuffered.
return true.
end.
function logStuff returns logical ( input msg as character ):
put stream logStream unformatted msg skip.
return true.
end.
and then call it like so:
function initLog returns logical ( input lgFile as character ) in super.
function logStuff returns logical ( input msg as character ) in super.
run ./ppstream.p persistent.
initLog( "log.txt" ).
logStuff( "test" ).
(I used a session super-procedure to avoid having to define handles -- you would not necessarily need to do that.)

Progress 4GL BUFFER-COPY FAILS

DO ON ENDKEY UNDO, LEAVE:
FIND FIRST STUDENT NO-LOCK WHERE ST-ID = "TEST" NO-ERROR.
IF AVAILABLE STUDENT THEN
DO:
CREATE SCHOOL no-error.
BUFFER-COPY STUDENT EXCEPT STUDENT.Location
SCHOOL ASSIGN SCHOOL.Location = "MY LOCATION" NO-ERROR.
IF ERROR-STATUS:ERROR THEN
DO:
DO i = 1 TO ERROR-STATUS:NUM-MESSAGES:
MESSAGE
" Error no " ERROR-STATUS:GET-NUMBER(i)
" txt: " ERROR-STATUS:GET-MESSAGE(i) VIEW-AS ALERT-BOX.
STOP.
END.
END.
END.
END.
This query is working fine but some time it was creating Empty Record. buffer-copy through some error that why it create empty record but i am not able verify the error because code was happen in LIVE. please help me how to FIX the problem. what type of error buffer-copy will through. 1000 times working fine 1 time it will FAIL. i know this is data defect but how to FIX. otherwise what type of errors BUFFER-COPY through.
Since you really don't know what errors occur - you need to start there.
To track general errors after a NO-ERROR statement you can do something like:
IF ERROR-STATUS:ERROR THEN DO:
DO i = 1 TO ERROR-STATUS:NUM-MESSAGES:
/* Replace MESSAGE with some kind of logging */
MESSAGE
"Error number " i
" error no " ERROR-STATUS:GET-NUMBER(i)
" txt: " ERROR-STATUS:GET-MESSAGE(i) VIEW-AS ALERT-BOX.
END.
END.
Once you have the specific error number(s) you can search the Progress Knowledge Base for more information.
I would write the code as follow. If you use no-error always handle the error. When assigning/buffer copy check if no error then do a validate to confirm that triggers fire correctly. The error handling code can be put in its own procedure and then just called every time you wish to process error messages. (amended it a bit as I would write it, I do not always trap all errors, but error handling depends on various things, but it is a good way during testing to make sure code is setup correctly. Afterwards can remove error handling where not needed before deploying.)
FIND FIRST STUDENT NO-LOCK WHERE ST-ID = "TEST" NO-ERROR.
IF AVAILABLE STUDENT THEN
DO:
CREATE SCHOOL.
BUFFER-COPY STUDENT EXCEPT STUDENT.Location
TO SCHOOL ASSIGN SCHOOL.Location = "MY LOCATION"
NO-ERROR.
IF ERROR-STATUS:ERROR THEN
DO:
/* insert error handling - for example as as per #Jensd */
/* this is in case there is something wrong with buffer copy */
/* maybe a required field is left empty? */
END.
ELSE DO:
VALIDATE SCHOOL NO-ERROR. /* good idea as it raises any issues with triggers */
IF ERROR-STATUS:ERROR THEN
DO:
/* insert error handling - for example as as per #Jensd */
END.
END.
END.
I will not make a buffer-copy from one to other table stright, you might have index issues as i think you are having.
Better define a temp-table like the target table.
Then make a buffer copy to the temp-table.
Set the rigth key into the temp-table.
Then make a buffer copy from the temp table with the right key fields to the target table.
Try as follow:
Define temp-table t-school like school.
find first student no-lock and so on.
buffer-copy student to t-school.
Assign t-school.location = "whatever".
buffer-copy t-school to school.
Voila!
One possible way to debug the issue is to use "Recent Messages" under Help in AppBuilder provided you are able to run the code directly from it.

How To Send a PDF File to a Progress AppServer?

I have a PDF file at client and i want to send this PDF file on AppServer. How can i send this pdf file at AppServer?
define temp-table ttFileList no-undo
field file-id as integer
field file-content as blob.
create ttFileList.
assign ttFileList.file-id = 1.
copy-lob from file("pdffilename") to ttFileList.file-content.
run DoSomethingWithAPDF on hAppServer
( input table ttFileList ).
This depends on the version of progress you are using, if you are using v9 then you will need to use small chunks of raw data streamed in segments. With OpenEdge (might have been 10.1B) we got CLOB and BLOB support, you can create a procedure which takes a temp-table as an argument.
It also depends on your calling language. For .NET and Java this will get translated into a byte array.
For your app-server create a procedure similar to the following:
def temp-table ObjectTransfer no-undo
field Code as char
field Number as int
field DataContent as blob
field MimeType as char.
procedure AddObjectData:
def input param table for ObjectTransfer.
def var k as int no-undo.
for each ObjectTransfer:
find last ObjectTable no-lock
where ObjectTable.Code = ObjectTransfer.Code
no-error.
if avail ObjectTable then
k = ObjectTable.Number + 1.
else
k = 1.
create ObjectTable.
assign
ObjectTable.Code = ObjectTransfer.Code
ObjectTable.Number = k
ObjectTable.MimeType = ObjectTransfer.MimeType
ObjectTable.DataContent = ObjectTransfer.DataContent
.
end.
end procedure.
Generate proxies, you will now call this from .NET and Java using a simple byte array as an input temp-table data-type.
Use raw datatype, you might need to send the file in chunks. Another alternative is to use character+BASE64.