Building multiple WAR files from one project. One WAR file for each client

I have developed an app for multiple clients to use. My plan is to deploy instances of my app to multiple folders using Tomcat using port 8080 such as
C:/Program Files/Apache Software Foundation/Tomcat 9.0/webapps/custapp1
C:/Program Files/Apache Software Foundation/Tomcat 9.0/webapps/custapp2
C:/Program Files/Apache Software Foundation/Tomcat 9.0/webapps/custapp3

with corresponding URLs:

http://localhost:8080/custapp1
http://localhost:8080/custapp2
http://localhost:8080/custapp3

I am using single WAR file build. Is this possible ?
I have been trying for days but getting an error :

Caused by: java.net.BindException: Address already in use: bind

What settings would I need ?

How do others deploy their app to multiple clients using the same code base ?

Hi,

This error message doesn’t relate to three CUBA applications deployed in the same Tomcat.
The message comes from Tomcat.
It means that port (8080 or any other port that you use for http protocol) is already occupied by another application running on the same computer.

You need to find what is this application. Maybe it’s the same Tomcat auto-starting as a system service in the background?

I already have a Cuba project deployed on that Tomcat in the same “webapps” folder.
So I was asking what settings do I have to have in order for multiple Cuba projects to be deployed in the same “webapps” folder, same port and each project as a single WAR file?

I already have different appName as well as different connectionUrlList in each build.grade

This is my log file:

