GRC
HR
SCM
CRM
BI
Expand +


Article

 

Why You Should Use CL_UJO_WRITE_BACK Instead of CL_UJK_WRITE

by George Chen

January 4, 2017

Using SAP BusinessObjects Planning and Consolidation (BPC), version for NetWeaver and an ABAP Business Add-In (BAdI), you can implement UJR_WRITE_BACK BADI, which is an enhancement spot designed for BPC. Using this pre-process enhancement spot, you can insert your own logic to manipulate data. Inside this BAdI, you can call CL_UJO_WRITE_BACK to write back data to a different model. Further, if you have to work with a write-back that was implemented with CL_UJK_WRITE, see what problems you will face and find out how to resolve them. This article provides analysis, an architect’s view, and actual code to correct the problems.


SAP BusinessObjects Planning and Consolidation (BPC), version for NetWeaver provides a Business Add-In (BAdI) to support custom logic before the standard program writes data back to BPC models. In this BAdI, you can have your own code to write data to other models. The first issue is that while CL_UJO_WRITE_BACK is the preferred class to use, some developers may choose to use CL_UJK_WRITE to do this task. There are problems and limitations using CL_UJK_WRITE. The second issue is that if in your write-back BAdI you have to call a colleague’s code in which CL_UJK_WRITE is used, it causes problems. I explain what the problems are and how to resolve them so that you have a better understanding of the architecture design of a BPC write-back.

For more detail about BAdIs see the “BPC BAdIs” section at the end of the article.

(Note: Prerequisite: Readers need to have BPC ABAP BAdI development experience.)

How People Use CL_UJK_WRITE and Its Limitations

Figure 1 shows the write method of class CL_UJK_WRITE. In transaction code SE24, enter the class name CL_UJK_WRITE Display. Then select the method write to display. Click the Signature button to display the parameters.


Figure 1
Write method of Class CL_UJK_WRITE

Figure 2 shows the method definition. From the menu, select Method > Display Method definition.


Figure 2
Method definition: public and static

Figure 3 shows how I see people using CL_UJK_WRITE=>WRITE() in their programs.


Figure 3
How people typically use the write() method from CL_UJK_WRITE

Because this is a static public method, some people like to use it directly. It is easy and convenient. However, this use has problems as follows.

Problem 1: Suppress zero or not. When you write back data with zero values, sometimes you want to write back zero values. However, other times you may want to bypass these zero values. The example above didn’t provide the value for this optional parameter.

Of course the description in Figure 4 (‘Y’ is Not to write) is not correct for this parameter. As type UJ_FLG, the only allowed values are either ABAP_TRUE (“X”) or ABAP_FALSE (“ ”).  This description is misleading. If you want to use this parameter, you have to enter either ‘X’ or leave it blank.


Figure 4
I_SUPPRESS_ZERO is an optional parameter and is often neglected when this method is called

Problem 2: The inflexibility of CL_UJK_WRITE=>WRITE(). The program flow of CL_UJK_WRITE=>WRITE() is shown in Figure 5.


Figure 5
The observed program flow of CL_UJK_WRITE=>WRITE()

So the core of CL_UJK_WRITE=>WRITE() is still CL_UJO_WRITE_BACK(), which SAP   recommends. From this program flow you can see CL_UJK_WRITE=>WRITE() is just a wrapper of CL_UJO_WRITE_BACK.

These write-back parameters, highlighted in Figure 6, are hardcoded in CL_UJK_WRITE’s WRITE_IN_SYNC_MODE2().


Figure 6
Hardcoded write-back parameters

In step 2 of the program flow shown in Figure 5, write-back parameters are populated with hardcoded values before calling CL_UJO_WRITE_BACK-> write_back(). Developers are not given a chance to change the write-back parameters if they want. This limitation shows the inflexibility of CL_UJK_WRITE=>WRITE().

However, these parameters are not available to input or change when calling CL_UJK_WRITE=>WRITE(). Instead, they are assigned values inside the method write_in_sync_mode2() without flexibility for the developer to make changes at method write(). Look at the list of write-back parameters for write-back as shown in Figure 7.


Figure 7
Write-back parameters
 

