Create "Global" Module in WildFly for Custom Library Using Java Delegate Classes

We have been be frustrated in our attempts to create a custom class that uses Camunda Java Delegate classes (e.g. org.camunda.bpm.engine.delegate.DelegateExecution), which we can then upload to our WildFly server so that processes can invoke the classes methods without having to compile the class into process’s war file. We want our classes to act like the Camunda engine classes whose scope we can set to “provided”.

We have tried many different configurations and can get the module itself to appear as a global module in WildFly. However, when we deploy a process with the custom class dependency scope set to “provided”, we get a class not found exception.

I have been through hours of documentation on WildFly class loading, etc., and we seem to be doing the things required for this to work. I’ve been able to install other common libraries (jsch, etc.) properly and then was able to change their scope to “provided” in the process.

Any insights would be very much appreciated.

Michael

Hi @mppfor_manu,

For this you need a bit of understanding of the engine’s classloading behavior when running processes.
When the engine encounters a java delegate, it will try to do a context switch into the classloading context of the matching process application. When there is no registered process application for the process, the engine will try to execute the java delegate call inside the engine’s classloader. This is a quick summary how it works on a high level :wink:
So basically, you have two choices when you want to have your classes as a module:

  1. You provide the classes in your process application / context classloader
  2. You add the classes to the engine’s classloader

Approach 1:
Reference the custom Wildfly module by either adding a jboss-deployment-structure.xml to the WAR/EAR like here or adding a dependencies entry inside the WAR’s MANIFEST.MF by using the maven-war-plugin for example.

Approach 2:
The Camunda Engine Wildfly subsystem implicitly adds the camunda-engine module to the classpath of a process application. You could leverage that by creating an own module and referencing it inside the camunda-engine module’s module.xml like it is done with the engine dependencies.

Does this help you?

Cheers,
Christian

Christian,

Well, I don’t claim to understand everything you’ve written here. Our attempts have been pretty straightforward configuration of the jar file into the “modules” directory under the correct sub-directory paths, and with the addition of a module.xml file. Unfortunately, I tend to acquire pieces of knowledge as needed rather than having a more complete knowledge of what is going on such as you have. In other words, I generally learn which buttons to press without always understanding why I’m pressing them.

My failings aside, I’m trying to understand what you’re saying here. My goal is to avoid having to include (i.e. use the “compile” scope in Maven) these classes in the process war files. The classes can get quite large and that makes testing more difficult. Also, we want everyone to use a consistent set of them and while we’ve put them in our Maven repository, we can’t guarantee that everyone will always download and compile with a consistent version. We want everyone to only use the one version on the WildFly server.

At a practical level, I’m trying to understand what you’re saying. So, in my case I have a custom class (SendCamundaProcessHistory) that allows us to create Camunda history records completely separate from Camunda (we have the history level set to “none”). We also have a class (SendCamundaProcessHistoryActivityListener) which implements an activity listener that invokes the methods in the SendCamundaProcessHistory class at the appropriate places as the process executes. Today, both of these classes are set as dependencies in the Maven pom.xml with a “compile” scope. We want to change that to “provided”.

So, we have current manifest entries like these in the plugin blocks:

<manifestEntries>
    <Dependencies>org.camunda.bpm.camunda-engine-plugin-spin, org.camunda.spin.camunda-spin-dataformat-json-jackson</Dependencies>
</manifestEntries>

Are you saying that we should be putting the custom classes into the manifest entries like above? For example, we would add “com.myco.camunda.history.SendCamundaProcessHistory” to the manifest list?

<manifestEntries>
    <Dependencies>org.camunda.bpm.camunda-engine-plugin-spin, org.camunda.spin.camunda-spin-dataformat-json-jackson, com.myco.camunda.history.SendCamundaProcessHistory</Dependencies>
</manifestEntries>

I did try something like this and put in entries in the section of the standalone.xml. It completely blew up the Camunda instance (well, that’s a bit dramatic, Camunda and all other wars wouldn’t deploy and I had to manually remove all of my process wars, revert the configuration, restart WildFly, and redeploy all the processes).

I know I’m asking alot, but I what I really need is a recipe for this, with an example. If you know of one, let me know, please.

Thanks for the prompt reply.

Michael

Hi @mppfor_manu,

Sorry that my explanation was a bit too much detailed.
Regarding your manifestEntries, you wouldn’t add your classes to it, instead you have to reference your custom module.

Given following directory structure of your custom module:

%WILDFLY_HOME%/modules/my/custom/module/main/module.xml
%WILDFLY_HOME%/modules/my/custom/module/main/mycustommodule.jar

You would reference the classes residing your module in the manifestEntries like

<manifestEntries>
    <Dependencies>my.custom.module, org.camunda.bpm.camunda-engine-plugin-spin, org.camunda.spin.camunda-spin-dataformat-json-jackson</Dependencies>
