Progress OpenEdge passing a dataset as parameter looses values - openedge

I am trying to pass a dataset as a parameter to a class, but the dataset keeps loosing it's values.
The idea is to put a customer number in the dataset, pass the dataset to the server and let the server fill the dataset with all the customer information and then pass it back to the client.
First calling procedure
This is a unittest procedure calling the ServiceInterface in the server.
USING OpenEdge.Core.Assert.
BLOCK-LEVEL ON ERROR UNDO, THROW.
{USS/Common/Invoice/Include/dsInvoice.i}
DEFINE VARIABLE hProc AS HANDLE NO-UNDO.
RUN USS/Server/Invoice/ServiceInterfaces.p PERSISTENT SET hProc.
TEMP-TABLE ttInvoice:TRACKING-CHANGES = TRUE.
ttInvoice.CustomerNr = CustomerNr.
TEMP-TABLE ttInvoice:TRACKING-CHANGES = FALSE.
RUN UpdateCustomer IN hProc(INPUT CustomerNr, INPUT-OUTPUT DATASET dsInvoice BY-VALUE).
Assert:Equals("MIDDELLANDBAAN 1 B", ttInvoice.DeliveryStreet).
DELETE PROCEDURE hProc.
Service interface on the server
At this moment the dataset still contains all the values. These values are passed to a Business Entity where other values should be added.
PROCEDURE UpdateCustomer:
DEFINE INPUT PARAMETER CustomerNr AS INT.
DEFINE INPUT-OUTPUT PARAMETER DATASET-HANDLE phdsInvoice.
USS.Server.Invoice.BusinessEntity.InvoiceEntity:Instance:UpdateCustomer(INPUT CustomerNr, INPUT-OUTPUT DATASET dsInvoice BY-REFERENCE).
RETURN.
END PROCEDURE.
Business entity
The Business Entity is a singleton, containing an UpdateCustomer method.
When the dataset is passed to this method, it is completely empty.
USING Progress.Lang.*.
USING USS.Common.Interfaces.IBusinessEntity.
USING USS.Server.Invoice.DataAccess.InvoiceBE-DA.
BLOCK-LEVEL ON ERROR UNDO, THROW.
CLASS USS.Server.Invoice.BusinessEntity.InvoiceEntity IMPLEMENTS IBusinessEntity:
{ USS\Common\Invoice\Include\dsInvoice.i &CLassAccess = "private" }
DEFINE PRIVATE VARIABLE InvoiceDA AS InvoiceBE-DA NO-UNDO.
DEFINE PRIVATE VARIABLE hDSEventHandlers AS HANDLE NO-UNDO.
DEFINE PUBLIC STATIC PROPERTY Instance AS USS.Server.Invoice.BusinessEntity.InvoiceEntity
GET.
PRIVATE SET.
CONSTRUCTOR STATIC InvoiceEntity ():
USS.Server.Invoice.BusinessEntity.InvoiceEntity:Instance = NEW USS.Server.Invoice.BusinessEntity.InvoiceEntity().
END CONSTRUCTOR.
CONSTRUCTOR PUBLIC InvoiceEntity ():
SUPER().
InvoiceDA = NEW InvoiceBE-DA().
END CONSTRUCTOR.
METHOD PUBLIC VOID UpdateCustomer(INPUT pCustomerNr AS INT, INPUT-OUTPUT DATASET dsInvoice ):
DEF VAR hUpdateCustomerService AS HANDLE NO-UNDO.
RUN USS/Server/Invoice/Services/UpdateCustomer.p PERSISTENT SET hUpdateCustomerService.
RUN UpdateCustomer IN hUpdateCustomerService (INPUT pCustomerNr, INPUT-OUTPUT DATASET dsInvoice BY-REFERENCE).
RETURN.
END METHOD.
END CLASS.
I have been working on this for a while now and I hope someone can help me figure this one out.