That’s why Class CL_UJO_WRITE_BACK is preferred because you can set up write-back parameters before you call the write-back method. The sample code could be what is shown in Figure 8.


Figure 8
Code for write-back parameters

Problem 3: The implicit dependency of CL_UJK_WRITE=>WRITE. If you insert a piece of custom code into an SAP program, such as a BAdI, you want it to be as independent as possible so that this piece of custom code can be called virtually from anywhere.

For example, if I were to develop custom code to write back data to a model, I would try to make it independent. As long as I provide the environment ID, model ID, and data to be written into, the code should write the data to the model.

When people use CL_UJK_WRITE=>WRITE() in their programs as a way to write back data to a model, there is an implicit dependency on module_id. This module_id indicates where the data to be written back comes from (for example, the 1.2 million USD mentioned in the sidebar that was entered for year 2016 as budget). If it comes from an Excel input template, this value is ‘MAN’. If it comes from execution of a Data Manager package, the module_id is ‘DM’. If a user enters this 1.2 million through a journal entry, the module_id is ‘JRN’.

In a standard SAP program, CL_UJK_WRITE=>WRITE() is called from UJK. This can be used in BAdI UJ_CUSTOM_LOGIC, but it cannot be used in UJO_WRITE_BACK BADI.

The SAP system’s standard program to call CL_UJK_WRITE=>WRITE() is in the path shown in Figure 9.


Figure 9
The SAP system’s standard program call stack when calling CL_UJK_WRITE

The module_id is assigned in step 2 of Figure 9. This step is the constructor method, meaning it is called when the instance of the class is created. In line 33 in Figure 10, you can see the attribute g_module_id is assigned with a value from import parameter i_module_id.


Figure 10
CL_UJK_MODEL’s public attribute, MODULE_ID, gets its value from the import parameter i_module_id

The module ID as an import parameter is assigned to a static public attribute g_module_id of class CL_UJK_MODEL. That means it can be retrieved anywhere in any further steps in the program flow, such as steps 3, 4, and 5 in Figure 9. If this value is blank and it is checked in step 5 of Figure 9, for example, it causes problems.

If you are calling from UJKT (program ujk_script_logic_tester), you can see in Figures 11 and 12 that the module_id is hardcoded to ‘DM’. See line 42 of Figure 12, in which uj00_c_mod_name_dm is constant with the value ‘DM’. In Figure 11, line 176, this parameter i_module is given the value of this constant ‘DM’.


Figure 11
The value of UJ00_C_MOD_NAME_DM is assigned to parameter i_module.

The value for UJ00_C_MOD_NAME_DM is a constant with the value ‘DM’ in Figure 12. This means the data is from a Data Manager package.


Figure 12
The value for UJ00_C_MOD_NAME_DM

If you are calling from a Data Manager package, the program path is as shown in Figure 13.


Figure 13
Data Manager package program path

The following program flows are the same as in step 2 of Figure 13. Here in method CL_UJD_BPC_RUN_LOGIC->RUN(), the module_id is determined to be ‘DM’ as well. See line 184 in Figure 14.


Figure 14
The module_id is assigned with the value ‘DM’

Inside CL_UJO_WRITE_BACK->DO_WRITE_BACK(), the program flow is as shown in Figure 15.


Figure 15
Program flow in CL_UJO_WRITE_BACK->DO_WRITE_BACK()

When the program checks the work status (step 5 of Figure 15), it calls method check_work_status_locks() as shown in Figure 16.


Figure 16
Call check_work_status_locks() from check_work_status()

In this method, it checks the module_id (Figure 17).


Figure 17
Checking the module_id (called i_module here)

This method is indifferent to data coming from any module_id, as you can see from the code.

The SAP system’s standard program gets module_id (value ‘DM’), either from a UJKT test program or from a Data Manager package (value ‘DM’), and then calls CL_UJK_WRITE=>WRITE(). When the program comes to method CHECK_WORK_STAUTS_LOCKS(), it passes the section highlighted in Figure 16 without a problem.