</manifestEntries>

This will add the jar to your deployment. Same concept applies to the jboss-deployment-structure.xml.
Also make sure that your custom module declares all needed dependencies in its module.xml.
For example, If it depends on classes from camunda-engine, you would need to the module to your dependencies section like

<dependencies>
...
  <module name="org.camunda.bpm.model.camunda-engine" />
...
</dependencies>

I hope that the explanation is easily understandable.

Cheers,
Christian

1 Like

Christian,

First, please don’t apologize for the detail. I’m grateful you took the time to provide it. I must apologize for my ignorance which necessitates further explanations.

Adding the custom class jar to the project deployment is precisely what I want to avoid, so I’m missing something here. Are you telling me I need to rebuild the Camunda engine itself with my custom classes? Why is it that I can simply include Camunda dependencies in my project and they work just fine? They’re just classes no different than my own, sitting in the exact same WildFly module directories as my class. Granted, there is a BOM for Camunda that manages these dependencies, but there are no other “special” configuration files in the deployment.

Thanks.

Hi @mppfor_manu,

You have to differentiate between build time dependencies, which are added to your deployment artifact, in this case your process application, and runtime (provided) dependencies.
Runtime / provided dependencies are for example jar files inside your application server.
Using Tomcat as an example, everything under %TOMCAT_HOME%/lib will be available to use for your deployment as it is put on the global classpath.

Coming back to WildFly, it has a bit different approach for providing those runtime dependencies than Tomcat.
It works with modules.
This means you have to put your custom jar under a custom path below the %WILDFLY_HOME%/modules like it is done for Camunda.
Then you have to create a module.xml etc. I am sure you already know that.
Those modules will not be put on the classpath by default like Tomcat does.

You have to tell WildFly which runtime libraries(modules) the deployment needs. This is done by giving WildFly the hint by adding the path of the module to the manifest of your deployment like I already wrote above.

<manifestEntries>
    <Dependencies>my.custom.module</Dependencies>
</manifestEntries>

Now, when you deploy your deployment (process application), WildFly will scan the manifest file and see that the manifestEntries contains dependencies. It will then include these dependencies to the classpath of your deployment, so your deployment can use them.

No, see my answer above.

Because the Camunda Wildfly subsystem includes the Camunda modules implicitly to the deployment when it detects a process application. That won’t happen for your class/custom module. It is part of the magic the Camunda subsystem does for common Camunda dependencies like camunda-engine etc.

Cheers,
Christian

I tried what you suggested and I get the following:

2017-07-13 13:04:32,816 ERROR [org.jboss.msc.service.fail] (MSC service thread 1-4) MSC000001: Failed to start service jboss.module.service."deployment.Process_PrintCounter1-0.0.4-SNAPSHOT.war".main: org.jboss.msc.service.StartException in service jboss.module.service."deployment.Process_PrintCounter1-0.0.4-SNAPSHOT.war".main: WFLYSRV0179: Failed to load module: deployment.Process_PrintCounter1-0.0.4-SNAPSHOT.war:main
	at org.jboss.as.server.moduleservice.ModuleLoadService.start(ModuleLoadService.java:91)
	at org.jboss.msc.service.ServiceControllerImpl$StartTask.startService(ServiceControllerImpl.java:1948)
	at org.jboss.msc.service.ServiceControllerImpl$StartTask.run(ServiceControllerImpl.java:1881)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)
Caused by: org.jboss.modules.ModuleNotFoundException: com.cop.camunda.history.SendCamundaProcessHistory:main
	at org.jboss.modules.Module.addPaths(Module.java:1093)
	at org.jboss.modules.Module.link(Module.java:1449)
	at org.jboss.modules.Module.relinkIfNecessary(Module.java:1477)
	at org.jboss.modules.ModuleLoader.loadModule(ModuleLoader.java:225)
	at org.jboss.as.server.moduleservice.ModuleLoadService.start(ModuleLoadService.java:68)
	... 5 more

