Adding ABAP logic

Objectives

After completing this lesson, you will be able to:

  • Implement the behavior of a Business Object

Validations

Checking the Semantic Key

In the ABAP RESTful application programming model, the key of a database table is often made up of the client field and a UUID field, whose value is assigned automatically by the runtime when you create a new instance of the business object. This field combination is sufficient to ensure that the system can identify each record in the table uniquely. However, as well as this technical key, our object also has a semantic key - in this case the combination of airline and flight number, which must also be unique according to the business logic. In order to ensure the uniqueness of this field combination, you must implement your own check in the form of a validation.

You declare validations in the behavior definition of the CDS view entity, and implement them in the behavior implementation class.

Input Checks in the App

As well as checking the semantic key, there are other checks that you need to perform. For example, although the generated app allows you to create, read, update and delete data, it does not yet contain any consistency checks. Consequently, you can create flight connections for airlines that don't exist, or where the departure and destination airports are the same.

To prevent this from happening, you define further validations in the behavior definition, and implement them in the behavior implementation class.

Creating Message Texts

Before you create the validation, you must create the texts that you want to display. You do this using a message class. A message class is a collection of up to 1000 messages that belong to a particular application area. As shown in the figure, each text has a number which identifies the message uniquely within the message class.

To create a new message class, proceed as follows:

  1. Choose File → New → Other… and type message into the filter field.

  2. Double-click the Message Class entry in the hit list, then enter a package, name, and description for the new message class. Choose Next.

  3. Assign the message class to a transport request and choose Finish.

Messages can also contain placeholders, which are replaced with concrete values when the message is displayed. Placeholders are denoted by the ampersand symbol followed by a number. You can use up to four placeholders in each message.

Defining the Validation

To define a validation, you add a validation declaration to the behavior definition of your business object. In this example, the validation should be performed whenever the user saves a data record, and this could be either when they create the record or if they subsequently change it.

When you define the validation in the behavior definition, a warning tells you that the corresponding method does not exist. Use a quick fix (key combination CTRL + 1) to add the method to the behavior implementation. The behavior implementation is a local class within your behavior pool. The method definition contains the addition FOR VALIDATE ON SAVE, which identifies it as the implementation of the validation. It has an importing parameter KEYS. This is an internal table containing the keys of the created or changed objects. You use these to read the actual data that the user has entered.

The addition FOR Connection~CheckSemanticKey links the method with the validation CheckSemanticKey from the behavior definition. Here, Connection is the alias name of the view entity Z_R_CONNECTION.

When you define a validation, you must also create its implementation. This is a method in the behavior pool. The easiest way to do this is to use a quick fix. Position the cursor on the name of the validation and press CTRL + 1. ADT proposes to create the method. Double-click the proposal to create the method.

The Validation Process

Note
Some code examples in this section use SELECT statements inside of loops. This has been done to keep the examples simple. Note that SELECTs in loops can cause performance problems and should be avoided.

When the system triggers a validation, it calls the corresponding implementation. The importing parameter KEYS contains the keys of the data records that have been changed. You use the keys to read the fields of the records that you need using Entity Manipulation Language (EML). EML is a special set of statements in ABAP that allows you to address business objects.

Once you have read the data, you can perform the checks that you need. If the check fails, you will need to issue an appropriate error message and, importantly, tell the framework not to write the changes to the database.

The first task in a validation is to read the user input. You do this using the Entity Manipulation Language (EML) statement READ ENTITIES. The keys of the new data records are passed to the validation using the importing parameter keys.

The fields that you need to validate the semantic key are CarrierID for the airline and ConnectionID for the flight number. You also need the UUID field.

The code snippet uses the corresponding operator and an inline declaration for the result set. Below, you see the equivalent code using explicitly-defined variables, which makes it easier to understand the types that are used.

Code snippet

