Search code examples
hibernatejakarta-eeglassfishwildflyh2

How to run Jakarta EE application with embedded server?


My goal is to create Jakarta EE web application which uses embedded web server and embedded database, so it will be possible to run this webapp with single command.

Currently I have an app which uses embedded H2 database, it works fine when deployed to separate Glassfish instance, but fails with embedded one.

I have tried both cargo-maven3-plugin and wildfly-maven-plugin.

Sample project

This is web app I'm trying to launch in embedded Jakarta EE server:

./pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>example</groupId>
    <artifactId>jakartaee-hello-world</artifactId>
    <version>0.1-SNAPSHOT</version>
    <packaging>war</packaging>

    <name>jakartaee-hello-world</name>
    <description>
    </description>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.report.sourceEncoding>UTF-8</project.report.sourceEncoding>
        <maven.compiler.release>17</maven.compiler.release>
        <jakartaee-api.version>10.0.0</jakartaee-api.version>
        <payara.version>6.2023.5</payara.version>
        <compiler-plugin.version>3.11.0</compiler-plugin.version>
        <war-plugin.version>3.3.2</war-plugin.version>
        <cargo.version>1.10.7</cargo.version>
        <wildfly.version>27.0.1.Final</wildfly.version>
        <wildfly-plugin.version>4.1.0.Final</wildfly-plugin.version>
    </properties>

    <dependencies>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.28</version>
        </dependency>

        <dependency>
            <groupId>jakarta.platform</groupId>
            <artifactId>jakarta.jakartaee-api</artifactId>
            <version>${jakartaee-api.version}</version>
            <scope>provided</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.hibernate.orm/hibernate-core -->
        <dependency>
            <groupId>org.hibernate.orm</groupId>
             <artifactId>hibernate-core</artifactId>
            <version>6.2.6.Final</version>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <version>2.1.214</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-dbcp2</artifactId>
            <version>2.9.0</version>
        </dependency>
        

    </dependencies>

    <build>
        <finalName>myapp</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>${compiler-plugin.version}</version>
            </plugin>
            <plugin>
                <artifactId>maven-war-plugin</artifactId>
                <version>${war-plugin.version}</version>
                <configuration>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                </configuration>
            </plugin>

            <!-- Execute 'mvn clean package cargo:run' to run the application. -->
            <plugin>
                <groupId>org.codehaus.cargo</groupId>
                <artifactId>cargo-maven3-plugin</artifactId>
                <version>${cargo.version}</version>
                <configuration>
                    <container>
                        <containerId>payara</containerId>
                        <artifactInstaller>
                            <groupId>fish.payara.distributions</groupId>
                            <artifactId>payara</artifactId>
                            <version>${payara.version}</version>
                        </artifactInstaller>
                    </container>
                </configuration>
            </plugin>

            <!-- Execute 'mvn clean package wildfly:dev' to run the application. -->
            <!-- It doesn't work for some reason in this Jakarta MVC project -->
            <plugin>
                <groupId>org.wildfly.plugins</groupId>
                <artifactId>wildfly-maven-plugin</artifactId>
                <version>${wildfly-plugin.version}</version>
                <configuration>
                    <version>${wildfly.version}</version>
                    <server-config>glassfish-resources.xml</server-config>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

src\main\webapp\WEB-INF\glassfish-resources.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE resources PUBLIC "-//GlassFish.org//DTD GlassFish Application Server 3.1 Resource Definitions//EN" "http://glassfish.org/dtds/glassfish-resources_1_5.dtd">
<resources>
    <jdbc-connection-pool 
                datasource-classname="org.apache.commons.dbcp2.BasicDataSource"
                name="embeddedConnPool"
                res-type="javax.sql.DataSource">

        <property name="URL" value="jdbc:h2:mem:dataSource;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=false" />
        <property name="User" value="root" />
        <property name="Password" value="qwerty" />

    </jdbc-connection-pool>

    <jdbc-resource jndi-name="java:app/jdbc/embeddedDS" pool-name="embeddedConnPool" enabled="true" />
</resources>

src\main\resources\META-INF\persistence.xml:

<persistence xmlns="https://jakarta.ee/xml/ns/persistence" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="https://jakarta.ee/xml/ns/persistence https://jakarta.ee/xml/ns/persistence/persistence_3_0.xsd" 
    version="3.0">
    
    <persistence-unit   name="only-unit"
                        transaction-type="JTA" >
        
        <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
        <jta-data-source>java:app/jdbc/embeddedDS</jta-data-source>
        
        <properties>
            <property name="jakarta.persistence.schema-generation.database.action" value="drop-and-create"/>
            <property name="hibernate.transaction.jta.platform" value="org.hibernate.service.jta.platform.internal.SunOneJtaPlatform" />
        </properties>
    </persistence-unit>
    
</persistence>

src\main\java\myapp\rest\DemoApplication.java:

package myapp.rest;

import jakarta.ws.rs.ApplicationPath;
import jakarta.ws.rs.core.Application;

@ApplicationPath("/rest/*")
public class DemoApplication extends Application {
}

src\main\java\myapp\rest\PersonResource.java:

package myapp.rest;

import java.util.List;

import jakarta.inject.Inject;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import myapp.dao.PersonDao;
import myapp.model.Person;

@Path("person")
public class PersonResource {

    @Inject
    PersonDao personDao;

