Hi Everyone,
In this blog, we will look into how custom business actions can be enabled in SAP Document Center for SAP DMS Repositories. SAP Document Center provides basic actions to handle documents such as Download, Delete, Rename, Copy, Paste, Check-in, and Checkout. However there could be some actions that are specific to business needs of a customer such as changing the status associated with a document, forwarding documents for approval and so forth. SAP Document Center provides us a possibility to include these custom actions to full fill specific business needs. The custom actions are henceforth referred to as business actions.
In a recent use-case, one of our customers wanted to store architectural images of certain plot designs. They were interested in performing two business actions: first was to open their internal plot management portal from the document, and another action was to approve or reject the document, based on the details in the tool. They had plans of executing these tasks several thousand times over the next few years. The workflow process of approval/rejection had to be performed in their SAP ERP on-premise systems. So in this blog, let us look into how they can achieve this process from SAP Document Center.
Pre-requisite: A CMIS complaint ABAP Repository that has been integrated as a Corporate Repository with SAP Document Center (
Reference guide to set this up). Knowledge on CMIS Workbench, and Secondary types would be beneficial.
From our customer requirements perspective, we classified the business action into the following 2 types:
- Business action that invokes a URL to open a web page where user can interact with the application to make further actions
- Business action that triggers any remote backend call to the ABAP repository and show result of the operation in SAP Document Center
This blog is structured into two parts. In the first part, we will look into how our customer enabled a business action that invokes a URL and in the latter part we will show how they implemented a Business action that invokes a backend call. For adding a business action, a secondary CMIS type needs to be created and an extension action should be attached to it. In this blog, the backend repository is in ABAP, hence we implement the business actions in ABAP. A similar implementation can also be performed in Microsoft Sharepoint, Alfresco or any other CMIS repository implementation. We can implement the business actions either at the folder level or at the document level (explained in step 7).
Adding a Business Action to invoke a URL from a repository:
To add this business action we need to add a few lines of code shown in the following steps:
1. Identify the repository where the business actions has to be supported. In SAP Document Center admin UI, we can see the CMIS Repository ID of all the corporate repositories; note down the CMIS Repository ID of the identified repository as shown in the below image.
Go to transaction code SM30 and navigate to the contents of view CMISD_SERVICE. Match the repository id column with the CMIS Repository ID from the admin UI and retrieve the corresponding class name of the repository implementation. If it is a standard SAP delivered class, then a custom Z class should be created inheriting the original standard class. You may need to add this custom class as another entry in the view in SM30 and also add it in the admin UI to be able to use it in SAP Document Center. If the class was a custom created class as shown in the image below, then proceed to Step 2.
2. Go to SE24 transaction code and edit the custom class to which the business action should be added. This class extends the abstract class, CL_CMIS_ABSTRACT_SERVICE. The custom class is referred as <YOUR_CLASS_NAME> in the code snippets below. Define a global constant with a value, say, "MYBUSINESSACTION", which is referred to as <YOUR_GLOBAL_CONSTANT> in the code snippets below:
3. Define a private method GET_BUSINESS_ACTIONS_TYPES as per the below signature. The logic to define the additional properties and corresponding extension definitions for all the business actions are implemented here.
METHOD GET_BUSINESS_ACTIONS_TYPES.
DATA: ls_prop_def TYPE cmis_s_property_definition,
lv_ba_desc TYPE string,
action_ext TYPE cmis_s_extension,
ext_name TYPE cmis_s_extension,
ext_button TYPE cmis_s_extension,
ext_position TYPE cmis_s_extension,
ext_version TYPE cmis_s_extension.
* Create a secondary type
lv_ba_desc = 'MyDescription'. "Secondary type description
es_ba_type-id = gc_ba_id_for_url.
es_ba_type-local_name = lv_ba_desc.
es_ba_type-local_namespace = 'com.sap.sdc'.
es_ba_type-display_name = lv_ba_desc.
es_ba_type-query_name = gc_ba_id_for_url.
es_ba_type-description = lv_ba_desc.
es_ba_type-base_id =
cl_cmis_constants=>base_type_id-cmis_secondary.
es_ba_type-parent_id =
cl_cmis_constants=>base_type_id-cmis_secondary.
es_ba_type-creatable = abap_false.
es_ba_type-fileable = abap_false.
es_ba_type-queryable = abap_false.
es_ba_type-full_text_indexed = abap_false.
es_ba_type-included_in_supertype_query = abap_false.
es_ba_type-controllable_policy = abap_false.
es_ba_type-controllable_acl = abap_false.
es_ba_type-type_mutability-create = abap_false.
es_ba_type-type_mutability-delete = abap_false.
es_ba_type-type_mutability-update = abap_true.
CLEAR ls_prop_def.
*Create property and extension for corresponding business action
ls_prop_def-id = 'ACTION:OpenURL'.
ls_prop_def-local_name = 'ACTION:OpenURL'.
ls_prop_def-local_namespace = 'com.sap.dms'.
ls_prop_def-display_name = 'OpenURL'.
ls_prop_def-query_name = 'ACTION:OpenURL'.
ls_prop_def-description = 'OpenURL'.
ls_prop_def-property_type =
cl_cmis_constants=>property_type-string.
ls_prop_def-cardinality =
cl_cmis_constants=>cardinality-single.
ls_prop_def-updatability =
cl_cmis_constants=>updatability-read_write.
ls_prop_def-inherited = abap_false.
ls_prop_def-required = abap_false.
ls_prop_def-queryable = abap_false.
ls_prop_def-orderable = abap_false.
ls_prop_def-openchoice = abap_false.
action_ext-name = 'mcm:action'.
action_ext-transport_id = 'mcm:action'.
ext_name-name = 'localizedDisplayName'.
ext_name-transport_id = '1'.
ext_name-transport_parent_id = action_ext-transport_id.
ext_name-value = 'OpenURL'.
ext_button-name = 'renderAsButton'.
ext_button-transport_id = '2'.
ext_button-transport_parent_id = action_ext-transport_id.
ext_button-value = 'true'.
ext_position-name = 'positionWeight'.
ext_position-transport_id = '3'.
ext_position-transport_parent_id = action_ext-transport_id.
ext_position-value = '1'.
APPEND action_ext TO ls_prop_def-extensions.
APPEND ext_name TO ls_prop_def-extensions.
APPEND ext_button TO ls_prop_def-extensions.
APPEND ext_position TO ls_prop_def-extensions.
APPEND ls_prop_def TO es_ba_type-property_definitions.
ENDMETHOD.
4. Fetch the definition of the secondary type added for business actions while fetching the type definitions defined for the repository by enhancing the implementation of the IF_CMIS_SERVICE~GET_TYPE_DEFINITION method.
METHOD if_cmis_service~get_type_definition.
IF iv_type_id = gc_ba_id_for_url.
CALL METHOD get_business_actions_types
EXPORTING
iv_type_id = iv_type_id
IMPORTING
es_ba_type = es_type.
ELSE.
CALL METHOD super->if_cmis_service~get_type_definition
EXPORTING
iv_repository_id = iv_repository_id
iv_type_id = iv_type_id
IMPORTING
es_type = es_type.
ENDIF.
ENDMETHOD.
5. Fetch the definition of the secondary type added for business actions while fetching the type children by enhancing the implementation of the private method GET_SECONDARY_OBJECTTYPES.
METHOD if_cmis_service~get_type_children.
CALL METHOD super->if_cmis_service~get_type_children
EXPORTING
iv_repository_id = iv_repository_id
iv_type_id = iv_type_id
iv_include_prop_definitions = iv_include_prop_definitions
iv_max_items = iv_max_items
iv_skip_count = iv_skip_count
IMPORTING
es_types = es_types.
IF iv_type_id EQ cl_cmis_constants=>base_type_id-cmis_secondary.
DATA ls_type TYPE cmis_s_type_definition.
get_business_actions_types(
EXPORTING
iv_type_id = iv_type_id
IMPORTING
es_ba_type = ls_type " CMIS Type Definition
).
APPEND ls_type TO es_types-types.
ENDIF.
ENDMETHOD.
6. The cmis object definition has to be enhanced in order to supply the additional property, which carries the business context and the action to be carried out. Create an additional method ENRICH_CMIS_OBJECT with the below signature.
METHOD enrich_cmis_object.
FIELD-SYMBOLS <ls_property> TYPE cmis_s_property.
READ TABLE cs_cmis_object-properties-properties ASSIGNING <ls_property> WITH KEY id = 'cmis:secondaryObjectTypeIds'.
IF sy-subrc = 0.
DATA ls_property_value TYPE cmis_s_property_value.
ls_property_value-string_value = gc_ba_id_for_url.
APPEND ls_property_value TO <ls_property>-value.
APPEND INITIAL LINE TO cs_cmis_object-properties-properties ASSIGNING <ls_property>.
cl_cmis_object_factory=>create_string_prop_single(
EXPORTING
iv_property_id = gc_ba_id_for_url && ':OpenURL'
iv_value = 'https://www.sap.com'
RECEIVING
rs_property = <ls_property>
).
ENDIF.
ENDMETHOD.
7. Redefine the methods IF_CMIS_SERVICE~GET_OBJECT and IF_CMIS_SERVICE~GET_CHILDREN to call the enrich method. Currently these calls are not based on any condition. But conditions can be introduced for example, the business action should be included only for files and not folders or the business action should be applied to files of a certain type and so forth.
METHOD if_cmis_service~get_object.
CALL METHOD super->if_cmis_service~get_object
EXPORTING
iv_repository_id = iv_repository_id
iv_object_id = iv_object_id
iv_include_acl = iv_include_acl
iv_filter = iv_filter
iv_include_relationships = iv_include_relationships
iv_rendition_filter = iv_rendition_filter
iv_include_allowable_actions = iv_include_allowable_actions
IMPORTING
es_object = es_object.
enrich_cmis_object( changing cs_cmis_object = es_object ).
ENDMETHOD.
METHOD if_cmis_service~get_children.
CALL METHOD super->if_cmis_service~get_children
EXPORTING
iv_repository_id = iv_repository_id
iv_folder_id = iv_folder_id
iv_max_items = iv_max_items
iv_skip_count = iv_skip_count
iv_order_by = iv_order_by
iv_filter = iv_filter
iv_include_relationships = iv_include_relationships
iv_rendition_filter = iv_rendition_filter
iv_include_allowable_actions = iv_include_allowable_actions
iv_include_path_segment = iv_include_path_segment
IMPORTING
es_children = es_children.
FIELD-SYMBOLS <ls_object_in_folder> TYPE cmis_s_object_in_folder.
LOOP AT es_children-objects_in_folder ASSIGNING <ls_object_in_folder>.
enrich_cmis_object( changing cs_cmis_object = <ls_object_in_folder>-object ).
ENDLOOP.
ENDMETHOD.
That’s it. Now our Customer can invoke the business action to open a URL from the corresponding repository in the web-app as shown below:
On clicking OpenURL Button, the link provided in the enrich_cmis_object method will be invoked.
The action can also be seen in the properties of the file as below:
The secondary type, 'MYBUSINESSACTION' and the action ''ACTION:OpenURL' can be found in the CMIS workbench by clicking on the Types button as shown:
Now moving on to the second part, let us see how our customer included another business action to approve the plot design documents. This requires triggering a backend code which could be an RFC, a class method or a function module in the ABAP repository. In the example shown below, we invoke another class method.
Adding a Business Action to trigger ABAP backend code:
8. Add the following lines of code to the GET_BUSINESS_ACTIONS_TYPES method that we created in Step 3.
METHOD GET_BUSINESS_ACTIONS_TYPES.
.........................
...Code added in Step 3...
.........................
DATA: lt_localization TYPE cmis_t_map,
ls_locale TYPE cmis_s_key_value.
*Create property and attach extension for the second business action
CLEAR ls_prop_def.
ls_prop_def-id = 'ACTION:Approve'.
ls_prop_def-local_name = 'ACTION:Approve'.
ls_prop_def-local_namespace = 'com.sap.dms'.
ls_prop_def-display_name = 'Approve'.
ls_prop_def-query_name = 'ACTION:Approve'.
ls_prop_def-description = 'Approve'.
ls_prop_def-property_type =
cl_cmis_constants=>property_type-string.
ls_prop_def-cardinality =
cl_cmis_constants=>cardinality-single.
ls_prop_def-updatability =
cl_cmis_constants=>updatability-read_write.
ls_prop_def-inherited = abap_false.
ls_prop_def-required = abap_false.
ls_prop_def-queryable = abap_true.
ls_prop_def-orderable = abap_true.
ls_prop_def-openchoice = abap_true.
action_ext-name = 'mcm:action'.
action_ext-transport_id = 'mcm:action'.
ext_name-name = 'localizedDisplayName'.
ext_name-transport_id = '1'.
ext_name-transport_parent_id = action_ext-transport_id.
*Business Action name that is displayed on the Web UI can be changed
*according to the location/language settings
CLEAR lt_localization.
CLEAR ls_locale.
ls_locale-id = 'default'.
ls_locale-value = 'Approve'.
APPEND ls_locale TO lt_localization.
CLEAR ls_locale.
ls_locale-id = 'en'.
ls_locale-value = 'Approve'.
APPEND ls_locale TO lt_localization.
CLEAR ls_locale.
ls_locale-id = 'de'.
ls_locale-value = 'Genehmigen'.
APPEND ls_locale TO lt_localization.
ext_name-attributes = lt_localization.
ext_name-value =
'{"default": "Approve", "en":"Approve",' &&
'"en-US":"Approve","de":"Genehmigen"}'.
* Note that the default key is mandatory
ext_button-name = 'renderAsButton'.
ext_button-transport_id = '2'.
ext_button-transport_parent_id = action_ext-transport_id.
ext_button-value = 'true'.
ext_position-name = 'positionWeight'.
ext_position-transport_id = '3'.
ext_position-transport_parent_id = action_ext-transport_id.
ext_position-value = '2'.
ext_version-name = 'version'.
ext_version-transport_id = '4'.
ext_version-transport_parent_id = action_ext-transport_id.
ext_version-value = '1.0'.
APPEND action_ext TO ls_prop_def-extensions.
APPEND ext_name TO ls_prop_def-extensions.
APPEND ext_button TO ls_prop_def-extensions.
APPEND ext_position TO ls_prop_def-extensions.
APPEND ext_version TO ls_prop_def-extensions.
APPEND ls_prop_def TO es_ba_type-property_definitions.
ENDMETHOD.
9. Enhance the ENRICH_CMIS_OBJECT method to support the new property definition. Add a new property.
METHOD enrich_cmis_object.
FIELD-SYMBOLS <ls_property> TYPE cmis_s_property.
READ TABLE cs_cmis_object-properties-properties ASSIGNING
<ls_property> WITH KEY id = 'cmis:secondaryObjectTypeIds'.
IF sy-subrc = 0.
DATA ls_property_value TYPE cmis_s_property_value.
ls_property_value-string_value = gc_ba_id_for_url.
APPEND ls_property_value TO <ls_property>-value.
APPEND INITIAL LINE TO cs_cmis_object-properties-properties
ASSIGNING <ls_property>.
cl_cmis_object_factory=>create_string_prop_single(
EXPORTING
iv_property_id = gc_ba_id_for_url && ':OpenURL'
iv_value = 'https://www.sap.com'
RECEIVING
rs_property = <ls_property>
).
*Begin of additional code
APPEND INITIAL LINE TO cs_cmis_object-properties-properties
ASSIGNING <ls_property>.
cl_cmis_object_factory=>create_string_prop_single(
EXPORTING
iv_property_id = gc_ba_id_for_url && ':Approve'
iv_value = space
RECEIVING
rs_property = <ls_property>
).
*End of additional code
ENDIF.
ENDMETHOD.
10.
We will set the DIR status (DIR is a Document Info Record that contains the metadata of a Document in SAP DMS) that backs the file displayed. Create a new method SET_DOCUMENT_STATUS with the following signature. Add the exception type CX_CMIS_NOT_SUPPORTED to the method definition as well.
METHOD set_document_status.
DATA : ls_bapi_msg TYPE bapiret2,
lv_return_msg TYPE string.
CALL FUNCTION 'BAPI_DOCUMENT_SETSTATUS'
EXPORTING
documenttype = iv_dokar
documentnumber = iv_doknr
documentpart = iv_doktl
documentversion = iv_dokvr
statusextern = iv_dokst
statusintern = iv_dokst
* STATUSLOG = 'Updated'
IMPORTING
return = ls_bapi_msg.
IF ls_bapi_msg-type EQ 'E' OR ls_bapi_msg-type EQ 'W'
OR ls_bapi_msg-type EQ 'A'.
CALL FUNCTION 'BAPI_TRANSACTION_ROLLBACK'.
CONCATENATE ls_bapi_msg-type ls_bapi_msg-message INTO
lv_return_msg SEPARATED BY space.
RAISE EXCEPTION TYPE cx_cmis_not_supported
EXPORTING
message_text = lv_return_msg.
ELSE.
CALL FUNCTION 'BAPI_TRANSACTION_COMMIT'.
ENDIF.
ENDMETHOD.
11. Introduce the call to the method in the IF_CMIS_SERVICE~UPDATE_PROPERTIES method to perform the call.
METHOD if_cmis_service~update_properties.
FIELD-SYMBOLS <ls_property> TYPE cmis_s_property.
READ TABLE is_properties-properties WITH KEY id = 'ACTION:Approve'
ASSIGNING <ls_property>.
IF sy-subrc = 0.
" Sample backend code invocation
DATA ls_file_id TYPE doc_s_file_id.
DATA lv_doc_status TYPE bapi_doc_draw-statusintern.
DATA lv_len TYPE i.
lv_len = strlen( iv_object_id ).
IF lv_len > 33.
ls_file_id = iv_object_id.
lv_doc_status ='Z4'. "update the document status to approved.
CALL METHOD me->set_document_status
EXPORTING
iv_dokar = ls_file_id-dokar
iv_doknr = ls_file_id-doknr
iv_doktl = ls_file_id-doktl
iv_dokvr = ls_file_id-dokvr
iv_dokst = lv_doc_status.
ENDIF.
RETURN.
ENDIF.
CALL METHOD super->if_cmis_service~update_properties
EXPORTING
iv_repository_id = iv_repository_id
iv_object_id = iv_object_id
is_properties = is_properties
iv_change_token = iv_change_token
IMPORTING
ev_change_token = ev_change_token
ev_object_id = ev_object_id.
ENDMETHOD.
Yes, that is it. Similarly another business action, perhaps to reject a document, could be added by repeating steps 8 to 11. Now our customer can open a URL or approve a document based on the backend functionality as shown below:
Again by clicking on "Types" button in the CMIS workbench we can view the second action for Approve appended to the same "MYBUSINESSACTION" secondary type.
By clicking on "Save Type Definition" button on the top we can save the JSON schema as shown.
"ACTION:OpenURL":{
"maxLength":0,
"id":"ACTION:OpenURL",
"localName":"ACTION:OpenURL",
"localNamespace":"com.sap.dms",
"displayName":"Open a URL",
"queryName":"ACTION:OpenURL",
"description":"Open a URL",
"propertyType":"string",
"cardinality":"single",
"updatability":"readwrite",
"inherited":false,
"required":false,
"queryable":false,
"orderable":false,
"openChoice":false,
"mcm:action":{
"localizedDisplayName":"OpenURL",
"renderAsButton":"true",
"positionWeight":"1"
}
},
"ACTION:Approve":{
"maxLength":0,
"id":"ACTION:Approve",
"localName":"ACTION:Approve",
"localNamespace":"com.sap.dms",
"displayName":"Approve",
"queryName":"ACTION:Approve",
"description":"Approve",
"propertyType":"string",
"cardinality":"single",
"updatability":"readwrite",
"inherited":false,
"required":false,
"queryable":true,
"orderable":true,
"openChoice":true,
"mcm:action":{
"localizedDisplayName":"{\"en\":\"Approve\",\"en-US\":\"Approve\",\"de\":\"Genehmigen\"}",
"renderAsButton":"true",
"positionWeight":"2",
"version":"1.0"
}
}
If this implementation needs to be performed on another repository such as MS Sharepoint or Alfresco, corresponding implementation to generate this JSON schema must be added. SAP Document Center allows us to handle business actions on any backend repository, be it in the cloud or on-premise. This is quite unique and powerful because of the flexibility to support business actions on documents or folders of any repository.
Hope this blog helps you to add your necessary business actions. If you come across interesting insights, challenges or tips feel free to post your comments below. All the best and happy coding.