Hi everyone,
A few weeks ago, I was looking for ways to get documents (pictures, PDF files) from SAP DMS to my iPhone. This might be possible via a direct connection between the content server and the iPhone. However, I didn't like exposing the content server in this way and was hoping to use some kind of Internet service in the R/3 system.
It turned out that this was quite easy to do with a custom REST service. It came with a nice bonus; I can also use the service in my browser and it will start the appropiate program (Acrobat Reader, Powerpoint, ...) as well. The last part depends on the correct configuration of DMS customizing, I will show you at the end of the blog how to do that.
The requirement was quite simple. I want an URL, similar to the RFC via SOAP URLs, to which I can pass an HTTP request with the key of the DMS document. I want response to contain the (binary) content of the document. Documents in DMS can be identified uniquely using the four key fields:
(DRAW-DOKAR)
(DRAW-DOKNR)
(DRAW-DOKVR)
(DRAW-DOKTL)
The simplest way of passing the document key is via an HTTP GET call, where the parameters are appended to the URL. So if the service URL is http://<sapserver>:<port>/sap/bc/zdoc
, the GET call is http://<sapserver>:<port>/sap/bc/zdoc?type=<type>&number=<number>&version=<version>&part=<part>
You can create custom HTTP services in transaction SICF. In the selection screen, choose Execute (F8) and the following screen will appear:
Within the tree, you will find some frequently used services. For example, BSP pages can be found under path default_host/sap/bc/bsp/sap/
, while WebDynpros reside under default_host/sap/bc/webdynpro/sap/
. The path in the tree corresponds to the resulting URL of the service.
To create a new service, right-click on its parent and choose New Subelement from the context menu.
Dismiss the popup with a warning that SAP upgrades might overwrite the new service. A new popup appears where you must provide the name of the service:
After you choose Input (Enter), a screen appears where you can enter more information. At this moment, nothing is needed but you can enter a simple description. Choose Store (Ctrl + S) afterwards; because a service is a repository object you must either declare it as local or put it in a transport request.
When you go back, you can see the service in the tree. It is still gray because it isn't active yet.
The next step is to create ABAP code which handles a call to the zdoc
service. For this, we need a class which implements the IF_HTTP_EXTENSION
interface. Start transaction SE24, enter a class name and choose Create (F5).
In the next popup, enter a description and choose Save (Enter). Like with the service, a popup will appear for choosing a transport request.
On the tabpage Interfaces, type IF_HTTP_EXTENSION
on the first line and press Enter.
If you go to tabpage Methods, you will see the interface method HANDLE_REQUEST. Double-click it, and confirm the popup that you want to save the class.
Replace the method with the following code:
METHOD if_http_extension~handle_request.
DATA:
lr_request TYPE REF TO if_http_request,
lr_response TYPE REF TO if_http_response,
lv_value TYPE string,
lv_data TYPE xstring,
ls_draw TYPE draw,
ls_checkout_def TYPE dms_checkout_def,
ls_doc_file TYPE dms_doc_file,
ls_phio TYPE dms_phio,
ls_file TYPE cvapi_doc_file,
lt_files TYPE TABLE OF cvapi_doc_file,
lt_content TYPE TABLE OF drao.
FIELD-SYMBOLS <fs_content> TYPE drao.
* 1.
lr_request = server->request.
lr_response = server->response.
IF lr_request->get_method( ) EQ 'GET'.
* 2.
* Retrieve document key
lv_value = lr_request->get_form_field( 'type' ).
ls_draw-dokar = lv_value.
lv_value = lr_request->get_form_field( 'number' ).
ls_draw-doknr = lv_value.
lv_value = lr_request->get_form_field( 'version' ).
ls_draw-dokvr = lv_value.
lv_value = lr_request->get_form_field( 'part' ).
ls_draw-doktl = lv_value.
* 3.
* Check document existence and read details
CALL FUNCTION 'CVAPI_DOC_GETDETAIL'
EXPORTING
pf_dokar = ls_draw-dokar
pf_doknr = ls_draw-doknr
pf_dokvr = ls_draw-dokvr
pf_doktl = ls_draw-doktl
IMPORTING
psx_draw = ls_draw
TABLES
pt_files = lt_files
EXCEPTIONS
not_found = 1
OTHERS = 2.
IF sy-subrc NE 0.
* Put the error in the response
MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno
WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4 INTO lv_value.
lr_response->set_cdata( lv_value ).
lr_response->set_status( code = 400 reason = 'Document not found!' ).
EXIT.
ENDIF.
* 4.
* Convert file information
READ TABLE lt_files INTO ls_file INDEX 1.
MOVE-CORRESPONDING ls_file TO: ls_doc_file, ls_phio.
* Checkout document
SELECT SINGLE kpro_use FROM tdwa INTO ls_checkout_def-kpro_use
WHERE dokar EQ ls_draw-dokar.
ls_checkout_def-comp_get = 'X'.
ls_checkout_def-content_provide = 'TBL'.
CALL FUNCTION 'CV120_DOC_CHECKOUT_VIEW'
EXPORTING
ps_cout_def = ls_checkout_def
ps_doc_file = ls_doc_file
ps_draw = ls_draw
ps_phio = ls_phio
TABLES
ptx_content = lt_content
EXCEPTIONS
error = 1
OTHERS = 2.
IF sy-subrc NE 0.
* Put the error in the response
MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno
WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4 INTO lv_value.
lr_response->set_cdata( lv_value ).
lr_response->set_status( code = 400 reason = 'Failed to read document!' ).
EXIT.
ENDIF.
* 5.
* Put document content in response
LOOP AT lt_content ASSIGNING <fs_content>.
CONCATENATE lv_data <fs_content>-orblk INTO lv_data IN BYTE MODE.
ENDLOOP.
lv_data = lv_data(<fs_content>-orln).
lr_response->set_data( lv_data ).
lr_response->set_status( code = 200 reason = '' ).
* 6.
* Set MIME type
SELECT SINGLE mimetype FROM tdwp INTO lv_value
WHERE dappl EQ ls_file-dappl.
IF sy-subrc EQ 0.
lr_response->set_header_field( name = 'Content-Type' value = lv_value ).
ENDIF.
ENDIF.
ENDMETHOD.
Some explanation of the code:
handle_request
has one parameter called server
. For us, only the HTTP request and the HTTP response are important. First of all, the HTTP method of the request is checked. CVAPI_DOC_GETDETAIL
serves two purposes: first, the existence of the document is checked; second, some of the details needed to view the document are retrieved. If there is an error, the error message is put into the body of the response (by method set_cdata
) so the user will see it in the browser. The HTTP status is a kind of error/success code. CV120_DOC_CHECKOUT_VIEW
with the right parameters. Again, if there is an error, it is returned in the response. set_data
for binary data, instead of set_cdata
which is suitable for character data only. After activating the handler class, you must indicate that requests to the ZDOC service are handled by this class. Return to transaction SICF and open the service (hint: you can enter the service name on the selection screen). Select tab Handler List, enter the class name and choose Store (Ctrl + S).
Return to the previous screen and choose Activate Service from the context menu. You'll have to confirm this in the popup that appears.
After activation, you can test the service by choosing Test Service from the same context menu. A browser window will open, asking you for your SAP credentials.
Notice that the SAP client is also a parameter in the URL. If you omit it, the request will be processed by the default client. The service will return some error message because you didn't specify a document yet.
To perform a real test, lookup the key of some DMS document in transaction CV03N.
If you extend the URL in the address bar with&type=<type>&number=<number>&version=<version>&part=<part>
so that it becomes something likehttp://10.150.1.232:8000/sap/bc/zdoc?sap-client=800&type=Z01&number=0000000000000010000000239&versio...
the document will appear on your screen. Please pay attention to the leading zeroes!