DATA read_keys   TYPE TABLE FOR READ IMPORT zs4d400_r_connection.
DATA connections TYPE TABLE FOR READ RESULT zs4d400_r_connection. 

read_keys = CORRESPONDING #( keys ). 

READ ENTITIES OF zs4d400_r_connection IN LOCAL MODE
       ENTITY Connection
       FIELDS ( uuid CarrierID ConnectionID ) 
         WITH read_keys
       RESULT connections. 

Expand

Once you have read the user input, you can use the values of CarrierID and ConnectionID to see if this semantic key has already been used in another data set than the one you are processing just now. Since the key combination could be in either the active table or the draft table, you need to look in both, and the most efficient way to do this is with a union.

The result set of this query should always be empty. If not, there are more records with the same combination of CarrierID and ConnectionID, this means that the record that the user is currently trying to create is a duplicate, and must be rejected.

If the combination of carrier ID and connection ID already exists, there will be an entry in the table check_result. In this case, you must issue a message.

The first step is to create a message object. You do this using the self-reference me and calling the method new_message( ). The parameters ID, number, and severity are mandatory. ID is the name of the message class that contains the message; number is the message number. Severity classifies the message as a success, information, warning, or error message. The behavior implementation class contains a structured constant ms whose components represent the different severity levels. In this case, you need the severity level ms-error.

The method also has optional importing parameters v1, v2, v3, and v4. You use these to replace placeholders with concrete values. In this example, the placeholder &1 is replaced with the airline code, placeholder &2 is replaced with the flight number.

The result of the method call is an object reference. In the next step, you will pass the object to the runtime so that the error message is returned the OData service and displayed in the app preview.

To make the runtime display a message, you must report it using the reported structure. This is an implicit changing parameter of all validation methods and is a deep structure. It contains a component with the alias name of the business object. This component is an internal table.

To report the message, you must do three things:

  1. Add the key of the affected record to the internal table. You can do this using the field group %tky. When you group fields like this, you can address the name of the group instead of having to address each field individually.
  2. Attach the message object to the table. You do this by assigning the object reference of the message object to the %msg component of the internal table.
  3. Bind the message to the affected field. This ensures that the field is emphasized in the app. This, in turn, helps the user to navigate the app better. You do this using the %element component of the internal table.

In this example, reported_record is a structure with the line type of the internal table reported-connection. You fill the %tky component with the contents of the %tky field group in the data record. This is the structure line that we used as a work area for the internal table containing the data that the user entered. Next, you assign the message object that you created using the new_message( ) method to the %msg component. Finally, to bind the message to the field CarrierID, you use the structure %element. %element is a structure, and contains a component for each element in the view entity. If you set a component to "true", the corresponding input field will be highlighted in the app. You do this using the structured constant if_abap_behv=>mk. This has the component on for checked/true and off for unchecked/false.

You cannot use the global constants abap_true and abap_false at this point, as their data types are not compatible.

As well as issuing the message, you must also tell the runtime not to save the incorrect data. To do this, you use the failed structure of the validation method. Failed is an implicit changing parameter that is present in all validation methods.

To report a record as failed, add its field group %tky to the field group %tky of the internal table failed-Connection.

The next validation checks that the airline that the user has entered actually exists. The first step is to read the user input using the EML statement READ ENTITIES. This time, you only need to read the field CarrierID.

The SELECT SINGLE statement reads data using the CDS view entity /dmo/i_carrier and checks whether the given airline exists. If it does, the value of the global constant abap_true ('X') is placed in the field exists. If exists is initial following the SELECT statement, you must issue a message, report it, and add the record to the failed structure as you did in the previous example.

The final validation checks that the origin and destination airports are different. The first step is to read the user input using a READ ENTITIES statement. This time, the fields AirportFromID and AirportToID are relevant.

If the departure and arrival airports are the same, you must issue the corresponding message and fill the reported and failed structures. The code extract shows the relevant coding to create the message. The coding to fill the reported and failed structures is the same as in the previous examples.

