Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
What
are
the
For each node, several types of entities can be defined to form the specific business logic part of a
business object. For migrations, conversions, See also the S 5 stage method Electrolux!
Customers can enhance SAP business objects and also create their own simple
business objects in the customer namespace. The creation of business objects,
nodes, actions, determinations, queries, or validations is extensively supported by
BO Builder (BOB) y
wizard-driven tools and utilities.
BO Builder is available through SAP GUI and is released for customers with SAP
Business Suite EHP5 upwards
.
This tool targets experienced BOPF developers.
It also provides SAP Business Suite customers with a comprehensive feature set for
BO Builder for building BO-centered applications so that they can use it for their own development
y
eXperts (BOBX) projects.
BO Builder for eXperts is available through SAP GUI and is released for customers
with SAP Business Suite EHP6 upward.
With SAP Business Suite EHP7 a new Eclipse-based client application
(Eclipse-Based)
will be provided as part of an ABAP Development Tools installation. This set of
ABAP
tools runs on top of the Eclipse-based ABAP IDE and is aimed at supporting all y
Development Tools
kinds of BO-centric use cases: it allows both the creation of new BOPF-applications
for BOPF
and the enhancement of existing ones.
BO Builder Test
Starting from development environment, you can always test the current range of
Environment y
functions of each business object (or enhancement).
(BOBT)
Debugging on the business object entities level enables you to speed up the
troubleshooting process:
BO-Specific
You can set breakpoints for entities such as actions, determinations, and validations. y
Debugging
Furthermore, you have the option of setting watchpoints for certain activities on the
nodes (for example, to update a particular node attribute).
Integrated BO Test BOPF supports test automation and test-driven development. From the outset, you y
Infrastructure can apply ABAP unit tests to test isolated units of business objects, such as actions,
validations, or determinations. In addition, with the scenario testing function, you
can extend the tests to processes that consist of several steps.
NOTE
As a SAP customer, you can use the BO Builder (BOB), the BO Builder for eXperts (BOBX), or the Eclipse-
based client for the development of your own business objects. Here, you will benefit from
that interoperability is ensured between these development tools. So you can, for example, create
new business objects in the BOB tool and then edit them in another tool on.
James Wood
Last year, I began working on a project which was rolling out the new SAP Environment Health and Safety Management (EHSM)
module. This module utilizes some of the more cutting edge technologies in the ABAP space: Floorplan Manager, Web Dynpro ABAP,
Adobe Interactive Forms, and so on. In addition to these standard technologies, EHSM also utilizes a technology framework that I had
not encountered previously: the Business Object Processing Framework (or BOPF).
Whenever I started investigating the BOPF, the first place I went to look was naturally right here at the SDN. However, between SDN
and Google, I found very little information to go on when it comes to working with the BOPF. Indeed, about the only useful
documentation I found was an enhancement guide entitled BOPF Enhancement Workbench. What I was really looking for though was
an in-depth description of the architecture of the BOPF, its API, and most importantly, some examples demonstrating its usage. Short of
that, I was left to muddle my way through much of the SAP standard code until I began to understand how the different pieces fit
together.
After working with the technology for the better part of a year, I thought I would launch a blog series documenting my findings for
others who share a similar plight. This includes other EHSM developers as well as developers working in other new dimension modules
such as SAP Transportation Management (TM), etc. I hope you will find it useful.
• Design Time
• At design time, BOs are modeled using the BOPF Workbench tool (Transaction /BOBF/CONF_UI). This tool makes it possible to
model a BO’s nodes/attributes, behaviors, associations, and so on. If you’re comfortable with OOP concepts, then this will seem
vaguely familiar to modeling classes in the Class Builder tool. (Note: So far, it seems that this tool is locked down for customer use.
This implies that we cannot create new BOs of our own…yet. As per Thea Hillenbrand’s comments below, the BOPF has been opened
up for general customer use. This happened with SAP Business Suite EHP5 SP11 and SAP Business Suite EHP6 SP05. The related note
• Behind the scenes, the BO metadata is stored in such a way that it can be introspected and leveraged by runtime APIs.
• Customers can enhance existing BOs using the BOPF Enhancement Workbench tool (Transaction /BOBF/CUST_UI). Here, we have
the option of defining new nodes and attributes, defining additional behaviors, and so on. We’ll see how this works up close in an
Runtime
• At runtime, BOs are instantiated and controlled via a standard API defined using ABAP Objects classes.
• To some extent, much of this will feel similar to ABAP Object Services. However, as you’ll soon see, the BOPF affords us a lot more
power.
The figure below illustrates how these different pieces fit together within an application. As you can see, the BOPF architecture utilizes
a layered approach:
Consumer Layer
• At the consumer layer, we can utilize the BOPF API methods to create new BOs, search for existing BOs, update selected BOs, and so
on.
• Frequently, BOPF BOs will be consumed by UI applications such as WDA applications, etc.
• Of course, that’s not to say that generic consumers cannot get in on the fun as well.
Transaction Layer
• Interactions with BOs within the BOPF are brokered through a centralized transaction layer which handles low-level transaction
• From the perspective of the consumer layer, interactions with the transaction layer consist of little more than a handful of intuitive API
calls.
• As you can see in the figure, the BOPF runtime utilizes the BOPF model definitions created at design time as metadata for instantiating
Persistence Layer
• One of the nice things about the BOPF is that it is rather flexible at the persistence layer. Though the end goal is normally to store BO
data within the database, the framework also supports data buffering via shared memory as well as the definition of transient nodes and
GENIL, and BOL), it is quite a bit more evolved than any prior business object model defined by SAP. This will become obvious as we
Though I will not endeavor to sell anyone on the merits of the BOPF within this blog series, I think it is useful to compare and contrast
the scope of a development project with and without the BOPF. Then, you can decide for yourself if the BOPF provides value.
To put this in perspective, let’s imagine that we’re tasked with developing a new module around some custom-defined business entity. A
minimal bill of materials in terms of development objects for this module are as follows:
• The entity data will be stored within a series of ABAP Dictionary tables. If desired, we can use ABAP Object Services to create an
• In order to synchronize access to the entity data, we’ll need to define one or more lock objects. Plus, we’ll need a mechanism to ensure
that the lock objects are used to control access to the data.
• To secure access to the entity data, we must create one or more authorization objects so that we can define granular authorization
control.
Now, in an an ideal world, we would attempt to encapsulate access to the entity data by creating a series of ABAP Object classes (e.g.
entity classes and the like). These classes would offer basic CRUD (Create, Remove, Update, and Display) operations that provide
users with a one-stop shop for accessing and updating the entity data.
With these basic components in place, we can then build the core application functionality.
Here, we’ll find a number of producers/consumers of the entity data:
• UI applications based on WDA, BSP, or even classic Dynpro technology.
• Custom accessor modules used to supply data to home grown reports (e.g. ALV), Adobe Interactive Forms, and so on
• Interface and conversion programs (or, in SOA initiatives, Web service wrappers)
• Others as needed
Overall, these kinds of development objects are pretty standard fare for the average ABAP developer. However, within the context of
your typical SAP project, these tasks are often distributed across a large development team. Here, it can be difficult to enforce best
practices and ensure that each developer accesses the entity data properly. Frequently, this is as a result of developers not understanding
how to access the data via a custom API (provided there is one). In short, there’s no overarching object model which ensures that
It is here that the BOPF shines. Within the BOPF, everything has its place. Business data is modeled consistently in BO nodes and
attributes. Behaviors are defined as actions. Validations are performed automatically via validation modules. Triggers can be defined
using determinations. The relationships between BOs is defined statically via associations. Once a developer becomes comfortable with
the framework, the BOPF takes all of the guessing out of business object development. The BO encapsulates all of the functionality and
In time, this sort of consistency can give rise to additional frameworks which sit on top of the BOPF. An example of this is
the Floorplan Manager BOPF Integration (FBI) framework which simplifies the way that FPM feeder classes access BOPF nodes.
Much of this will become clearer as we move on. For now, suffice it so say that the BOPF provides us with a tremendous springboard
Congratulations on this excellent series on BOPF. We feel a bit ashamed not having delivered these informations by our own.
BOPF was an internal tool to ease and govern our application development. Last year we saw an increasing interest from partners and
customers and we decided to open it also for customer usage. Not the /BOPF/CONF_UI transaction – good enough for internal usage –
but the enriched enhancement workbench which we renamed to Business Object Builder (transaction BOB). This transaction can be
This happend with SAP Business Suite EHP5 SP11 and SAP Business Suite EHP6 SP05. The related note is 1760610.
Regards Thea
In my previous blog post, I briefly introduced the BOPF framework and its positioning within the ABAP
development landscape. With that information in tow, we’re now ready to begin peeling back the layers of
the BOPF and seeing how all the pieces fit together from a technical perspective. In this blog post, we’ll get
things started by taking a look at the design time aspects of business objects.
representation of a type of uniquely identifiable business entity described by a structural model and an
•Have a well-defined process model which governs the business object lifecycle, behaviors, etc.
•Execute within a container-like environment which handles low-level tasks such as caching, transaction
In this regard, BOs in the BOPF are not unlike objects developed in other component architectures (e.g. EJBs
Nodes
•Nodes are used to model a BO’s data.
•Nodes are arranged hierarchically to model the various dimensions of the BO data. This hierarchy is
organized underneath a single root node (much like XML). From there, the hierarchy can be nested
•There are several different node types supported by the BOPF. However, most of the time you’ll find
yourself working with persistent nodes (e.g. nodes which are backed by the database). It is also possible to
define transient nodes whose contents are loaded on demand at runtime. These types of nodes can come in
handy whenever we want to bridge some alternative persistence model (e.g. data obtained via service
calls).
•Each node consists of one or more attributes which describe the type of data stored within the node:
•Attributes come in two distinct varieties: persistent attributes and transient attributes. Persistent attributes
represent those attributes that will be persisted whenever the BO is saved. Transient attributes are volatile
•A node’s attributes are defined in terms of structure definitions from the ABAP Dictionary.
•At runtime, a BO node is like a container which may have zero, one, or many rows. If you’re familiar with
the concept of controller contexts with the Web Dynpro programming model, then this concept should feel
familiar to you. If not, don’t worry; we’ll demonstrate how this works whenever we look at the BOPF API.
Actions
•Actions define the services (or behavior) of a BO.
•The functionality provided by an action is (usually) defined in terms of an ABAP Objects class that
implements the /BOBF/IF_FRW_ACTION interface.
•To some extent, it is appropriate to think of actions as being similar to the methods of an ABAP Objects
class.
Associations
•Though BOs are designed to be self-contained, autonomous entities, they do not have to exist in isolation.
With associations, we can define a direct and unidirectional relationship from one BO to another.
•For example, in just a moment, we’ll take a look at a sample BO called /BOBF/DEMO_SALES_ORDER which is
used to model sales orders. Here, we’ll see how the product assignments for sales order items is defined in
terms of an association with a product BO called /BOBF/DEMO_PRODUCT. This composition technique makes it
possible to not only leverage the product BOs data model, but also its behaviors, etc.
•Associations allow us to integrate BOs together in complex assemblies à la Legos™.
to a business object node that describes internal changing business logic on the business
object”.
•In some respects, determinations are analogous to database triggers. In other words, they are functions
that are triggered whenever certain triggering conditions are fulfilled. These conditions are described in
•This pattern allows us to react to changes made to a given BO node. For example, we might use this event
•This pattern allows us to hang some custom logic on a given BO node before it is saved. This could be as
simple as using a number range object to assign an ID value to a node attribute or as complex as triggering
an interface.
•This pattern is often used in conjunction with UI development. Here, we might want to load labels and
•Note: This determination can be bypassed via the API if the lookup process introduces unnecessary
overhead.
•This pattern allows us to load transient nodes into memory on demand. Here, for example, we might
lookup real-time status data from a Web service and load it into the attributes of a transient node from
downstream consumption.
•Determination patterns are described in detail within the aforementioned BOPF enhancement guide.
•The logic within a determination is defined via an ABAP Objects class that implements
the /BOBF/IF_FRW_DETERMINATION interface.
Validations
•According to the BOPF enhancement guide, validations are “an element of a business object node that
•Action Validations
•Action validations are used to determine whether or not a particular action can be executed against a BO
node.
•Consistency Validations
•As the name suggests, consistency validations are used to ensure that a BO node is consistent. Such
validations are called at pre-defined points within the BOPF BO transaction cycle to ensure that BO nodes
•The validation logic is encapsulated within an ABAP Objects class that implements
the /BOBF/IF_FRW_VALIDATION interface.
Queries
•Queries are BO node entities which allow us to search for BOs using various types of search
criteria.
•Queries make it possible for consumers to access BOs without knowing the BO key up front.
•Queries also integrate quite nicely with search frameworks and the like.
•Node attribute queries are modeled queries whose logic is defined within the BOPF runtime. These simple
queries can be used whenever you simply need to search for BO nodes by their attributes (e.g. ID =
‘12345’).
• Custom Queries
•Custom queries allow you define your own query logic by plugging in an ABAP Objects class that
implements the /BOBF/IF_FRW_QUERY interface.
The figure below illustrates how all of these entities fit together within a BO node definition. Here, I’ve
pulled up a BO called /BOBF/DEMO_SALES_ORDER in Transaction /BOBF/CONF_UI. Here, the BO metadata is
organized into several different panels:
•On the top left-hand side of the screen, you can see the BO’s node structure. Here, you can see that the
node structure is organized underneath a top-level ROOT node which models sales order header data.
Underneath this node are several child nodes which model sales order items, customer assignment, and
texts. The ITEM node in turn encompasses its own child nodes to model item-level data.
•On the bottom left-hand side of the screen, we can browse through the node collection of a BO and view
the entity assignments of a given node. As you can see in the figure, each node contains folders which
tcodes
BOB
BOPF config
BOBT testing
BOBX config
We’ll have an opportunity to get a little more hands on with these entities in upcoming blog entries. For
now, our focus is on grasping how pieces fit together and where to go to find the information we need to get
Next Steps
At this point, you should have a decent feel for how BOs are modeled at design time. In my next blog, we’ll
shift gears and begin manipulating BOs using the provided BOPF APIs. This will help put all of these entities
into perspective.
Queries
are often misused at runtime, therefore just some small addition:Oliver Jaegle
It’s (almost) true that Queries identify a set of instance of the node at which they are modeled, but: (as per contract) they only return
persisted data!
As during transactional processing it can happen, that instances which shall be found have just been created during the session, queries
should not be used while implementing transactional entities (such as determination, actions, validations).
If a collection of instances has to be identified which can’t be resolved via associations, alternative keys allow a translation of
Now you are wondering how a key can translate to a collection of keys? The answer is in the non-uniqueness of the key. E. g. a
purchasing group can be thought of identifying a set of purchasing documents. It thus acts as a non-unique alternative key.
Cheers,
Oliver
P.s.:Excellent post, James! It’s quite tricky to publish good non-redundant-content about BOPF after your series �
I have some difficulties regarding the following statements, could you clarifiy these a bit?
Thanks for mentioning the non-unique alternative key to build up groups. So is it recommended to create groups by using the same
GUID for an alternative group-key? or should the alt-key strictly be semantical in contrast to a purely technical GUID?
Cheers, Kai
queries return a set of node rows for the node they’re defined against.
1.James Wood Post author
Hi Kai,
Regarding the first statement, what he’s saying is that queries return a set of node rows for the node
they’re defined against. Here, you have to distinguish between the design time view of the BO model vs.
the runtime view. At runtime, you can think of nodes as being rather like internal tables/containers which
contain 0..n node rows. Queries fetch the node rows which match up with whatever query selection criteria
specified.
The second point refers to the fact that queries are always performed against persistent data (i.e. node
rows that have been persisted to the system database). With that in mind, he’s saying that you wouldn’t
want to use a query to fetch the current state of a node within a transactional entity such as a
determination or action. Why? Because there may be node rows existing in memory that wouldn’t be
accounted for in the query selection. For these kinds of tasks, we must use the BOPF API to fetch the
complete set of node rows (in memory + persisted) and then filter the result set as needed based on our
business requirements.
ABAP Development
abapbopffloorplan managerweb dynpro abap
w
2.In my previous blog post, we explored the anatomy of business objects within the BOPF. There, we were
able to observe the various entities that make up a BO: nodes/attributes, actions, associations,
determinations, validations, and queries. Now that you have a feel for what these entities are, we’re ready
to begin taking a look at the API that is used to manipulate these entities. To guide us through this
demonstration, we’ll explore the construction of a simple ABAP report program used to perform CRUD
operations on a sample BOPF BO shipped by default by SAP: /BOBF/DEMO_CUSTOMER. You can download the
complete example program source code here.
Note: The code bundle described above has been enhanced as of 9/18/2013. The code was reworked to factor out a BOPF utilities class
of sorts and also demonstrate how to traverse over to dependent objects (DOs).
of the main classes that make up the BOPF API. At the end of the day, there are three main objects that we’ll be working with to
• /BOBF/IF_TRA_TRANSACTION_MGR
• This object reference provides a transaction manager which can be used to manage transactional changes. Such transactions could
contain a single step (e.g. update node X) or be strung out across multiple steps (add a node, call an action, and so on).
• /BOBF/IF_TRA_SERVICE_MANAGER
• The service manager object reference provides us with the methods we need to lookup BO nodes, update BO nodes, trigger validations,
• /BOBF/IF_FRW_CONFIGURATION
• This object reference provides us with metadata for a particular BO. We’ll explore the utility of having access to this metadata coming
up shortly.
In the upcoming sections, I’ll show you how these various API classes collaborate in typical BOPF use cases. Along the way, we’ll
encounter other useful classes that can be used to perform specific tasks. You can find a complete class listing within
package /BOBF/MAIN.
Note: As you’ll soon see, the BOPF API is extremely generic in nature. While this provides tremendous flexibility, it also adds a certain
amount of tedium to common tasks. Thus, in many applications, you may find that SAP has elected to wrap the API up in another API
that is more convenient to work with. For example, in the SAP EHSM solution, SAP defines an “Easy Node Access” API which
simplfies the way that developers traverse BO nodes, perform updates, and so on.
To demonstrate the BOPF API, we’ll build a custom report program which performs basic CRUD operations on a sample BO provided
by SAP: /BOBF/DEMO_CUSTOMER. The figure below shows the makeup of this BO in Transaction /BOBF/CONF_UI.
Our sample program provides a basic UI as shown below. Here, users have the option of creating, changing, and displaying a particular
customer using its ID number. Sort of a simplified Transaction XK01-XK03 if you will.
Getting Started
To drive the application functionality, we’ll create a local test driver class called LCL_DEMO. As you can see in the code excerpt below,
this test driver class loads the core BOPF API objects at setup whenever the CONSTRUCTOR method is invoked. Here, the factory
classes illustrated in the UML class diagram shown in the previous section are used to load the various object references.
CLASS lcl_demo DEFINITION CREATE PRIVATE.
PRIVATE SECTION.
DATA mo_txn_mngr TYPE REF TO /bobf/if_tra_transaction_mgr.
DATA mo_svc_mngr TYPE REF TO /bobf/if_tra_service_manager.
DATA mo_bo_conf TYPE REF TO /bobf/if_frw_configuration.
METHODS:
constructor RAISING /bobf/cx_frw.
ENDCLASS.
me->mo_bo_conf =
/bobf/cl_frw_factory=>get_configuration(
/bobf/if_demo_customer_c=>sc_bo_key ).
ENDCLASS.
For the most part, this should seem fairly straightforward. However, you might be wondering where I came up with
the IV_BO_KEY parameter in the GET_SERVICE_MANAGER() and GET_CONFIGURATION()factory method calls. This value is
provided to us via the BO’s constants interface (/BOBF/IF_DEMO_CUSTOMER_C in this case) which can be found within the BO
configuration in Transaction /BOBF/CONF_UI (see below). This auto-generated constants interface provides us with a convenient
mechanism for addressing a BO’s key, its defined nodes, associations, queries, and so on. We’ll end up using this interface quite a bit
during the course of our development.
Creating New Customers
Once we have the basic framework in place, we are ready to commence with the development of the various CRUD operations that
our application will support. To get things started, we’ll take a look at the creation of a new customer instance. For the most part, this
involves little more than a call to the MODIFY() method of the /BOBF/IF_TRA_SERVICE_MANAGER object reference. Of course, as
you can see in the code excerpt below, there is a fair amount of setup that we must do before we can call this method.
CLASS lcl_demo DEFINITION CREATE PRIVATE.
PUBLIC SECTION.
CLASS-METHODS:
create_customer IMPORTING iv_customer_id
TYPE /bobf/demo_customer_id.
...
ENDCLASS.
CLASS lcl_demo IMPLEMENTATION.
METHOD create_customer.
"Method-Local Data Declarations:
DATA lo_driver TYPE REF TO lcl_demo.
DATA lt_mod TYPE /bobf/t_frw_modification.
DATA lo_change TYPE REF TO /bobf/if_tra_change.
DATA lo_message TYPE REF TO /bobf/if_frw_message.
DATA lv_rejected TYPE boole_d.
DATA lx_bopf_ex TYPE REF TO /bobf/cx_frw.
DATA lv_err_msg TYPE string.
FIELD-SYMBOLS:
<ls_mod> LIKE LINE OF lt_mod.
IF lv_rejected EQ abap_true.
lo_driver->display_messages( lo_message ).
RETURN.
ENDIF.
"If we get to here, then the operation was successful:
WRITE: / 'Customer', iv_customer_id, 'created successfully.'.
CATCH /bobf/cx_frw INTO lx_bopf_ex.
lv_err_msg = lx_bopf_ex->get_text( ).
WRITE: / lv_err_msg.
ENDTRY.
ENDMETHOD. " METHOD create_customer
ENDCLASS.
As you can see in the code excerpt above, the majority of the code is devoted to building a table which is passed in
the IT_MODIFICATION parameter of the MODIFY() method. Here, a separate record is created for each node row that is being
modified (or inserted in this case). This record contains information such as the node object key (NODE), the edit mode
(CHANGE_MODE), the row key (KEY) which is an auto-generated GUID, association/parent key information, and of course, the actual
data (DATA). If you’ve ever worked with ALE IDocs, then this will probably feel vaguely familiar.
Looking more closely at the population of the node row data, you can see that we’re working with data references which are created
dynamically using the CREATE DATA statement. This indirection is necessary since the BOPF API is generic in nature. You can find the
structure definitions for each node by double-clicking on the node in Transaction /BOBF/CONF_UI and looking at the Combined
Structure field (see below).
Once the modification table is filled out, we can call the MODIFY() method to insert the record(s). Assuming all is successful, we can
then commit the transaction by calling the SAVE() method on the /BOBF/IF_TRA_TRANSACTION_MANAGER instance. Should any
errors occur, we can display the error messages using methods of the /BOBF/IF_FRW_MESSAGE object reference which is returned
from both methods. This is evidenced by the simple utility method DISPLAY_MESSAGES() shown below. That’s pretty much all there
is to it.
CLASS lcl_demo DEFINITION CREATE PRIVATE.
PRIVATE SECTION.
METHODS:
display_messages IMPORTING io_message
TYPE REF TO /bobf/if_frw_message.
ENDCLASS.
"Sanity check:
CHECK io_message IS BOUND.
If you look closely at the customer creation code illustrated in the previous section, you can see that each node row is keyed by an auto-
generated GUID of type /BOBF/CONF_KEY (see below). Since most users don’t happen to have 32-character hex strings memorized,
we typically have to resort to queries if we want to find particular BO instances. For example, in our customer demo program, we want
to provide a way for users to lookup customers using their customer ID value. Of course, we could have just as easily defined an
alternative query selection to pull the customer records.
As we learned in the previous blog post, most BOs come with one or more queries which allow us to search for BOs according to
various node criteria. In the case of the /BOBF/DEMO_CUSTOMER business object, we want to use the SELECT_BY_ATTRIBUTES query
attached to the ROOT node (see below). This allows us to lookup customers by their ID value.
The code excerpt below shows how we defined our query in a method called GET_CUSTOMER_FOR_ID(). As you can see, the query is
executed by calling the aptly named QUERY() method of the /BOBF/IF_TRA_SERVICE_MANAGER instance. The query parameters are
provided in the form of an internal table of type /BOBF/T_FRW_QUERY_SELPARAM. This table type has a similar look and feel to a
range table or SELECT-OPTION. The results of the query are returned in a table of type /BOBF/T_FRW_KEY. This table contains the
keys of the node rows that matched the query parameters. In our sample case, there should be only one match, so we simply return the
first key in the list.
CLASS lcl_demo DEFINITION CREATE PRIVATE.
PRIVATE SECTION.
METHODS:
get_customer_for_id IMPORTING iv_customer_id
TYPE /bobf/demo_customer_id
RETURNING VALUE(rv_customer_key)
TYPE /bobf/conf_key
RAISING /bobf/cx_frw.
ENDCLASS.
With the query logic now in place, we now know which customer record to lookup. The question is, how do we retrieve it? For this
PRIVATE SECTION.
METHODS:
get_node_table IMPORTING iv_key TYPE /bobf/conf_key
iv_node_key TYPE /bobf/obm_node_key
iv_edit_mode TYPE /bobf/conf_edit_mode
DEFAULT /bobf/if_conf_c=>sc_edit_read_only
RETURNING VALUE(rr_data) TYPE REF TO data
RAISING /bobf/cx_frw,
METHOD get_node_table.
"Method-Local Data Declarations:
DATA lt_key TYPE /bobf/t_frw_key.
DATA ls_node_conf TYPE /bobf/s_confro_node.
DATA lo_change TYPE REF TO /bobf/if_tra_change.
DATA lo_message TYPE REF TO /bobf/if_frw_message.
METHOD get_node_row.
"Method-Local Data Declarations:
DATA lr_t_data TYPE REF TO data.
METHOD get_node_table_by_assoc.
"Method-Local Data Declarations:
DATA lt_key TYPE /bobf/t_frw_key.
DATA ls_node_conf TYPE /bobf/s_confro_node.
DATA ls_association TYPE /bobf/s_confro_assoc.
DATA lo_change TYPE REF TO /bobf/if_tra_change.
DATA lo_message TYPE REF TO /bobf/if_frw_message.
METHOD get_node_row_by_assoc.
"Method-Local Data Declarations:
DATA lr_t_data TYPE REF TO data.
Note: In this simple example, we didn’t bother to drill down to display the contents of the ROOT_LONG_TEXT node. However, if we had
wanted to do so, we would have needed to create a separate service manager instance for
the /BOBF/DEMO_TEXT_COLLECTION business object since the data within that node is defined by that delegated BO as opposed to
the /BOBF/DEMO_CUSTOMER BO. Otherwise, the process is the same.
Modifying Customer Records
The process of modifying a customer record essentially combines logic from the display and create functions. The basic process is as
follows:
2. Next, we use the RETRIEVE*() methods to retrieve the node rows we wish to modify. Using the returned structure references, we can
modify the target attributes using simple assignment statements.
3. Finally, we collect the node row changes into the modification table that is fed into MODIFY()method provided by
the /BOBF/IF_TRA_SERVICE_MANAGER instance.
The code excerpt below shows how the changes are carried out. Here, we’re simply updating the address string on the customer. Of
FIELD-SYMBOLS:
<ls_mod> LIKE LINE OF lt_mod.
IF lv_rejected EQ abap_true.
lo_driver->display_messages( lo_message ).
RETURN.
ENDIF.
I often find that the best way to learn a technology framework is to see how it plays out via code. At this level, we can clearly visualize
the relationships between the various entities and see how they perform at runtime. Hopefully after reading this post, you should have a
better understanding of how all the BOPF pieces fit together. In my next blog post, we’ll expand upon what we’ve learned and consider
51 Comments
like (0)
http://www.bowdark.com/downloads/BOPFDemoProgram.zip
Thanks.
like (0)
1. SB
James,
I have downloaded the full program. Neither the full program nor the blog post have the logic to read the
long text from node ROOT_LONG_TEXT. Could you please share that logic if you have so.
Thanks
like (0)
1. James Wood Post author
Ah, I missed the long text part. Please find an updated version of the code at the download link above. I
made some wholesale changes to the code so that it’s cleaner and more organized. Alas, the original demo
was rather primitive. Let me know if you have any further questions. Thanks.
like (0)
1. SB
James,
like (0)
2. Eric Peterson
Hi James,
This blog seems very helpful, so I’d like to start at the beginning of the series. Unfortunately the link in your
Thanks,
Eric
like (0)
1. Manish Kumar
It looks like the blogs were originally posted in personal space, and then moved to ABAP Development
space.
like (0)
3. Andrew Hampton
Hi James
I have a question though. I have a requirement for my client for a monster “super query” against the
EHHSS_INCIDENT business object which basically would allow them to query all Incidents by any
I have already told them I think this is out of the question. However I want to at least be able to give them
something. My main challenge is that it seems that Queries are attached to nodes, but what I really want is
a cross-node query.
For example there is one node called “Persons involved” (table EHHSSD_INC_PINV) and another node called
“Person Role” (table EHHSSD_INC_PROLE). This would allow me to query the persons involved in an
Incident (selecting by John Smith for example) or the roles of people involved in an incident (eg Witness).
But what it does not allow me to do is to query the Incidents where John Smith is a Witness. To do that I
have to use a foreign key relationship in a GUID on EHHSSD_INC_PROLE to point to the DB_KEY on
EHHSSD_INC_PINV.
So my main question is: Is it possible to do cross-node queries? If so how?
I thought about creating a database view as a join of the two tables, but then I don’t know how to hook this
database view onto the BO processing framework and how to attach queries to it. Or is the way of having a
transient structure which is a join of the two tables & somehow hook this into database retrieval & queries.
Thanks
Andy
like (0)
To answer your question: yes. For your specific use case(s), I think the approach would be to create custom
queries. So, for example, if you want to create a query in which you pull all incidents where John Smith is a
witness, I think you’d simply want to create a custom query against the ROOT node. Here, you’d have a
data structure to capture the selection criteria (e.g. person name and role) and a custom class to implement
the query logic using a SQL JOIN. A decent example of this would be the SELECT_BY_OSHA_CRIT query
Thanks,
James
like (0)
1. Andrew Hampton
Thanks James, good to know it is possible. I’ll be giving it a try over the next few days.
like (0)
4. Zhenbo Wang
Great article, thanks. I am just beginning to like BOPF.
like (0)
5. Shubhada Gore
Hi James,
Thank you for publishing such an informative blog series to understand the BOPF.
I have passed ROOT node and BI_CHEMICAL nodes. But I am getting error ‘Mandatory node ROLE is
missing’.
When I tried to add ROLE node , in the code its giving errors like ‘Mandatory node ROLE is missing’ OR can
I went through the node structure for the BO. In it we have Root – > Revision -> Role.
So my query is how to pass information for ROLE node? We need to add REVISION node also?
I have added REVISION node as below but still getting error ‘Mandetory node ROLE missing’.
lr_s_revision->key = /bobf/cl_frw_factory=>get_new_key( ).
lr_s_revision->root_KEY = lr_s_root->key.
<ls_mod>-node = IF_EHFND_CHM_C=>sc_node-revision.
<ls_mod>-change_mode = /bobf/if_frw_c=>sc_modify_create.
<ls_mod>-source_node = IF_EHFND_CHM_C=>sc_node-root.
<ls_mod>-association =
IF_EHFND_CHM_C=>sc_association-root-revision.
<ls_mod>-source_key = lr_s_root->key.
<ls_mod>-key = lr_s_revision->key.
<ls_mod>-data = lr_s_revision.
lr_s_role->key = /bobf/cl_frw_factory=>get_new_key( ).
lr_s_role->PARENT_KEY = lr_s_revision->key.
lr_s_role->ROOT_KEY = lr_s_root->key.
lr_s_role->chemical_role = ‘1’.
<ls_mod>-node = if_ehfnd_chm_c=>sc_node-role.
<ls_mod>-change_mode = /bobf/if_frw_c=>sc_modify_create.
<ls_mod>-source_node = if_ehfnd_chm_c=>sc_node-revision.
<ls_mod>-association =
if_ehfnd_chm_c=>sc_association-revision-role.
<ls_mod>-source_key = lr_s_revision->key.
<ls_mod>-root_key = lr_s_root->key.
<ls_mod>-key = lr_s_role->key.
<ls_mod>-data = lr_s_role.
like (0)
1. James Wood Post author
Hi Shubhada,
What version of EHSM are you on? I’m looking at an EHSM 3.0 system with SP 4 installed and I don’t see a
REVISION or ROLE node available in EHFND_CHEMICAL. These types of nodes are not uncommon to master
data objects, so it wouldn’t surprise me that they were added in a later release of the software. However, as
I can’t see the nodes myself, it’s hard to speculate what the error condition might be. At a glance, your code
One thing I might suggest is to look closely at the contents of EO_MESSAGE after you attempt to modify
and/or save the BO. Here, I’d recommend scanning through the MT_MESSAGE table to find the error
message in question and see if the NODE_KEY/VAL_KEY fields are populated. This might give you more of a
clue about where the error condition is emanating from. Hope this helps.
Thanks,
James
like (0)
1. Shubhada Gore
Hi James,
Thank you.
like (0)
6. Surender reddy
Very good in detail information for techies.
Regards,
Surender reddy
like (0)
7. Bob Varghese
Hi James,
Regards,
Bob.
like (0)
8. Paul Hardy
Mr.James,
Here is a very technical question about the mechanism whereby a number (like a customer number) gets
When I debug the SERVICE_MANAGER->QUERY method I see that a fuly dynamic SQL statement is being
built up and then the database table queried using the customer number.
As there is no index on custmer number I would expect a full table scan to occur, and the performance to be
dreadful. Yet this does not seem to be the case – performance is OK and the ST05 trace did not say “full
table scan” but some Oracle gobbledegook I had not seen before.
Is there some black magic at work here to get around the fact you are selecting on a field where there is no
index?
Cheersy Cheers
Paul
like (0)
So am I correct in assuming that you’re testing with the /BOBF/DEMO_CUSTOMER business object
demonstrated in this blog post? If so, I’m seeing that SAP has in fact created an index on the CUSTOMER_ID
field in the table behind the ROOT node (/BOBF/DM_CST_HDR). Are you seeing something different on your
end?
In general, I would say that there’s nothing real special going on with these node attribute queries. When
you get past all of the dynamic code, the SQL queries executed by the BOPF runtime are basically OpenSQL
as per usual.
Anyway, I hope this helps. If I’m off base with my analysis here, let me know a little more details about what
Thanks,
James
like (0)
1. Paul Hardy
As a test I had created my own object with a “number” field which is what the human in front of the
computer would use to search for the object. An invoice number let us say.
As the primary key was the GUID and I deliberately did not put an index on the “number” then I expected
in the table until it finds a match and then stops, which is in effect a full table scan.
I was just wondering if there was anything magic happening here, but it seems not.
This does throw into question the entire wisdom of having a database table with a GUID as the primary key
– if you have an object where people are always going to search by number – invoices are a great example –
then isn’t having two indexes – the primary key and the index on the number – just a doubling up of
resources?
I know in HANA world this is not going to matter, but realistically most people are not going to be there any
time soon.
Cheersy Cheers
Paul
like (0)
1. James Wood Post author
Hi Paul,
OK, I’m with you now. You make a good point here on the wisdom of using GUIDs vs. semantic keys. In
standalone environments, I frequently find this approach to be painful to work with (CRM comes to mind). In
the dynamic world of the BOPF though, I think that the choice to use GUIDs actually makes a lot of sense.
Being able to traverse from node to node by joining on PARENT.DB_KEY = CHILD.PARENT_KEY makes it very
easy to build generic BO query frameworks where the performance is actually quite good. The primary
overhead is when you hit the header table which would normally require an index on the semantic key. I
suppose anytime you build a framework like this, there’s going to be some overhead, but in my mind, what
Thanks,
James
like (0)
9. Pankaj Lal
Hi James
I am new to BOPF, and trying to set up a condition in TM using BOPF. Standard SAP provide a BO
/SCMTMS/SUPPLIER which is a Master data Object. I can read a Carrier in run time using this. There is
another BO /SCMTMS/TOR, where I can read the data from a freight order for example Customer (in
ConsigneeID field).
So when in SAP TM, I select a carrier to be assigned to a Freight Order, I can read Carrier separately using
/SCMTMS/SUPPLIER and Customer from the FO separately under /SCMTMS/TOR. Once the carrier is assigned
and FO is saved, I can read the carrier under /SCMTMS/TOR as well under TSP_ID but before that TSP_ID is
blank.
My requirement is to read the “carrier to be assigned” under /SCMTMS/TOR before FO is saved, so that I
can check a condition between customer and carrier before saving it. In other words I want to read the
Partner value of /SCMTMS/SUPPLIER (Master data Object) in /SCMTMS/TOR in Business Process Object. Is
this feasible? How to achieve this. Looking forward for your response. Thanks for the Help.
Regards
Pankaj
like (0)
I haven’t worked with TM before, nor do I have access to a TM system, so I can only speculate on some of
hierarchy?
If my assumptions above are correct, I expect that you should be able to back track from the XBO
association class to figure out how the two BOs are physically linked. If the carrier’s being identified before
the save event occurs, I’d expect that you’d be able to get your hands on the foreign key to the carrier
somewhere inside the /SCMTMS/TOR BO. From here, you may need to enhance/create a determination to
preemptively fill the TSP_ID field using a SQL query based on the selected carrier key.
Again, I’m sort of flying blind here, so let me know if I’m off base or need further clarification. Hope this
helps.
Thanks,
James
like (0)
3. Pankaj Lal
Hi James,
For the ease of understanding you can consider Freight order as a Sales Order and Carrier as a TSP
(transport service provider partner which is not yet enter in the SO) and consignee as a ship to party..
Suppose you have saved the sales order with out the partner.. So you can see the Order number in VBAK
table, you can see the Ship to party also in VBPA. But Since the carrier partner TSP is not yet assigned in the
SO, it will be not be there in VBPA, although it exists as a master data in LFA1 table.
Now Consider LFA1 as /SCMTMS/SUPPLIER BO which is just a master data, and VBAK/PA as /SCMTMS/TOR BO
which is the transaction data. When I pass the SO number in VBPA table, I can read ship to party, and when
I pass the carrier number in LFA1, I can read the Carrier number from there.
Similarly I am using a data access determination using /SCMTMS/SUPPLIER (~LFA1) in a condition Cond1
and dad using /SCMTMS/TOR (~VBPA) in a condition Con2, when I pass the Carrier in cond1, I can read the
carrier there and when I pass the freight order number in cond2, I can read ship to (consignee) in cond2, but
since they are read in two different condition, I am not able to do some logical operations on them..
So I want to read both (Carrier to be assigned) and the Consignee under one condition. To do so I am trying
to create a dad for /SCMTMS/SUPPLIER, and dad for /SCMTMS/TOR under same condition. Technically its not
possible to so I am trying to read the /SCMTMS/SUPPLIER in /SCMTMS/TOR using some association and
data crawler.
The TSP_ID field stores the carrier for a freight order and is directly under the ROOT node of TOR BO. But it
can be read only once the Carrier is entered and freight order is saved with it.
/SCMTMS/SUPPLIER is a master data BO, and stores the Carrier under ROOT node in Partner field.
I tried to find an association in Trxn BOPF for /SCMTMS/TOR and I could find an association named
BO_TSP_ROOT, But I am not sure if it links with /SCMTMS/SUPPLIER or not, don’t know how to check it.
I am looking for your help to find out more insight about association, and how to see what how two BO
nodes associate.. and In case there in no association, is there any mechanism to read the Master data from
a Master data node into the Business process Object in run time?
Sorry for such a lengthy post, I appreciate your help and patiently helping me out here.
like (0)
1. James Wood Post author
Hi Pankaj,
This all makes logical sense. One question though: in your description above you mention that the carrier is
not yet assigned to the freight order. Assuming that’s the case, I’m curious to understand when the
condition you’re building is supposed to fire? Am I correct in assuming that you want this to start kicking in
at the point when the carrier’s assigned but before the freight order’s saved?
Anyway, can you send some screenshots in BOPF of the /SCMTMS/TOR BO? Looking specifically for
screenshots with the expanded node hierarchy, association definitions, etc. That would help point you in the
Thanks,
James
like (0)
1. Pankaj Lal
Hi James
Actually its related to one Incompatibility setting, where when I select a Carrier and Freight Order, System
checks the Carrier and Consignee (assigned in FO), and based on the condition result either allow or gives
error. Standard SAP has given two separate condition for Carrier and FO, hence I have some limitation. I am
trying to club both under one condition. My guess was that BO_TSP_ROOT can be one association, but
somehow its not working as its not the Master data BO..
Thanks
Pankaj
like (0)
1. James Wood Post author
Can you also please send me a screenshot of the BO_TSP_ROOT association (highlighted above) when you
double-click on it? In that definition, you should get a sense for how these two BOs are related. From here,
perhaps we can backtrack and see if we can artificially build a linkage to satisfy your condition.
Thanks,
James
like (0)
1. Pankaj Lal
Hi James
The association is already there in the picture, If you click open it. Its bigger image hence not visible
comment box..
Regards
Pankaj
like (0)
1. James Wood Post author
Yes, but what we need is the details around the association. For instance, is there an association class
defined? On the Association Binding tab, what do the attribute bindings look like? To get where you want to
go, you’ll need to figure out how to hoist the carrier ID up to a point where you can access it in your
condition. So, you may have to create another determination to achieve this which utilizes similar logic to
Thanks,
James
like (0)
1. Pankaj Lal
Hi James
Thanks once again for helping me. I am adding some screenshots around association.
Regards
Pankaj
like (0)
1. James Wood Post author
Hi Pankaj,
Given the way this association is defined, I’m thinking that you may have to get clever with this. I’m
2. Implement some enhancement logic to intercept the carrier assignment event and store the
selected carrier ID in a shared memory object (which internally uses a hash table to associate the
3. Create a transient attribute on the root node of the freight order BO to expose the carrier ID.
4. Create a custom determination to populate the transient attribute with the carrier ID value from
shared memory.
I think this should allow you to access the carrier ID from your condition record before the FO is saved. What
do you think?
Thanks,
James
like (0)
4. Pankaj Lal
Hi James
I am not very strong in technical area specially in BOPF as I mostly work in functional side only, but If I
1.- I need to read the Carrier data in a custom Table (at the time of carrier creation event, the carrier will be
and
3. – then read from there at the run time while saving the FO using the custom determination..
Please let me know if this is correct understanding.. So could you please help with the steps to do it. I will
try and see if this works. Also I can use this idea in other problems as well.
I also have one basic query.. Once I find the association for a BO node, how can I find out what other BO
Thanks
Pankaj
like (0)
1. James Wood Post author
Hi Pankaj,
My thought with point #1 was to capture the event when the carrier is associated with the freight order (but
before the FO is saved). Based on what you commented earlier (see below), I gathered that this was the
gap you were struggling with: figuring out how to read the carrier ID in a condition before the FO is saved.
The logic described above was intended to provide you with a separate field which makes it easy to link up
the FO and carrier data from within the FO business object. Am I off base here?
…Once the carrier is assigned and FO is saved, I can read the carrier under /SCMTMS/TOR as well under
TSP_ID but before that TSP_ID is blank…
Thanks,
James
like (0)
1. Pankaj Lal
No James, you are absolutely right.. I think I misunderstood pt. 1.. So how to proceed on this?
like (0)
1. James Wood Post author
Hi Pankaj,
The next step is to start writing code. Perhaps there’s a developer on your project you can work with to take
Thanks,
James
like (0)
1. Pankaj Lal
Sure James.. I will check with the development team here..
Thanks a lot for your valuable suggestions on this topic.. I will keep you updated if this works.
Pankaj
like (0)
The issue with your code is in the way you’re creating the PERSON_INVOLVED record. For both records,
you’re mapping SY-UNAME to the PERSON_INVOLVED.ID field. To create separate records, you need to map
different IDs for each distinct person. Here, you have three different types to choose from:
Without knowing a ton about your use case, it would seem that if all you have to go on is contact
information, you probably will have to either use that information to look up one of the three person types
listed above or create an ad hoc business partner on the fly. Regardless of the path you take, the resultant
ID is what you would plug into the ID field. Do that, and I think you’re on the right track.
Thanks,
James
like (0)
1. Md Shakeel Ahmed
Hi James,
I have tried by your above inputs and know two Involved person are getting created.
But how to create the REPORTING PERSON under the same INVOLVED_PERSON node. I.e mean who is
1.Reporting Person
2.Injured Person
3. Witness Person
Thanks a lot.
Regards,
Shakeel.
like (0)
1. James Wood Post author
The reporting person would be created just like all the others. In this case though, you’d probably want to
UNAME into an EHSM party key. Then, plug that key into the ID field and assign the reporting person role in
like (0)
1. Md Shakeel Ahmed
Hi James,
‘INJURY_ILLNESS_BP_DESC’ in EHHSS_INCIDENT.
I wrote this code to create the injury description but it is not updating.
lr_s_inj_info->key = /bobf/cl_frw_factory=>get_new_key( ).
lr_s_inj_info->oc_inc_type = ‘EHHSS_OIT_ACC_ON_WAY’.
<ls_mod>–node = if_ehhss_inc_c=>sc_node–person_inj_info.
<ls_mod>–change_mode = /bobf/if_frw_c=>sc_modify_create.
<ls_mod>–source_node = if_ehhss_inc_c=>sc_node–person_involved.
<ls_mod>–association = if_ehhss_inc_c=>sc_association–person_involved–person_inj_info.
<ls_mod>–root_key = lr_s_root->key.
<ls_mod>–source_key = lr_s_per_inv->key.
<ls_mod>–key = lr_s_inj_info->key.
<ls_mod>–data = lr_s_inj_info.
lr_s_injury_illness->key = /bobf/cl_frw_factory=>get_new_key( ).
lr_s_injury_illness->inj_ill = ‘EHHSS_ILLC_INJ’.
* lr_s_INJURY_ILLNESS->TYPE = ‘EHHSS_OIT_ACC_ON_WAY’.
lr_s_injury_illness->type_desc = ‘DESCR’.
<ls_mod>–node = if_ehhss_inc_c=>sc_node–injury_illness.
<ls_mod>–change_mode = /bobf/if_frw_c=>sc_modify_create.
<ls_mod>–source_node = if_ehhss_inc_c=>sc_node–person_inj_info.
<ls_mod>–association = if_ehhss_inc_c=>sc_association–person_inj_info–injury_illness.
<ls_mod>–root_key = lr_s_root->key.
<ls_mod>–source_key = lr_s_inj_info->key.
<ls_mod>–key = lr_s_injury_illness->key.
<ls_mod>–data = lr_s_injury_illness.
lr_s_root_txt->key = /bobf/cl_frw_factory=>get_new_key( ).
<ls_mod>–node = if_ehhss_inc_c=>sc_node–INJURY_ILLNESS_DESC.
<ls_mod>–change_mode = /bobf/if_frw_c=>sc_modify_create.
<ls_mod>–source_node = if_ehhss_inc_c=>sc_node–INJURY_ILLNESS.
<ls_mod>–association = if_ehhss_inc_c=>sc_association–INJURY_ILLNESS–injury_illness_desc.
<ls_mod>–root_key = lr_s_root->key.
<ls_mod>–source_key = lr_s_INJURY_ILLNESS->key.
<ls_mod>–key = lr_s_root_txt->key.
<ls_mod>–data = lr_s_root_txt.
lr_s_INJURY_ILLNESS_DESC->key = /bobf/cl_frw_factory=>get_new_key( ).
lr_s_INJURY_ILLNESS_DESC->TEXT_EXISTS_IND = ‘X’.
<ls_mod>–node = if_ehhss_inc_c=>sc_node–INJURY_ILLNESS_DESC.
<ls_mod>–change_mode = /bobf/if_frw_c=>sc_modify_create.
<ls_mod>–source_node = if_ehhss_inc_c=>sc_node–INJURY_ILLNESS.
<ls_mod>–association = if_ehhss_inc_c=>sc_association–INJURY_ILLNESS–INJURY_ILLNESS_DESC.
<ls_mod>–root_key = lr_s_root->key.
<ls_mod>–source_key = lr_s_INJ_INFO->key.
<ls_mod>–key = lr_s_INJURY_ILLNESS_DESC->key.
<ls_mod>–data = lr_s_INJURY_ILLNESS_DESC.
“Create the TEXT node:
lr_s_text->key = /bobf/cl_frw_factory=>get_new_key( ).
lr_s_text->TEXT_TYPE = ‘DESCR’.
<ls_mod>–node = lo_driver->mo_bo_conf-
<ls_mod>–change_mode = /bobf/if_frw_c=>sc_modify_create.
<ls_mod>–source_node = if_ehhss_inc_c=>sc_node–injury_illness_desc.
<ls_mod>–source_key = lr_s_INJURY_ILLNESS_DESC->key.
<ls_mod>–root_key = lr_s_root->key.
<ls_mod>–key = lr_s_text->key.
<ls_mod>–data = lr_s_text.
<ls_mod>–association =
lo_driver->mo_bo_conf->query_assoc(
iv_node_key = if_ehhss_inc_c=>sc_node–injury_illness_desc
iv_assoc_name = ‘TEXT’ ).
lr_s_txt_cont->key = /bobf/cl_frw_factory=>get_new_key( ).
<ls_mod>–node = lo_driver->mo_bo_conf-
<ls_mod>–change_mode = /bobf/if_frw_c=>sc_modify_create.
<ls_mod>–source_node = if_ehhss_inc_c=>sc_node–injury_illness_desc.
<ls_mod>–root_key = lr_s_root->key.
<ls_mod>–key = lr_s_txt_cont->key.
<ls_mod>–data = lr_s_txt_cont.
<ls_mod>–association =
lo_driver->mo_bo_conf->query_assoc(
iv_node_key = if_ehhss_inc_c=>sc_node–injury_illness_desc
iv_assoc_name = ‘TEXT_CONTENT’ ).
Thanks,
Shakeel
like (0)
1. James Wood Post author
For near misses, you can fill in the description field directly in the NEAR_MISS node using the DESC_TEXT
field. Behind the scenes, BOPF determinations will copy the text into the subordinate node automagically.
Thanks,
James
like (0)
1. Md Shakeel Ahmed
Hi James,
Thank You.
Shakeel
like (0)
1. Md Shakeel Ahmed
Hi James,
I am trying to attach the documents like images, videos, .doc and .xls file to sap but it is uploading.
Here is my code.
lr_s_att_document->key = /bobf/cl_frw_factory=>get_new_key( ).
lr_s_att_document->key_ref = lr_s_root->key.
lr_s_att_document->FILE_SIZE = lv_filesize .
lr_s_att_document->FORM_NAME = ‘INC_INFO_WITNESS’.
lr_s_att_document->MIME_CODE = ‘/SAP/PUBLIC/BOBF’.
lr_s_att_document->FILE_NAME = ‘EHS_Image_file’.
lr_s_att_document->CONTENT = ‘C:\Users\Desktop\mobo_logo.png’.
<ls_mod>–node = if_ehhss_inc_c=>sc_node–ATT_DOCUMENT.
<ls_mod>–change_mode = /bobf/if_frw_c=>sc_modify_create.
<ls_mod>–source_node = if_ehhss_inc_c=>sc_node–root.
<ls_mod>–association = if_ehhss_inc_c=>sc_association–root–ATT_DOCUMENT.
<ls_mod>–source_key = lr_s_root->key.
<ls_mod>–key = lr_s_att_document->key.
<ls_mod>–data = lr_s_att_document.
exporting
iv_act_key = if_ehhss_inc_c=>sc_action–att_document–upload_document
it_key = lt_att_doc_key ).
* is_parameters = is_attachment ).
Shakeel Ahmed.
like (0)
1. James Wood Post author
Hi Shakeel,
Send me an e-mail and I can send you some sample code to look at.
Thanks,
4. Navigating the BOPF: Part 4 – Advanced BOPF API Features
January 29, 2013 | 1,107 Views |
James Wood
•
more by this author
ABAP Development
abapbopffloorplan managerWeb Dynpro
3.In my previous blog post, I introduced the BOPF API and demonstrated how the API could be used to
perform routine CRUD operations on a business object. With this basic introduction out of the way, we’re
now ready to tackle more advanced API features such as consistency checks, the execution of actions, and
In keeping with the object-oriented paradigm, business objects (BOs) are designed to combine business data and business functions
together into one tidy capsule (hence the term encapsulation). One of the primary benefits of combining these entities is to ensure that
updates to business data are reliably filtered through a set of business rules. To put this concept into perspective, imagine a BO which
defines a header-level status field (e.g. 01 = Initial, 02 = In Process, 03 = Closed). Now, from a pure data perspective, there’s nothing
stopping us from updating the status field using the MODIFY()method of the /BOBF/IF_TRA_SERVICE_MANAGER interface (or heck,
even via an SQL UPDATEstatement). However, from a business perspective, there are probably some rules which define when, where,
and how we should change the status field. For example, it might be that the BO cannot be closed until any open line items are closed
out, etc.
Whatever the business rules might be, the point is that we want to ensure that a BO is consistent throughout each checkpoint in its
object lifecycle. As we learned in part 2 of this blog series, the BOPF allows us to define these consistency checks in the form
of validations. For example, in the screenshot below, you can see how SAP has created a validation called CHECK_ROOT for
the ROOT node of the /BOBF/DEMO_SALES_ORDER demo BO. This validation is used to perform a consistency check on the sales order
header-level fields to make sure that they are valid before an update is committed to the database.
One of the nice things about validations like CHECK_ROOT is that they are automatically called by the BOPF framework at specific
points within the transaction lifecycle. However, sometimes we might want to trigger such validations interactively. For example, when
building a UI on top of a BO, we might want to provide a check function which validates user input before they save their changes.
This is demonstrated in the /BOBF/DEMO_SALES_ORDER Web Dynpro ABAP application shown below.
From a code perspective, the heavy lifting for the check operation is driven by the CHECK_CONSISTENCY() method of
the /BOBF/IF_TRA_SERVICE_MANAGER interface as shown in the code excerpt below. Here, we simply provide the service manager
with the target node key and the BO instance key and the framework will take care of calling the various validations on our behalf. We
can then check the results of the validation by looking at the /BOBF/IF_FRW_MESSAGE instance which was introduced in the previous
blog.
DATA lt_key TYPE /bobf/t_frw_key.
FIELD-SYMBOLS <ls_key> LIKE LINE OF lt_key.
DATA lo_message TYPE REF TO /bobf/if_frw_message.
TRY.
APPEND INITIAL LINE TO lt_key ASSIGNING <ls_key>.
<ls_key>-key = iv_key. "<== Sales Order BO Key
CALL METHOD mo_svc_mngr->check_consistency
EXPORTING
iv_node_key = /bobf/if_demo_sales_order_c=>sc_node-root
it_key = lt_key
iv_check_scope = '1'
IMPORTING
eo_message = lo_message.
...
CATCH /bobf/cx_frw INTO lx_frw.
...
ENDTRY.
I’ll show you how to implement validations within a BO in an upcoming blog entry.
Triggering Actions
The behaviors of a business object within the BOPF are defined as actions. From a conceptual point-of-view, actions are analogous to
methods/functions in the object-oriented paradigm. The following code excerpt demonstrates how actions are called using the BOPF
API. Here, we’re calling the DELIVER action defined in the ROOT node of the /BOBF/DEMO_SALES_ORDER demo BO. As you can see,
the code reads like a dynamic function/method call since we have generically pass the name of the action along with its parameters to
the DO_ACTION() method of the /BOBF/IF_TRA_SERVICE_MANAGERinterface. Other than that, it’s pretty much business as usual.
DATA lt_key TYPE /bobf/t_frw_key.
FIELD-SYMBOLS <ls_key> LIKE LINE OF lt_key.
DATA ls_parameters TYPE /bobf/s_demo_sales_order_hdr_d.
DATA lr_s_parameters TYPE REF TO data.
DATA lo_change TYPE REF TO /bobf/if_tra_change.
DATA lo_message TYPE REF TO /bobf/if_frw_message.
DATA lt_failed_key TYPE /bobf/t_frw_key.
DATA lt_failed_act_key TYPE /bobf/t_frw_key.
TRY.
"Set the BO instance key:
APPEND INITIAL LINE TO lt_key ASSIGNING <ls_key>.
<ls_key>-key = iv_key. "<== Sales Order BO Key
...
CATCH /bobf/cx_frw INTO lx_frw.
...
ENDTRY.
We can verify if a BOPF action call was successful by querying the EO_MESSAGE object reference and/or the ET_FAILED_KEY table
parameters returned by the DO_ACTION() method. Refer back to my previous blog for an example of the former technique. As always,
remember to commit the transaction using the
SAVE() method defined by the /BOBF/IF_TRA_TRANSACTION_MGR interface.
Action Validations
In part two of this blog series, I mentioned that there are technically two different types of validations: the consistency check
validations discussed earlier in this blog and action validations which are used to determine whether or not an action can be executed at
runtime. Since the BOPF framework invokes action validations automatically whenever actions are invoked, it is rare that you will have
a need to invoke them directly. However, the /BOBF/IF_TRA_SERVICE_MANAGER interface does provide the DO_ACTION() method
if you wish to do so (see the code excerpt below).
DATA lt_key TYPE /bobf/t_frw_key.
FIELD-SYMBOLS <ls_key> LIKE LINE OF lt_key.
DATA ls_parameters TYPE /bobf/s_demo_sales_order_hdr_d.
DATA lr_s_parameters TYPE REF TO data.
DATA lo_message TYPE REF TO /bobf/if_frw_message.
DATA lt_failed_key TYPE /bobf/t_frw_key.
DATA lt_failed_act_key TYPE /bobf/t_frw_key.
TRY.
"Set the BO instance key:
APPEND INITIAL LINE TO lt_key ASSIGNING <ls_key>.
<ls_key>-key = iv_key. "<== Sales Order BO Key
...
CATCH /bobf/cx_frw INTO lx_frw.
...
ENDTRY.
Transaction Management
Another element of the BOPF API that we have glossed over up to now is the transaction manager
interface /BOBF/IF_TRA_TRANSACTION_MGR. This interface provides us with a simplified access point into a highly sophisticated
transaction management framework. While the details of this framework are beyond the scope of this blog series, suffice it to say that
the BOPF transaction manager does more here than simply provide basic object-relational persistence. It also handles caching,
transactional locking, and more. You can see how some of these features are implemented by looking at the Transactional
Behavior settings of a business object definition in Transaction /BOBF/CONF_UI (see below).
So far, we have seen a bit of the /BOBF/IF_TRA_TRANSACTION_MGR on display whenever we looked at how to insert/update records.
Here, as you may recall, we used SAVE() method of the /BOBF/IF_TRA_TRANSACTION_MGR interface to save these records. In many
respects, the SAVE()method is analogous to the COMMIT WORK statement in ABAP in that it commits the transactional changes to the
database. Here, as is the case with the COMMIT WORK statement, we could be committing multiple updates as one logical unit of work
(LUW) – e.g. an insert followed by a series of updates.
Once a transaction is committed, we can reset the transaction manager by calling the CLEANUP()method. Or, alternatively, we can also
use this method to abandon an in-flight transaction once an error condition has been detected. In the latter case, this is analogous to
using the ROLLBACK WORK statement in ABAP to rollback a transaction.
During the course of a transaction, the BOPF transaction manager tracks the changes that are made to individual business objects
internally so that it can determine what needs to be committed and/or rolled back. If desired, we can get a peek of the queued up
changes by calling the GET_TRANSACTIONAL_CHANGES() method of the /BOBF/IF_TRA_TRANSACTION_MGR interface. This
method will return an object reference of type /BOBF/IF_TRA_CHANGE that can be used to query the change list, modify it in certain
cases, and so on.
Next Steps
At this point, we have hit on most of the high points when it comes to interacting with the BOPF API from a client perspective. In my
next blog, we’ll shift gears and begin looking at ways of enhancing BOs using the BOPF toolset.
16 Comments
You must be Logged on to comment or reply to a post.
1. Oliver Jaegle
Dear James,
If this is the ID of the sales order item you’re “delivering”, this action design was one I’d discourage.
Let me try to explain that: Usually, the instance(s) you are executing an action on is/are referred to by its
The parameter shall be used for passing variable options which influence the behavior of the action (e. g.
delivering a split quantity which makes the action create a subsequent delivery item).
You should only pass the reference to the instance as an action parameter if you intend not to operate on
this instance but to use it as parameter of another logic (e. g. if you create a new instance based on the
The advantage of is obvious if you intend to prevent the execution of the action with a validation or when it
comes to mass-processing: You can always refer to the KEYs which are passed to the action: You can
validate them, exclude some of them from the execution as they failed to pass a validation (failed keys),
If you use a static action, you will be redundantly converting the parameter into a technical key within the
implementations and you cannot refer to the instance in created message objects.
Oliver
like (0)
This action is not something I defined; rather it’s part of a demo BO provided by SAP. Here, the DELIVER
action defines a parameter structure which receives the item number of the sales order item being
delivered. I agree that this is not the best of conventions, but my purpose with this article is more to
demonstrate how to work with the API and pass parameters than it was to critique the validity of the sample
Thanks,
James
like (0)
1. Oliver Jaegle
Hi James,
Of course, your demo for using the APIs makes perfect sense. I also understood it that way.We’ll have to talk
However, with your blog being the prime source for everyone who has a first glance at the framework, I
wanted to comment on the sample for clarifying that instances and parameters are not interchangeable –
Cheers, Oliver
like (0)
2. Suhas Sondur
Excellent post! Thanks for the information. I have a query regarding the service manager.
My UI right now shows the current BO data (persisted in the DB) in read mode. If some other application
changes this BO data later, how does the service manager know that the data in the DB has changed? How
Thanks, Suhas
like (0)
Hmm, that’s a bit of a loaded question. The FBI (view) layer is generally aware of changes to BOPF records,
so it does have the ability to refresh data. If you can provide me with more specifics, I could comment
further.
Thanks,
James
like (0)
1. Suhas Sondur
Hi James,
Let’s take an example. We have a customer BO and have built a FBI UI on it to display the data. We also
have the mode handling in place. If the UI is right now displaying data for ID=ABC in read mode(APPL1) and
another application(APPL2) updates the data for the same ID(e.g. change of status or review date), how will
APPL1know that the data has been updated? Does the APPL1 need to do anything specific to update itself?
Thanks,
Suhas.
like (0)
1. James Wood Post author
Hi Suhas,
A couple of questions:
1. Is APPL2 also a UI application?
Thanks,
James
like (0)
3. Suhas Sondur
Hi James,
The APPL2 could be a background process too. But it is safe to assume that the changes are committed.
Thanks,
Suhas
like (0)
2. Oliver Jaegle
Dear Suhas,
Two separate browser windows running a webdynpro-application are isolated sessions in the backend.
Meaning: They don’t know anything about each other and there are no in-built-mechanisms to notify each
Assuming that the changes in the separate session are committed, they will be visible to other sessions. As
APPL1 has already loaded its state though, APPL1 will have to actively re-read the newly committed data in
This is what a good UI-controller should anyway do: Before switching from read-only to edit-mode, re-read
the current data and – in the very same request – lock it exclusively. And this is exactly what FBI does: On
the EDIT-event, all UIBBs are requested to reload from the database (technically, a retrieve with edit mode
This way, the APPL2 will appear to have been notified about the changes of the other session.
In contrast to that, the changes occurring within a session are propagated differently: Each core-service via
the service manager returns a change-object (/bobf/if_tra_change) which provides the caller with
information which data has been modified (by the consumer itself or implicitly within a determination).
Based upon this change-object, it’s up to the consumer (e. g. the feeder) to re-read the data (e. g. in order
to pass it into the UI-structures). This is what the generic FBI-components provide.
Oliver
like (0)
1. Suhas Sondur
Dear Oliver,
Thanks for the explanation. I now understand that the FBI controller re-reads the data again via an
implicit call to retrieve. However, for the FBI based UI(APPL1), I need to implement a “refresh” kind of
Thanks,
Suhas
like (0)
1. Oliver Jaegle
As per the refresh-action, this is something I don’t think is provided “out-of-the-box” by FBI, but I believe
you have to on the event by yourself. I couldn’t test this in my system now, but I assume that you should
be fine doing by
• Having a button raise an event for the refresh-request (whether you use the FPM_REFRESH or a
custom name does not matter technically – I don’t know the semantics of the FPM_REFRESH event,
• As feeder of the UIBB which represents the root of the data which shall be refreshed (there might be
dependent nodes in other UIBBs), configure a custom one: Inherit from the FBI-feeder, redefine
process_event.
• When processing the event, retrieve the data again (requesting to read the current image and lock it
The rest (refreshing the data in the UI-structures and in dependent (wired) UIBBs should be taken care of by
Let us know (either here in the comments or an own blogpost) whether and how you succeeded!
Cheers,
Oliver
like (0)
3. Thirumoorthy Rajaram
Hi James,
I have a small query. I have created an action, when i am trying to trigger that action using the DO_ACTION
method provided by the trx_mgr, it is not getting triggered. Failed key table is filled. I am not getting what
exactly is happening, tried debugging but no luck. Could you please me what exactly i am missing in
calling the action. The key which i am passing as an import parameter is the SC_BO_KEY of my custom
Regards,
Thiru
like (0)
best I can.
Thanks,
James
like (0)
1. Thirumoorthy Rajaram
Hi James,
The issue which I have raised in my previous comment is solved now. First of all I want to let you know that
your blog and the docs related to BOPF framework is very nice and understandable, in simple words it is
awesome for someone like me who is a new comer. Thank you for sharing the knowledge.
Root->Parent->Child
Is there any option or possibility to restrict the records based on cardinality of the nodes by caller
program(or class) or any configuration set up. we tried by setting the association cardinality of Root(1..1)
and Parent node(1..1) but when tried creating a record in BOBT it is allowing more than one record even
Our main requirement is to have only one record at root and parent node, but child node can have multiple
records corresponding to parent node key. Please find the below image of our BOPF object structure in
pictorial representation.
Thiru
like (0)
1. Oliver Jaegle
Hi Thiru,
BOPF does not automatically validate cardinalities as this is not always requested and as every validation
However, BOPF offers a generic validation implementation (/BOBF/CL_LIB_V_ASSOC_CA) which you can
CL /BOBF/CL_LIB_V_ASSOC_CARD
____________________________________________________
Short Text
Validation: Checks if association cardinality is consistent
Functionality
Generic validation to check the cardinality of associations. The association cardinalities are checked for the
1. which have cardinality one or one-to-many, it is checked, whether at least one node instance is
existing.
2. which have cardinality zero to one or one, it is checked, whether at most one node instance is
existing.
Relationships
The validation is intended to be used as action validation at “check before save” .
Notes
As the validation is quite generic is first of all intended for prototype use or during an early implementation
state. If performance problems occur the validation implementation should be replaced by a specific
implementation.
Cheers,
Oliver
P.s.: I would recommend to ask such good questions in the BOPF community: BOPF Application Framework.
This has multiple advantages: More persons knowing about your question, more people sharing the problem
and answer afterwards, I don’t have a mixed feeling about answering questions directed to James
1. If the BO instance that we want to maintain/display exists already, then we can load it into context using the Load Instances button
menu. As you can see in the screenshot below, this menu affords us with several different alternatives for loading node instances: via a
BOPF node query, by the node instance key, or by an alternative key (e.g. ID). Regardless of the menu path that we take, the system
will attempt to find the target node instance(s) and then load them into the editor window. From here, we can select individual node
instances by double-clicking on them in the Metadata and Instances tree located on the left-hand side of the screen.
To edit node instances, we can select the node instance record in the editor on the right-hand side of the screen and choose the
appropriate option from the Edit button menu (see below). Then, we can edit attributes for a node instance using the provided input
fields. Alternatively, we also have the option of deleting a node instance (or indeed an entire BO instance in the case of a root node
Regardless of whether or not we’re creating a new BO instance or editing an existing one, the entire scope of our changes is tracked via
a BOPF transaction like the one we would create if we were doing all this by hand using the BOPF API. At any point along the way, we
can choose to commit the changes using the Save Transaction button, or revert the changes using the Cleanup Transaction button.
Then, we can start the process over by selecting another BO instance or editing the existing one in place. All in all, it’s kind of like table
In addition to the basic CRUD operations described earlier, the test UI also provides functions to call actions, validations, and even
trigger determinations. For a given node instance, these functions can be accessed in the node instance toolbar via
the Check and Actions button menus (see below). If you read through my blog posts related to the BOPF API, then these should feel
quite intuitive.
specific applications of the BOPF (e.g. in Transportation Management or EHSM) since these products add additional layers on top of
the BOPF that can sort of cloud the picture a bit if you don’t understand core principles of the BOPF itself. However, before I bring this
blog series to a close, I would be remiss if I didn’t point out one important (and relatively generic) framework built on top of the
BOPF: the Floorplan Manager BOPF Integration (FBI) framework. As the name suggests, this framework links BOs from the BOPF
with Web UIs based on the Floorplan Manager (FPM) framework and Web Dynpro ABAP (WDA).
If you’re developing Web UIs on top of BOs from the BOPF, then the FBI is definitely something to take a look at. Essentially, the FBI
exploits the genericity of the BOPF API and the accessibility of BO model data to enable the rapid development of Generic User
Interface Building Blocks (GUIBBs) based on BO nodes. Here, for example, we could create a form GUIBB that allows users to
populate the data for a BO node using a simple input form. In many applications, this can be achieved without having to write a single
line of code. While a detailed discussion of the FBI is beyond the scope of this blog series, a quick Google search will lead you to some
pretty decent resource materials. If you’re new to FPM, I would also offer a shameless plug for my book Web Dynpro ABAP: The
Conclusion
When I first started working with the BOPF almost a year ago, I was surprised at how little documentation there was to get started with.
So, what you’ve seen in this series is the result of a lot of trial-and-error and lessons learned by debugging past application-specific
frameworks into the heart of the BOPF itself. If you’re just getting started with the BOPF, then I hope that you’ll find this series useful
to get you up and running. In the coming months and years, I think many more learning resources will materialize to supplement what
I’ve offered here. Indeed, the number of new dimension applications based on the BOPF appears to be growing by the day…
One complaint I sometimes hear from other developers is that the BOPF API is cumbersome to work with. On this point, I can agree to
a point. However, I would argue that such complexities can be abstracted away pretty easily with a wrapper class or two and some good
old fashioned RTTI code. Other than that, once you get used to the BOPF, I think you’ll find that you like it. And this is coming from a
developer who has had many bad experiences with BO frameworks (both in and outside SAP…curse you EJBs!!!). All in all though, I
have found the BOPF to be very comprehensive and flexible. For me, one of the feel tests I normally conduct to gauge the effectiveness
of a framework is to ask myself how often the framework gets in my way: either because it’s too intractible, limited in functionality or
whatever. I have yet to run into any such occurrences with the BOPF. It does a good job of providing default behaviors/functionality
while at the same time affording you the opportunity to tweak just about everything. For example, if I want to build my own caching
mechanism, I can do so by plugging in my own subclass. If I want to pull data from a HANA appliance in real time, I can do so in a
determination. You get the idea. It’s all there, so just poke around a bit and I think you’ll find what you need.
13 Comments
You must be Logged on to comment or reply to a post.
1. Rajashiva Ramalingam
Hi James,
its very nice series of blog.Now i hope the BOPF stuff is available in SCN with your contibution !
like (0)
2. Bharath Komarapalem
Hi James,
I should thank and appreciate your efforts for preparing such blog series. I just started with SAP TM and
BOPF. I am in the same situation now in what situation you were 1 year back.
But your blog is really helpful. Although it is very difficult to understand and digest in the first read but i
hope over and after some period there will be some improvement in understanding..
Regards,
Bharath.
like (0)
3. sapkrisboedge mac
Hi James,
It was very nice blog. Now, I hope the BOPF stuff are avaible in community..
Thanks,
kris.
like (0)
4. Rajesh Sharma
Really good stuff james
like (0)
5. sapkrisboedge mac
It is Really Good James..
like (0)
6. Sagar Pilli
Hi James,
Its really very good stuff and I encourage who ever comes on my way to work on BOPF to refer your blogs
Cheers
Sagar
like (0)
7. Zhenbo Wang
Hi James,
great article. It has changed my opinion regarding the BOPF framework (more or less).
Do you know a way to convert the bopf-key to a readable name during debuging? Search it in the constant
interface is disturbing.
Zhenbo
like (0)
Unfortunately, no. This is a pain point for sure. Sorry I don’t have a better answer for you.
Thanks,
James
like (0)
8. Paul Hardy
Mr.James,
Have you ever got change documents working in the BOPF with external keys?
I can get entries popping up in CDHDR and CDPOS easily enough, but redefining the method
The code seems to be looking in table TCD0B which I think is the SCD0 and then seeing if the entry is a
table type as opposed to a transparent table. Only then is the GET_EXTERNAL_ID called. If this is true then
you have to define your Z table type with a really short name, as the SCDO transaction still thinks we are in
Cheersy Cheers
Paul
like (0)
I’ve used these callback classes as a hook for triggering downstream interfaces, but I’ve never tried to
implement a scenario like what you’re describing. That being said, have you tried setting a break-point in
you might enhance the framework to support what you’re looking for)?
Thanks,
James
like (0)
1. Paul Hardy
Hello,
What I did was go through the steps in the official SAP help to set up the change document adapter for my
custom business object i.e. attach the standard BOPf change document object as a delegated object.
This works fine, entries appear in CDHDR/CDPOS, but with the GUID. It is no big thing to get the proper ID
i.e. a number a human can relate to based on the GUID but the SAP help says that you can redefine the
method ion the callback class, and there is an example in the demo customer callback class, not that the
demo customer business object actually works for change documents (does not have a change document
delegated object).
This value in turn is created during ,ethod INIT_CDO_NODES_AND_TABS. this method looks at the entries in
TCDOB for the object class. I think TCDOB is the table where the entries in transaction SCDO are stored.
The method loops through the entries in TCDOB looking for an entry which NOT a transparent table e.g. a
structure, in fact i think it is looking for /BOFU/CDTEXTID specifically. Then that structure gets analysed and
the program decides if the first include is /BOFU/S_CD_EXTID_KEYS. if this is true, then the
I will have a go at playing with this, probably adding /BOFU/CDTEXTID to SCdO for my custom object and
There also does not seem to be any standard way to get the CDHDR/CDPOS entries back outside of the
WDA/FPM environment. What if you wanted to use this with UI5 or whatever comes next after that?
Cheersy Cheers
Paul
like (0)
1. Paul Hardy
Yes, that worked fine.
Now i get two entries in CDPOS, both with the GUID as the OBJECTID, but one of them has /BOFU/CDTEXTID
as the “table” being changed and that entry contains the external ID.
I am really happy bunny about getting this working, if the SAP help had just told me waht to do it would not
In my previous two blog posts, we explored the BOPF API from a client point-of-view. Here, we learned how to perform basic CRUD
operations, execute actions, and so on. Now that you have a feel for how the API operates, we’re ready to take a peek behind the curtain
and see these services are implemented within the business objects themselves. For now, our emphasis will be on enhancing these
services since SAP does not yet support the creation of new business objects. However, whether we’re enhancing existing business
objects or creating new ones from scratch, the concepts remain the same.
What to Enhance?
Before we dive into the exploration of specific enhancement techniques, let’s first take a look at the kinds of entities we’re allowed to
enhance in a business object. Aside from implicit enhancements applied to implementation classes using the Enhancement Framework,
the types of entities that we can enhance within a business object are as follows:
• Custom Attributes
• For a given node, we might want to define a handful of additional custom attributes.These attributes could be persistent (i.e., they get
appended to the target database table which contains the node data) or transient in nature.
• New Sub-Nodes
• In some cases, we may need to do more than simply define a few new attributes on an existing node. Using the relational data model as
our guide, we may determine that a new sub-node is needed to properly model some new dimension of data (e.g. 1-to-many relations,
etc.). Depending on the requirement, the sub-node(s) might be persistent or transient in nature.
• Determinations
• If we add new custom attributes to a given node, it stands to reason that we might also want to create a custom determination to manage
these attributes.
• Or, we might have a standalone requirement which calls for some sort of “trigger” to be fired whenever a specific event occurs (e.g. fire
• Consistency Validations
• If we are enhancing the data model of a business object, we might want to define a consistency validation to ensure that the new data
• A custom validation might also be used to graft in a new set of business rules or a custom security model.
• Actions
• If we have certain operations which need to be performed on a business object, we would prefer to encapsulate those operations as an
action on the business object as opposed to some standalone function module or class.
• Queries
• In some cases, the set of defined queries for a business object might not be sufficient for our needs. In these situations, we might want
to define custom queries to encapsulate the selection logic so that we can use the generic query services of the BOPF API as opposed to
is provided as a separate download in SAP Note #1457235. This document provides a wealth of information concerning the use of the
BOPF Enhancement Workbench, enhancement strategies, and even the BOPF framework in general. Given the amount of detail
provided there, I won’t attempt to re-invent the wheel in this blog post. Instead, I’ll simply hit on the high points and leave the nitty-
accessed using Transaction BOPF_EWB. Here, enhancement projects are organized into enhancement objects. From a conceptual
point-of-view, enhancement objects bear a lot of similarities to sub-classes in the object-oriented programming (OOP)
paradigm. This is to say that enhancement objects inherit all of the entities of their parent BO. With this foundation in place, we can
begin defining custom entities in much the same way we might add new attributes/methods to a subclass in the ABAP Class Builder
tool. However, as is the case with classes in the OOP world, we cannot extend BOs which are marked as final or that do not have the
Workbench shown below. To create an enhancement, simply right-click on the BO that you wish to enhance and select the Create
Enhancement menu option (see below). From here, the BOPF Enhancement Workbench will guide you through a wizard process which
allows you to select the name of the enhancement object, the constants interface for the enhancement object, and so on.
Once the enhancement is created, you will be able to edit your enhancement object in the workbench perspective of the BOPF
Enhancement Workbench shown below. As you can see, it has a similar look-and-feel to that of the normal BO browser tool
(Transaction /BOBF/CONF_UI). From here, we can begin adding custom entities by right-clicking on the target node and selecting
from the available menu options. We’ll see how this works in the upcoming sections.
One final item I would draw your attention to with enhancement objects is the assigned constants interface (highlighted above). This
constants interface can be used to access the enhancement object entities in the same way that the super BO’s constants interface is used
Perhaps the most common type of enhancement to BOs in the BOPF is the addition of new fields. Here, we have the option of adding
new fields to existing nodes or creating sub-nodes to model more complex relationships. In the former case, we sometimes don’t even
need to create an enhancement object; just a simple append structure will suffice (see below).
For more complex data requirements, we typically need to define sub-nodes. This can be achieved by right-clicking on the parent node
and selecting the Create Subnode menu option. This kicks off a wizard process in which you can select the sub-node’s name, its
persistent and/or transient structures, and the rest of the auto-generated dictionary types which go along with a node definition (e.g.
combined structure/table type, database table, etc.). Most of this is pretty standard stuff, but I would draw your attention to the step
which creates the persistent and/or transient structures. Note that these structures must exist in the database before you move on from
the Attributes step in the wizard process. And, in the case of the persistent structure, you must include the /BOBF/S_ADMIN structure as
the first component.
After the custom sub-node is created, you can fill out its attributes by adding components to the persistent/transient structures defined
by the sub-node. If the sub-node is a persistent node, then we can create, modify, and retrieve node instances using the BOPF API as per
usual. However, in the case of transient nodes, we need determinations to pre-fetch the data for us. We’ll see how to define such
determinations next.
Defining Determinations
According to the help documentation, determinations encapsulate internal changing business logic on a business object. Unlike the
logic encapsulated in actions which can be triggered at any time, the business logic contained within determinations is triggered as
specific times within the BO life cycle (e.g. right before a node is saved, etc.). So, in a way, it is appropriate to think of determinations
as being a little bit like user exits/BAdIs/enhancement spots in that they provide a place to hang custom logic at particular points within
Once we determine (no pun intended) that we want to create a determination for a given node, we can do so by simply right-clicking on
that node and selecting the Create Determination menu option. This will spawn a wizard which guides us through the process. Here,
1. Implementing Class:
• We must create or assign an ABAP Objects class that implements the /BOBF/IF_FRW_DETERMINATION interface.
2. Determination Pattern:
• This property defines the event which triggers the determination. As you can see below, the set of available patterns will vary
depending on the type of node you’re enhancing, its location in the node hierarchy, and so on.
• Once a pattern is selected, you may be presented with additional options for refining when an event is triggered. For example, if we
select the pattern “Derive dependent data immediately after modification”, we will have the opportunity to specify if the dependent data
should be created/modified after any modification, only when the node is created the first time, etc.
Because determinations can be used for a lot of different things, they can be implemented in a lot of different ways. Here, it is very
important that you pay close attention to selecting the right pattern for the right job. The aforementioned help documentation provides a
good set of guidelines to assist here. Other valuable resources include the interface documentation for
the /BOBF/IF_FRW_DETERMINATIONinterface in the Class Builder tool and SAP standard-delivered determinations implementations
available in the system you’re working on.
Defining Consistency Validations
The process of defining a custom consistency validation is quite similar to the one used to define determinations. Walking through the
wizard process, there are three main properties that we must account for:
1. Implementing Class:
• Here, we must create/assign an ABAP Objects class which implements the /BOBF/IF_FRW_VALIDATION interface.
2. Request Nodes:
• This property allows us to specify which node operations should force a validation to occur (e.g. during creates, updates, etc.)
3. Impact:
• With this property, we can specify the behavior of the BOPF framework in cases where the validation fails. For example, should we
simply return an error message, prevent the requested operation from proceeding, or both?
From an implementation perspective, the /BOBF/IF_FRW_VALIDATION interface provides us with everything we need to perform the
validation check: the context of the validation, the current data within the node instance being validated, and so on. For more
information about how to implement the validation class, I would highly recommend that you read through the interface documentation
for the /BOBF/IF_FRW_VALIDATION interface in the Class Builder tool. It can also be helpful to look at various standard-delivered
classes which already implement this interface to see common patterns/idioms used by SAP.
Working with Actions
• We can create a brand new action definition for a given node (standard or custom).
The first case is pretty straightforward. Basically, we simply follow along with the wizard process up to the point that we reach the
Settings step shown below. Here, we must define three main properties for the action:
• Implementing Class:
• This property is used to specify the ABAP Objects class which encapsulates the action logic. The class must implement
the /BOBF/IF_FRW_ACTION interface.
• Action Cardinality:
• The action cardinality property defines the scope of the action. This is somewhat analogous to the way we have the option of defining
class methods or instance methods within a regular ABAP Objects class. In this case however, we also have the third option of defining
• Parameter Structure:
• If we wish to pass parameters to the action, we can plug in an ABAP Dictionary structure here to encapsulate the parameters.
Once the action is created, we simply need to plug in the relevant logic in the defined implementation class. You can find
implementation details for this in the interface documentation and/or sample action classes in the system.
In order to create a pre/post action enhancement, the target action definition in the super BO must have its “Action Can Be Enhanced”
flag set (see below). Assuming that the flag is set, then we can proceed through the corresponding wizard process in much the same
way we would if we were creating a custom action from scratch. Indeed, as is the case with regular actions, the implementation
class(es) for pre/post action enhancements must implement the /BOBF/IF_FRW_ACTION interface.
Before you go to implement a pre/post action enhancement, I would definitely recommend that you read through the help
documentation so that you understand what you can and cannot do within an action enhancement. Most of the rules are intuitive, but
you can definitely get into trouble if you abuse these enhancements by using them for things they weren’t designed for.
Compared to the various enhancement options we’ve seen thus far, custom queries are perhaps the easiest entities to create within an
enhancement object. Indeed, if all we want is a simple node attribute query, we can zip through the wizard and have a working model
up and running in a matter of minutes. If we want something a little more custom/sophisticated, our job is only marginally more
difficult (at least from a configuration perspective) in that we must assign an implementing class and an optional data type which serves
as the parameter structure passed into the query from the client side (see below).
From an implementation perspective, all of the query logic for a custom query gets encapsulated in the implementation class (which
must implement the /BOBF/IF_FRW_QUERY interface). For the most part, you’ll find that the framework doesn’t really get in the way
with regards to how we go about implementing the query. Basically, it passes in the query parameters up front and it’s up to us to figure
out how to find all of the node instances which match the given parameters. Here, we must pay careful attention to the SQL statements
that we use since the query may be used extensively by a number of different clients.
Next Steps
Hopefully by now you have a general feel for how BOs are enhanced and the basic steps required to achieve these enhancements. As is
the case with most programming-related subjects, the best way to really drive these concepts home is to look at live examples and
experiment for yourself. I would also highly recommend that you read through the aforementioned help documentation as it devotes
quite a bit of time to understanding when and where to apply specific enhancement techniques.
In my next and final blog post in this series, I’ll demonstrate another useful tool within the BOPF toolset: the BO test tool. This tool can
be used to experiment with BOs and perform ad hoc unit tests, etc.
15 Comments
You must be Logged on to comment or reply to a post.
1. Werner Herold
Hello James,
is the BOPF based on object service SAP-persistency or does it use an own way to exchange data with the
database?
Werner
like (0)
It actually uses its own mechanisms which are in many respects more sophisticated (e.g. buffering/caching,
etc.).
Thanks,
James
like (0)
1. lakshmi chinta
Hi James,
regards
luxmi
like (0)
2. Bob Varghese
Hi James,
I tried by enhancing FPM configurations by unchecking enable and setting visible property to not visible.
Thanks in advance.
Regards,
Bob.
like (0)
3. aparna trivedi
Hi James,
These BOPF blogs are truly an insight. Thanks for publishing them.
I want to implement an enhancement on Location BO (EHSM) for that i have created a freestyle Webdynpro
component . Now as per the SAP help portal it says i can use the freestyle helper class
Please advice how to use this class in the freestyle Webdynpro component.
Thanks in Advance.
Regards,
Aparna
like (0)
2. Implement the IF_FPM_UIBB_MODEL WDA component interface in your freestyle UIBB (just like you
3. Implement the GET_MODEL_API() method by returning an instance of your assistance class (e.g.
WD_ASSIST).
4. Enhance the regular callback methods from IF_FPM_UI_BUILDING_BLOCK by calling the HANDLE_*()
methods of the assistance class. Of particular importance here is probably the HANDLE_FLUSH()
method.
5. Use the FPM wire model to connect your freestyle UIBB as the source/target of wire connections as
per usual. Here, the pre-built implementations for methods from the IF_FPM_FEEDER_MODEL
interface in /BOFU/CL_FREESTYLE_BOPF_HELPER should allow you to use your freestyle UIBB just like
Thanks,
James
like (0)
6. aparna trivedi
Hi James,
When i see the enhanced standard component configuration wiring model it doesn’t show this freestyle
component in the Wiring model to connect with the existing LIST and FORM UIBB’s.
Regards,
Aparna
like (0)
1. James Wood Post author
Hi Aparna,
Have you already performed steps 1-4? Without step #2 in particular, the component’s not really wire-
model enabled, so I expect FLUID wouldn’t detect it in the wire editor. I’d recommend stubbing this out and
Thanks,
James
like (0)
1. aparna trivedi
Hi James,
The freestyle WD component has both the interfaces implemented IF_FPM_UIBB_MODEL WDA and
IF_FPM_UI_BUILDING_BLOCK.
But in the component configuration i can see there is an Error saying ‘Error at instantiation of component
ZWDXXXX with config ID ZCC_XXX’. This error disappears the moment i save my Comp Config
enhancement.
Do you think this can be a reason my component is not appearing in FPM wiring model.
Please see an image of how i am configuring my freestyle component.
like (0)
1. James Wood Post author
Hi Aparna,
It’s kind of hard to speculate without looking at your component, but I’d look very closely at the initialization
methods in your WDA component’s component controller to see if that’s the culprit here. Bear in mind that
FLUID will instantiate your component in order to pull in relevant metadata at design time. Normally, I see
this error whenever developers attempt to perform initialization routines which reference runtime-related
data that’s not there at design time (e.g., application parameters, shared data from an application
controller, etc.). I’d set some external breakpoints and see if that’s the issue.
Thanks,
James
like (0)
1. aparna trivedi
Hi James,
I am able to use my freestyle webdynpro component now to enhance the standard location object.
But i am stuck with a strange issue here. In the WDC i have a table to add some fields and update them in
location BO. After saving the fields for first time it gets saved in BO but the SAVE button is disabled due to
Have you faced this kind of issue before or any suggestions how we can get rid of this.
Regards,
Aparna
like (0)
1. James Wood Post author
Hi Aparna,
How are you synchronizing your table entries with the BOPF? Normally the FBI application controller class
will check to see if save is required/enabled based on whether or not there are changes queued up in the
BOPF. I’d look over your implementation there and also check your application controller settings around
transaction management to see if the pattern is set to change the edit mode after a save.
Thanks,
James
like (0)
4. Arunkumar Subramanian
Hi James,
There is an option to create Business Object Enhancement from transaction BOBX where I can provide my
Super Business Object. In this case I can use the enhanced BO for my custom actions and other custom
functionality.
Now if we try to create a BO in the transaction /BOBF/CONF_UI, then it provides an option to enter a Super
Business Object. In this case too I will be able to create my custom functionality.
But in the second case it doesn’t show as Enhancement object. What is the difference between these two?
When can we use the first option and when the second one.
Thanks
Arun
like (0)
5. Vladimir Staroseltsev
Hello James,
In this article enhancement means subclassing but whether after subclassing existing software which use
superclass BO will use my enhancements? or subclassed BO can only be used with new business code?
I need to add validations to existing business object and want it will take effect on existing code. Should I
Thanks,
Vladimir
like (0)
The inheritance/subclassing metaphor was meant mostly to describe the structural relationship between an
enhancement BO and its parent/base BO. However, while this metaphor is sufficient for describing the
design time relationship(s), it does break down at runtime since the BOPF does not allow one to
address/instantiate enhancement BOs separately. In effect, any enhancement elements you define (e.g.
determinations, validations, etc.) become grafted into the overall runtime model for the parent BO (though
at a somewhat lower precedence level in that they generally run after the standard elements). Thus, any
custom validations you define would take effect immediately without any modifications to the code that
So, to answer your question, yes – any validations you add will take effect on existing code. And yes, the
steps outlined in this article would apply for your use case.
6. Navigating the BOPF: Part 6 – Testing & UI Integration
March 4, 2013 | 1,207 Views |
ABAP Development
• share 0
• share 0
• tweet
• share 0
Follow
In my previous blog post, we looked at ways of enhancing business objects (BOs) defined within the BOPF. Once these enhancements
are in place, it is a good idea to unit test the changes to make sure that the BO is working correctly. If you subscribe to the test-driven
development (TDD) model, then an obvious choice here would be to use the BOPF API to write some ABAP Unit test cases. However,
sometimes we just want to run a quick ad hoc test. Or, we might just want to display BO data at a glance without constructing a multi-
table join in Transaction SQVI. For these tasks and others, SAP provides us with a very slick tool: the BOPF Test UI. In this final blog
post, I will introduce this transaction and demonstrate its basic capabilities. Then, to round things out, I’ll briefly touch on UI
with a screen like the one shown below. From here, we can begin working with a BO instance by plugging in the BO type in the Select
BO input field (either key it in or use the provided input help) and hitting the ENTER key. This causes the BO metadata to be loaded
into context so that we can use it to guide ourselves through the editing process.
Editing BO Instances
Once the BO metadata is loaded, we have two choices for maintenance:
2. To create a new BO instance, we can double-click on the root node contained in the “Metadata and Instances” tree on the left-hand side
of the editor screen and then select the Create button in the toolbar (see below). This will cause a new record to be created and loaded
into an editable ALV grid. From here, we can begin filling in node attributes, creating sub-node instances, and so on. Here, I would
draw your attention to the Messages panel located in the bottom left-hand corner of the editor. These messages can be used to help you