Skip to Content

DMS documents via HTTP service

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

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:

  1. Type (DRAW-DOKAR)
  2. Number (DRAW-DOKNR)
  3. Version (DRAW-DOKVR)
  4. Part (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>


Creating the service

You can create custom HTTP services in transaction SICF. In the selection screen, choose Execute (F8) and the following screen will appear:

2-001.PNG

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.

2-002.PNG

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:

2-003.PNG

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.

2-004.PNG

When you go back, you can see the service in the tree. It is still gray because it isn’t active yet.

2-005.PNG


Creating a handler

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).

2-006.PNG

In the next popup, enter a description and choose Save (Enter). Like with the service, a popup will appear for choosing a transport request.

2-007.PNG

On the tabpage Interfaces, type IF_HTTP_EXTENSION on the first line and press Enter.

2-008.PNG

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.

2-009.PNG

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:

  1. The method 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.
  2. The document key is extracted from the URL. Note that the URL construct is regarded as a kind of form.
  3. The call of function module 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.
  4. The document must be ‘checked out’ to an internal table, which is done by calling function module CV120_DOC_CHECKOUT_VIEW with the right parameters. Again, if there is an error, it is returned in the response.
  5. The (binary) data in the internal table is put into a (binary) string of the correct length and put into the response. Note that we use the method set_data for binary data, instead of set_cdata which is suitable for character data only.
  6. Your operating system can tell if a file is a picture or a PDF file by looking at its extension. Within HTTP, we don’t have extensions, so we have to use another way to indicate the type of content. This is the MIME type. The MIME type should be maintained within DMS customizing, on something that is called a Workstation Application. Start transaction SPRO, and start customizing activity Cross-Application Components -> Document Management -> General Data -> Define Workstation Application. If you double click on a row, you can enter a mime type:
    2-010.PNG
    This is an important step, because the browser will not be able to select the right program or plugin to open the file. Otherwise, you might see just some unintelligible text.

Assign the handler to the service

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).

2-011.PNG

Return to the previous screen and choose Activate Service from the context menu. You’ll have to confirm this in the popup that appears.

2-012.PNG

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.

2-013.PNG

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.

2-014.PNG

To perform a real test, lookup the key of some DMS document in transaction CV03N.

2-015.PNG

If you extend the URL in the address bar with
&type=<type>&number=<number>&version=<version>&part=<part>
so that it becomes something like
http://10.150.1.232:8000/sap/bc/zdoc?sap-client=800&type=Z01&number=0000000000000010000000239&version=01&part=000
the document will appear on your screen. Please pay attention to the leading zeroes!

2-016.PNG

To report this post you need to login first.

15 Comments

You must be Logged on to comment or reply to a post.

    1. Gerwin de Groot Post author
      Yes, I thought about creating/changing files as well, though I didn’t implement it yet. I suppose you could use a HTTP POST or PUT request, detect the method in the ZCL_HTTP_ZDOC class and use standard functions in DMS (e.g. ) to change the document.

      However, it is more difficult to make such a request in the browser. You could use an HTML form with an ‘Upload file’ functionality to make a POST request, but this doesn’t work for the iPhone browser (it won’t let you save files on disk, unless you use another app to open the file). You probably will have to create a custom app, where you can define an HTTP request manually.

      (0) 
        1. Gerwin de Groot Post author
          I don’t know much about WebDMS, but it is a complete application. My service is intended as a building block in for example an iPhone app or a .NET application.
          (0) 
    1. Gerwin de Groot Post author
      Hi Uwe,

      That is a very good suggestion, to conform the URL to standard REST policies. In that case, you have to replace the code:

      *   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.

      with:

      *   Retrieve document key
          lv_value = lr_request->get_header_field( name = ‘~PATH_INFO’ ).
          SPLIT lv_value AT ‘/’ INTO lv_value
            ls_draw-dokar ls_draw-doknr ls_draw-dokvr ls_draw-doktl.

      (0) 
  1. Christoph Brinster

    Hello Gerwin,

    I read your article with great interest. Is it possible to get into the screenshots. The embedded URLs in your posts do not work.

    Christoph

    (0) 
    1. Gerwin de Groot Post author

      Hi Frank,

      Apparently, SAP doesn’t know how to migrate blog posts from one system to another without a) removing the images and b) mangling the example code up to a point where it is unreadable. You’d almost think they use their own developed systems for this.

      Fortunately, I was able to reconstruct the entire post from what was left on my computer.

      SAP, where can I send an invoice for this lost hour?

      Best regards,

      Gerwin

      (0) 
  2. Martin Richter

    Hi,

    I am getting the Message “Im Puffer ist kein Dokumentdatensatz vorhanden” (Buffer does not contain document info record)

    … i am sure that the DIR does exist and the url should be okay. Because when I type in a obviously wrong document number i get the message that the DIR does not exist.

    URL: http://Here is my server name:8000/sap/bc/zdoc?sap-client=100&type=VED&number=0000000000000010000000250&version=01&part=000

    What is wrong? What can I do to get this work?

    I have stored my originals (4 files) in the default storage category. How does the method know which file to download? I need only one file out of my DIR.

    Thanks well,

    Martin

    (0) 
  3. Paul McNamara

    Hi,
    I’m a bit late to this and having an issue hoping someone can help with.

    Using an incorrect document number I get:

    Document QQA/T9999.9988/004/07 does not exist
    So the find is working ok.

    Using a correct number I get:

    Kerberos library not loaded.

    Tried various MIMe settings.
    Any suggestions appreciated.
    thanks

     

    (0) 

Leave a Reply