If you have a custom code to call CL_UJK_WRITE=>WRITE() from BAdI UJ_CUSTOM_LOGIC, it should not be a problem as well, because BAdI UJ_CUSTOM_LOGIC is attached to CL_UJK_RUN_LOGIC. That means it goes through the same path as an SAP system’s standard program path. Thus, it gets the module_id (value ‘DM’) assigned as well.

However, if you want to call this custom code (with CL_UJK_WRITE=WRITE()) from write-back BAdI UJR_WRITE_BACK, it causes an exception in line 111 in the code in Figure 17.

In UJR_WRITE_BACK, the SAP system’s program passes module_id (value man in the code in Figure 17, represented by uj00_c_mod_name_man). However, when you call the custom code, it jumps to CL_UJK_WRITE without calling either UJK_SCRIPT_LOGIC_DISPATCH or UJK_SCRIPT_LOGIC_EXECUTE. Either of the two function modules are critical because they produce the module_id that is needed in the method check_work_status_locks().

If you call this custom code (with CL_UJK_WRITE=WRITE()) from any customized SAP system program, such as an ABAP program in a process chain, a standalone program via transaction code SE38, or a routine from an SAP BW transformation, it will not work because this module_id is an implicit dependency of method CL_UJK_WRITE=>WRITE().

Think of the application scenarios. In the custom code, if you can make the write-back piece completely independent, then the custom code can be called from anywhere. It can be called from BAdI UJ_CUSTOM_LOGIC via script logic. It can be called from write-back BAdI UJR_WRITE_BACK, from a process chain ABAP program, and from an SAP BW transformation routine if needed. The way to do this is you need to be able to send the module_id to this new flexible custom code as an import parameter so that you never miss this module id regardless of from where you call it. The way to do it is to follow SAP’s suggestion to use cl_ujo_write_back() with the write-back parameter is_wb_param instead of using cl_ujk_write.

Problems Caused by CL_UJK_WRITE=>WRITE() in a Complex Write-Back BAdI Implementation

In a BPC write-back program, the SAP system creates an instance of a write-back class (CL_UJO_WRITE_BACK) with a unique key date and module_name. If you have complex development in a write-back BAdI in which you plan to write different data to different models while you in are in the context of one particular model, you have two options:

  1. Create your own instance of a write-back class to write back.
  2. Use the SAP system’s instance of a write-back class to write back. However, be careful to make sure that each time you need to change the application ID (i.e., the model ID) to your desired target model ID to write data, you change the application ID back to the SAP system’s instance of the write-back class’s original model ID.

When you use option 1, you don’t enter a key date; thus, you are creating your own instance. When you use option 2, you enter the key date and call the SAP system’s instance. Many times you don’t intend to do option 2, but due to the use of CL_UJK_WRITE(), the program is forced to have a key date in place, and many developers are unaware of this.

Problem 1: Method CL_UJ0_IMPL_FACTORY=>CREATE_FACTORY. The method is shown in Figure 18.


Figure 18
Maintain instances of classes in hashed table gth_factory with a unique key module_name and keydate

Here the module_name is not the module_id that I mentioned above. Check the definition of the hashed table GTH_FACTORY in Figure 19. Line 12 shows the unique keys for this table type TYTH_FACTORY are module_name and keydate. GTH_factory is of type TYTH_FACTORY.


Figure 19
Definition of hashed table GTH_FACTORY

This means the method maintains a list of records in table GTH_FACTORY with a unique module_name and keydate. As far as write-back is concerned, this table is likely to have the contents shown in Table 1 if you pass the optional import parameter of keydate.

Module_name

keydate

O_factory

UJR

20161024

(an instance of CL_UJO_WRITE_BACK)

Table 1
Sample entries maintained in table GTH_FACTORY

Problem 2: The SAP system’s way of calling this method. When a user enters data in an input template and clicks the Save Data button in the EPM ribbon of Excel, the SAP system follows the program path shown in Figure 20.


Figure 20
Program flow when you write back data from an Excel input form

Here in step 1 the program is assigned the value of the key date. Check line 152 in Figure 21. It carries today’s date from a BPC context.


Figure 21
The key date (represented by import parameter i_keydate) is brought in when calling write_back_int()

