Chapter 15. Embedded Java source code

[Note]Note
Please note that this chapter is incomplete. An update will be published in the future.

Description

Java source code can directly be inserted into Obix source code. Moreover, Java code can also call Obix code, and data between Obix and Java can easily be exchanged.

The possibility to embed Java source code into Obix source code is certainly one of the most valuable features of Obix, because it enables Obix programmers to use and take advantage of the huge amount of existing and proven Java software.

Embedding Java into Obix is easy. We just have to observe the following simple rules.

Rules

  1. Java source code must be embedded between a java and end java instruction.

    Example:

    java
       System.out.println("Hello from Java");
    end java
  2. Java source code can appear at different locations in Obix source:

    • Java source code can appear any number of times in any location of any script

      In the following example, a message to the system console is first written with an Obix instruction, and then with a Java statement. (Remark: the word 'statement' is used in the world of Java, rather than the word 'instruction' which is used in Obix).

      service java_examples
      
         command example_2
            script
               system.console.write_line ( "Hello from Obix " )
               java
                  System.out.println ( "Hello from Java" );
               end java
            end
         end
      
      end

      Running the above code will output the following on the system console:

      Hello from Obix
      Hello from Java
    • A java_header element can appear at the beginning of a type, factory or service, just after the first instruction that declares the RSE.

      java_header elements are inserted before the class declaration in the Java code. They are typically used to add import statements, and to add annotations to the Java class or interface.

      Example:

      service java_examples
      
         java_header
            import javax.jws.*;
            import javax.crypto.*;
            
            @WebService
         end java_header
      
         ...
    • Java source code can appear at the beginning of a type, factory or service, just after the first instruction that declares the RSE.

      Example:

      service java_examples
      
         java
            private static String java_string = "nice";
      
            private static void shared_method ( String s ) {
               int i = 3;
               int j = i + 10;
               // more Java statements
            }
         end java
      
         // Obix code
      
      end

      Java source code at the beginning of an RSE is typically used to store local data in Java fields, and to provide shared methods used from Java source code embedded in the RSE's scripts.

      [Note]Note
      java_header must precede java if both are used at the beginning of an RSE.
    • Java source code can be embedded just after the first instruction that declares an attribute or command.

      This kind of Java code is always inserted before the declaration of the attribute or command in the Java code. It can thus be used to add annotations.

      Example:

      service java_examples
      
         attribute name type:string default:"Unknown"
            java
               @javax.xml.bind.annotation.XmlValue
            end java
         end attribute
      
      end
  3. Variables, constants, input arguments and output arguments declared in Obix can be accessed in embedded Java by simply specifying the prefixed identifier of the variable, constant, input argument or output argument.

    See following rule for an example.

  4. Scalar values in Obix can be converted to their corresponding primitive Java values by applying the getValue().java_value() methods to the object.

    The following example demonstrates how to access primitive Java values stored in Obix variables, constants, input arguments and output arguments:

    service java_examples
    
       command example_4
          in i1 type:string end
          in i2 type:positive32 end
    
          out result type:string end
    
          script
             var string name = "Albert"
             const string friday = "Friday"
    
             // at this point we have 2 input arguments, one output argument, a variable, and a constant declared in Obix
             // we will now access them in embedded Java code:
             java
                System.out.println ( "values in Java:" );
                
                // access Obix input argument
                System.out.println ( "i1: " + i_i1.getValue().java_value() );
    
                // store Obix scalar value into Java primitive value
                int i2 = i_i2.getValue().java_value();
                System.out.println ( "i2: " + i2 );
    
                // access Obix variable
                System.out.println ( "name: " + v_name.getValue().java_value() );
    
                // access Obix constant
                System.out.println ( "friday: " + c_friday.getValue().java_value() );
                
                // assign Obix variable to Obix output argument
                o_result = v_name;
                System.out.println ( "result: " + o_result.getValue().java_value());
    
                System.out.println();
             end java
    
             // o_result has been set in Java code; now display it with Obix
             system.console.write_line ( "result in Obix: " & result )
          end
       end
    
    
    end

    Executing

    se_java_examples.co_example_4 ( i1 = "foo"; i2 = 17 )

    will produce the following output on the system console:

    values in Java:
    i1: foo
    i2: 17
    name: Albert
    friday: Friday
    result: Albert
    
    result in Obix: Albert
  5. Scalar values in Obix can be created from primitive Java values by using specific Java constructors.

    The following example shows the different Java constructors that can be used to create scalar values:

    service java_examples
    
       command example_5
          script
             // declare some scalars in Obix
             var string first_name
             var positive32 i
             var character char
             var yes_no flag
    
             // now create Obix objects in Java and assign them to the above variables
             java
                v_first_name = new fa_string ( "Albert" );
                v_i = new fa_positive32 ( 17 );
                v_char = new fa_character ( 'A' );
                v_flag = new fa_yes_no ( true );
             end java
    
             // check values in Obix:
             check script first_name =v "Albert"
             check script i =v 17
             check script char =v 'A'
             check script flag =v yes
          end
       end
    
    end

    Other scalar values (zero_positive32, non_empty_string, etc.) are created analogously to the examples above.

  6. The value of an attribute can be accessed in Java by using the Java Beans syntax for accessing properties, i.e. get{unprefixed_and_capitalized_attribute_identifier}().

    Suppose the following type exists in Obix, and a factory has also been defined to create dogs:

    type dog
       attribute name type:string end
       command bark end
    end type
    

    We can then create a dog and access its name in Java as shown below:

    service java_examples
    
       command example_6
          script
             // create dog in Obix
             var dog lassie = fa_dog.co_create ( "Lassie" )
    
             // now access the dog's name in Java
             java
                System.out.println ( "dog's name: " + v_lassie.getName().getValue().java_value() );
             end java
          end
       end
    
    end

    Here is the result displayed:

    dog's name: Lassie
    dog's name: Lassie
  7. The value of an attribute can be set in Java by using the Java Beans syntax for setting properties, i.e. set{unprefixed_and_capitalized_attribute_identifier}({value}).

    Let's reuse the following type defined in the section called “Attribute property setable:

    type person_2 default_factory:yes
    
       attribute_list type:string
    
          attribute first_name end             // attribute is immutable. it can only be set in the creator.
                                               // (property 'kind' defaults to 'constant', in which case
                                               // property 'setable' defaults to 'creator')
          attribute last_name &
             kind:variable setable:factory end // only scripts in the factory that creates the object can change 'last_name'
    
          attribute city &
             kind:variable setable:all end     // any script can change the value
    
       end attribute_list
    
    end type

    We can now create a person and change the city through embedded Java as follows:

    service java_examples
    
       command example_7
          script
             // create person in Obix
             var person_2 paganini = fa_person_2.co_create ( &
                first_name = "Nicolas" &
                last_name = "Paganini" &
                city = "unknown" )
    
             // now set attribute 'city' in Java
             java
                v_paganini.setCity ( new fa_string("Genoa, Italy") );
             end java
    
             // check in Obix
             check script paganini.city =v "Genoa, Italy"
          end
       end
    
    end
  8. Whenever a method of a type, factory or service is called in Java, the package path must be specified if no import statements are defined between java_header and end java_header. The package path consists of the library path defined in Obix. For each library, the prefixed library identifier must be used (i.e. li_bar for library bar)

    Example:

    If a service foo exists in library bar.zar, then the service is accessed in Java with li_bar.li_zar.se_foo.

    See also the following rule for an additional example.

  9. An object or service command (including object creators) can be executed in Java by calling the method {prefixed_command_identifier}_command({input_values}), where {prefixed_command_identifier} denotes the actual prefixed identifier of the command, and {input_values} denotes the list of actual input arguments passed to the command.

    Example:

    service java_examples
    
       command example_8
          script
             // use Obix service command to display "Hello" on system console
             system.console.write_line ( "Hello from Obix" )
    
             java
                // now use same command in Java
                org.obix.obix_core.system.se_system.getConsole().co_write_line_command ( new fa_string("Hello from Java") );
                // the same result can of course also be achieved with the following statement:
                System.out.println ("Hello from Java");
             end java
    
             // create dog in Obix
             var dog bello = fa_dog.co_create ( "Bello" )
             // use Java to make him bark
             java
                v_bello.co_bark_command();
             end java
    
             // we can also make the inverse: create the dog in Java and make him bark in Obix:
             java
                v_bello = manual_examples.li_basics.li_RSE.fa_dog.co_create_command(new fa_string("Bello"));
             end java
             system.console.write_line ( "Hello from Obix again" )
             bello.bark
          end
       end
    
    
    end

    The result on the system console will be:

    Hello from Obix
    Hello from Java
    Hello from Java
    bark bark
    Hello from Obix again
    bark bark
