Plugins with new Entities in CUBA Application

I have application that can reasonably extended via some kind of “plugins”. The set of plugins is static for a particular deployment of the application, so we know this set at the startup (but not at the build time). The plugins contain new entities which shall be persisted to the main datastore.

To begin with I tried to use a separate module inside my application. I created a separate module with an entity inside my application and linked into core and web modules.

In build.gradle I have:

configure([globalModule, coreModule, webModule,
           pluginGlobalModule]) {
/* ... as created by CUBA Studio ... */
}

configure(pluginGlobalModule) {
    dependencies {
        implementation group: 'com.haulmont.cuba', name: 'cuba-global', version: "${cubaVersion}"
        implementation globalModule
    }

    entitiesEnhancing {
        main {
            enabled = true
        }
    }
}

configure(coreModule) {
// ...
dependencies {
        compile(pluginGlobalModule)
//...
}
// ...
}

// same for web module

I created plugin-persistence.xml which mentions the entity class and added it to app.properties and web-app.properties:

cuba.persistenceConfig = +com/example/persistence.xml com/example/plugin-persistence.xml

Now when I start the application I get the following exception:

17:11:54.234 ERROR c.h.c.c.s.AbstractWebAppContextLoader   - Error initializing application
java.lang.reflect.UndeclaredThrowableException: Failed to invoke event listener method
HandlerMethod details: 
Bean [com.haulmont.cuba.core.sys.MetadataImpl]
Method [protected void com.haulmont.cuba.core.sys.MetadataImpl.initMetadata()]
Resolved arguments: 

	at org.springframework.context.event.ApplicationListenerMethodAdapter.doInvoke(ApplicationListenerMethodAdapter.java:361) ~[spring-context-5.3.5.jar:5.3.5]
	at org.springframework.context.event.ApplicationListenerMethodAdapter.processEvent(ApplicationListenerMethodAdapter.java:229) ~[spring-context-5.3.5.jar:5.3.5]
	at org.springframework.context.event.ApplicationListenerMethodAdapter.onApplicationEvent(ApplicationListenerMethodAdapter.java:166) ~[spring-context-5.3.5.jar:5.3.5]
	at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:176) ~[spring-context-5.3.5.jar:5.3.5]
	at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:169) ~[spring-context-5.3.5.jar:5.3.5]
	at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:143) ~[spring-context-5.3.5.jar:5.3.5]
	at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:421) ~[spring-context-5.3.5.jar:5.3.5]
	at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:378) ~[spring-context-5.3.5.jar:5.3.5]
	at com.haulmont.cuba.core.sys.EventsImpl.publish(EventsImpl.java:33) ~[cuba-global-7.2.13.jar:7.2.13]
	at com.haulmont.cuba.web.sys.WebEvents.publish(WebEvents.java:36) ~[cuba-web-7.2.13.jar:7.2.13]
	at com.haulmont.cuba.core.sys.AbstractAppContextLoader.initAppContext(AbstractAppContextLoader.java:69) ~[cuba-global-7.2.13.jar:7.2.13]
	at com.haulmont.cuba.core.sys.AbstractWebAppContextLoader.contextInitialized(AbstractWebAppContextLoader.java:86) ~[cuba-global-7.2.13.jar:7.2.13]
	at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:4685) [catalina.jar:9.0.27]
	at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5146) [catalina.jar:9.0.27]
	at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183) [catalina.jar:9.0.27]
	at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:717) [catalina.jar:9.0.27]
	at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:690) [catalina.jar:9.0.27]
	at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:705) [catalina.jar:9.0.27]
	at org.apache.catalina.startup.HostConfig.deployDirectory(HostConfig.java:1133) [catalina.jar:9.0.27]
	at org.apache.catalina.startup.HostConfig$DeployDirectory.run(HostConfig.java:1867) [catalina.jar:9.0.27]
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) [na:1.8.0_222]
	at java.util.concurrent.FutureTask.run(FutureTask.java:266) [na:1.8.0_222]
	at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:75) [tomcat-util.jar:9.0.27]
	at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:112) [na:1.8.0_222]
	at org.apache.catalina.startup.HostConfig.deployDirectories(HostConfig.java:1045) [catalina.jar:9.0.27]
	at org.apache.catalina.startup.HostConfig.deployApps(HostConfig.java:429) [catalina.jar:9.0.27]
	at org.apache.catalina.startup.HostConfig.start(HostConfig.java:1576) [catalina.jar:9.0.27]
	at org.apache.catalina.startup.HostConfig.lifecycleEvent(HostConfig.java:309) [catalina.jar:9.0.27]
	at org.apache.catalina.util.LifecycleBase.fireLifecycleEvent(LifecycleBase.java:123) [catalina.jar:9.0.27]
	at org.apache.catalina.util.LifecycleBase.setStateInternal(LifecycleBase.java:423) [catalina.jar:9.0.27]
	at org.apache.catalina.util.LifecycleBase.setState(LifecycleBase.java:366) [catalina.jar:9.0.27]
	at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:936) [catalina.jar:9.0.27]
	at org.apache.catalina.core.StandardHost.startInternal(StandardHost.java:841) [catalina.jar:9.0.27]
	at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183) [catalina.jar:9.0.27]
	at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1384) [catalina.jar:9.0.27]
	at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1374) [catalina.jar:9.0.27]
	at java.util.concurrent.FutureTask.run(FutureTask.java:266) [na:1.8.0_222]
	at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:75) [tomcat-util.jar:9.0.27]
	at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:134) [na:1.8.0_222]
	at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:909) [catalina.jar:9.0.27]
	at org.apache.catalina.core.StandardEngine.startInternal(StandardEngine.java:262) [catalina.jar:9.0.27]
	at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183) [catalina.jar:9.0.27]
	at org.apache.catalina.core.StandardService.startInternal(StandardService.java:421) [catalina.jar:9.0.27]
	at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183) [catalina.jar:9.0.27]
	at org.apache.catalina.core.StandardServer.startInternal(StandardServer.java:930) [catalina.jar:9.0.27]
	at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183) [catalina.jar:9.0.27]
	at org.apache.catalina.startup.Catalina.start(Catalina.java:633) [catalina.jar:9.0.27]
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_222]
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_222]
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_222]
	at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_222]
	at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:344) [bootstrap.jar:9.0.27]
	at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:475) [bootstrap.jar:9.0.27]