Step 2 creates an instance with the date as shown in Figure 22. It creates an instance of cl_ujo_write_back with the key date (represented by keydate in line 73 in Figure 22) inherited from Figure 21.


Figure 22
Instance with the date

In step 3, the program calls the create_factory method() (Figure 23) as I mentioned earlier in this section. You need to check the GTH_FACTORY table to see if there already is such an instance of class cl_ujo_write_back.


Figure 23
Check the GTH_FACTORY table

The method create_factory() is to validate the following rule of singleton: If you have the module name and the key date, then you are going to use one and only one instance.

The logic is:

  • You create an instance of cl_ujo_write_back.
  • You call write_back with this instance.
  • The SAP system checks see if it has the instance already created with the same module name and key date. If yes, it uses the existing one. If not, it creates a new one and stores it in the GTH_FACTORY table.

Problem 3: The current situation with multiple write-back actions. Since the key date is an optional import parameter, I usually create a write-back instance as shown in Figure 24.

Data: lo_wb type ref to if_ujo_write_back.

Lo_wb = cl_ujo_wb_factory=>create_write_back( ).

Figure 24
A write-back instance

If I do write back to multiple models, my program has the flow shown in Figure 25.


Figure 25
An example program flow if you write data to multiple models

So at step 3, the GTH_FACTORY table looks like the records shown in Table 2.

Module_name

keydate

O_factory

UJR

20161024

(SAP system instance of CL_UJO_WRITE_BACK)

UJR

00000000

(my instance of CL_UJO_WRITE_BACK)

Table 2
The record maintained in table GTH_FACTORY

At step 4, the GTH_FACTORY table remains unchanged from step 3, because each time I create a write-back instance, I don’t provide a key date. The SAP system’s program reuses the second entry.

Problem 4: The attribute D_APPL_ID. There is an important class attribute for CL_UJO_WRITE_BACK called D_APPL_ID. I add this to Table 2 just to demonstrate an interesting result. This D_APPL_ID is an attribute of the class instance in column O_factory. D_APPL_ID is not in table GTH_FACTORY. Here for the sake of visibility, I attach a new column to Table 3 to show how this attribute is changed in the program flow. At step 2 of Figure 25, you write data to BPC model A. The class has attribute D_APPL_ID = ‘A’).

Module_name

keydate

O_factory

D_APPL_ID

UJR

20161024

(SAP system instance of CL_UJO_WRITE_BACK)

Model A

Table 3
The entry in table GTH_FACTORY

Because the user triggers a BAdI when inputting data for model A, this is what happens at step 3 of Figure 25 (Table 4). The BAdI creates an instance without a key date and starts to write data to model A. This activity generates another instance in table GTH_FACTORY because the key date is different (this time it is 00000000).

Module_name

keydate

O_factory

D_APPL_ID

UJR

20161024

(SAP system instance of CL_UJO_WRITE_BACK)

Model A

UJR

00000000

(SAP system instance of CL_UJO_WRITE_BACK)

Model A

Table 4
The key date is different

At step 4 of Figure 25, you write data to BPC model B. You create an instance without a key date. The factory method create_factory() checks table GTH_FACTORY and uses the existing instance (i.e., the second record in Table 5, the object in column O_factory). This time it changes the attribute value of d_appl_id of the second instance to ‘B’.

Module_name

keydate

O_factory

D_APPL_ID

UJR

20161024

(SAP system instance of CL_UJO_WRITE_BACK)

Model A

UJR

00000000

(SAP system instance of CL_UJO_WRITE_BACK)

Model B

Table 5
D_APPL_ID of second instance is changed from model A to model B

The D_APPL_ID is changed for the instance since I am writing to model B. At step 5, when I return, the SAP system’s program continues to work on the first entry to the end, with D_APPL_ID never being changed (Table 6). The D_APPL_ID of the first instance never changes. It is still A because this record has the unique key date of 20161024.

Module_name

keydate

O_factory

D_APPL_ID

UJR

20161024

(SAP instance of CL_UJO_WRITE_BACK)

Model A

Table 6
D_APPL_ID is still A

