Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
c662f63
Initial plan
Copilot Mar 14, 2026
01b47fe
Add GlassFish Jakarta EE shopservice implementation
Copilot Mar 14, 2026
0476468
Improve code review comments in glassfish-resources.xml files
Copilot Mar 14, 2026
5483ccb
Disable client-side connection pooling in GlassFish JDBC config
Copilot Mar 14, 2026
cf64c8e
Remove max-pool-size limit from GlassFish JDBC pool config
Copilot Mar 14, 2026
ff78555
Add GlassFish integration test step to main workflow
Copilot Mar 14, 2026
cc7eb57
Fix GlassFish test failures: add EclipseLink H2 dialect hint and CDI …
Copilot Mar 14, 2026
a470a40
Fix GlassFish JNDI DataSource registration: add OjpDriverDataSource a…
Copilot Mar 14, 2026
2b66d9c
Fix GlassFish tests: use @DataSourceDefinition instead of glassfish-r…
Copilot Mar 14, 2026
e14a5fc
Remove OJP from GlassFish tests: connect directly to H2 for baseline
Copilot Mar 14, 2026
2d9c79e
Fix EclipseLink H2 platform: use FQN instead of short alias H2
Copilot Mar 14, 2026
eef35a0
Fix H2 2.x DDL incompatibility: add MODE=LEGACY to H2 URL
Copilot Mar 14, 2026
cb6788c
Fix WELD-001524: use bean-discovery-mode=annotated in beans.xml inste…
Copilot Mar 14, 2026
f3e8fed
Fix WELD-001524: add --add-opens JVM args to Surefire for GlassFish E…
Copilot Mar 15, 2026
8f86142
Fix invalid XML comment in pom.xml: remove double-dash from comment text
Copilot Mar 15, 2026
2be3f73
Re-introduce OJP driver in TestDataSourceProducer with H2 backend URL
Copilot Mar 15, 2026
2f243ad
Switch to org.openjproxy.jdbc.OjpDataSource and update README
Copilot Mar 15, 2026
8eca0f1
Fix NoClassDefFoundError: add slf4j-api + slf4j-simple test deps for …
Copilot Mar 15, 2026
c74a738
Fix table-already-exists: use FK-safe drop.sql for test schema genera…
Copilot Mar 15, 2026
a836a4e
Fix NULL not allowed for ID on all tables via explicit create.sql
Copilot Mar 15, 2026
ddf16f9
Fix create.sql: put each CREATE TABLE on a single line
Copilot Mar 15, 2026
741586b
Fix NULL not allowed for ID: disable EclipseLink auto-DDL and add CAS…
Copilot Mar 15, 2026
b5aba26
Fix EclipseLink DDL race: redirect proprietary DDL to target/ to unbl…
Copilot Mar 15, 2026
f586242
Fix NULL not allowed for column ID: switch entities to GenerationType…
Copilot Mar 15, 2026
83ac129
Fix sequences not found: use output-mode=sql-script instead of ddl-ge…
Copilot Mar 15, 2026
05068f0
Revert GenerationType.SEQUENCE workaround: switch back to IDENTITY no…
Copilot Mar 15, 2026
90cc535
Fix Table not found: use eclipselink.ddl-generation=none so JPA creat…
Copilot Mar 15, 2026
597fda3
Fix Table not found: revert to drop-and-create + MODE=LEGACY now that…
Copilot Mar 15, 2026
58362b0
Revert to direct H2 baseline: remove OJP from tests, restore working …
Copilot Mar 15, 2026
ea8592d
Reintroduced OJP configurations.
rrobetti Mar 16, 2026
f7dda4c
Reintroduced OJP configurations.
rrobetti Mar 16, 2026
1efdecc
Temporarily remove non Glassfish tests from workflow.
rrobetti Mar 16, 2026
c621c59
Run Glassfish first in the testing order so it does not find the tabl…
rrobetti Mar 16, 2026
b30a0de
Fix OJP port, add integration-test warnings to READMEs, document test…
Copilot Mar 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
207 changes: 207 additions & 0 deletions glassfish/shopservice/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
# ShopService – GlassFish / Jakarta EE 10

A REST-based shop service implemented with **GlassFish 7** and **Jakarta EE 10**, demonstrating OJP (Open JDBC Proxy) integration using the standard Jakarta EE stack.

---

## Tech Stack

