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.
@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:
@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 Name | Description |
---|---|
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) |
iniFile | Session 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.
<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.
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 Name | Description |
---|---|
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.