Therefore, this is never a problem if you always create the instance of cl_ujo_write_back without populating the key date. You can write to multiple models in the middle of the write-back BADI of one model because you are adding and working on an instance with the key date 00000000.

So the conclusion is:

a. The SAP system creates the first instance of CL_UJO_WRITE_BACK with the key date in Table 5.

b. You create your own, the second instance of CL_UJO-WRITE_BACK in table gth_factory without a key date. You will never have a problem.

c.If you ever change the appl_id of the first instance in table gth_factory you are touching the SAP instance of CL_UJO_WRITE_BACK. You must change it back to its original D_APPL_ID because, as shown in Figure 26 step 1, you are in model A. If you do step 2 in Figure 26, you must follow D_APPL_ID of step 1 of Figure 26.

d.. The only way to change or touch the SAP instance in the first line in table GTH_FACTORY is to create cl_ujo_write_back with a key date.

Remember this point d, as you will see it below.

Problem 5: Problem with CL_UJK_WRITE=>WRITE(). For integration purposes, say some developers used CL_UJK_WRITE=>WRITE() in their BAdI UJ_CUSTOM_LOGIC via the script logic. I am told to reuse their BAdI in the body of my write-back BAdI, as shown in Figure 26. Note the change in step 4.


Figure 26
Program flow: Call my write_back BADI in step 2 and then execute the other developer’s BAdI in step 4

This is causing a problem in an unwanted change of D_APPL_ID in the first line in table gth_factory. CL_UJK_WRITE=>WRITE is creating an instance of cl_ujo_write_back with the key date. So it touches the SAP instance (i.e., the first record in GTH_FACTORY). See line 101 in Figure 27.

From CL_UJK_WRITE=>WRITE(), line 101 you can see it creates an instance of cl_ujo_write_back with the key date.


Figure 27
Create an instance of CL_UJO_WRITE_BACK with key date at line 101

The key date is given when creating the instance of CL_UJO_WRITE_BACK. Here again, because of same module name and same key date, the SAP system does not create a new instance. It just returns with the first existing record (Table 7).

What is worse is that this is step 4 of Figure 26, and you are trying to write data to model B, so the D_APPL_ID of the first instance is changed from model A to model B.

Module_name

keydate

O_factory

D_APPL_ID

UJR

20161024

(SAP instance of CL_UJO_WRITE_BACK)

Model B

UJR

00000000

(SAP instance of CL_UJO_WRITE_BACK)

Model A

Table 7
D_APPL_ID of the first instance is changed from model A to model B

For the SAP system’s write-back’s instance, the D_APPL_ID is changed to BPC model B. This causes a problem because you are in a BAdI of the context of BPC model A. After the write-back BAdI, (after step 5 of Figure 26), you go back to the SAP system’s standard program in method package(). The method package() is expecting D_APPL_ID to be model A unchanged because you are in model A. You just want to write some data to model B, and come back to continue with the SAP system’s standard program.

In line 48 of Figure 28, the structure <ls_array_hash> is carrying the structure of model B because D_APPL_ID of this instance of CL_UJO_WRITE_BACK is changed to B by cl_ujk_write=>write in the other programmer’s BAdI. It never was restored to ‘A’, which is wrong. However, dt_dim_list still holds the dimension list of model A, which is true. If model A is different from model B in structure, and very likely that will happen, this causes an ABAP dump.


Figure 28
The conflict in method package() causes a dump

So in summary:

1. If the other developer’s code does not have CL_UJK_WRITE=>WRITE(), and instead he or you always create your own instance of CL_UJO_WRITE_BACK without the key date, there is no problem. The D_APPL_ID is changed only for the second entry in table GTH_FACTORY in Table 5. The SAP system’s method package only looks for D_APPL_ID of the first entry of Table 5. So there is no problem.

2. If the other developer’s code does not have CL_UJK_WRITE=>WRITE(), and instead he or you always create your own instance of CL_UJO_WRITE_BACK with a key date, then each time you write data, the D_APPL_ID is changed to the model to which the data is written. Then the solution is to change the sequence of writing (e.g., we are in model A) as shown in Figure 29.

a. You write to model A, and D_APPL_ID remains ‘A’ (i.e., no change).