How to Validate the Semantic Key

Determinations

Determine Cities Based on Airport Codes

In the example app, the flight connection entity contains a departure airport, city, and country, and an arrival airport, city, and country. While it would be possible to force the user to enter all of this information, it is better in terms of user experience and data consistency to have the user enter only the airport codes and for the app to read the corresponding city and country information from the database. In the ABAP RESTful application programming model, you can perform this kind of task using a determination.

You will first implement the determination. Afterwards, you will learn how to deactivate input for the fields that will be filled automatically.

Defining the Determination

You define a determination in the behavior definition of a business object. The determination here is called getCities, it will be called whenever the business object is saved and at least one of the AirportFromID and AirportToID fields has changed. You can use a quick fix in the behavior definition to create the corresponding method in the behavior implementation.

The Determination Process

Let's explore each step of the determination process.

When the system triggers a determination, it calls the corresponding implementation. The importing parameter KEYS contains the keys of the data records that have been changed. In the determination method, you use EML to read the data based on the keys in exactly the same way that you did in the validations. However, in a determination, you also manipulate the data in the method and you must consequently update the data held by the framework using the EML statement UPDATE.

Note
Some code examples in this section use SELECT statements inside of loops. This has been done to keep the examples simple. Note that SELECTs in loops can cause performance problems and should be avoided.

At the beginning of the determination you read the user input using EML. You need the AirportFromID and AirportToID fields and will use these to complete the city and ciountry information.

The demonstration data model provides a CDS view entity /dmo/i_airport that you can use to read the city and country in which a particular airport is located. The example uses the variant of the INTO clause in which you explicitly specify the fields of the structure that you want to fill. Remember that the changes to the data are in the work area of the internal table and that you must return them to the table itself using the MODIFY statement.

The READ ENTITIES statement returns an internal table with the derived type FOR READ RESULT. To change the data in the transactional buffer, you need a MODIFY ENTITIES statement. You pass the data that you want to change to this statement using an internal table with the derived type FOR UPDATE. The data fields are identical in both types, however the FOR UPDATE table has an additional structure called %control that contains administrative information.

You cannot pass the connections table to the MODIFY ENTITIES statement. You therefore need to copy your data into an appropriately-typed internal table (connections_upd) before you perform the actual modification.

To update the data with the fields that you filled in the determination, you use the MODIFY ENTITIES statement. In it, you specify which fields should be updated in the FIELDS clause, and pass the data in an internal table using the WITH addition. This table must have the correct derived data type, which in this case would be TYPE TABLE FOR UPDATE zsd4d400_r_connection.

The MODIFY ENTITIES statement can return messages, which you receive using the REPORTED clause. You then propagate these messages to your own business object by copying the contents of the internal table to the REPORTED structure of the determination method.

How to Determine the Cities and Countries

Validate Price and Currency

In this exercise you define and implement validations for the price and currency code.

Template:

  • none

Solution:

  • /LRN/S4D400_R_Flight (Behavior Definition)
  • /LRN/BP_S4D400_R_FLIGHT (global Class)

Prerequisites

You completed the previous exercises. You created and filled the database table Z##AFLGT (where ## is your group number) and generated the development objects for an OData UI Service.

Task 1: Validate the Price

Define and implement a validation to check that the field Price has a positive value (suggested name: validatePrice). If the value is negative or equals zero, reject the change and report a suitable error message from message class /LRN/S4D400.

