Step 6: File input/output

Now that we can create contribution objects and convert them to and from JSON, the next step is to manage a list of contributions and make this list persistent in a JSON file.

We will achieve this with a service called se_contribution_list. Besides maintaining a persistent list of contribution objects, this service will have two more responsibilities:

se_contribution_list will have two public commands:

The signature of command add_from_strings is defined as follows:

   command add_from_strings
      in first_name type:string end
      in last_name type:string end
      in random_number type:string end
      in error_handler type:system_error_handler default:se_system_utilities.default_system_error_handler end
      
      out result type:contribution voidable:yes end
      out error type:system_error voidable:yes end
      out_check check: i_result =r void xor i_error =r void end
      
      script
      end
   end

As you can see, there is again an error_handler input argument and an error output argument because many things can go wrong. For example the input can be invalid or there is a file write error.

Command get_list can also fail and is therefore defined like this:

   command get_list
      in error_handler type:system_error_handler default:se_system_utilities.default_system_error_handler end
      
      out result type:!indexed_list<contribution> voidable:yes end
      out error type:system_error voidable:yes end
      out_check check: i_result =r void xor i_error =r void end
      
      script
      end
   end

Note the type for output argument result: !indexed_list<contribution>. Here we use a generic type that denotes an indexed_list containing only contribution objects. The ! introduces the usage of a generic type, and the value between < and > defines the type of object stored in the list.

[Note]Note
For Java programmers: Although the syntax for generic types in Obix is similar to the syntax used in Java, generic types in Obix are conceptually different from those in Java. For example, in Obix there is no type erasure at runtime. This is important because type safety is always guaranteed and type parameter information is available at runtime.

Before writing the code for se_contribution_list there is one feature we have to add to factory random_1_15 we created in a previous chapter. The underlying data type of our random value is an integer number. The value that will be provided through the web user interface, however, will be a string, because all data transmitted through HTTP requests are strings. Therefore we need a feature that converts a string object into a random_1_15 object. The best way to do this is to add a creator in factory random_1_15; a creator which takes a string as input and returns a random_1_15 object. Converting a string into an integer is again a very common feature needed in many applications. Therefore Obix's standard library provides a source code template that you can use, so that your code is reduced to a one-liner. Here is the template's source code, as defined in the standard library:

template integer32_create_from_string_creator

   param type_suffix end

   creator create_from_string
      in string type:string end
      %system_error_handler_input_argument
      
      %result_xor_string_to_object_conversion_error_output < {type_suffix} >
      
      script
         var integer32_value value
         fa_integer32_value.create_from_string_value ( &
            value = i_string.value &
            error_handler = i_error_handler ) &
            ( v_value = result &
            o_error = error )
         if o_error #r void then
			o_result = void
            exit script
         end if
         
         const attribute_check_error attribute_check_error = ty_{type_suffix}.value.check ( v_value )
         if c_attribute_check_error #r void then
            o_result = void
            o_error = fa_string_to_object_conversion_error.create ( &
               id = "string_to_{type_suffix}_conversion_error" &
               description = c_attribute_check_error.description &
               java_exception = void &
               data = i_string &
               type_of_result = "{type_suffix}" &
               error_position = void )
            i_error_handler.handle_system_error ( o_error )
            exit script
         end if
         
         o_result = create ( v_value )
      end
   end

end template

To add creator create_from_string in factory random_1_15:


Now we are ready to write se_contribution_list. The full code with comments and a global test script at the end is shown below. To create the service:


[Note]Note

It is interesting to note that the above add_from_strings command could also be written in a much shorter way.

In the above script we carefully check each value for its validity and we manage each possible error individually. This is the preferred way to write code because we have fine-grained control over every possible invalid data and we can provide explicit error messages and error handling.

Instead of checking each potential error programmatically, however, we could also rely on the fact that a program error (also called an exception) occurs immediately whenever we try to store invalid data in an object. For example, if we created a contribution object with an invalid first_name value, then the data check ensured through Contract Programming (Design by Contract) in type user_name would immediately raise a program error at runtime.

Program errors can be programmatically catched, in the same way that exceptions can be catched in other programming languages.

The following alternative code for command add_from_strings shows how to do this in Obix. Besides relying on program errors thrown in case of invalid data or other runtime problems, the following (over-simplified) command also doesn't have an error_handler input argument nor an error output argument. In case of a problem at runtime the command simply returns a void result and writes an error message to the system's error device.

   command add_from_strings
      in first_name type:string end
      in last_name type:string end
      in random_number type:string end
      
      out result type:contribution voidable:yes end
      
      script
         // if a program error (an exception) occurs within the following section then
         // - the program error is stored in the implicitly defined variable 'v_program_error_'
         // - script execution continues after the 'end section' instruction
         section add_contribution on_error:continue

            // create the 'contribution' object
            // if data is invalid then 
            // - a program error (exception) will occur
            // - the error will be stored in variable 'v_program_error_'
            // - script execution will continue after the 'end section' instruction
            o_result = fa_contribution.create ( &
               identifier = se_text_file_IO.get_next_counter_from_text_file.result ( file = a_next_identifier_file ) &
               first_name = fa_user_name.create ( i_first_name.value ) &
               last_name = fa_user_name.create ( i_last_name.value ) &
               random_number = fa_random_1_15.create_from_string.result ( string = i_random_number ) &
               date_time = se_date_time.current_local_date_time )

            // create file 'a_contributions_file' if it doesn't exists yet
            if not a_contributions_file.exists then
               a_contributions_file.create_physically
            end if
         
            // append the JSON representation of the 'contribution' object to file 'a_contributions_file'
            // if an error occurs then return 'void' as result
            if se_text_file_IO.append_string_to_file ( &
               string = o_result.to_JSON & se_string_constants.current_OS_new_line &
               file = a_contributions_file ) #r void then
               o_result = void
            end if

         end section

         // check if anything went wrong
         if v_program_error_ #r void then
            o_result = void
            system.err.write_line ( string = "Contribution object could not be stored to file" )
         end if
      end
   end