[Note]Note

Whenever in doubt about the correct syntax to use in embedded Java code, it can be useful to first write the instruction(s) in Obix and then look at the Java statements produced by the Obix compiler. Java source code produced by the Obix compiler is created in the application's java\src\java\ sub-directory.

For example, the following code:

service java_examples

   command example_9
      script
         var string capital = "New York"
         capital = capital.to_upper_case
         var zero_positive32 count = capital.item_count
      end
   end

end

results in the following Java code produced by the Obix compiler:

org.obix.obix_core.basics.scalars.string.in_string v_capital = new fa_non_empty_string ("New York");
v_capital = v_capital.co_to_upper_case_command ();
org.obix.obix_core.basics.scalars.number.integer.integer32.in_zero_positive32 v_count = v_capital.getItem_count();

Writing a Java wrapper

Suppose that a certain software component developed in Java is used again and again in an Obix application.

A first approach for using this component from within Obix would be to embed Java source code each time we need the component, by applying the rules explained above.

However, this would result in two serious inconveniences:

  • First, the Obix source code would be cluttered with lots of embedded Java source code which makes the application more difficult to write and maintain, because programmers working on the project have to cope with two languages.

  • Secondly, some features existing in Obix, such as contract programming, are unavailable in embedded Java code, which increases the risk for less reliable code.

A much better approach is to write an Obix wrapper for the existing Java component. An Obix wrapper is simply an Obix software component that internally uses the Java component to implement the functionality.

The advantages are important:

  • Embedded Java code now only exists in the Obix software component that implements the functionality. All other code using the component is written in Obix.

  • The interface to the component can be adapted and enhanced. For example, contract programming can be used to make the component more robust. Features can be renamed. Features not existing in the Java component can be added in the Obix wrapper. And obsolete or unused features in the Java component can be made inaccessible.