Project

General

Profile

Debugging Techniques

This chapter discusses some advanced (and perhaps not-so-advanced, but nevertheless useful) techniques for tracking down problems in converted (and hand-written) business logic code, as well as in the FWD runtime environment itself. We will not be starting from scratch; the Debugging Converted Code chapter of the FWD Conversion Handbook is prerequisite reading. While that chapter discusses an approach to confirming deviations from the original application, and a survey of the tools available to help isolate problems, here we will dig a bit deeper into the specifics of the FWD runtime infrastructure and how these affect debugging. We will cover the FWD logging facility from the point of view of a developer, and we will review how the structure of converted code affects interactive debugging.

Block Processing

One of the most striking mismatches between Progress 4GL code and Java code is that 4GL code is block-structured, while Java is not. That is, certain sets of 4GL statements, from an external procedure down to a simple FOR EACH loop, define constructs known as blocks. While certain groups of Java statements are said to form a block as well, Java is not considered a block-structured language. Understanding this mismatch, and how FWD compensates for it, is important when reading and debugging converted application code.

A block in the 4GL sense is much more than the code it contains. The block construct defines a great deal of implicit behavior, which is not necessarily obvious to a developer. This includes defining, expanding, or otherwise modifying the scope of variables, data record buffers, transactions, and user interface frames. A block can be assigned properties (some are assigned implicitly) which define responses to user events, errors, or other conditions. These can impact the values of variables or database table fields, which may have changes made to them within the scope of the block undone, under certain circumstances. The way a block exits is dependent upon such properties.

This implicit block behavior is preserved in a converted application, but the requirement to do so has implications on the structure of the Java business logic generated by the conversion process. In an earlier version of the conversion technology, the control flow logic to mimic this behavior explicitly was emitted as Java code into the business logic. This “scaffold code” required an ugly combination of loops and nested try-catch blocks for every converted Progress block. In many cases the scaffold code was several times larger than the business logic statements themselves! This extremely verbose representation was duplicative and difficult to read and maintain.

The current version of the conversion technology emits a much more compact representation of Java business logic code. It achieves the same behavioral result, but with much less emitted application code. The control flow governing implicit block behavior is now encapsulated, to the degree possible, in FWD runtime code. To do this, a callback mechanism is employed. Converted code for each Progress block is emitted within methods of an anonymous, inner class. This class is a subclass of the abstract com.goldencode.p2j.util.Block class. The class com.goldencode.p2j.util.BlockManager drives the appropriate control flow for the type of block which was converted, invoking the appropriate, call-back methods implemented in the anonymous inner class in the business logic.

Consider the following FOR EACH loop from the sample test case primes.p. This code defines a temp-table named list. At some point, we assume the temp-table has been populated, and then the loop below displays the value of the num field of each record in list:

def temp-table list field num as int.
...

for each list where list.num >= 0:
   display list.num label "Prime Number" with 10 down.
end.

This is converted to the following Java application code:

import static com.goldencode.p2j.util.BlockManager.*;
...

TempRecord1 list = TemporaryBuffer.define(TempRecord1.class, "list", false);

PrimesFrame0 frame0 = GenericFrame.createFrame(PrimesFrame0.class, "");

...

   forEach("loopLabel2", new Block()
   {
      AdaptiveQuery query0 = null;

      public void init()
      {
         frame0.openScope();
         query0 = new AdaptiveQuery(list, "list.num >= 0", null, "list.id asc");
      }

      public void body()
      {
         query0.next();

         FrameElement[] elementList1 = new FrameElement[]
         {
            new Element(new FieldReference(list, "num"), frame0.widgetNum())
         };

         frame0.display(elementList1);
      }
   });

The block defined by the FOR EACH loop in the Progress code is implemented here as a subclass of the abstract class com.goldencode.p2j.util.Block, with the default init method overridden and the abstract body method implemented. This anonymous inner class is instantiated and passed as the second argument to the static forEach method of com.goldencode.p2j.util.BlockManager.