| Component | Technology |
|------------------------|---------------------------------------|
| **Language** | Java 21 |
| **Application Server** | GlassFish 7 (Jakarta EE 10) |
| **REST** | JAX-RS (Jersey 3, bundled in GlassFish)|
| **Persistence** | JPA 3 / EclipseLink (bundled) |
| **DI / Transactions** | CDI 4 / JTA (bundled in GlassFish) |
| **JSON** | JSON-B / Jakarta JSON Binding (bundled)|
| **Database (prod)** | PostgreSQL via OJP proxy |
| **Database (test)** | H2 in-memory via OJP proxy |
| **Testing** | Arquillian + GlassFish Embedded + REST-assured |
| **Build** | Maven 3.8+, WAR packaging |

---

## Features

- CRUD operations for **Users**, **Products**, **Orders**, **Order Items**, and **Reviews**
- Pure Jakarta EE 10 API – no framework-specific code
- CDI beans (`@ApplicationScoped`) for repositories with container-managed `EntityManager`
- JAX-RS resources (`@RequestScoped`) with container-managed transactions (`@Transactional`)
- JNDI-configured JDBC datasource via `WEB-INF/glassfish-resources.xml`
- OJP driver used as the JDBC driver for both production and test environments
- Arquillian integration tests that deploy the WAR to an embedded GlassFish 7 instance

---

## Project Structure

```
glassfish/shopservice/
├── pom.xml
└── src/
├── main/
│ ├── java/com/example/shopservice/
│ │ ├── ShopServiceApplication.java # @ApplicationPath("") JAX-RS entry point
│ │ ├── entity/
│ │ │ ├── User.java
│ │ │ ├── Product.java
│ │ │ ├── Order.java
│ │ │ ├── OrderItem.java
│ │ │ └── Review.java
│ │ ├── repository/
│ │ │ ├── UserRepository.java # @ApplicationScoped CDI bean
│ │ │ ├── ProductRepository.java
│ │ │ ├── OrderRepository.java
│ │ │ ├── OrderItemRepository.java
│ │ │ └── ReviewRepository.java
│ │ └── resource/
│ │ ├── UserResource.java # @Path("/users") JAX-RS resource
│ │ ├── ProductResource.java
│ │ ├── OrderResource.java
│ │ ├── OrderItemResource.java
│ │ └── ReviewResource.java
│ ├── resources/
│ │ └── META-INF/persistence.xml # JTA persistence unit (jdbc/shopservice)
│ └── webapp/
│ └── WEB-INF/
│ └── glassfish-resources.xml # Production datasource (PostgreSQL via OJP)
└── test/
├── java/com/example/shopservice/
│ ├── DeploymentFactory.java # Shared ShrinkWrap archive builder
│ └── resource/
│ ├── ProductResourceTest.java
│ ├── UserResourceTest.java
│ ├── OrderResourceTest.java
│ └── ReviewResourceTest.java
└── resources/
├── arquillian.xml # Embedded GlassFish port config
├── glassfish-resources-test.xml # Test datasource (H2 via OJP)
└── META-INF/
└── persistence-test.xml # Test persistence unit (drop-and-create)
```

---

## REST API

| Entity | Method | Path | Description |
|--------------|--------|------------------------------------|------------------------|
| Users | GET | `/users` | List all users |
| | POST | `/users` | Create a user |
| | GET | `/users/{id}` | Get user by ID |
| | PUT | `/users/{id}` | Update user |
| | DELETE | `/users/{id}` | Delete user |
| Products | GET | `/products` | List all products |
| | POST | `/products` | Create a product |
| | GET | `/products/{id}` | Get product by ID |
| | PUT | `/products/{id}` | Update product |
| | DELETE | `/products/{id}` | Delete product |
| Orders | GET | `/orders` | List all orders |
| | POST | `/orders` | Create an order |
| | GET | `/orders/{id}` | Get order by ID |
| | PUT | `/orders/{id}` | Update order |
| | DELETE | `/orders/{id}` | Delete order |
| Order Items | GET | `/orders/{orderId}/items` | List items in order |
| | POST | `/orders/{orderId}/items` | Add item to order |
| | GET | `/orders/{orderId}/items/{itemId}` | Get order item |
| | PUT | `/orders/{orderId}/items/{itemId}` | Update order item |
| | DELETE | `/orders/{orderId}/items/{itemId}` | Remove order item |
| Reviews | GET | `/reviews` | List all reviews |
| | POST | `/reviews` | Create a review |
| | GET | `/reviews/{id}` | Get review by ID |
| | PUT | `/reviews/{id}` | Update review |
| | DELETE | `/reviews/{id}` | Delete review |