    @GET
    @Path("all")
    @Produces({ MediaType.APPLICATION_JSON })
    public List<Person> findAll() {
        var list = personDao.findAll();
        return list;
    }
    @POST
    @Path("new")
    @Produces({ MediaType.APPLICATION_JSON })
    @Consumes(MediaType.APPLICATION_JSON)
    public Person create(Person person) {
        person.setId(null);
        System.out.println(person);
        personDao.create(person);
        return person;
    }
}

src\main\java\myapp\dao\PersonDao.java:

package myapp.dao;

import java.util.List;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.transaction.Transactional;
import myapp.model.Person;

@ApplicationScoped
public class PersonDao {

    @PersistenceContext(unitName = "only-unit")
    private EntityManager em;

    @Transactional
    public Person create(Person person) {
        em.persist(person);
        return person;
    }

    public List<Person> findAll() {
        String queryString = "SELECT p FROM Person p";
        var query = em.createQuery(queryString, Person.class);
        var resultList = query.getResultList();
        return resultList;
    }

}

src\main\java\myapp\model\Person.java:

package myapp.model;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
@Entity
public class Person {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    private String name;
    
}

Expectations

I was expecting to execute mvn clean package cargo:run or mvn clean package wildfly:dev, and rest endpoint will become available: curl.exe -X GET http://localhost:8080/myapp/rest/person/all/

Reality

mvn clean package cargo:run results in this stacktrace:

[INFO] [talledLocalContainer] Payara 6.2023.5 starting...
[INFO] [talledLocalContainer] WARNING: You are running the product on an unsupported JDK version and might see unexpected results or exceptions.
[INFO] [talledLocalContainer] Attempting to start cargo-domain.... Please look at the server log for more details.....
[INFO] [talledLocalContainer] Payara 6.2023.5 is stopping...
[INFO] [talledLocalContainer] Payara 6.2023.5 is stopped
[WARNING] [talledLocalContainer] org.codehaus.cargo.util.CargoException: At least one GlassFish deployment has failed: org.codehaus.cargo.util.CargoException: GlassFish admin command with args (--interactive=false --host localhost --port 4848 --user admin --passwordfile C:\JAVA2023\JakartaPlatformHiberRestTinyH2\target\cargo\configurations\payara\password.properties deploy --force --contextroot myapp C:\JAVA2023\JakartaPlatformHiberRestTinyH2\target\myapp.war) failed: asadmin exited 1: remote failure: Error occurred during deployment: Exception while preparing the app : org/hibernate/bytecode/enhance/spi/EnhancementContextWrapper. Please see server.log for more details.
Command deploy failed.
[ERROR] Starting container [org.codehaus.cargo.container.payara.PayaraInstalledLocalContainer@3c6bd624] failed
org.codehaus.cargo.util.CargoException: At least one GlassFish deployment has failed: org.codehaus.cargo.util.CargoException: GlassFish admin command with args (--interactive=false --host localhost --port 4848 --user admin --passwordfile C:\JAVA2023\JakartaPlatformHiberRestTinyH2\target\cargo\configurations\payara\password.properties deploy --force --contextroot myapp C:\JAVA2023\JakartaPlatformHiberRestTinyH2\target\myapp.war) failed: asadmin exited 1: remote failure: Error occurred during deployment: Exception while preparing the app : org/hibernate/bytecode/enhance/spi/EnhancementContextWrapper. Please see server.log for more details.
Command deploy failed.

And mvn clean package wildfly:dev results in this:

[INFO] STANDALONE server is starting up.
13:34:48,329 INFO  [org.jboss.modules] (main) JBoss Modules version 2.0.3.Final
java.lang.IllegalStateException: WFLYCTL0214: Could not load configuration file: glassfish-resources.xml. The configuration file argument must specify the path to a file located in the configuration directory. The path must be a relative path, and must be relative to the configuration directory C:\JAVA2023\JakartaPlatformHiberRestTinyH2\target\server\standalone\configuration.
        at [email protected]//org.jboss.as.controller.persistence.ConfigurationFile.determineMainFile(ConfigurationFile.java:387)
        at [email protected]//org.jboss.as.controller.persistence.ConfigurationFile.<init>(ConfigurationFile.java:207)
        at [email protected]//org.jboss.as.server.ServerEnvironment.<init>(ServerEnvironment.java:590)
        at [email protected]//org.jboss.as.server.Main.determineEnvironment(Main.java:425)
        at [email protected]//org.jboss.as.server.Main.main(Main.java:97)
        at org.jboss.modules.Module.run(Module.java:353)
        at org.jboss.modules.Module.run(Module.java:321)
        at org.jboss.modules.Main.main(Main.java:604)
13:34:48,687 FATAL [org.jboss.as.server] (main) WFLYSRV0239: Aborting with exit code 1

Solution

  • Provided sample application can be run with this single command:

    mvn clean package org.codehaus.cargo:cargo-maven3-plugin:run -Dcargo.maven.containerId=glassfish7x
    

    All plugins from pom.xml can be removed, apart from maven-compiler-plugin and maven-war-plugin. No additional configuration is needed.

    In console, there will be message:

    [INFO] [talledLocalContainer] GlassFish 7.0.6 started on port [8080]
    [INFO] Press Ctrl-C to stop the container...
    

    But pressing Ctrl-C can result in failed attempt of stopping the container - you will have to kill java process manually.

    To stop container more properly, first send web-request to server:

    curl.exe -X GET http://localhost:4848/__asadmin/stop-domain -u admin:adminadmin
    

    After that press Ctrl+C, all resources will be released and container will stop properly.

    Other containers and usecases available in Codehaus Cargo Maven 3 Plugin can be found here: https://codehaus-cargo.github.io/cargo/Maven+3+Plugin.html