Project

General

Profile

Shared Resources

Buffers

The NEW and SHARED keyword options for the DEFINE BUFFER statement are used to share buffers across external procedures. The syntax is:

DEFINE [[ NEW ] SHARED ] BUFFER <buffer-name> ...

A shared buffer can be defined for a permanent table or for a shared temp-table.

Example 1: Creation of a new shared buffer.

DEFINE NEW SHARED BUFFER x-customer FOR customer.

When the external procedure using this declaration ends, the buffer is no longer available.

Converted code:

Customer.Buf xCustomer = RecordBuffer.define(Customer.Buf.class, "fwd", "xCustomer", "x-customer");

public void execute()
{
   externalProcedure(SharedExample.this, TransactionType.FULL, new Block((Body) () -> 
   {
      RecordBuffer.openScope(xCustomer);
      SharedVariableManager.addBuffer("xCustomer", xCustomer);
      ...
   }));
}

Details: The declaration and initialization of the xCustomer instance variable is identical to the case of a simple buffer for a persistent table. In addition, within the main execute() method of the converted program, the xCustomer buffer instance is added as a shared buffer to the SharedVariableManager, using the static method addBuffer.

Storing the buffer in this manner will allow it to be looked up and accessed later by other, converted procedures which are launched (either directly or indirectly) by this program. In 4GL code, this would be achieved using the DEFINE SHARED BUFFER (i.e., no NEW keyword) in the called procedure, using the same buffer name and table.

Example 2: Using a shared buffer.

DEFINE SHARED BUFFER x-customer FOR customer.

Converted code:

Customer.Buf xCustomer = SharedVariableManager.lookupBuffer("xCustomer", "customer", Customer.Buf.class);

public void execute()
{
   externalProcedure(SharedExample.this, TransactionType.FULL, new Block((Body) () -> 
   {
      RecordBuffer.openScope(xCustomer);
      ...
   }));
}

Details: when processing the above DEFINE SHARED BUFFER statement, the FWD conversion understands that the x-customer shared buffer already was created and shared by another program, somewhere up the call stack. As such, it determines that a buffer creation call is not needed in this case. Rather, it emits the above Java code to look up the associated xCustomer buffer instance by name using the SharedVariableManager. Since the same instance of the xCustomer buffer is used in both the calling and in the called program, all changes made to that buffer are automatically shared.

Frames

Menus

Queries

The NEW and SHARED keyword options for the DEFINE QUERY statement are used to share queries across external procedures. This query can be used in procedures called directly or indirectly by the current procedure. The syntax is:

DEFINE [[ NEW ] SHARED ] QUERY <query-name> FOR buffer-name [, buffer-name] ...

For shared queries, each <buffer-name> must be the name of a shared buffer (for a permanent or a temporary table).

Example 1: Creation of a new shared query.

DEFINE NEW SHARED BUFFER x-customer FOR customer.
DEFINE NEW SHARED QUERY q FOR x-customer.

Converted code:
Customer.Buf xCustomer = RecordBuffer.define(Customer.Buf.class, "fwd", "xCustomer", "x-customer");

final QueryWrapper query0 = SharedVariableManager.addQuery("q", false);

public void execute()
{
   externalProcedure(SharedExample.this, new Block((Body) () -> 
   {
      query0.addBuffer(xCustomer, true);
      RecordBuffer.openScope(xCustomer);
      SharedVariableManager.addBuffer("xCustomer", xCustomer);
      ...
   }));
}

Details: The declaration and initialization of the query0 instance variable is similar to the case of a non-shared query. But in this case SharedVariableManager.addQuery function is used to implicitly create a QueryWrapper and add it to SharedVariableManager. Storing the query in this manner will allow it to be looked up and accessed later by other, converted procedures which are called by this program. In 4GL code, this would be achieved using the DEFINE SHARED QUERY (i.e., no NEW keyword) in the called procedure, using the same query name.

Example 2: Using a shared query.

DEFINE SHARED BUFFER x-customer FOR customer.
DEFINE SHARED QUERY q FOR x-customer.

Converted code:

Customer.Buf xCustomer = SharedVariableManager.lookupBuffer("xCustomer", "customer", Customer.Buf.class);