---

## Getting Started

### Prerequisites

- Java 21+
- Maven 3.8+
- GlassFish 7 (for manual deployment)
- OJP JDBC driver (`ojp-jdbc-driver-0.0.1-SNAPSHOT-INT-TEST-TMP-VERSION.jar`) installed in your local Maven repository

### Build the WAR

```bash
mvn clean package -DskipTests
```

This produces `target/shopservice-1.0.0.war`.

### Run integration tests (embedded GlassFish)

```bash
mvn clean verify
```

Arquillian starts an embedded GlassFish 7 instance on port **9090**, deploys the test WAR, runs the REST-assured integration tests, and stops the server.

---

## Production Deployment

### 1. Install the OJP JDBC Driver

Copy the OJP driver JAR to GlassFish's domain library directory so it is accessible to the connection pool manager:

```bash
cp ojp-jdbc-driver-*.jar $GLASSFISH_HOME/domains/domain1/lib/
```

### 2. Start GlassFish

```bash
$GLASSFISH_HOME/bin/asadmin start-domain
```

### 3. Deploy the WAR

```bash
$GLASSFISH_HOME/bin/asadmin deploy target/shopservice-1.0.0.war
```

GlassFish will process `WEB-INF/glassfish-resources.xml` during deployment and create the JDBC connection pool (`ShopServicePool`) and JNDI resource (`jdbc/shopservice`).

### 4. Access the API

The application is accessible at:

```
http://localhost:8080/shopservice/products
http://localhost:8080/shopservice/users
...
```

---

## Key Differences from Other Framework Implementations

| Aspect | Spring Boot | Micronaut | Quarkus | **GlassFish / Jakarta EE** |
|----------------------|--------------------------------|------------------------------|-------------------------------|-------------------------------------|
| DI | `@Autowired` / Spring DI | `@Inject` / Micronaut DI | `@Inject` / CDI | `@Inject` / CDI (standard) |
| REST | `@RestController` | `@Controller` (Micronaut) | `@Path` (JAX-RS / RESTEasy) | `@Path` (JAX-RS / Jersey) |
| Persistence | Spring Data JPA | Micronaut Data JPA | Hibernate ORM + Panache | JPA 3 + EclipseLink (standard) |
| Transactions | `@Transactional` (Spring) | `@Transactional` (CDI) | `@Transactional` (CDI) | `@Transactional` (CDI / JTA) |
| Datasource config | `application.properties` | `application.properties` | `application.properties` | `glassfish-resources.xml` (JNDI) |
| Packaging | Fat JAR (embedded Tomcat) | Fat JAR (embedded Netty) | Fast JAR / native | WAR → deployed to GlassFish |
| Test framework | Spring Boot Test / MockMvc | MicronautTest | `@QuarkusTest` / REST-assured | Arquillian + GlassFish Embedded |
| Server lifecycle | Embedded, starts automatically | Embedded, starts automatically | Embedded, starts automatically | External server required (or embedded for tests) |

### Notable GlassFish / Jakarta EE specifics

1. **WAR packaging**: Unlike embedded-server frameworks, GlassFish requires a WAR file deployed to the server. The application does not contain its own HTTP server.

2. **No `main()` method**: The application entry point is the `@ApplicationPath`-annotated `Application` subclass. There is no equivalent to `SpringApplication.run()`.

3. **JNDI datasource**: Datasource configuration is done at the server level via `glassfish-resources.xml` (or the GlassFish admin console / `asadmin` CLI), not in an `application.properties` file. The persistence unit references the datasource by JNDI name (`jdbc/shopservice`).

4. **OJP driver placement**: For production use, the OJP driver JAR must be placed in GlassFish's `domain/lib/` directory so it can be loaded by the server-level JDBC pool manager. For embedded testing, the driver is available on the JVM system classpath via Maven test-scope dependencies.

5. **EclipseLink as JPA provider**: GlassFish bundles EclipseLink (the Jakarta EE Reference Implementation for JPA), not Hibernate. Hibernate-specific features (e.g., `@Formula`, Panache) are not available; standard JPA APIs are used throughout.