2017-07-13 13:04:32,817 ERROR [org.jboss.as.controller.management-operation] (management-handler-thread - 8) WFLYCTL0013: Operation ("full-replace-deployment") failed - address: ([]) - failure description: {
    "WFLYCTL0080: Failed services" => {"jboss.module.service.\"deployment.Process_PrintCounter1-0.0.4-SNAPSHOT.war\".main" => "org.jboss.msc.service.StartException in service jboss.module.service.\"deployment.Process_PrintCounter1-0.0.4-SNAPSHOT.war\".main: WFLYSRV0179: Failed to load module: deployment.Process_PrintCounter1-0.0.4-SNAPSHOT.war:main
    Caused by: org.jboss.modules.ModuleNotFoundException: com.cop.camunda.history.SendCamundaProcessHistory:main"},
    "WFLYCTL0412: Required services that are not installed:" => ["jboss.module.service.\"deployment.Process_PrintCounter1-0.0.4-SNAPSHOT.war\".main"],
    "WFLYCTL0180: Services with missing/unavailable dependencies" => undefined
}
2017-07-13 13:04:33,504 INFO  [org.jboss.as.server.deployment] (MSC service thread 1-1) WFLYSRV0028: Stopped deployment Process_PrintCounter1-0.0.4-SNAPSHOT.war (runtime-name: Process_PrintCounter1-0.0.4-SNAPSHOT.war) in 684ms
2017-07-13 13:04:33,505 ERROR [org.jboss.as.controller.management-operation] (management-handler-thread - 8) WFLYCTL0190: Step handler org.jboss.as.server.deployment.DeploymentHandlerUtil$4@5a1e0d68 for operation {"operation" => "full-replace-deployment","address" => [],"name" => "Process_PrintCounter1-0.0.4-SNAPSHOT.war","content" => [{"input-stream-index" => 0}],"enabled" => true,"operation-headers" => {"caller-type" => "user","access-mechanism" => "NATIVE"},"runtime-name" => undefined,"persistent" => true,"owner" => undefined} at address [] failed handling operation rollback -- java.util.NoSuchElementException: No child 'name' exists: java.util.NoSuchElementException: No child 'name' exists
	at org.jboss.dmr.ModelValue.requireChild(ModelValue.java:377)
	at org.jboss.dmr.ObjectModelValue.requireChild(ObjectModelValue.java:299)

Here’s the module.xml file contents:

  <resources>
	<resource-root path="SendCamundaProcessHistory-0.0.2-SNAPSHOT.jar" />
  </resources>
  
  <dependencies>
	<module name="com.jcraft.jsch" export="true" />
	<module name="com.github.fge.json-schema-validator" export="true" />
	<module name="com.jayway.jsonpath.json-path" export="true" />
	<module name="com.mysql" export="true" />
  </dependencies>
</module>

Could you please write down the path of your module and attach the dependencies entry in the manifest?

The folder main should be left out of the path for the module.
So your module (your jar SendCamundaProcessHistory-0.0.2-SNAPSHOT.jar and its module.xml) should be located in the folder %WILDFLY_HOME%/modules/com/cop/camunda/history/main/.
Then your entry for the dependencies in the manifest must be com.cop.camunda.history, nothing more.
That should work.

Cheers,
Christian

<Dependencies>org.camunda.bpm.camunda-engine-plugin-spin, org.camunda.spin.camunda-spin-dataformat-json-jackson, com.cop.camunda.history.SendCamundaProcessHistory</Dependencies>

I suspect this might be a dependency within the class itself. I’ve never traced the class loading, which is very tedious.

When you add this to the manifest, Wildfly will try to lookup the module (module.xml) inside modules/com/cop/camunda/history/SendCamundaProcessHistory/main/.

And that is where the module.xml and custom class jar file are located. I just followed the same pattern as many other modules.

When I did this manually for the MySQL connector so that we wouldn’t have to include that in the war file, it worked perfectly. This is why I felt there was something “special” about building a custom class that used Camunda Java delegate libraries.

My suspicion all along has been that this exception is actually masking underlying dependency problem. This custom class uses another custom class, which has its own dependencies. This additional custom class, which is not called by the Camunda process directly, is compiled into the SendCamundaProcessHistory class. Like I said, my gut tells me this may be a case of “dependency roulette” where the actual problem is hidden behind the class loading mechanism.

You are discussing global modules in wildfly - there is no clear steps how to make global modules only a way to make normal module.

It depends upon what you are trying to do. In many cases, all you need to do is put the jar file of the class you want to be globally available in the “$JBOSS_HOME/standalone/modules/system/layers/base/X/main” directory, where “X” is the Maven coordinate (package name) of the class.

For example, if I have a class named “MyDataCheck”, with a package name of “com.mycompany.myutils”, then you would copy the class jar file to:

$JBOSS_HOME/standalone/modules/system/layers/base/com/mycompany/myutils/main/MyDataCheck-1.0.0.jar

You would then need to create a “module.xml” file in the “main” directory. Here’s an example:

<?xml version="1.0" encoding="UTF-8"?>

Note that the “” section is optional and I’ve just shown you an example here. Your class may not have any external dependencies.

If everything is set up correctly, after you bounce WildFly, the module should be available globally.

Hello,

In my case I can install org.json and works good! But in org.apache.kafka send me this error:
Caused by: java.lang.ClassNotFoundException: org.slf4j.LoggerFactory from [Module “org.apache.kafka” from local module loader @5f049ea1 (finder: local module finder @72cc7e6f (roots: /camunda/modules,/camunda/modules/system/layers/base))]
Looks like the org.apache.kafka is installed but can’t load the dependencie slf4j… You can help me?