final QueryWrapper query0 = SharedVariableManager.lookupQuery("q");

@LegacySignature(type = Type.MAIN, name = "shared-example.p")
public void execute()
{
   externalProcedure(SharedExample.this, new Block((Body) () -> 
   {
      RecordBuffer.openScope(xCustomer);
   }));
}

In this case FWD emits the above Java code to look up the associated query0 query instance by name using the SharedVariableManager.lookupQuery. Since the same instance of the query0 query is used in both the calling and in the called program, all changes made to that query are automatically shared.

Streams

For conversion of non-shared streams, see Defining Streams section.

The syntax of the statement which allows to define a stream is:

DEFINE [ [ NEW [ GLOBAL ] ] SHARED ] STREAM <stream> ...

The stream definition syntax allows the streams to be shared with other procedures down the call stack or can be global to the program, in a similar way normal variables are. Thus for defining a shared stream in a procedure, the following flavors of DEFINE STREAM can be used:

DEFINE STREAM Statement Details
DEFINE NEW SHARED STREAM s. Defines a stream that can be shared with other procedures. When the procedure using the DEFINE NEW SHARED STREAM statement ends, the stream is no longer available to any procedure.
DEFINE NEW GLOBAL SHARED STREAM s. Defines a stream that can be shared with other procedures and that will remain available even after the procedure that contains the DEFINE NEW GLOBAL SHARED STREAM statement ends.
DEFINE SHARED STREAM s. Defines a stream that was created by another procedure using the DEFINE NEW SHARED STREAM statement or the DEFINE NEW GLOBAL SHARED STREAM statement.

In all above cases the value used as stream name must be a string which satisfies the same rules as the normal variable names. The following table summarizes the stream opening statement options with their description:

4GL syntax Description Supported
NEW Defines the stream that can be used either inside the single procedure or shared with the other procedure. In case of using GLOBAL suffix the stream remains opened even when the creating procedure ends. Yes
SHARED Flag to indicate stream sharing by several procedures. Only one can define with prefix NEW. This means creating the stream by parent process. The other procedure can declare and use the stream by SHARED for the stream that already being created by NEW SHARED options in a “master” procedure. Yes
GLOBAL Using this option in addition to defining stream as SHARED allows to use the defined stream even when the procedure creating this stream terminates. Yes

Shared streams are supported using the SharedVariableManager, as normal shared variables are. This class has stream-specific methods to add and lookup streams. So, if the new stream is shared in any way according to 4GL semantic (defined with NEW SHARED or NEW GLOBAL SHARED), the StreamWrapper instance needs to be registered with SharedVariableManager.addStream() and TransactionManager.registerFinalizable():

SharedVariableManager.addStream([ ScopeLevel scope, ] String variable_name, Stream stream);
TransactionManager.registerFinalizable(Stream stream, boolean global);

Here variable_name designates a name identifying the stream inside the TransactionManager while the SharedVariableManager and scope identifies the scope to which the stream is added. For global variables the scope needs to be set explicitly to ScopeLevel.GLOBAL for other shared variables the scope defaults to ScopeLevel.NEXT and can be omitted. The global parameter here is true if stream stream is a globally shared stream and false otherwise.

If the stream is shared but is defined by other procedure, the current procedure merely needs to look it up with

SharedVariableManager.lookupStream(String stream_name);

The following example illustrates how the stream definitions are converted by FWD:

define new shared stream s1.
define new global shared stream s2.
define shared stream s3.

Converted code:

Stream s1Stream = new StreamWrapper("s1");
Stream s2Stream = new StreamWrapper("s2");
Stream s3Stream = SharedVariableManager.lookupStream("s3Stream");

public void execute()
{
   externalProcedure(SharedExample.this, new Block((Body) () -> 
   {
      TransactionManager.registerFinalizable(s1Stream, false);
      SharedVariableManager.addStream("s1Stream", s1Stream);

      TransactionManager.registerFinalizable(s2Stream, true);
      SharedVariableManager.addStream(ScopeLevel.GLOBAL, "s2Stream", s2Stream);

    }));
}