6. **Client-side connection pooling disabled**: The GlassFish JDBC pool is configured with `max-connection-usage-count="1"` and `steady-pool-size="0"` so that each connection is discarded after a single use and no connections are held idle — exactly equivalent to `SimpleDriverDataSource` (Spring Boot), the bare `DriverManager` wrapper (Micronaut), and `unpooled=true` (Quarkus). OJP manages all connection pooling at the proxy (server) level; an active client-side pool would interfere with OJP's connection management.

7. **JSON-B for serialization**: GlassFish uses JSON-B (via EclipseLink MOXy) as the default JSON provider in JAX-RS. The `@JsonbTransient` annotation (from `jakarta.json.bind.annotation`) is used to break the circular reference between `Order` and `OrderItem`.
133 changes: 133 additions & 0 deletions glassfish/shopservice/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
<?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>com.example</groupId>
<artifactId>shopservice</artifactId>
<version>1.0.0</version>
<packaging>war</packaging>

<properties>
<java.version>21</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<glassfish.version>7.0.21</glassfish.version>
<arquillian.version>1.9.1.Final</arquillian.version>
<junit.version>5.10.2</junit.version>
<rest-assured.version>5.4.0</rest-assured.version>
</properties>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.jboss.arquillian</groupId>
<artifactId>arquillian-bom</artifactId>
<version>${arquillian.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<!-- Jakarta EE 10 API - provided by GlassFish at runtime -->
<dependency>
<groupId>jakarta.platform</groupId>
<artifactId>jakarta.jakartaee-api</artifactId>
<version>10.0.0</version>
<scope>provided</scope>
</dependency>

<!-- OJP JDBC Driver -->
<dependency>
<groupId>org.openjproxy</groupId>
<artifactId>ojp-jdbc-driver</artifactId>
<version>0.0.1-SNAPSHOT-INT-TEST-TMP-VERSION</version>
</dependency>

<!-- H2 in-memory database (for testing) -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.2.224</version>
<scope>test</scope>
</dependency>

<!-- Arquillian JUnit 5 support -->
<dependency>
<groupId>org.jboss.arquillian.junit5</groupId>
<artifactId>arquillian-junit5-container</artifactId>
<scope>test</scope>
</dependency>

<!--
GlassFish 7 Embedded Arquillian adapter (OmniFaces project).
Requires glassfish-embedded-all on the test classpath.
-->
<dependency>
<groupId>org.omnifaces.arquillian</groupId>
<artifactId>arquillian-glassfish-server-embedded</artifactId>
<version>1.4</version>
<scope>test</scope>
</dependency>

<!-- GlassFish 7 Embedded server (Jakarta EE 10) -->
<dependency>
<groupId>org.glassfish.main.extras</groupId>
<artifactId>glassfish-embedded-all</artifactId>
<version>${glassfish.version}</version>
<scope>test</scope>
</dependency>

<!-- REST-assured for HTTP-level integration tests -->
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<version>${rest-assured.version}</version>
<scope>test</scope>
</dependency>

<!-- JUnit 5 -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<release>${java.version}</release>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.4.0</version>
<configuration>
<!-- No web.xml required; JAX-RS Application class acts as the descriptor -->
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.5</version>
<configuration>
<includes>
<include>**/*Test.java</include>
<include>**/*IT.java</include>
</includes>
</configuration>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.example.shopservice;

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

/**
* JAX-RS application entry point. By extending {@link Application} and annotating
* with {@link ApplicationPath}, no {@code web.xml} servlet mapping is required.
* The empty path "" means resources are served at the WAR's context root.
*/
@ApplicationPath("")
public class ShopServiceApplication extends Application {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.example.shopservice.entity;

import jakarta.json.bind.annotation.JsonbTransient;
import jakarta.persistence.*;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

@Entity
@Table(name = "orders")
public class Order {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@ManyToOne(optional = false)
@JoinColumn(name = "user_id")
private User user;

@Column(nullable = false)
private LocalDateTime orderDate = LocalDateTime.now();

@OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true)
@JsonbTransient
private List<OrderItem> orderItems = new ArrayList<>();

public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public User getUser() { return user; }
public void setUser(User user) { this.user = user; }
public LocalDateTime getOrderDate() { return orderDate; }
public void setOrderDate(LocalDateTime orderDate) { this.orderDate = orderDate; }
public List<OrderItem> getOrderItems() { return orderItems; }
public void setOrderItems(List<OrderItem> orderItems) { this.orderItems = orderItems; }
}
Loading
Loading