Explore Mendix Java Actions
The Mendix runtime is built on top of Scala, hence the object-oriented nature of
Mendix. This brings another benefit to the developer of Mendix applications,
extensibility of the platform. Mendix provides a Java API around their runtime.
With this capability, the endless
possibilities of the Java programming language are available to you as the
developer.
If some functionality cannot be directly achieved with the standard
Mendix tools at the developer’s disposal, implementing a Java action with maybe
just a few lines of code will do the trick. In this article, we will be exploring the concept of java actions in mendix.
Offerings
- Anatomy of java actions
- External libraries
- Java runtime API
- Java actions consideration
- Debugging your java code
- Marketplace action
Anatomy of Java Action
The Mendix runtime is built using Java and Scala technologies, both object-oriented programming languages.
The runtime provides a Java wrapper as an API
exposed to the developer and allows us to add functions to our application with
the help of Java classes.
In a microflow, we can add an activity named Java action
call.
From these actions, we can call our custom Java code that works in the same
way as calling a microflow, which takes zero or more inputs and delivers zero or
more outputs.
A Java action is a separate document we can add to a module.
To
add a Java action, we open the context menu in the application explorer panel and
select the appropriate document.
After providing a name, the properties for the
Java action will be opened in the central canvas.
A Java action can have multiple parameters and we can view these in the same
way we look at the input parameters of a micro-flow.
By clicking the Add button,
the Add Java action parameter dialog is opened as depicted in the figure.
We need to provide a name for the parameter that clearly identifies the input. Then
we need to select a type for the parameter.
- Object — A single object of a specific type or a generic type.
- List — A list of objects of a specific or generic type.
- Entity — This option will pass the name of the entity selected when calling
the Java action. - Microflow — This allows us to pass a micro-flow to the Java action.
- Boolean — A Boolean primitive to pass to the Java action.
- Date time — A date time primitive to pass to the Java action.
- Decimal — A decimal primitive to pass to the Java action.
- Enumeration — An enumeration value to pass to the action for a selected
specific enumeration. - Integer/Long — We can select integer or long values in the java action call
activity, be aware that the value is always passed to the class as a long
value. - String — A string primitive to pass to the Java action.
- String template — This allows us to pass a templated string, like 'Hello
{1}, {2}’, which can be passed as text or as an SQL statement. The
difference between this parameter type and a string is that the input
parameter is created as an IStringTemplate type that provides specific
features, such as retrieving the template used. - Import mapping — This type will pass the name of the selected import
mapping to the Java action. - Export mapping — This type will pass the name of the selected import
mapping to the Java action.
Generic types are types that we define on the Type parameters tab as a name.
This type parameter can be selected for an object or list of objects
and has the effect that the type is determined when the Java action is triggered.
The difference is that with a standard object type parameter, imagine we choose
Order as our entity, we can only pass objects of the entity Order.
When we define
a Type parameter (SomeObject) and select that as the entity in the Add/Edit
Java action parameter dialog, we can select any object as input for the Java
action.
The type of object is then passed to the Java action as a generic
IMendixObject.
We can use this when we want the action, we are performing on the object to be used with multiple types of objects or lists of objects.
We can add categories to our parameters, this will visually create sections that use
the category names within the properties of a call Java action activity.
For
example, when we have many input parameters that we can separate based on
parameter type, it might be useful to see the different types grouped together
rather than the parameters to be shown as a long list under the default input
category when adding the call Java activity.
In this figure, we see two categories,
Price Info and Objects, both with their own set of input parameters.
The last two options for our input parameter are the options to ensure the input
parameter is required when calling the Java action.
The second option is the description, which provides additional information when using and configuring
the call Java activity.
When we want to work with a Java action, it is very convenient to have a Java
IDE available.
In this chapter, we use Eclipse as our IDE and explain how to
implement your Java actions with the IDE.
Other Java IDEs can be used and have
similar functions to achieve the same result.
Let us create our first Java action and name this JA_Test and make sure we have a
single input parameter of type String named inputStr.
The first step in
implementing the action is to import our Mendix project into Eclipse.
- Make sure your Java action is saved.
- Open the App menu and select the option Deploy for Eclipse.
- Open your Eclipse IDE.
- From the File menu, select the Import option as depicted in the figure.
5. In the Import dialog, select the option Existing Projects into workspace.
6. Now, select the root directory of your Mendix application and press Finish.
7. Make sure the Projects section has a selected line available when there is
nothing in this section, or you are unable to select the project you either
missed step two of this process or the project is already available in your
workspace.
Now, the Mendix project is available as a Java project in Eclipse.
Let us have a
closer look at the project in Eclipse.
In the project, we see a folder named
javasource, a folder named userlib and other folders that we already
encountered in the project directory.
The userlib folder is used to store our
external Java libraries that we might use in our Java actions.
The javasource
directory shows us all the modules of our application as packages.
We see
different kinds of packages depending on the postfix used. For example, for the
module Order
- order.actions — This package contains all the Java actions for the Order
module. - order.datasets — This package contains the datasets for the module.
- order.proxies — This package contains the Java class representation for the
entities of the Order module as a proxy to conveniently get and set data,
for example. - order.proxies.constants — This package contains the constants for a
module as a Java class. - order.proxies.microflows — This package contains a Java class
representation of the micro-flows of the Order module as a proxy.
Additional packages might be shown, these are often folders in the javaactions
directory of your application where the additional organization of the Java actions
has been implemented and classes are defined outside the Java actions that we
can create from the Mendix IDE.
If we open our newly created JA_Test Java action, we will see the code
in Eclipse.
The autogenerated comments are omitted in the next code listing for clarity.
package module.actions;
import
com.mendix.systemwideinterfaces.core.IContext;
import com.mendix.webui.CustomJavaAction;
public class JA_Test extends
CustomJavaAction<java.lang.Void>
{
private final java.lang.String inputStr;
public JA_Test(
IContext context,
java.lang.String _inputStr
)
{
super(context);
this.inputStr = _inputStr;
}
@java.lang.Override
public java.lang.Void executeAction() throws
Exception
{
// BEGIN USER CODE
throw new
com.mendix.systemwideinterfaces.MendixRuntimeException(
"Java
action was not implemented");
// END USER CODE
}
@java.lang.Override
public java.lang.String toString()
{
return "JA_Test";
}
// BEGIN EXTRA CODE
// END EXTRA CODE
}
Lines 01 through 04 contain the package declaration and the import statements.
The lines 05 through 16 provide the class declaration and the constructor for the
class.
This part contains our defined input parameter(s), see line 10, and a special
object named context.
Every action in Mendix runs in a context; in this case, this
is the context of the user triggering the Java action.
On line 20 and line 23, we see
a comment that provides the section that we can utilize to write our Java action
implementation.
Additionally, we can add code in the section marked by the
comments on lines 30 and 31.
The code between these comments will not be
touched by Mendix when deploying your application for Eclipse.
Make sure the
comments remain in your code.
Otherwise, the IDE will ensure the comments are
added and you might lose your code.
Lines 25 through 29 are a standard part of
the Java action and are required as we override a generic executeAction method,
as we see on lines 17 and 18.
The newly created Java action will work when we
run the application, and the result is that the code on lines 21 and 22 is executed,
the user is confronted with a generic error message and the application log will
show that an exception has occurred.
Whenever you see this happening in the logs
when running a Java action, the issue is that the code for the action was not
implemented.
We still need to implement the code. For example, imagine we want
to calculate the length of the input string.
inputStr.lenght();
When we do this, we will see that we run into a compilation error.
The cause is
that the Java action we are creating has an output type named void.
This means in
the Mendix context that the method needs to return null.
By adding these lines to the code, the error is fixed and we can deploy our application.
return null;
This will result in the Java action determining the length by providing an input
string value, but nothing useful as we do not get the value from the Java action.
For this, we need to define the output parameter in the Java action in the Mendix
IDE.
Open the Java action in the Mendix IDE and select the appropriate return
type at the bottom of the Properties panel in the figure, in our case
Integer/Long.
Now, deploy your application for Eclipse and refresh the project in
Eclipse by pressing F5.
We now see that the method has changed and reads as follows.
@java.lang.Override
public java.lang.Long executeAction() throws Exception
This means that the method now expects to return a long value. When we change
our code to
return inputStr.length();
We again run into a compilation error as the length() method returns an integer
value as Java does not incur automatic type conversion. We need to cast the result
to a long value like so.
return (long) inputStr.length();
Now, our Java action can be used in a microflow, requires us to set a value for the
input string and returns a value for the length of the string as a long value which
we can store in a variable in analogy to the call microflow action where the
microflow returns a value.
External Libraries
The previous example was very simple and could be achieved with standard
Mendix functions as well.
Let us examine a more complex example where we use
external libraries.
In this case, we want to create a barcode with the help of an
external library where we input a string and create a barcode image as a file.
In the
example, we will use the Google Zxing library to generate the barcode.
We will
start with downloading the Zxing jar file and adding this to the userlib directory
in the application directory of our Mendix application.
The files can be
downloaded from sites like sourceforge.net and the like.
The next step is to create a new Java action named JA_GenerateBarcode.
This
Java action has two input parameters.
- A string with the name text.
- An Object with the name imageFile and the entity is defined as the Type
parameter ‘img’, ensuring this will work for every entity that inherits
from the System.Image entity.
The next step is to create a new entity (OurBarcodeImage) that inherits from the
System.Image entity.
Make sure the permissions are set correctly so that your user
has access to this entity.
Now, we will create a microflow and assign this to a button in our application and
ensure the permissions are set correctly.
In the microflow, we will add a create
object activity in which we will create an object for the entity OurBarcodeImage
and set the name to mybarcode.png.
The second activity is the call Java action and we will call the new Java action.
For the imageFile parameter, we will select the created object and add a string for
the text, for example, 123456789.
The third activity in the microflow is a download file activity with the option to
show the file in the browser if possible activated.
Now, we will deploy our application for Eclipse and make sure to add this code between the start and end user comments.
// BEGIN USER CODE
try {
Code128Writer writer = new Code128Writer();
BitMatrix matrix = writer.encode(text,
BarcodeFormat.CODE_128, 500, 300);
BufferedImage bufImage =
MatrixToImageWriter.toBufferedImage(matrix);
ByteArrayOutputStream pngOutputStream = new
ByteArrayOutputStream();
MatrixToImageWriter.writeToStream(matrix,"png",
pngOutputStream);
try {
ImageIO.write(bufImage, "png",
pngOutputStream);
Core.storeFileDocumentContent(getContext(), imageFile,
new
ByteArrayInputStream(pngOutputStream.toByteArray()));
}
finally {
pngOutputStream.close();
}
Core.getLogger("barcode").info("Barcode
created...");
}
catch(Exception e) {
System.out.println("Error while
creating barcode");
}
return null;
// END USER CODE
We also need to ensure the correct imports are added, these are listed as follows.
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import javax.imageio.ImageIO;
import com.google.zxing.BarcodeFormat;
import
com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.oned.Code128Writer;
import com.mendix.core.Core;
import com.mendix.systemwideinterfaces.core.IContext;
import
com.mendix.systemwideinterfaces.core.IMendixObject;
import com.mendix.webui.CustomJavaAction;
What happens in this small code sample is that we create a barcode and store this
barcode in the input image file with the method from the Mendix Core class,
storeFileDocumentContent().
We will also use the getLogger() method to
implement writing a log message to the log file of the application.
Now, we have a
function that leverages the implementation of the Zxing library to create the
barcode image and connected this to our application with the help of a Java action
and an external library.
When using external libraries, always ensure they come from a trusted source as
the last thing you want is that adding a library has the effect of poking a hole in
your carefully constructed security for your application.
A second consideration
for using a library should be how active the library is maintained.
When a library
is not maintained for a longer period, the chances that the function stops working
with newer versions of Java increases and then the functionality in your
application breaks with the option to resolve the problem.
Java Runtime API
We have just scratched the surface with two examples in this guide.
The Java
runtime API for Mendix is quite extensive and allows for many complex implementations of functions. The API is well documented on the Mendix
website.
https://apidocs.rnd.mendix.com/10/runtime/
Alternatively, the javadoc can be found in the installation directory of your
Mendix version generally under.
C:\ProgramFiles\Mendix\{10.1.1.12532}\runtime\javadoc
Other API documentation can be found on the Mendix website.
Java Action Consideration
One of the pitfalls of knowing how to implement Java actions in Mendix is to
implement features in Java too quickly.
Most functionalities can be implemented
without needing any Java actions.
We only use Java action when either the
functionality cannot be created with standard Mendix microflow activities or
when the performance of microflows becomes an issue and an implementation in
Java allows for far better performance.
In most of these two cases, replacing a part
of a microflow with a Java action is an acceptable option.
When implementing functions in Java, the readability of your application is
decreasing as we now need to deal with classic code.
The complexity of classic
lines of code is far higher than the visual coding performed in the Mendix IDE.
When using Java actions excessively the complexity of your application increases and the maintainability decreases at the same time.
Also, remember that not every
member of your Mendix low code development team will have the required skills
to debug and maintain the Java actions.
Java actions can be implemented in such a
way that they seem to be standardized microflow actions.
This option is available
from the Java action properties on the Expose as microflow action tab.
On this
tab, we can enter the caption for the action and enter a categorization, wherein the
list of activities will this action be shown.
We can add an icon and an image for
recognizability and optionally add customized icons for users using dark mode in
the Mendix IDE.
In this figure, we see the barcode generator Java action
exposed as micro-flow action.
By exposing our Java actions as microflow actions, we bring the action closer to
the other implementations of actions and lower the barrier for using these actions,
although the previous warnings on complexity and maintainability still apply.
Always make sure that you document your Java actions.
The documentation can
be added in the Documentation tab of the Java action and this will become part of
the javadoc for the corresponding Java file.
This will be presented in the exported
documentation that can be accessed from the context menu opened from the top-level node in the App Explorer panel.
Debugging Your Java Code
Just like your Mendix microflows, there might be situations where not everything
is working as expected.
With microflows, we have seen that the Mendix IDE has a
built-in debugger and Eclipse has the same options.
To debug your Java code with
the help of Eclipse, make sure the latest version of your application is available in
Eclipse by running the Deploy for Eclipse command.
Open the Java action you
want to debug and place your breakpoints by opening the context menu on the line
number and clicking the option Toggle breakpoint or selecting a line and pressing
Ctrl+Alt+B.
Now, select the application node in Eclipse and click on the debug
icon in the menu bar as depicted in the figure.
Alternatively, you can start
debugging by selecting the same node and from the context menu, select the
option Debug As | Java application.
When the deployment process is ready, open the application in your browser and
trigger the Java action.
The end-user will notice that the application hangs and you
will see the Eclipse icon flashing on the Windows task bar.
When you open
Eclipse, you will now see the debug perspective of Eclipse or a dialog asking if
you want to open the debug perspective.
By clicking Step into (F5) or Step over (F6), you can move to the next step in
the Java action.
The difference between step into and step over is only noticeable
if you run into a function call.
step into means that the debugger steps into the
function and step over moves the debugger to the next line in the same Java
action.
With Step Return (F7), you can instruct the debugger to leave the
function; this is basically the opposite of step into.
Clicking Resume (F8) instructs
the debugger to continue until it reaches another breakpoint.
When a breakpoint is triggered, you can inspect the values of the variables either
by placing your cursor over a variable or in the values panel of the debugging
perspective.
This debugging can help you pinpoint the cause of the runtime error. In many
cases, when you run into errors in the runtime that are caused by Java actions, the
error is caused by.
- Duplicate libraries — In many cases, the user-lib directory in the
application directory will contain multiple versions of the same library
(.jar file). This is caused by modules that are created independently and
module 1 would contain apache.commons version 1.3, and module 2
contains version 1.6. When you download both modules from the
Marketplace, issues might occur where the older version is used by the runtime that does not contain the new functions that module 2 needs. The
solution is to inspect the user-lib directory and remove duplicates. - Deployment directory files — Sometimes an error is caused by the data in
your deployment directory. To fix this, select the option Clean deployment
directory from the App menu in the Mendix IDE.
Explore more: (https://code.google.com/archive/p/jar-explorer/downloads)
Marketplace Actions
Many of the actions you will need in your applications have already been built by
other Mendix developers.
The Community Commons module from the Mendix
Marketplace is the equivalent of the Swiss army knife and is implemented in
almost every Mendix application.
The module comes with a set of utility Java
actions ready for use in your application.
https://docs.mendix.com/appstore/modules/community-commons-function-library/
There are many more modules in the Mendix Marketplace providing excellent
Java actions that deliver feature rich functionality to your application.
These
modules include Single Sign On, importing and exporting data, connecting to
AWS functions, sending and receiving mail, and many more.
These modules are
also a good starting point to sharpen your skills for implementing Java actions in
Mendix.
Importing modules from the Marketplace, opening the project in Eclipse and inspecting the implementation of the functions will help you understand the
runtime API better which will help in building your own Java actions.