Steps

  1. In the behavior definition Z##_R_FLIGHT, define a new validation validatePrice. Ensure that the validation is always executed during the standard operation create but only if the value of Price changed for all other operations.

    Hint
    Use code-completion wherever possible to enter the code.
    1. In the Project Explorer, navigate to the behavior definition Z##_R_FLIGHT.

    2. Add the highlighted code :

      Code snippet
      
      
        create;
        update;
        delete;
      
      Expand
  2. Activate the behavior definition.

    1. Press Ctrl + F3 to activate the behavior definition.

  3. Use a quick fix to create the validation implementation method in the behavior handler class.

    1. Position the cursor on the name of the validation (validatePrice) and choose Ctrl + 1.

    2. Double-click the entry Add validation....

  4. At the beginning of the method validatePrice, declare a structured data object that you type with the line type of failed-flight (suggested name: failed_record). Similarly, declare a structured data object that you type with the line type of reported-flight (suggested name: reported_record).

    Note
    These structures will be used to add rows to failed-flight and reported-flight, in case the validation finds an error.
    1. Add the following code:

      Code snippet
      
          DATA failed_record   LIKE LINE OF failed-flight.
          DATA reported_record LIKE LINE OF reported-flight.
      
      Expand
  5. Use a READ ENTITIES statement to read the user input from the transactional buffer. Use the statement IN LOCAL MODE and ensure that only the key fields and the field Price are read. Use an inline declaration for the result set (suggested name: flights).

    Note
    It is not necessary to list the key fields after the FIELDS addition. READ ENTITIES always reads the key fields.
    1. After the declarations, add the following code, replacing ## with your group number:

      Code snippet
      
           READ ENTITIES OF z##_r_flight IN LOCAL MODE
                 ENTITY flight
                 FIELDS ( price )
                   WITH CORRESPONDING #(  keys )
                 RESULT DATA(flights).
      
      Expand
  6. Implement a loop over the data that you just read. Use an inline declaration for the work area (suggested name: flight).

    1. After the EML statement, add following code:

      Code snippet
      
        LOOP AT flights INTO DATA(flight).
        
        ENDLOOP.
      
      Expand
  7. Inside the loop, check if the component Price is greater than zero. If not, fill the structure failed_record with the key of the current flight and add the new code row to failed-flight. Similarly, fill structure reported_record with the key of the current flight and add the new code row to reported-flight.

    Hint
    In draft-enabled business objects, it is recommended that you use the component %tky to assign the key.
    1. Inside the loop, add the following code:

      Code snippet
      
            IF flight-price <= 0.
      
              failed_record-%tky = flight-%tky.
              APPEND failed_record TO failed-flight.
      
              reported_record-%tky = flight-%tky.
              APPEND reported_record TO reported-flight.
            ENDIF.
      
      Expand
  8. Before the APPEND statement fill the component %msg of the structure reported_record with a reference to a message object. To create the message object, call the method me->new_message with the following input:

    Parameter nameValue
    id'/LRN/S4D400'
    number'101'
    severityif_abap_behv_message=>severity-error
    1. Add the highlighted code:

      Code snippet
      
      LOOP AT flights INTO DATA(flight).
        IF flight-price <= 0.
      
          failed_record-%tky = flight-%tky.
          APPEND failed_record TO failed-flight.
      
          reported_record-%tky = flight-%tky.
      
          APPEND reported_record TO reported-flight.
      
        ENDIF.
      ENDLOOP.
      
      Expand
  9. Activate the class.

    1. Press Ctrl + F3 to activate the class.

Task 2: Validate Currency

Define a validation validateCurrencyCode to ensure that CurrencyCode contains a valid value. Use CDS view entity I_Currency to check whether a currency code is valid or not.