Yes, as Tim mentioned, you use 2 different datasets in internal procedure UpdateCustomer in ServiceInterfaces.p.
You can change the PARAMETER DATASET-HANDLE to:
DEFINE INPUT-OUTPUT PARAMETER DATASET FOR dsSelectionList.

Related

Optional Input Parameters in Procedures/Apps

I've being doing some research about this, but all the results are from a couple of years ago.
Is there a way to define an input parameter as optional when calling a Procedure or a .W file?
My program works in this way - I have an external procedure that sends email using SMTP and it needs a few input parameters (Like Subject, Send To, etc) and I need to add a new input field that will add an attachment for the email.
To not edit and compile old codes, is possible to create this new parameter as optional and if it doesn't receive any value the program consider as blank?
DEFINE INPUT PARAMETER p_tipo_alerta AS CHAR.
DEFINE INPUT PARAMETER p_grupo_email AS CHAR.
DEFINE INPUT PARAMETER p_obs AS CHAR.
DEFINE INPUT PARAMETER p_site AS CHAR.
DEFINE INPUT PARAMETER p_CodeRequest AS CHAR.
DEFINE INPUT PARAMETER p_DescRequest AS CHAR.
// New-Optional
DEFINE INPUT PARAMETER p_AttachPath AS CHAR.
Thanks!
The ABL language does not support this kind of programming. The closest you can come is using classes and have multiple overloaded methods, which then call the 'primary' method passing in whatever the default values are.
class Foo:
method public void m1(p1 as character):
define variable nullObj as Object.
define variable defaultDate as date initial today.
this-procedure:m1(p1, defaultDate, nullObj, nullObj).
end method.
method public void m1(p1 as character, p2 as date).
define variable nullObj as Object.
this-procedure:m1(p1, p2, nullObj, nullObj).
end method.
method public void m1(p1 as character, p2 as date, p3 as Object).
define variable nullObj as Object.
this-procedure:m1(p1, p2, p3, nullObj).
end method.
method public void m1(p1 as character, p2 as date, p3 as Object, p4 as Object):
// does what it does
end method.
end class.
Doing this with procedural code is more complex since (internal) procedure names cannot be overloaded, so the names needs need to be unique.
If you don't want to change the calling code, though, you may be out of luck.

Provide a Converter for data-binding by defining a pair of SerializableFunction objects

In Vaadin 8 Framework, and Vaadin 10 Flow, the data-binding capability lets us provide a Converter to mediate between the widget’s expected data type (such as String for a TextField) and the data type of the backing bean property (such as Integer number).
In this example, the built-in Converter implementation StringToIntegerConverter is used.
binder
.forField( this.phaseField )
.withConverter(
new StringToIntegerConverter( "Must enter an integer number" )
)
.bind( Panel::getPhase , Panel::setPhase ) ;
But what about defining a Converter for other types? How can I easily define a short-and-sweet Converter? For example, a String-to-UUID converter. I want to show the canonical 36-character hex string in a TextField, and going the other direction, parse that string back into a UUID.
// String to UUID
UUID uuid = UUID.fromString( myString ) ;
// UUID to String
String myString = uuid.toString() ;
I see that Binder.BindingBuilder offers the pair of methods withConverter that both take a pair of SerializableFunction objects.
Binder.BindingBuilder::withConverter(SerializableFunction<TARGET,NEWTARGET> toModel, SerializableFunction<NEWTARGET,TARGET> toPresentation)
Binder.BindingBuilder::withConverter(SerializableFunction<TARGET,NEWTARGET> toModel, SerializableFunction<NEWTARGET,TARGET> toPresentation, String errorMessage)
➥ So how do I define the pair of SerializableFunction objects/classes?
I noticed that this interface lists a known subinterface ValueProvider<SOURCE,TARGET>. That looks familiar, and I have a hunch it is the key to easily defining a short simple converter. But I do not quite comprehend the syntax with lambdas and all that is going on here.
I am not asking how to write a class implementing Converter. I am asking how to write the pair of SerializableFunction arguments to pass to the Binder.BindingBuilder::withConverter methods listed above as bullet items.
Quoting that JavaDoc:
Interface Binder.BindingBuilder<BEAN,TARGET>
…
withConverter
default <NEWTARGET> Binder.BindingBuilder<BEAN,NEWTARGET> withConverter(SerializableFunction<TARGET,NEWTARGET> toModel, SerializableFunction<NEWTARGET,TARGET> toPresentation)
Maps the binding to another data type using the mapping functions and a possible exception as the error message.
The mapping functions are used to convert between a presentation type, which must match the current target data type of the binding, and a model type, which can be any data type and becomes the new target type of the binding. When invoking bind(ValueProvider, Setter), the target type of the binding must match the getter/setter types.
For instance, a TextField can be bound to an integer-typed property using appropriate functions such as: withConverter(Integer::valueOf, String::valueOf);
Type Parameters:
NEWTARGET - the type to convert to
Parameters:
toModel - the function which can convert from the old target type to the new target type
toPresentation - the function which can convert from the new target type to the old target type
Returns:
a new binding with the appropriate type
Throws:
IllegalStateException - if bind has already been called
You can do it by passing two lambda expressions to withConverter, so something like this:
binder.forField(textField)
.withConverter(text -> UUID.fromString(text), uuid -> uuid.toString())
.bind(/* ... */);
If you need a more complicated conversion, then the right-hand side of the lambda can be surrounded with brackets, e.g.
binder.forField(textField).withConverter( text -> {
if ( text == null ) {
return something;
} else {
return somethingElse;
}
}, uuid -> { return uuid.toString(); } )
.bind(/* ... */);
If you need your converter multiple times, I recommend creating a separate class implementing interface com.vaadin.data.Converter. However, using lambdas is possible, too, as you already know (see answer of #ollitietavainen). But this is not Vaadin specific, it's a Java 8+ feature you can read about e.g. here. Basically, you can use lambdas whereever an object implementing an interface with only one method is required.

asp.net VB best way to return result code

For asp.net framework 4.5 vb
I know that I can use a session variable.. but wondering if there is a more efficient way to return a variable (we call it a condition code) that we set almost in every function /sub being called.
Example
Function xyz(bla as String, blab as String) as dataset
.. do the deed
**Cond.code = -1**
Return ds
We use this Cond.code everywhere. Obviously it can not be a Public Shared variable... but want the most efficient way to always set a "condtion code" within any sub or function... It is valid, only from the time it is set, to the time it is checked by the calling function... so the lifetime is very short.
It's unclear what, exactly the condition code is used for. It's possible that a proper use of custom exception types would replace the need for it. However, if you really need it, it would be better to have it be returned by the method, rather than essentially setting a global, which is pretty rotten. What happens if two methods ever got called simultaneously? It's just brittle and unnecessarily confining.
For instance, you could make a container to hold the return value plus the condition code, like this:
Public Class ReturnValue(Of T)
Public Sub New(value As T, conditionCode As Integer)
Me.Value = value
Me.ConditionCode = conditionCode
End Sub
Public ReadOnly Property Value As T
Public ReadOnly ConditionCode As Integer
End Class
By requiring the code in the constructor, like that, it forces every method to always specify a condition code in its return value. That way it could never be forgotten:
Function xyz(bla As String, blab As String) As ReturnValue(Of DataSet)
' do the deed
Return New ReturnValue(ds, -1)
End Function
However, even then, I would still strongly recommend making the condition code use an enumeration or, at the very least, a set of constants. -1 isn't very self documenting.

initializing variable with select in init method using x++

I'm new with X++ and I'm trying to modify the INIT method of a form in order to greet the user wit a message on top of the form.
The greeting message should look like "Happy Birthday EmplTable.name!".
The code from the INIT METHOD looks like this till now:
public void init()
{
CustName custName = SELECT EmplTable.name FROM Empltabe JOIN UserLogInfo WHERE EmplTable.EmplId == UserInfo.UserId;
;
//"#NET4183"
super();
GreetingMessage.text(strfmt("#NET4183", custName));
}
I have a hard time understanding what is wrong here and why I can't initialize the custName variable.
Thank for the help!
Have a great day!
All variables must be declared before they can be used. X++ does not allow variable declarations to be mixed with other X++ statements; variables must be declared before X++ statements.
Declaration of Variables
You should declare variables EmplTable, UserInfo before you can use them in select statement.
Results of a select statement are returned in a table buffer variable. If you use a field list in the select statement, only those fields are available in the table variable.
You can assign value to your custName variable using this peace of code
custName = emplTable.name;
This link will give you a hint how to find the current user
curUserId Function

Send an HttpRequest without response in Progress OpenEdge

I am using IHttpRequest to send a request to a service that has to create a message.
At this moment the Progress procedure holds until it receives a request from the service, while it should just continue. (Fire and forget principle)
This is the class I use to make the call to the service:
USING Progress.Lang.*.
USING OpenEdge.Net.HTTP.ClientBuilder.
USING OpenEdge.Net.HTTP.IHttpRequest.
USING OpenEdge.Net.HTTP.IHttpResponse.
USING OpenEdge.Net.HTTP.RequestBuilder.
USING Progress.Json.ObjectModel.JsonObject.
BLOCK-LEVEL ON ERROR UNDO, THROW.
CLASS TOOLS.externals.zato.SendDesadv:
DEFINE VARIABLE baseURL AS CHARACTER NO-UNDO.
DEFINE VARIABLE servicePath AS CHARACTER NO-UNDO INITIAL send/json".
DEFINE VARIABLE serviceURL AS CHARACTER NO-UNDO.
DEFINE VARIABLE oRequest AS IHttpRequest NO-UNDO.
DEFINE VARIABLE oResponse AS IHttpResponse NO-UNDO.
DEFINE VARIABLE input_json AS JsonObject NO-UNDO.
CONSTRUCTOR PUBLIC SendDesadv ( INPUT wave_nr AS INTEGER ):
input_json = NEW JsonObject().
input_json:Add("wave_nr", wave_nr).
invokeZato(input_json).
END CONSTRUCTOR.
METHOD PUBLIC VOID invokeService( INPUT input_json AS JsonObject ):
setServiceUrl().
SESSION:DEBUG-ALERT = TRUE.
oRequest = RequestBuilder:Put(serviceURL, input_json)
:AcceptJson()
:REQUEST.
oResponse = ClientBuilder:Build():Client:Execute(oRequest).
END METHOD.
METHOD PUBLIC VOID setBaseUrl( ):
IF LENGTH(PDBNAME(2)) = 3
THEN baseURL = "http://foo".
ELSE baseURL = "http://bar".
END METHOD.
METHOD PUBLIC VOID setServiceUrl( ):
setBaseUrl().
serviceUrl = baseUrl + servicePath.
END METHOD.
END CLASS.
I have already tried to just leave oResponse = ClientBuilder:Build():Client:Execute(oRequest). out of it, but then nothing is sent.
How could I use the "fire and forget" principle here?
Thanks in advance.
I know this is quite late -- but I add this for the benefit of future searchers.
If you have an AppServer, you can do an asynchronous run there. (And can even get a response back when the process finishes, if you like.)
If timing is not critical, there are other possibilities -- like adding the request to a queue and have a separate process to monitor that queue. I am toying with the idea of setting up an IPC mechanism to tell the background monitor that it should look NOW, thus minimizing the lag time between queue and dequeue.
As far as I know it's not possible to do asynchronous communication with IHttpRequest. A huge disadvantage!
A possible workaround might be to use a OS utility like curl or wget for the call instead. There are ways of making os calls asynchronous. They will however spawn new procedures (and not just new threads).
In windows you can do like this basic example:
OS-COMMAND VALUE("dir") NO-WAIT.
In Unix/Linux you can do
OS-COMMAND VALUE("ls &").
This will however introduce new problems: how to input/output data to wget/curl, how to handle "stay" procedures, possibly higher system load etc.

Resources