In one of my previous posts I described Java Component and entry point resolvers as a way to invoke Java Code in Mule 3.x. In this article I will focus on completely new approach in Mule 4. Mule presents brand new Java Module capable of creating new instances, invoking methods on those instances and invoking static methods. Although you can invoke Java using DataWeave 2.0 and Groovy scripting you are losing additional metadata (DataSense). So lets walk through some sample application.

Java module overview

Mule 4 has replaced mule expression language with DataWeave 2.0. As DataWeave 1.0 had not allowed easily to call Java classes new Java Module has been introduced.  Java Module has following message processors:

Message Processor Short description
 Invoke  Invokes method on provided instance
 Invoke static  Invokes static method on provided class
 New  Creates new instance
 Validate type  Verifies if provided instance is instance of specified class

In order to call method, we need to provide two informations:

  • method signature,
  • arguments.

Both elements are fairly simple. By method signature I mean method name and  parameters’ types provided like below

generate()
generate(String)
generate(String, int)

The first line instructs to invoke generate method that does not accept any parameters. The second one expect only one String. The last one expects two parameters String and int.

When method accepts parameters we also need to provide them. We do it using DataWeave expression like

#[{
paramName: paramValue
}]

Invoke own static method

I would like to expose a service that returns gender randomly. I have already prepared class called GenderGenerator that is in com.profitonline.generators package. This class has only one static method with following signature

public static String generate()

Here is the sample flow that implements it. In order to use Java Module you need to add it as it is not included by default. Drag Invoke static message processor on canvas and configure like below:

  • Class: fully qualified class name (package + class name)
  • Method: method signature, empty parantheses are required when paramers’ list is emtpy
  • Args: leave blank as methods does not accept any parameter

It is time to test it how smooth it works :).

Or it does not work

We may be surprised but in console logs we should receive something like this

********************************************************************************
Message : Failed to load Class with name [GenderGenerator]. Class not found.
Element : mht-java-moduleFlow/processors/0 @ mht-java-module:mht-java-module.xml:14 (New)
Element XML : <java:new doc:name="New" doc:id="935445b7-7569-485c-a0eb-d444d4b77bb4" class="com.profitonline.generators.GenderGenerator" constructor="Test()"></java:new>
Error type : JAVA:CLASS_NOT_FOUND
Payload Type : org.mule.runtime.core.internal.streaming.bytes.ManagedCursorStreamProvider
--------------------------------------------------------------------------------

It may be surprise but when we want to use classes defined by us we need to configure exported packages in file called mule-artifact.json.

Within this file we need to add property exportedPackages, after line 13


{
 "configs": [
   "java-module.xml"
 ],
 "secureProperties": [],
 "redeploymentEnabled": true,
 "name": "java-module",
 "minMuleVersion": "4.1.1",
 "requiredProduct": "MULE_EE",
 "classLoaderModelLoaderDescriptor": {
   "id": "mule",
   "attributes": {
     "exportedResources": [],
     "exportedPackages":[
       "com.profitonline.generators"
     ]
   }
 },
 "bundleDescriptorLoader": {
   "id": "mule",
   "attributes": {}
 }
}

In exportedPackages we provide an array of packages that should be exported and taken into consideration by Java Module. Even default package needs to be mentioned as empty string “”.  According to Mule’s Team they may consider populating this automatically.

Save your changes and restart Mule. It should work like a charm now.

Passing parameters

I have prepared another flow. This time it returns random number. However we may instruct range of the generated number. In NumberGenerator class are present two methods with following signatures:

public static double generateVal()
public static double generateVal(int min, int max)

If we send query parameters min and max, mule should invoke the second method otherwise the first one. So lets start with method without prameters.

  • Display Name: Generate Number
  • Class: com.profitonline.generators.NumberGenerator
  • Method: generateVal()
  • Args: leave blank

This one is fairly simple and similar to what we have already done. So now we invoke static method that requires two parameters