Caused by: java.lang.NoClassDefFoundError: com/haulmont/cuba/core/sys/CubaEnhanced
	at java.lang.ClassLoader.defineClass1(Native Method) ~[na:1.8.0_222]
	at java.lang.ClassLoader.defineClass(ClassLoader.java:763) ~[na:1.8.0_222]
	at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142) ~[na:1.8.0_222]
	at java.net.URLClassLoader.defineClass(URLClassLoader.java:468) ~[na:1.8.0_222]
	at java.net.URLClassLoader.access$100(URLClassLoader.java:74) ~[na:1.8.0_222]
	at java.net.URLClassLoader$1.run(URLClassLoader.java:369) ~[na:1.8.0_222]
	at java.net.URLClassLoader$1.run(URLClassLoader.java:363) ~[na:1.8.0_222]
	at java.security.AccessController.doPrivileged(Native Method) ~[na:1.8.0_222]
	at java.net.URLClassLoader.findClass(URLClassLoader.java:362) ~[na:1.8.0_222]
	at java.lang.ClassLoader.loadClass(ClassLoader.java:424) ~[na:1.8.0_222]
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357) ~[na:1.8.0_222]
	at java.lang.Class.forName0(Native Method) ~[na:1.8.0_222]
	at java.lang.Class.forName(Class.java:348) ~[na:1.8.0_222]
	at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1351) ~[catalina.jar:9.0.27]
	at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1188) ~[catalina.jar:9.0.27]
	at com.haulmont.bali.util.ReflectionHelper.loadClass(ReflectionHelper.java:67) ~[cuba-global-7.2.13.jar:7.2.13]
	at com.haulmont.cuba.core.sys.MetaModelLoader.loadModel(MetaModelLoader.java:92) ~[cuba-global-7.2.13.jar:7.2.13]
	at com.haulmont.cuba.core.sys.MetadataLoader.loadMetadata(MetadataLoader.java:114) ~[cuba-global-7.2.13.jar:7.2.13]
	at com.haulmont.cuba.core.sys.MetadataImpl.initMetadata(MetadataImpl.java:111) ~[cuba-global-7.2.13.jar:7.2.13]
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_222]
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_222]
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_222]
	at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_222]
	at org.springframework.context.event.ApplicationListenerMethodAdapter.doInvoke(ApplicationListenerMethodAdapter.java:344) ~[spring-context-5.3.5.jar:5.3.5]
	... 52 common frames omitted
Caused by: java.lang.ClassNotFoundException: com.haulmont.cuba.core.sys.CubaEnhanced
	at java.net.URLClassLoader.findClass(URLClassLoader.java:382) ~[na:1.8.0_222]
	at java.lang.ClassLoader.loadClass(ClassLoader.java:424) ~[na:1.8.0_222]
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357) ~[na:1.8.0_222]
	... 76 common frames omitted

What am I missing? Are there best practices to the original (plugins) goal.

Hi,
The recommended way to split your application into parts is to use Application Components (or so called add-ons).

I’d recommend to read information below:

The way with application components is supported well by the Studio and framework mechanisms.

What you are trying to do now (create additional modules in the same project) is also feasible. But it:

  • requires deep knowledge of the platform (build scripts, gradle plugin mechanism), and you’ll spend much time to get things working;
  • not documented;
  • will not get much help from the Studio.

Thank you, I just got an impression the app components are added at build time. I’ll study the link in detail.

Yes, components are added in build time.

The idea is that if you need a project A with plugin B, you need to publish A and B as add-on, and create 3rd project C that includes both A and B.

The list of included components is declared statically in the web.xml file of the final project, e.g.:

    <context-param>
        <param-name>appComponents</param-name>
        <param-value>com.haulmont.cuba com.haulmont.reports com.haulmont.charts de.diedavids.cuba.instantlauncher
            com.haulmont.addon.admintools com.haulmont.addon.emailtemplates com.haulmont.addon.restapi</param-value>
    </context-param>

The platform doesn’t provide ability to determine list of active app components dynamically in the run time.

Thank you.