26-Apr-2021 19:59:55.363 INFO [main] org.apache.catalina.core.ApplicationContext.log Initializing AtmosphereFramework
26-Apr-2021 19:59:55.812 INFO [main] org.apache.catalina.core.ApplicationContext.log No Spring WebApplicationInitializer types detected on classpath
26-Apr-2021 20:00:19.299 INFO [main] org.apache.catalina.core.ApplicationContext.log Initializing Spring RemotingServlet 'remoting'
26-Apr-2021 20:00:28.310 SEVERE [main] org.apache.catalina.core.StandardContext.listenerStart Exception sending context initialized event to listener instance of class [com.haulmont.cuba.web.sys.singleapp.SingleAppWebServletListener]
	java.lang.RuntimeException: An error occurred while starting single WAR application
		at com.haulmont.cuba.web.sys.singleapp.SingleAppWebServletListener.contextInitialized(SingleAppWebServletListener.java:85)
		at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:4716)
		at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5172)
		at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183)
		at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:717)
		at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:690)
		at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:706)
		at org.apache.catalina.startup.HostConfig.deployWAR(HostConfig.java:1023)
		at org.apache.catalina.startup.HostConfig$DeployWar.run(HostConfig.java:1903)
		at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
		at java.util.concurrent.FutureTask.run(FutureTask.java:266)
		at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:75)
		at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:112)
		at org.apache.catalina.startup.HostConfig.deployWARs(HostConfig.java:824)
		at org.apache.catalina.startup.HostConfig.deployApps(HostConfig.java:474)
		at org.apache.catalina.startup.HostConfig.start(HostConfig.java:1611)
		at org.apache.catalina.startup.HostConfig.lifecycleEvent(HostConfig.java:319)
		at org.apache.catalina.util.LifecycleBase.fireLifecycleEvent(LifecycleBase.java:123)
		at org.apache.catalina.util.LifecycleBase.setStateInternal(LifecycleBase.java:423)
		at org.apache.catalina.util.LifecycleBase.setState(LifecycleBase.java:366)
		at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:936)
		at org.apache.catalina.core.StandardHost.startInternal(StandardHost.java:843)
		at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183)
		at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1384)
		at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1374)
		at java.util.concurrent.FutureTask.run(FutureTask.java:266)
		at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:75)
		at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:134)
		at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:909)
		at org.apache.catalina.core.StandardEngine.startInternal(StandardEngine.java:262)
		at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183)
		at org.apache.catalina.core.StandardService.startInternal(StandardService.java:433)
		at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183)
		at org.apache.catalina.core.StandardServer.startInternal(StandardServer.java:930)
		at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183)
		at org.apache.catalina.startup.Catalina.start(Catalina.java:772)
		at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
		at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
		at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
		at java.lang.reflect.Method.invoke(Method.java:498)
		at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:342)
		at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:473)
	Caused by: java.lang.reflect.InvocationTargetException
		at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
		at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
		at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
		at java.lang.reflect.Method.invoke(Method.java:498)
		at com.haulmont.cuba.web.sys.singleapp.SingleAppWebServletListener.contextInitialized(SingleAppWebServletListener.java:79)
		... 41 more
	Caused by: org.springframework.jmx.export.UnableToRegisterMBeanException: Unable to register MBean [com.haulmont.cuba.web.jmx.ConfigStorage@754e7586] with key 'kmdoffice.cuba:type=ConfigStorage'; nested exception is javax.management.InstanceAlreadyExistsException: kmdoffice.cuba:type=ConfigStorage
		at org.springframework.jmx.export.MBeanExporter.registerBeanNameOrInstance(MBeanExporter.java:626)
		at org.springframework.jmx.export.MBeanExporter.lambda$registerBeans$2(MBeanExporter.java:552)
		at java.util.LinkedHashMap.forEach(LinkedHashMap.java:684)
		at org.springframework.jmx.export.MBeanExporter.registerBeans(MBeanExporter.java:552)
		at org.springframework.jmx.export.MBeanExporter.afterSingletonsInstantiated(MBeanExporter.java:435)
		at com.haulmont.cuba.core.sys.jmx.MBeanExporter.afterSingletonsInstantiated(MBeanExporter.java:77)
		at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:914)
		at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:879)
		at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:551)
		at org.springframework.context.support.AbstractRefreshableConfigApplicationContext.afterPropertiesSet(AbstractRefreshableConfigApplicationContext.java:154)
		at com.haulmont.cuba.core.sys.CubaClassPathXmlApplicationContext.<init>(CubaClassPathXmlApplicationContext.java:42)
		at com.haulmont.cuba.web.sys.singleapp.SingleAppWebContextLoader.createApplicationContext(SingleAppWebContextLoader.java:201)
		at com.haulmont.cuba.web.sys.singleapp.SingleAppWebContextLoader.createApplicationContext(SingleAppWebContextLoader.java:49)
		at com.haulmont.cuba.core.sys.AbstractAppContextLoader.initAppContext(AbstractAppContextLoader.java:65)
		at com.haulmont.cuba.core.sys.AbstractWebAppContextLoader.contextInitialized(AbstractWebAppContextLoader.java:86)
		at com.haulmont.cuba.web.sys.singleapp.SingleAppWebContextLoader.contextInitialized(SingleAppWebContextLoader.java:73)
		... 46 more
	Caused by: javax.management.InstanceAlreadyExistsException: kmdoffice.cuba:type=ConfigStorage
		at com.sun.jmx.mbeanserver.Repository.addMBean(Repository.java:437)
		at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.registerWithRepository(DefaultMBeanServerInterceptor.java:1898)
		at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.registerDynamicMBean(DefaultMBeanServerInterceptor.java:966)
		at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.registerObject(DefaultMBeanServerInterceptor.java:900)
		at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.registerMBean(DefaultMBeanServerInterceptor.java:324)
		at com.sun.jmx.mbeanserver.JmxMBeanServer.registerMBean(JmxMBeanServer.java:522)
		at org.springframework.jmx.support.MBeanRegistrationSupport.doRegister(MBeanRegistrationSupport.java:138)
		at org.springframework.jmx.export.MBeanExporter.registerBeanInstance(MBeanExporter.java:680)
		at org.springframework.jmx.export.MBeanExporter.registerBeanNameOrInstance(MBeanExporter.java:616)
		... 61 more