b. You call the other developer’s BAdI to write to model B, and D_APPL_ID is changed from ‘A’ to ‘B’.

c. Finish. Return to the SAP system’s standard program, call method package().

d. You get the dt_dim_list of model A and <ls_array_hash> of model B. This causes a dump (Figure 28).

If you change the sequence

e. You call the other developer’s BAdI to write to model B, and D_APPL_ID is changed from ‘A’ to ‘B’.

f. You write to model A, and D_APPL_ID is changed from ‘B’ to ‘A’.

g. Finish. Return to the SAP standard program, and call method package().

h. You get the dt_dim_list of model A and <ls_array_hash> of model A. There is no problem. It is like what is shown in Figure 29.

3. If other developers use CL_UJK_WRITE=>WRITE() in their BAdIs, as shown in Figure 27, it is mandatory without any option for the developer to opt to skip the key date (i.e., the D_APPL_ID will be changed from ‘A’ to ‘B’ for sure). Then you don’t have a choice. You have to do two things:

a. Create a CL_UJO_WRITE_BACK instance with a key date so that you have a chance to change D_APPL_ID of the first record in table GTH_FACTORY.

b. Make sure you are the last one to change D_APPL_ID of that instance because the write-back is to write back to model A. Thus, the D_APPL_ID you are going to change to is ‘A’. Since you are in the context of model A, you do not have a problem in calling standard method package() after you finish all the write-backs.


Figure 29
Change the write-back sequence to ensure you write back to model A at last (step 4)

This is to ensure that I finally change D_APPL_ID back to A. Since GTH_FACTORY is a private attribute, I can’t change the D_APPL_ID of the instance in this table from the outside.

BPC BAdIs

A Business Add-In (BAdI) is an enhancement spot that SAP provides where you can insert custom logic. SAP provides a few BAdIs for SAP BusinessObjects Planning and Consolidation (BPC). The most important ones are UJ_CUSTOM_LOGIC BADI and UJR_WRITE_BACK BADI.

For example, without the UJR_WRITE_BACK BADI, the system cannot write a budget of 1.2 million USD to the year 2016, which is a parent of 12 months. You have to specify to which base member (here the month) you will post this amount. However, it is too much work for a user to enter the amount for each month. Rather, the user wants to enter a total amount for the year 2016, give an allocation logic, and let the system do this.

With help from UJR_WRITE_BACK BADI, the user can write data to the year 2016 and click the Save Data button in the EPM ribbon. The SAP system’s write-back program is triggered. Before the system tries to write data to the year 2016 (which will fail for sure), you have the UJR_WRITE_BACK BADI in place to process the data. You can write ABAP code to distribute the 1.2 million USD to the 12 months, following specific allocation rules. The allocation rule can be as simple as just to split the 1.2 million dollars evenly across the 12 months, or as complex as looking up the previous year’s sales record for each period to get the weight for each month.

A user usually executes a Data Manager package in a BPC Microsoft Excel interface to execute a business function. These business functions can be standard business functions, such as currency conversion or legal consolidation. They can also be customized functions, such as an allocation based on a user’s own special drivers—for example, a trend-based forecast. If it is simple custom logic, you can implement it in script logic and let the Data Manager package call the script. If it is complex custom logic, you have to insert a few lines of code in the script logic. The code calls a UJ_CUSTOM_LOGIC BADI in ABAP, with import parameters to accomplish the task, as shown in Figure 30.


Figure 30
How the UJ_CUSTOM_LOGIC BADI is triggered by a Data Manager package

An email has been sent to:





 

George Chen

George Chen is the owner of Lynk, Inc. An SAP consultant, he has worked on SAP Netweaver ABAP/HANA/BPC/BW/IP/TPM/CRM for the past 17 years. He specializes in process integration, performance tuning, and introducing advanced ABAP design patterns in development. He has worked with SAP America many times to conduct ABAP performance tuning and troubleshooting. His focuses are ABAP for HANA and ABAP OO in SAP NetWeaver.



COMMENTS

Please log in to post a comment.

No comments have been submitted on this article. Be the first to comment!


SAPinsider
FAQ