<java:invoke-static doc:name="Generate Restricted Number" class="com.profitonline.generators.NumberGenerator" method="generateVal(int, int)">
  <java:args>
    <![CDATA[#[{ arg0:attributes.queryParams.min as Number, arg1: attributes.queryParams.max as Number }]]]>
  </java:args>
</java:invoke-static>

As you can see in line 4 and 5 we defined two parameters. As you may notice we pass parameters as an object. Each property name start with prefix arg and then ordinal number. Imagine now that I would like to name properties more meaningfully. I created a method with following signature

public static double generateVal(int min, int max)

I would try then with following arguments

min:attributes.queryParams.min as Number, 
max: attributes.queryParams.max as Number

When I run the example I would receive following error:

********************************************************************************
Message : Failed to invoke Method [generateVal(int, int)] in Class [com.profitonline.generators.NumberGenerator] with arguments [Integer min, Integer max]. Expected arguments are [int arg0, int arg1].
Element : generate-number-flow/processors/0/route/0/processors/0 @ java-module:java-module.xml:30 (Generate Restricted Number)
Element XML : <java:invoke-static doc:name="Generate Restricted Number" doc:id="b99898ff-f45c-499f-8552-9118eb4494c6" class="com.profitonline.generators.NumberGenerator" method="generateVal(int, int)">
 <java:args>#[{
min:attributes.queryParams.min as Number,
max: attributes.queryParams.max as Number
}]</java:args>
 </java:invoke-static>
Error type : JAVA:ARGUMENTS_MISMATCH
Payload Type : org.mule.runtime.core.internal.streaming.bytes.ManagedCursorStreamProvider
--------------------------------------------------------------------------------

In error message we got some tip. Mule instructs us that expected are arguments called arg0 and arg1. When you receive this error and you are sure that parameters are called arg[0-9]+ error is located elsewhere. Again in error message you should get some tip. Probably you are trying to send value of unsupported type.

Method name uniqueness

In case of a flow where you have more than one invoke-static call you may encounter odd behavior. Here is the excerpt from mule flow:

<flow name="sample">
...
<java:invoke-static doc:name="Generate Gender" class="com.profitonline.generators.GenderGenerator" method="generate()"/>
...
<java:invoke-static doc:name="Generate Number" class="com.profitonline.generators.NumberGenerator" method="generate()" />
</flow>

 

We have here to calls with the same method signatures but class are different. The second call would be redirected to the first class. In order words mule would invoke two following calls:

com.profitonline.generators.GenderGenerator.generate()
com.profitonline.generators.GenderGenerator.generate()

instead of

com.profitonline.generators.GenderGenerator.generate()
com.profitonline.generators.NumberGenerator.generate()

This is an error though which I have reported and should be fixed soon.

Call method on an instance

To introduce call on already created instance I will use newly introduced in Java 8 date and time classes. Here is the code that I would mimic using Mule:

java.time.LocalDate now = java.time.LocalDate.now();
java.time.format.DateTimeFormatter formatter = java.time.format.DateTimeFormatter.ofPattern("MMMM dd, yyyy")
now.format(formatter);

To keep it short this code takes current date and displays it as MMMM dd, yyyy. For example assume that today is 25th of March 2018 we should receive March 25, 2018.

We have here three steps:

  1. call static method now() on class java.time.LocalDate
  2. call static method ofPattern(String) on class java.time.format.DateTimeFormatter
  3. call method format() on instance of LocalDate passing instance of java.time.format.DateTimeFormatter

In the second step in order to create an instance of formatter we pass a parameter. So we send following arguments:

<java:args ><![CDATA[#[{
arg0: “MMMM dd, yyyy”
}]]]></java:args>

We also have set target property to save created instance into a variable formatter. As a result in payload we have still instance of LocalDate.

Here is how we configured last step:

  • Instance: #[payload]
  • Class: java.time.LocalDate

You need to provide class of the instance. If instance is not an instance of provided class you will receive error like Expected an instance of type java.time.LocalTime but was java.time.LocalDate

  • Method: format(DateTimeFormatter)

Have you notice that parameter class has been provided without package? This is by design. If you try to set it to format(java.time.format.DateTimeFormatter) you will receive error like No public Method found with name [format(java.time.format.DateTimeFormatter)] in class java.time.LocalDate with arguments DateTimeFormatter arg0.

  • Args: as arg0 we pass vars.formatter

We should now have the same result as with previously shown Java code.

Source Code

Source is available at GitHub.

Summary

It is fairly simple to call method on a static class using invokestatic message processor. We only need to provide class, method signature and arguments if are present. Call method on already created instance is fairly the same. We use invoke message processor. This processor expects instance, class and method signature. If method contains parameters we need provide them as well. Remember that method signature does not require java packages.

In comparison to Entry Point Resolvers and Java Component in Mule 3. Mule 4 has simplified it by introducing Java Module. We do not need to implement custom interfaces and wonder which resolver to use. We specify clearly in new message processor what should be invoked. I think that this is a great step for simplification.

Tagged on:         

2 thoughts on “Mule 4 new Java Module

Leave a Reply

Your email address will not be published. Required fields are marked *