26-Apr-2021 20:00:28.359 SEVERE [main] org.apache.catalina.core.StandardContext.listenerStop Exception sending context destroyed event to listener instance of class [com.haulmont.cuba.web.sys.singleapp.SingleAppWebServletListener]
	java.lang.RuntimeException: An error occurred while destroying context of single WAR application
		at com.haulmont.cuba.web.sys.singleapp.SingleAppWebServletListener.contextDestroyed(SingleAppWebServletListener.java:99)
		at org.apache.catalina.core.StandardContext.listenerStop(StandardContext.java:4762)
		at org.apache.catalina.core.StandardContext.stopInternal(StandardContext.java:5418)
		at org.apache.catalina.util.LifecycleBase.stop(LifecycleBase.java:257)
		at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:187)
		at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:717)
		at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:690)
		at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:706)
		at org.apache.catalina.startup.HostConfig.deployWAR(HostConfig.java:1023)
		at org.apache.catalina.startup.HostConfig$DeployWar.run(HostConfig.java:1903)
		at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
		at java.util.concurrent.FutureTask.run(FutureTask.java:266)
		at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:75)
		at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:112)
		at org.apache.catalina.startup.HostConfig.deployWARs(HostConfig.java:824)
		at org.apache.catalina.startup.HostConfig.deployApps(HostConfig.java:474)
		at org.apache.catalina.startup.HostConfig.start(HostConfig.java:1611)
		at org.apache.catalina.startup.HostConfig.lifecycleEvent(HostConfig.java:319)
		at org.apache.catalina.util.LifecycleBase.fireLifecycleEvent(LifecycleBase.java:123)
		at org.apache.catalina.util.LifecycleBase.setStateInternal(LifecycleBase.java:423)
		at org.apache.catalina.util.LifecycleBase.setState(LifecycleBase.java:366)
		at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:936)
		at org.apache.catalina.core.StandardHost.startInternal(StandardHost.java:843)
		at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183)
		at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1384)
		at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1374)
		at java.util.concurrent.FutureTask.run(FutureTask.java:266)
		at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:75)
		at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:134)
		at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:909)
		at org.apache.catalina.core.StandardEngine.startInternal(StandardEngine.java:262)
		at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183)
		at org.apache.catalina.core.StandardService.startInternal(StandardService.java:433)
		at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183)
		at org.apache.catalina.core.StandardServer.startInternal(StandardServer.java:930)
		at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183)
		at org.apache.catalina.startup.Catalina.start(Catalina.java:772)
		at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
		at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
		at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
		at java.lang.reflect.Method.invoke(Method.java:498)
		at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:342)
		at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:473)
	Caused by: java.lang.reflect.InvocationTargetException
		at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
		at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
		at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
		at java.lang.reflect.Method.invoke(Method.java:498)
		at com.haulmont.cuba.web.sys.singleapp.SingleAppWebServletListener.contextDestroyed(SingleAppWebServletListener.java:93)
		... 42 more
	Caused by: java.lang.NullPointerException
		at com.haulmont.cuba.core.sys.AbstractWebAppContextLoader.contextDestroyed(AbstractWebAppContextLoader.java:137)
		at com.haulmont.cuba.web.sys.singleapp.SingleAppWebContextLoader.contextDestroyed(SingleAppWebContextLoader.java:107)
		... 47 more

Hi,
Unfortunately, CUBA WAR building tasks don’t support out-of-the-box deploying one WAR file several times to the same Tomcat instance.

The problem is with ‘cuba.webContextName’ application which should be different for each web application (and it’s not automatically determined by WAR file name in runtime).

The easiest solution would be to use several Tomcat servers, one for each application.

However I’ve spent some time and with some tweaking of configuration files I managed to successfully run two instances of the same web application built as single WAR (using CUBA 7.2).
You can use the same approach in your project.

You will need to create separate buildWar task for each web application you want to deploy. Renaming assembled WAR files won’t work.