Details: as it can be seen, the shared stream s3, defined in a caller program, is only looked up in SharedVariableManager, while the new streams s1 and s2 are registered in the TransactionManager, as well as in SharedVariableManager. Note the difference in registering the shared stream s1 vs the global shared stream s2: the latter is given the scope ScopeLevel.GLOBAL in the call to SharedVariableManager and has the boolean global flag set to true in the call to TransactionManager.registerFinalizable().

Procedures using the shared streams defined in the example above (s1 and s2) would have to look them up with SharedVariableManager.lookupStream(), in the same way the code looked up stream s3.

Note that during conversion legacy names are passed at stream initialization, e.g. Stream s1Stream = new StreamWrapper("s1") where s1 is the legacy stream name. But the stream is registered in TransactionManager and SharedVariableManager using a different, converted name, e.g. s1Stream: SharedVariableManager.addStream("s1Stream", s1Stream). And later it is searched with SharedVariableManager.lookupStream using the converted name: Stream s1Stream = SharedVariableManager.lookupStream("s1Stream"), and not the legacy name.

Temp-Tables

For conversion of non-shared temp-tables, see DEFINE TEMP-TABLE section.

The portion of the syntax for the DEFINE TEMP-TABLE Progress language statement relevant to creation of a shared temp-table:

DEFINE [ [ NEW [ GLOBAL ] ] SHARED ] TEMP-TABLE <temptable> ...

where <temptable> is the name of the temp-table being defined, as well as the name of the default buffer which will be defined.

Example 1: Creation of a new, shared temp-table:

DEFINE NEW SHARED TEMP-TABLE tmp-customer ...

Converted code:

TmpCustomer_1_1.Buf tmpCustomer = SharedVariableManager.addTempTable("tmpCustomer", TmpCustomer_1_1.Buf.class, "tmpCustomer", "tmp-customer");

public void execute()
{
   externalProcedure(SharedExample.this, new Block((Body) () -> 
   {
      RecordBuffer.openScope(tmpCustomer);
      ...  
   }));
   }

Details: in addition to the schema artifacts associated with the defined temp-table, conversion defines an explicit, default buffer for the new, shared temp-table and shares the buffer via the SharedVariableManager, to make it accessible to programs invoked later. This shared buffer and temp-table last only as long as the procedure that creates them.

Example 2: Creation of a new, global shared temp-table:

DEFINE NEW GLOBAL SHARED TEMP-TABLE tmp-customer ...

Converted code:

TmpCustomer_1_1.Buf tmpCustomer = SharedVariableManager.addTempTable(ScopeLevel.GLOBAL, "tmpCustomer", TmpCustomer_1_1.Buf.class, "tmpCustomer", "tmp-customer");

@LegacySignature(type = Type.MAIN, name = "shared-example.p")
public void execute()
{
   externalProcedure(SharedExample.this, new Block((Body) () -> 
   {
      RecordBuffer.openScope(tmpCustomer);
      ...
   }));
}

Details: the only difference between this example and Example 1 is the use of the GLOBAL keyword, indicating that scope of the default buffer associated with this temp-table should survive until the end of the user's session. This difference is minor from a conversion standpoint; the only effect is that the ScopeLevel.GLOBAL added as the first parameter to the SharedVariableManager.addTempTable method.

Example 3: 4GL code to access from a separate, external procedure the shared, default buffer created in Example 1 or Example 2:

DEFINE SHARED TEMP-TABLE tmp-customer ...

Converted code:

TmpCustomer_1.Buf tmpCustomer = (TmpCustomer_1.Buf) TemporaryBuffer.useShared("tmpCustomer", TmpCustomer_1.Buf.class);

@LegacySignature(type = Type.MAIN, name = "shared-example.p")
public void execute()
{
   externalProcedure(SharedExample.this, new Block((Body) () -> 
   {
      RecordBuffer.openScope(tmpCustomer);
      ...
   }));
}

Details: we are looking up the shared, default buffer previously stored in the SharedVariableManager, not defining a new buffer. The tmpCustomer variable is declared as an instance member of the Java class which represents the converted external procedure. It is initialized with the value returned by TemporaryBuffer.useShared, using the buffer name assigned previously in the respective calling program's invocation of SharedVariableManager.addTempTable.

Variables

Old style Progress 4GL often uses shared variables to communicate data between procedures. FWD provides full support for this mechanism.

In the 4GL, there are 3 forms which can create or access shared variables. Each of these is a form of the <sharing_flags> in the DEFINE VARIABLE statement (see the section DEFINE VARIABLE):

4GL Syntax Purpose Java Initialization Code
NEW SHARED Creates a new non-global instance of a shared variable in the current scope. This will "hide" global shared variables and non-global shared variables of the same name (which are defined in an enclosing scope). This shared variable will cease to exist when this scope exits. SharedVariableManager.addVariable(String name, Object var)
NEW GLOBAL SHARED Creates a new instance of a shared variable in the global scope. This variable will exist until the user's session exits, since it is not associated with any single program scope. SharedVariableManager.addVariable(ScopeLevel.GLOBAL, String name, Object var)
SHARED Accesses ("imports") a reference to an already existing shared variable (either shared or global). The instance will be found based on a lookup process that tries to find the variable matching the given name in the current scope, and in each enclosing scope in turn, returning the first found instance. Once all scopes are checked, the global scope is checked. If no instance is found an error will occur. The expected class parameter is specified only in order to display a 4GL error in the case of type mismatch with an already existing shared variable with the same name. SharedVariableManager.lookupVariable(String name, Class expected)

The effect of these flags is to change the initialization code for the associated Java variable. All other processing of the DEFINE VARIABLE remains as described in the DEFINE VARIABLE section. For example, this 4GL:

define new shared        variable txt1 as character initial "Hello World!".
define new global shared variable num2 as decimal.
define shared            variable date3 as date.

This is the resulting Java code:

public class SharedExample
{
   @LegacySignature(type = Type.VARIABLE, name = "txt1")
   character txt1 = SharedVariableManager.addVariable("txt1", new character("Hello World!"));

   @LegacySignature(type = Type.VARIABLE, name = "num2")
   decimal num2 = SharedVariableManager.addVariable(ScopeLevel.GLOBAL, "num2", new decimal(0));

   @LegacySignature(type = Type.VARIABLE, name = "date3")
   date date3 = SharedVariableManager.lookupVariable("date3", date.class);

   @LegacySignature(type = Type.MAIN, name = "shared-example.p")
   public void execute()
   {
      externalProcedure(SharedExample.this, new Block((Body) () -> 
      {
         // variables can be accessed here
      }));
   }
}

The initialization of shared variable differs from initialization of non-shared variables. For non-shared variables, the initialization occurs with a default constructor, a constructor which takes a literal value parameter or a factory method (e.g. date.instantiateUnknownDate()). With shared variables, the normal initialization is either moved inside a SharedVariableManager.addVariable() method call or the initialization is completely replaced (in the case of importing a reference to an existing shared variable). In each case where a new instance is created, the same use of a constructor or a factory method (not shown above) will occur. The result of that construction will be passed as the 2nd or 3rd parameter to addVariable() depending on if the variable is to be shared in the global scope or not. In the lookupVariable() case, no new instance is needed so there is no use of a constructor or factory method. Explicit and default initialization works just as it normally would (see the Initialization section below). The only exception is that in the lookupVariable() case, since there is no new instance, the value will be the same as it has been previously assigned by the calling code.

Support for shared variables is handled via a shared variable manager. This runtime class is called SharedVariableManager . It is based on the ScopedSymbolDictionary and is designed to exist in a per client/session basis (stored in the security context). This allows all transactions to access these shared variables but for this access to be done maintaining context specific state. This approach is a replacement for features that would normally be provided via thread local variables which can't be used in the transaction server.

Whenever a new external procedure scope is started, if new shared variable references are added, the corresponding Java source code will add a scope. Likewise this same Java method will use a finally {} clause to delete the scope on exit. Global variables are added to the global scope and regular shared variables are added to the current (top) scope. Such additions only occur if the definition is defined with the NEW keyword. This allows "downstream" methods to access the most current instance of a shared variable by executing a lookup using a given name and when the method that defines a new shared (non-global) variable ends, the scope must be deleted (deleting all references to variables defined in that scope). Any process that accesses a shared variable gets the shared variable manager out of the current security context and then does a lookup of the variable name and assigns the reference to the resulting local instance.


© 2004-2022 Golden Code Development Corporation. ALL RIGHTS RESERVED.