Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Mule 4.0: New Java Module

DZone's Guide to

Mule 4.0: New Java Module

A look at how Mule 4 has changed the rules of the game (for the better) by introducing Java Module and how to go about coding in this new version of Mule.

· Integration Zone ·
Free Resource

SnapLogic is the leading self-service enterprise-grade integration platform. Download the 2018 GartnerMagic Quadrant for Enterprise iPaaS or play around on the platform, risk free, for 30 days.

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 a completely new approach in Mule 4. Mule presents a 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 let's walk through some sample applications.

Java Module Overview

Mule 4 has replaced the Mule Expression Language with DataWeave 2.0. As DataWeave 1.0 had not allowed developers to easily call Java classes, Java Module has been introduced. Java Module has the following message processors:

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

In order to call a method, we need to provide two pieces of information:

  • Method signature
  • Arguments

Both elements are fairly simple. By method signature, I mean the method name and the parameters' types, like what I've got below:

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

The first line instructs Mule to invoke the generate method, which does not accept any parameters. The second method expects only one String. The last method expects two parameters, String and in.When a method accepts parameters we also need to provide them. We do it using a DataWeave expression like the following: 

#[{
paramName: paramValue
}]

Invoke Your Own Static Method

I would like to expose a service that returns gender randomly. I have already prepared class called GenderGenerator that is in the com.profitonline.generators package. This class has only one static method with the 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 the Invokestatic message processor onto the canvas and configure it like below:

  • Class: fully qualified class name (package + class name).
  • Method: method signature, empty parentheses are required when a parameter's list is empty.
  • Args: leave blank as methods do 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 the console log, 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 a surprise but when we want to use classes defined by us we need to configure exported packages in a file called mule-artifact.json. Within this file we need to add the property exportedPackages, after line 13 of the below code:

{
 "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 a default package needs to be mentioned as an 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 a random number. However, we may instruct a range of the generated numbers. In the NumberGenerator class, two methods are present with the following signatures:

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

If we send the query parameters min and max, Mule should invoke the second method, otherwise the first one will be invoked. So let's start with the method without parameters.

  • DisplayName: Generates a 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 a 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 lines 4 and 5, we defined two parameters. As you may have noticed, we passed these parameters as objects. Each property name starts with the prefix arg and then ordinalnumber. Imagine now that I would like to name these properties more meaningfully. I created a method with the following signature:

public static double generateVal(int min, int max)

I would then try the following arguments:

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

When I run the example, I receive the 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 the error message, we got some tips. Mule instructs us that the expected are arguments called arg0 and arg1. When you receive this error and you are sure that the parameters are called arg[0-9]+, the error is located elsewhere. Again, in the error message, you should get some tips. You are probably trying to send a value of an 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>

Here, we have calls with the same method signatures but their classes are different. The second call will be redirected to the first class. In other words, Mule will invoke the 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 a Method on an Instance

To introduce a call on an already created instance, I will use the date and time classes that were introduced in Java 8. Here is the code that I will 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 the 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.

Here, we have three steps:

  1. Call the static method now() on the class java.time.LocalDate
  2. Call the static method ofPattern(String) on the class java.time.format.DateTimeFormatter.
  3. Call the method format() on the instance of LocalDate, passing an 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 the following arguments:

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

We also have set a target property to save the created instance into a variable formatter. As a result, in the payload, we still have an instance of LocalDate. Here is how we configured the last step:

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

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

  • Method: format(DateTimeFormatter)

Have you noticed that parameter class has been provided without a package? This is by design. If you try to set it to format(java.time.format.DateTimeFormatter) you will receive an 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

The source code is available on GitHub.

Summary

It is fairly simple to call a method on a static class using the invoke-static message processor. We only need to provide a class, method signature, and arguments if they are present. Calling a method on an already created instance operates in much the same way. We use the invoke message processor. This processor expects instance, class, and the method signature. If the method contains parameters we need provide them as well. Remember that the method signature does not require Java packages. In comparison to Entry Point Resolvers and JavaComponent in Mule 3, Mule 4 has simplified it by introducing JavaModule. We do not need to implement custom interfaces and wonder which resolver to use. We specify clearly in a new message processor what should be invoked. I think that this is a great step for simplification.

With SnapLogic’s integration platform you can save millions of dollars, increase integrator productivity by 5X, and reduce integration time to value by 90%. Sign up for our risk-free 30-day trial!

Topics:
mule 4 ,java module ,mulesoft ,integration

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}