Dynamic Proxies in ABAP Part 3: Code Generation
Using the results from the previous blog, we are ready to tackle the next point on our agenda, i.e. obtaining the canonical XML representation of the proxy descriptor structure that will later drive the code generation. But first we need to provide ourselves with some example interfaces to try things out with. The following random bunch, masquerading as part of some rather phoney simulation framework, will have to suffice for this purpose:
interface ZIF_DPX_CLOCK public. class-events TICKED exporting VALUE(INCREMENT) type FLOAT. methods MAKE_TICK. endinterface. interface ZIF_DPX_SIMULATED_OBJECT public. methods GET_NAME returning VALUE(RV_NAME) type STRING. class-methods GET_SPECIES returning VALUE(RV_SPECIES) type STRING. methods HANDLE_TICKED for event TICKED of ZIF_DPX_CLOCK importing !INCREMENT. endinterface. interface ZIF_DPX_GUINEA_PIG public. interfaces ZIF_DPX_SIMULATED_OBJECT. events HUNGRY. events THIRSTY. methods FEED importing !IV_CALORIES_PROVIDED type FLOAT !IV_FOOD_TYPE type STRING optional returning VALUE(RV_CALORIES_CONSUMED) type FLOAT raising ZCX_DPX_INDIGESTION. methods GET_AGE returning VALUE(RV_AGE) type FLOAT. methods WATER importing !IV_LITRES_PROVIDED type FLOAT returning VALUE(RV_LITRES_CONSUMED) type FLOAT exceptions DYSENTERY. endinterface.
As you might note right away, I am no expert on guinea pigs. However, we now have some methods, a number of events (even a static one!) and a composite interface to work with. Our present goal is to retrieve the XML document describing interface ZIF_DPX_GUINEA_PIG. This is trivially accomplished by the following code snippet:
data LR_DESCR type ref to ZIF_DP_PROXY_DESCRIPTOR. data LT_INTFNAMES type ZIF_DP_PROXY_MANAGER=>INTFNAME_TAB. data LV_INTFNAME type ABAP_INTFNAME. data LS_PROXYDESCR type ZIF_DP_PROXY_DESCRIPTOR=>PROXYDESCR. data LV_DESCR_XML type STRING. start-of-selection. LV_INTFNAME = 'ZIF_DPX_GUINEA_PIG'. append LV_INTFNAME to LT_INTFNAMES. create object LR_DESCR type ZCL_DP_PROXY_DESCRIPTOR exporting IT_INTFNAMES = LT_INTFNAMES. LS_PROXYDESCR = LR_DESCR->GET_DESCRIPTION( ). call transformation ID source ABAP = LS_PROXYDESCR result xml LV_DESCR_XML.
Later, when we are ready to generate the actual proxy code, all we have to do is replace the canonical transformation ID with our own XSL transformation that contains the template code for the dynamic proxy class. For now, the ID transformation serves the useful purpose of giving us an idea how the source XML our XSLT will operate upon looks like. Executing the above code yields the following XML document:
One thing is probably worth pointing out: Only classical exceptions are listed in ABAP_METHDESCR and consequently appear in the above XML document. So far, I have found no way to retrieve information about the class based exceptions a method might throw at runtime (any suggestions?). This is not fatal for our present purpose, but bothersome nevertheless, since it deprives us of the possibility to deal with exceptions raised by the invocation handler but not declared by the calling method in our own way. Instead, the default behaviour will be enacted: Raising an undeclared exception in the invocation handler will result in a CX_SY_NO_HANDLER exception propagated to the proxy’s caller.
Figuring Out the Code
Before we can actually start generating code, quite obviously we have to know what code to generate. Therefore, we start by writing the relevant parts of a dynamic proxy class by hand and then generalize the resulting code into an XSLT template. In the process we will determine how to deal with issues like the handling of errors, the raising of events, etc.
To make our proxy class executable, we will need to put it into a subroutine pool or save it as a report, so we will create it as a local class (to generate a global class would be several orders of magnitude more complicated and is consequently left as an exercise to the reader). As justified in the first blog, our class will implement interface ZIF_DP_PROXY; in addition, we will derive it from an abstract base class ZCL_DP_PROXY_BASE that will serve to capture all the common code that can be shared between different proxies. Unfortunately, due to our earlier decision to define MR_STATIC_HANDLER as class attribute in ZIF_DP_PROXY, the interface can not already be implemented by ZCL_DP_PROXY_BASE because then all proxy class instances would share the same static invocation handler.
We end up with the following class structure:
A minimalist implementation of class ZCL_DP_PROXY_BASE makes only provisions for holding a reference to its per-instance invocation handler in an attribute that gets initialized in the constructor:
class ZCL_DP_PROXY_BASE definition public abstract create public. public section. methods CONSTRUCTOR importing !IR_HANDLER type ref to ZIF_DP_INVOCATION_HANDLER. protected section. data MR_HANDLER type ref to ZIF_DP_INVOCATION_HANDLER. endclass. class ZCL_DP_PROXY_BASE implementation. method CONSTRUCTOR. MR_HANDLER = IR_HANDLER. endmethod. endclass.
Maybe we will find more commonalities between different proxy classes later that can be factored out and pushed up into the base class. However, with ZCL_DP_PROXY_BASE as base class, a skeleton implementation of the dynamic proxy for interface ZIF_DPX_GUINEA_PIG takes on the form:
class lcl_proxy definition inheriting from zcl_dp_proxy_base. public section. interfaces zif_dp_proxy. interfaces ZIF_DPX_GUINEA_PIG. interfaces ZIF_DPX_SIMULATED_OBJECT. endclass. class lcl_proxy implementation. method zif_dp_proxy~set_static_handler. zif_dp_proxy~mr_static_handler = ir_static_handler. endmethod. method zif_dp_proxy~raise_event. * TO DO endmethod. method zif_dp_proxy~raise_static_event. * TO DO endmethod. method ZIF_DPX_GUINEA_PIG~FEED. * TO DO endmethod. method ZIF_DPX_GUINEA_PIG~GET_AGE. * TO DO endmethod. method ZIF_DPX_GUINEA_PIG~WATER. * TO DO endmethod. method ZIF_DPX_SIMULATED_OBJECT~GET_NAME. * TO DO endmethod. method ZIF_DPX_SIMULATED_OBJECT~GET_SPECIES. * TO DO endmethod. method ZIF_DPX_SIMULATED_OBJECT~HANDLE_TICKED. * TO DO endmethod. endclass.
Strictly speaking, there is no need to list interface ZIF_DPX_SIMULATED_OBJECT explicitly since it gets included via ZIF_DPX_GUINEA_PIG anyway; owever, ABAP takes care of this redundancy for us so we can get away with just reproducing the list of interfaces contained in our proxy descriptor. Therefore, the above code can be generated by processing the proxy descriptor’s XML representation with the XSLT shown below:
Let’s have a short look at the anatomy of this XSL transformation:
The instruction <xsl:output \ method=”text” omit-xml-declaration=”yes”/> informs the XSLT processor the transformation’s output will not necessarily be ell-formed XML and disables the escaping of special characters like ‘>’ \ (>) that would occur otherwise.
The instruction <xsl:strip-space elements=”*”/> strips all extraneous whitespace (i.e. every text node that consists entirely of whitespace characters) from the source XML and the XSLT itself. If we specifically need to insert a string of whitespace characters into the output document we can enclose it in <xsl:text> elements whose contents are exempt from this stripping process.
The template <xsl:template \ match=”ABAP”> is triggered by the <ABAP> tag in the source document. Here we include the definition and implementation of the proxy class into the output, calling additional templates through <xsl:apply-templates> to take care \ of its dynamic parts. The first template, <xsl:template \ match=”INTERFACES/item”>, inserts the list of implemented interfaces into the proxy’s definition, while the second one, <xsl:template match=”METHODS/item”>, produces the corresponding method stubs.
This overview concludes the current instalment in this series of blogs. In the next blog we will focus on implementing the more demanding parts of the dynamic proxy class, for now conveniently hidden behind the ever popular “TO DO” comments.
I already told you that I am working on something similar so this series is a must read for me.
Love this line it's definately a classic 🙂
"to generate a global class would be several orders of magnitude more complicated and is consequently left as an exercise to the reader"
I think you have to change this line to:
"there is no need to list interface ZIF_DPX_SIMULATED_OBJECT explicitly since it gets included via ZIF_DPX_GUINEA_PIG (instead of: ZIF_DPX_SIMULATED_OBJECT) anyway"
I will look into the exception problem but I doubt that I will have more luck than you.
thanks a lot for spotting the error, of course you’re completely right and I corrected the line accordingly. It’s gratifying to know there are readers (well, at least one) who aren’t too bored to follow the blog up to this point 😉
Considering your commentary I presume that in your own framework you’re making use of the services in packages SEO* to generate a global class. How is this project coming along? I’d also be very interested to hear about any progress you’re able to make regarding the exception problem.
in the first version my framework will only create proxies definitions in memory (nearly no need for seo* package stuff). This means that I am more focusing on stuff like how I can push the 30 subroutine pools limit as far as possible 🙂 I am also generating the code without XSL mostly because of lack of experience.
The classes Thorsten mentions look really interesting cannot wait to give them a closer look.
one way to find the object-oriented exceptions used in a method's signature is to use:
Class CL_OO_CLASS_COMPONENTS_FLAT or CL_OO_INTERF_COMPONENTS_FLAT
thanks a lot for the suggestion. I’ll try to make use of it in a future revision of the framework.