Here are the steps:

  1. Modify single-war-web.xml file (main web application descriptor used for single WAR building).
    <context-param>
        <description>List of app properties files for Web Client</description>
        <param-name>appPropertiesConfigWeb</param-name>
        <param-value>
            classpath:com/company/xxxx/web-app.properties
            /WEB-INF/local.app.properties
            /WEB-INF/web.local.app.properties
            "file:${app.home}/local.app.properties"
        </param-value>
    </context-param>
    

    <!-- Middleware parameters -->

    <context-param>
        <description>List of app properties files for Middleware</description>
        <param-name>appPropertiesConfigCore</param-name>
        <param-value>
            classpath:com/company/xxxx/app.properties
            /WEB-INF/local.app.properties
            /WEB-INF/core.local.app.properties
            "file:${app.home}/local.app.properties"
        </param-value>
    </context-param>

Here I added two lines:

            /WEB-INF/web.local.app.properties

and

            /WEB-INF/core.local.app.properties
  1. Create extended CubaWarBuilding task class in the end of the build.gradle file.
    It’s necessary to correctly fill ‘cuba.webContextName’ and ‘cuba.connectionUrlList’ properties.
class MyCubaWarBuilding extends CubaWarBuilding {
    @Override
    protected Map<String, Object> collectProperties(Project theProject) {
        Map<String, Object> map = super.collectProperties(theProject)
        if (theProject == webProject) {
            map.put('cuba.connectionUrlList', "http://localhost:8080/${appName}-core")
        }
        return map
    }

    @Override
    protected void writeLocalAppProperties(Project theProject, Object properties) {
        super.writeLocalAppProperties(theProject, properties)

        Map<String, String> coreProps = [
                'cuba.webContextName': "${appName}-core"
        ]
        writeAdditionalProps(theProject, 'core.local.app.properties', coreProps)

        Map<String, String> webProps = [
                'cuba.webContextName': "${appName}"
        ]
        writeAdditionalProps(theProject, 'web.local.app.properties', webProps)
    }

    private void writeAdditionalProps(Project theProject, String fileName, Map<String, Object> properties) {
        File appPropFile = new File("${warDir(theProject)}/WEB-INF/$fileName")

        project.logger.info("[CubaWarBuilding] writing $appPropFile")
        appPropFile.withWriter('UTF-8') { writer ->
            properties.each { key, value ->
                writer << key << ' = ' << value << '\n'
            }
        }
    }
}
  1. For each additional WAR you need to deploy as separate web application, manually create a task in the build.gradle.
    E.g. my application name is “app”, but I want to deploy additional WAR “sales.war”.
// normal buildWar for app.war
task buildWar(type: CubaWarBuilding) {
    includeJdbcDriver = true
    appProperties = ['cuba.automaticDatabaseUpdate': true,
                     'cuba.dataSource.username'    : 'root',
                     'cuba.dataSource.password'    : 'root',
                     'cuba.dataSource.dbName'      : 'myproject',
                     'cuba.dataSource.host'        : 'localhost',
                     'cuba.dataSource.port'        : '',
                     'cuba.dataSourceProvider'     : 'application']
    webXmlPath = 'modules/web/web/WEB-INF/single-war-web.xml'
}

// custom task for sales.war
task buildWarSales(type: MyCubaWarBuilding) {
    includeJdbcDriver = true
    appProperties = ['cuba.automaticDatabaseUpdate': true,
                     'cuba.dataSource.username'    : 'root',
                     'cuba.dataSource.password'    : 'root',
                     'cuba.dataSource.dbName'      : 'myproject_sales',
                     'cuba.dataSource.host'        : 'localhost',
                     'cuba.dataSource.port'        : '',
                     'cuba.dataSourceProvider'     : 'application']
    webXmlPath = 'modules/web/web/WEB-INF/single-war-web.xml'
    appName = 'sales'
}
  • task name must be unique for each WAR
  • appName - is the name of the WAR file
  • note using of custom MyCubaWarBuilding task class
  • call “gradlew buildWarSales” in terminal or through Gradle tool window to build additional WAR file.

Thank you Alex, I followed these instructions and it worked !

I spent days, trying to get it to work. I believe that these instructions will help many persons who had these issues before :slight_smile: