The Annotation based Type Descriptor

The ABL allows the insertion of annotations in the source code. Runtime access to annotations would allow a class to provide further information about its usage.

We do not expect that Progress will add access to annotations as runtime as part of the ABL in the OpenEdge 10 or OpenEdge 11 releases. As this would require significant additions to the Progress R-Code format breaking the R-Code compatibility rules.

As there is no out of the box utility provided by PSC to allow querying annotations of a class at runtime the SmartComponent Library provides a development time (when source code is available) scanner for class and class member annotations as well as a service which can make this data information at runtime.

The Annotation based TypeDescriptor is the foundation of the Business Entity Descriptor.

Sample of an ABL class with class annotations
@BusinessEntityGenerator (entityname="Test.SCL835.OrderBusinessEntity", type="BusinessEntity") .
@BusinessEntityView (name="order", isdefault="true", entitytable="eOrder", entityview="eCustomer", 
                     listcolumns="eOrder.OrderNum,eOrder.OrderDate,eOrder.CustNum,eCustomer.Name",
                     viewercolumns="eOrder.OrderNum,eOrder.OrderDate,eOrder.CustNum,eCustomer.Name,eOrder.OrderStatus") .
@BusinessEntityView (name="orderline", entitytable="eOrderLine", entityview="eItem", 
                     listcolumns="eOrderLine.LineNum,eOrderLine.Qty,eOrderLine.ItemNum,eItem.ItemName,eOrderLine.Price",
                     viewercolumns="eOrderLine.LineNum,eOrderLine.Qty,eOrderLine.ItemNum,eItem.ItemName,eOrderLine.Price,eOrderLine.Orderlinestatus") .
@BusinessEntityTable (name="eOrder", mandatoryColumns="ordernum,orderstatus", readonlyColumns="ordernum") .
@BusinessEntityTable (name="eCustomer", mandatoryColumns="ordernum,orderstatus", readonly="true") .
@BusinessEntityTable (name="eOrderLine", mandatoryColumns="ordernum,linenum", readonlyColumns="ordernum,linenum") .
@BusinessEntityTable (name="eItem", mandatoryColumns="ordernum,orderstatus", readonly="true") .


CLASS Test.SCL835.OrderBusinessEntity 
    INHERITS BusinessEntity
    USE-WIDGET-POOL: 
Sample of an ABL class method with annotations
    @InvokeMethod (template="invoke-receive-dataset", parameterClassName="Consultingwerk.CharacterHolder", datasetInput="true", datasetOutput="true") . 
    /*------------------------------------------------------------------------------
        Purpose:  
        Notes:   
        @param dsOrder INPUT-OUTPUT DATASET
        @param poParameter The Parameter Object for this method 
    ------------------------------------------------------------------------------*/
    METHOD PUBLIC VOID SampleMethod (INPUT-OUTPUT DATASET dsOrder, poParameter AS Consultingwerk.CharacterHolder):
        
    END METHOD .

Scanning for class annotations at development time

The SmartComponent Library provides a utility to scan for class and class member annotations in the form of a Progress procedure which can be executed from an ANT / PCT task.

See https://github.com/Riverside-Software/pct for details on PCT.

This utility scans class files (source code) in a given directory with a given file make for annotations. For every class with given annotations the utility writes a .annotation file. This file must be available at runtime, so that the annotation data can be made accessible at runtime.

The procedure Consultingwerk/Studio/ExtractClassAnnotations/extract-class-annotations.p can be started from an PCTRun task with the following paramters:

Parameter NameDescription

directory

The directory to scan for classes

fileMask

The file mask to scan

excludeAnnotations

Comma delimited list of annotations to ignore

overwriteWriteProtected

Overwrite of write protected .annotation files

verbose

Verbose output

assemblies-assemblies Parameter
tempDir-T Parameter (session temp-directory)
cpinternal-cpinternal (iso8859-1 default)
cpstream-cpinternal (iso8859-1 default)
iniFileSession ini file

The ANT script in Consultingwerk/Studio/ExtractClassAnnotations/extract-class-annotations.xml (attached: extract-class-annotations.xml) provides a sample ANT macro script to invoke the annotation scanner. 

Sample ANT Script to extract annotations
    <import file="Consultingwerk/Studio/ExtractClassAnnotations/extract-class-annotations.xml"/>
 
    <target name="ExtractClassAnnotations">
        <extractClassAnnotations directory="Consultingwerk"
                                 fileMask="*BusinessEntity.cls"
                                 overwriteWriteProtected="true"
                                 excludeAnnotations="@Test,@TestIgnore,@BusinessEntityGenerator"
                                 verbose="true">

            <!-- If propath is required to find the tools -->
            <propathentries>
                <pathelement path="." />
                <pathelement path="../SmartComponentLibrary" />
            </propathentries>
        </extractClassAnnotations>
    </target> 

This ANT script will extract annotations from all classes in the Consultingwerk directory that match the fileMask *BusinessEntity.cls. Write protected files will be overwritten and @Test, @TestIgnore and @BusinessEntityGenerator annotations will be ignored. 

Accessing annotations at runtime

The Consultingwerk.Framework.TypeDescritor.IClassAnnotationProvider service interface and the default implementation with the ClassAnnotationProvider provide access to the annotations extracted with the steps above at runtime. The ClassAnnotationProvider reads the annotations from the .annotation file of a class and makes annotations available using a set of holder classes:

The method GetClassAnnotations returns the annotations for a given Progress.Lang.Class instance. 

Code sample to access to annotations of a class
USING Consultingwerk.SmartComponentsDemo.OERA.Sports2000.* FROM PROPATH.
USING Consultingwerk.Framework.TypeDescriptor.* FROM PROPATH.

DEFINE VARIABLE oClass       AS Progress.Lang.Class      NO-UNDO . 
DEFINE VARIABLE oProvider    AS IClassAnnotationProvider NO-UNDO . 

DEFINE VARIABLE oAnnotations AS ClassAnnotationContainer NO-UNDO . 

oClass = GET-CLASS (OrderBusinessEntity) . /* Or use Progress.Lang.Class on OpenEdge releases that do not support the GET-CLASS function */

oProvider = {Consultingwerk/get-service.i Consultingwerk.Framework.TypeDescriptor.IClassAnnotationProvider
                                          "NEW Consultingwerk.Framework.TypeDescriptor.ClassAnnotationProvider()"} .

oAnnotations = oProvider:GetClassAnnotations (oClass) .

The result is a ClassAnnotationContainer instance with the following properties:

Property NameDescription

ClassName

Name of the Class File

TimeStamp

Date when Annotations have been extracted

Annotations

The List of Annotations of the class

Constructors

The List of Constructor type class members with annotations

Destructor

The Destructor type class member when it has annotations

Events

The List of Event type class members with annotations

Methods

The List of Method type class members with annotations

Properties

The List of Property type class members with annotations

For every class member a SerializableClassMemberAnnotation instance is returned as entries in one of the lists:

Property Name

Description

Name

The Name of the class member

Annotations

The List of Annotations of the class member

For every annotation a SerializableAnnotation instance is returned:

Property Name

Description

Name

The Name of the annotation

Parameters

The ListNameValuePair with the parameters of the annotation

The IClassAnnotationProvider provides caching of the annotations to improve the runtime performance when accessing the annotations of classes. To clear the cache the ClearCache() method is provided.