The init method explicitly defines a scope for frame0, which corresponds with the default frame created in the Progress runtime. It also constructs an object to perform the database query, in this case an instance of com.goldencode.p2j.persist.AdaptiveQuery, which commonly is used to perform the data access work of a converted FOR EACH statement. This method will be invoked once by BlockManager.forEach, to initialize the block.

After the block is initialized, the body method is invoked by BlockManager.forEach. The call to query0.next() attempts to load into a record buffer named list the next available record from the backing temporary table. If a record exists, it is loaded into the list buffer, which represents a Data Model Object (DMO) of type TempRecord1. Next, an array of FrameElement objects is defined, within which the DMO property list.num is associated with a user interface widget in frame frame0. Finally, frame0 is asked to display this list of FrameElement objects.

Control then drops back into the BlockManager.forEach method, which performs some internal processing for the block, and eventually calls the body method again. It will do this repeatedly, until the invocation of query0.next() throws an exception to indicate there are no more records to be retrieved. Alternatively, something else in the body method could cause an error or otherwise create a condition which triggers an exception. In any event, BlockManager.forEach will catch the exception and handle it accordingly, performing the block behavior which is appropriate to the original application.

This inversion of control model, in which application logic is implemented in callback methods invoked by the runtime environment, has important implications for debugging converted code. It is important to remember that this model is pervasive in converted business logic, since the use of blocks is such an integral aspect of writing Progress 4GL programs. It is not uncommon to see deeply nested examples of this structure in converted code, where the body method of one Block implementation contains many other Block instances. This is simply because blocks are naturally and commonly nested in Progress code.

An understanding of this model, its reason for being, and the mechanics of the runtime code which supports it, is crucial when reading converted code. This must be kept in mind when deciding where to insert logging statements, where to place breakpoints and how best to step through code for interactive, source-level debugging, and when interpreting profiling results. We will discuss this model as it relates to these issues in greater detail in the sections below.

Logging

Impact of inversion of control in block code

Source Level Debugging

Eclipse project setup

Let's take a look how we can create a Java project into Eclipse in order to be able to debug converted sources of your project and FWD framework.

  • Initiate creation of a Java project from the Eclipse menu.

  • Specify the project name and select a proper JRE (which has p2jspi.jar installed as an extension).

  • Attach FWD sources which reside into p2j/src. Depending on your needs, you can import them (files will be copied to the source folder of the project) or link them (files will be edited in their original locations). In our example we will link them.

  • Attach all third-party libraries (jars) used by FWD which reside into p2j/lib directory.

 

 

  • Attach the sources of your converted project (usually reside in the <project_home>/src directory).

  • Your project source tree will look something like this:

  • In order to simplify debugging, you can run FWD server right from the Eclipse. You should create debug a configuration where you should specify:
    1. main class: com.goldencode.p2j.main.ServerDriver
    2. optionally, into program arguments: the server bootstrap configuration file name, usually server.xml
    3. VM parameters: the parameters that will allow the server to have enough resources, e.g. -Xmx1024m -server -XX:MaxPermSize=256m
    4. working directory: the FWD server home directory (where the server start up script resides).

 

 

  • Now you can run the newly created debug configuration and watch the server staring:

 

Remote debugging of FWD server and client using Eclipse

In order to perform remote debugging you should create an Eclipse project containing sources of FWD framework and/or sources of your converted project. See previous section on how to do it.

Follow these steps to perform debugging of a FWD server:

  • Create a configuration for remote debugging. Specify a target host on which the target server will run and debug port number (usually 2080 + <server instance number>)

  • Run FWD server with enabled debug mode: ./server.sh -d
  • Attach to the FWD server from the Eclipse using the created debug configuration.

Follow these steps to perform debugging of a FWD client:

  • Create a configuration for remote debugging. Specify a target host on which the target client will run and custom debug port number.