Steps

  1. In the behavior definition Z##_R_FLIGHT, define a new validation validateCurrency. Set the trigger conditions to create and field CurrencyCode.

    Hint
    Use code-completion wherever possible to enter the code.
    1. In the Project Explorer, navigate to the behavior definition Z##_R_FLIGHT.

    2. Add the highlighted code :

      Code snippet
      
      
        create;
        update;
        delete;
      
        validation validatePrice        on save { create; field Price; }
      Expand
  2. Activate the behavior definition and use a quick fix to create the validation implementation.

    1. Press Ctrl + F3 to activate the behavior definition.

    2. Position the cursor on the name of the validation (validateCurrencyCode) and choose Ctrl + 1.

    3. Double-click the entry Add validation....

  3. In the method validateCurrencyCode, declare structured data objects failed_record and reported_record as before.

    1. Add the following code:

      Code snippet
      
          DATA failed_record   LIKE LINE OF failed-flight.
          DATA reported_record LIKE LINE OF reported-flight.
      
      Expand
  4. For the flights that need to be validated, read the field CurrencyCode and the key fields from the transactional buffer and implement a loop over the data.

    1. After the declarations, add the following code, replacing ## with your group number:

      Code snippet
      
         READ ENTITIES OF z##_r_flight IN LOCAL MODE
               ENTITY flight
               FIELDS ( currencycode )
                 WITH CORRESPONDING #(  keys )
               RESULT DATA(flights).
      
         LOOP AT flights INTO DATA(flight).
        
         ENDLOOP.
      
      
      Expand
  5. Inside the loop, check if the value of the component CurrencyCode is valid.

    Hint
    Implement a SELECT statement with CDS view entity I_Currency as data source and Currency = flight-CurrencyCode as WHERE clause.
    1. At the beginning of the method, add the highlighted code:

      Code snippet
      
        DATA failed_record   LIKE LINE OF failed-flight.
        DATA reported_record LIKE LINE OF reported-flight.
      
      Expand
    2. Inside the loop, add the following code:

      Code snippet
      
            exists = abap_false.
      
            SELECT SINGLE FROM i_currency
                  FIELDS @abap_true
                  WHERE currency = @flight-currencycode
                  INTO @exists.
      
            IF  exists = abap_false. " the currency code is not valid
      
            ENDIF.
      
      Expand
      Note
      There are different ways to implement such an existence check. This example has the advantage that no data is actually retrieved from the database. The FIELDS list contains only a constant. However, it is technically not possible to entirely omit the FIELDS list and the INTO clause .
  6. If the currency code is not valid, add a row to failed-flight and reported-flight. Do not forget to fill reported_record-%msg with a reference to a suitable message object. To create the message object, call the method me->new_message with the following input:

    Parameter nameValue
    id'/LRN/S4D400'
    number'102'
    severityif_abap_behv_message=>severity-error
    v1flight-currencycode
    1. Inside the IF-Block, add the following code:

      Code snippet
      
          failed_record-%tky = flight-%tky.
          APPEND failed_record TO failed-flight.
      
          reported_record-%tky = flight-%tky.
          reported_record-%msg =
             new_message(
                  id       = '/LRN/S4D400'
                  number   = '102'
                  severity = if_abap_behv_message=>severity-error
                  v1       = flight-currencycode
              ).
      
          APPEND reported_record TO reported-flight.
      
      Expand
  7. Activate the class.

    1. Press Ctrl + F3 to activate the class.

Task 3: Test and Debug

Set breakpoints in the validation implementations. Restart the preview of the OData UI service and change the price and currency code of an existing flight. Make valid and invalid entries, and debug the validations.

Steps

  1. Set breakpoints at the READ ENTITIES statement of both methods.

    1. In the implementation of the method validateprice, find the READ ENTITIES statement and double-click the area to the left of the row number to set a breakpoint.

    2. Do the same in the implementation of the method validateCurrencyCode.

  2. Restart the preview of the OData Service.

    1. Close the browser window or browser tab that contains the preview.

    2. Open your service binding. In the Entity Set and Associations list on the right-hand side, first choose entity Flight and then choose Preview....

  3. In the app, display a list of flights, open the details for one of the flights and switch to change mode.

    1. Proceed as you have done in previous exercises.

  4. Make some changes to the price and currency code, and then choose Save. Analyze the validations in the debugger.

    1. Proceed as you have done in previous exercises.

Log in to track your progress & complete quizzes