Enterprise-grade Jakarta EE 10 REST API powering the TechMart Online e-commerce platform.
Stateless · Stateful · Singleton · Async EJBs · JMS Messaging · JPA/Hibernate · BCrypt Security
- Project Overview
- Tech Stack
- Architecture Overview
- Project Structure
- API Reference
- EJB Design
- JMS Messaging Architecture
- Database & Persistence
- Security & Authentication
- Configuration & CORS
- Exception Handling
- Performance Monitoring
- Getting Started
- Build & Deploy
- Testing
- Design Decisions
TechMart Online is an enterprise e-commerce modernization project built on Jakarta EE 10. The backend replaces a legacy monolithic system struggling with 1,000+ concurrent users and delivers a scalable, asynchronous, message-driven architecture capable of handling 10,000+ concurrent users with sub-second response times.
| Legacy Problem | Solution Implemented |
|---|---|
| Monolithic system, 1,000+ user bottleneck | Stateless EJB pool, connection-pool tuned JPA |
| Manual inventory causing overselling | InventoryCacheBean (Singleton) + InventorySyncMDB |
| Delayed order processing at peak load | Async OrderQueue → OrderProcessingMDB pipeline |
| No real-time customer notifications | JMS NotificationQueue + NotificationMDB |
| Poor third-party integration | Dedicated ShippingQuoteBean + PricingEngineBean |
- ✅ 10,000+ concurrent users with sub-second response times
- ✅ 99.9% uptime via fail-fast startup validation (
DatabaseStartupBean) - ✅ Real-time inventory sync across multiple warehouses
- ✅ Asynchronous order processing decoupled via JMS queues
- ✅ Cloud deployment ready (WAR packaging, JNDI-externalised config)
- ✅ Performance observable via built-in metrics endpoints
| Layer | Technology | Version |
|---|---|---|
| Language | Java (OpenJDK) | 17 |
| Platform | Jakarta EE | 10.0.0 |
| Application Server | Payara Server | 6.2025.11 |
| ORM / Persistence | JPA (Hibernate, bundled in Payara) | Jakarta Persistence 3.0 |
| Database | PostgreSQL | 18 |
| JDBC Driver | PostgreSQL JDBC | 42.7.3 |
| JSON | Jackson | 2.17.1 |
| Build Tool | Apache Maven | 3.x |
| Security | jBCrypt (password hashing) | 0.4 |
| Messaging | JMS (Jakarta Messaging, Payara embedded ActiveMQ) | - |
| Unit Testing | JUnit 5 | 5.10.2 |
| Mocking | Mockito | 5.11.0 |
The backend follows a layered Jakarta EE architecture with strict separation of concerns:
┌─────────────────────────────────────────────────────────────────┐
│ React Frontend │
│ (TechMartOnline-frontend) │
└────────────────────────┬────────────────────────────────────────┘
│ HTTP / JSON (port 8080)
▼
┌─────────────────────────────────────────────────────────────────┐
│ JAX-RS Layer (api/) │
│ CorsFilter · ApplicationConfig (/api/v1) │
│ AuthResource · ProductResource · OrderResource · ... │
│ admin/ → AdminDashboard · JmsMonitor · EjbMonitor · AsyncTask │
└────────────┬──────────────────────────────┬────────────────────┘
│ │ JMS (async)
▼ ▼
┌────────────────────────┐ ┌─────────────────────────────────┐
│ EJB Service Layer │ │ Messaging Layer │
│ │ │ Producers: │
│ Stateless: │ │ OrderMessageProducer │
│ ProductServiceBean │ │ NotificationMessageProducer │
│ OrderProcessorBean │ │ MDBs (Message-Driven Beans): │
│ PricingEngineBean │ │ OrderProcessingMDB │
│ ShippingQuoteBean │ │ InventorySyncMDB │
│ │ │ NotificationMDB │
│ Stateful: │ │ │
│ CartSessionBean │ │ Queues: │
│ │ │ jms/OrderQueue │
│ Singleton: │ │ jms/InventoryQueue │
│ DatabaseStartupBean │ │ jms/NotificationQueue │
│ InventoryCacheBean │ │ jms/EmailQueue │
│ SystemMetricsBean │ └─────────────────────────────────┘
│ │
│ Async: │
│ AsyncOrderServiceBean│
│ InventorySyncService │
└────────────┬───────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Repository / Data Access Layer │
│ ProductRepository · OrderRepository · InventoryRepository │
│ UserRepository · CategoryRepository · NotificationRepository │
│ WarehouseRepository · MetricsRepository │
└────────────┬────────────────────────────────────────────────────┘
│ JTA / JDBC (jdbc/techmart)
▼
┌─────────────────────────────────────────────────────────────────┐
│ PostgreSQL 18 (schema: techmart) │
│ jdbc:postgresql://localhost:5432/techmart_online │
└─────────────────────────────────────────────────────────────────┘
TechMartOnline-backend/
├── pom.xml # Maven build descriptor (Java 17, Jakarta EE 10)
├── src/
│ ├── main/
│ │ ├── java/com/cusaldev/bcd/techmart/
│ │ │ ├── api/ # JAX-RS REST controllers
│ │ │ │ ├── AuthResource.java # POST /auth/login|register|logout
│ │ │ │ ├── CategoryResource.java # GET /categories
│ │ │ │ ├── InventoryResource.java # GET /inventory[/{productId}][/events]
│ │ │ │ ├── NotificationResource.java # GET /notifications; PUT /*/read
│ │ │ │ ├── OrderResource.java # GET|POST /orders; GET /orders/{id}
│ │ │ │ ├── ProductResource.java # GET /products[/{slug}]
│ │ │ │ ├── UserResource.java # GET|PUT /users/me
│ │ │ │ ├── WarehouseResource.java # GET /warehouses
│ │ │ │ └── admin/
│ │ │ │ ├── AdminDashboardResource.java # GET /admin/dashboard
│ │ │ │ ├── AsyncTaskResource.java # GET|POST /admin/async
│ │ │ │ ├── EjbMonitorResource.java # GET /admin/ejb
│ │ │ │ └── JmsMonitorResource.java # GET /admin/jms
│ │ │ ├── config/
│ │ │ │ ├── ApplicationConfig.java # @ApplicationPath("api/v1") – JAX-RS entry
│ │ │ │ ├── CorsFilter.java # Pre-matching CORS + OPTIONS preflight
│ │ │ │ └── JsonConfig.java # Jackson ObjectMapper customisation
│ │ │ ├── dto/
│ │ │ │ ├── request/ # Inbound JSON payloads (LoginRequest, etc.)
│ │ │ │ └── response/ # Outbound JSON shapes (ProductDetailDto, etc.)
│ │ │ ├── ejb/
│ │ │ │ ├── async/
│ │ │ │ │ ├── AsyncOrderServiceBean.java # @Asynchronous order tasks
│ │ │ │ │ └── InventorySyncServiceBean.java # @Asynchronous inventory sync
│ │ │ │ ├── singleton/
│ │ │ │ │ ├── DatabaseStartupBean.java # @Startup DB health check
│ │ │ │ │ ├── InventoryCacheBean.java # In-memory inventory cache
│ │ │ │ │ └── SystemMetricsBean.java # @Schedule performance metrics
│ │ │ │ ├── stateful/
│ │ │ │ │ └── CartSessionBean.java # Per-user shopping cart
│ │ │ │ └── stateless/
│ │ │ │ ├── OrderProcessorBean.java # Order lifecycle business logic
│ │ │ │ ├── PricingEngineBean.java # Dynamic pricing calculations
│ │ │ │ ├── ProductServiceBean.java # Product catalogue + filtering
│ │ │ │ └── ShippingQuoteBean.java # Shipping cost estimation
│ │ │ ├── entity/ # JPA @Entity classes
│ │ │ │ ├── AsyncTask.java # Async task tracking record
│ │ │ │ ├── Category.java # Product categories
│ │ │ │ ├── EjbMetric.java # EJB performance metrics
│ │ │ │ ├── Inventory.java # Stock levels per product/warehouse
│ │ │ │ ├── InventoryEvent.java # Audit log for stock changes
│ │ │ │ ├── InventoryId.java # Composite PK (productId+warehouseId)
│ │ │ │ ├── JmsEvent.java # Raw JMS event log
│ │ │ │ ├── JmsQueue.java # JMS queue metadata / metrics
│ │ │ │ ├── Notification.java # User notification records
│ │ │ │ ├── Order.java # Customer orders
│ │ │ │ ├── OrderItem.java # Individual line items in an order
│ │ │ │ ├── Product.java # Product catalogue entry
│ │ │ │ ├── ProductImage.java # Product image gallery entries
│ │ │ │ ├── ProductSpec.java # Key/value specification entries
│ │ │ │ ├── RevenueDaily.java # Daily revenue aggregation view
│ │ │ │ ├── User.java # User accounts (ADMIN/CUSTOMER/STAFF)
│ │ │ │ └── Warehouse.java # Warehouse location + capacity
│ │ │ ├── exception/
│ │ │ │ ├── ApiExceptionMapper.java # Global JAX-RS exception → HTTP response
│ │ │ │ ├── NotFoundException.java # 404 semantic exception
│ │ │ │ └── ValidationException.java # 400 semantic exception
│ │ │ ├── mapper/
│ │ │ │ ├── OrderMapper.java # Order entity ↔ DTO conversion
│ │ │ │ └── ProductMapper.java # Product entity ↔ DTO conversion
│ │ │ ├── messaging/
│ │ │ │ ├── dto/
│ │ │ │ │ ├── InventorySyncMessage.java # JMS message payload for inventory
│ │ │ │ │ └── OrderMessage.java # JMS message payload for orders
│ │ │ │ ├── mdb/
│ │ │ │ │ ├── InventorySyncMDB.java # Consumes jms/InventoryQueue
│ │ │ │ │ ├── NotificationMDB.java # Consumes jms/NotificationQueue
│ │ │ │ │ └── OrderProcessingMDB.java # Consumes jms/OrderQueue
│ │ │ │ └── producer/
│ │ │ │ ├── NotificationMessageProducer.java
│ │ │ │ └── OrderMessageProducer.java
│ │ │ ├── repository/ # @Transactional data-access objects
│ │ │ │ ├── CategoryRepository.java
│ │ │ │ ├── InventoryRepository.java
│ │ │ │ ├── MetricsRepository.java
│ │ │ │ ├── NotificationRepository.java
│ │ │ │ ├── OrderRepository.java
│ │ │ │ ├── ProductRepository.java
│ │ │ │ ├── UserRepository.java
│ │ │ │ └── WarehouseRepository.java
│ │ │ └── util/
│ │ │ ├── Constants.java # JNDI names, roles, pagination limits
│ │ │ └── PerformanceTimer.java # Nano-second timing utility
│ │ ├── resources/
│ │ │ └── META-INF/
│ │ │ └── persistence.xml # JPA persistence-unit "TechMartPU"
│ │ └── webapp/
│ │ └── WEB-INF/
│ │ ├── beans.xml # CDI bean discovery mode
│ │ └── web.xml # Servlet descriptor
│ └── test/
│ └── java/com/cusaldev/bcd/techmart/
│ ├── api/ # JAX-RS endpoint tests
│ ├── repository/ # Repository / DAO tests
│ └── service/ # Business-logic unit tests
└── target/
└── TechMartOnline-backend.war # Deployable artefact
All endpoints are served under the base path:
http://<host>:8080/TechMartOnline-backend/api/v1
All requests and responses use Content-Type: application/json.
The auth layer uses BCrypt (work factor 12) for password hashing and issues a Base64-encoded prototype token (id:role:timestamp) returned in the token field.
| Method | Endpoint | Description | Auth Required |
|---|---|---|---|
POST |
/auth/login |
Validate credentials, return user info + token | No |
POST |
/auth/register |
Create new CUSTOMER account (auto-login) | No |
POST |
/auth/logout |
Client-side token disposal (stateless server) | No |
Request body:
{
"email": "[email protected]",
"password": "secret123"
}Response 200 OK:
{
"id": 42,
"fullName": "Jane Doe",
"email": "[email protected]",
"role": "CUSTOMER",
"token": "NDI6Q1VTVE9NRVI6MTcxOTkwMDAwMDAwMA=="
}Error responses: 401 Unauthorized – wrong credentials; 400 Bad Request – missing fields.
Request body:
{
"fullName": "Jane Doe",
"email": "[email protected]",
"password": "mypassword"
}Response 201 Created: Same as login response.
Validation rules:
- All three fields are required.
- Password must be ≥ 8 characters.
- Email must be unique (returns
400if already registered).
| Method | Endpoint | Description | Auth Required |
|---|---|---|---|
GET |
/products |
Paginated + filtered product catalogue | No |
GET |
/products/{slug} |
Full product detail by URL slug | No |
| Parameter | Type | Default | Description |
|---|---|---|---|
category |
string | - | Filter by category slug |
brand |
string | - | Filter by brand name (case-insensitive) |
search |
string | - | Free-text search across product name and brand |
inStock |
boolean | false |
If true, exclude out-of-stock items |
sort |
string | popular |
Sort order: popular, new, low (price), high (price) |
page |
int | 0 |
Zero-based page index |
size |
int | 20 |
Items per page (max: 100) |
Response 200 OK:
{
"items": [ { "id": "...", "name": "...", "slug": "...", "price": 999.99, ... } ],
"totalItems": 240,
"totalPages": 12,
"currentPage": 0
}Returns full product detail including images, specifications, inventory summary, and related products.
| Method | Endpoint | Description | Auth Required |
|---|---|---|---|
GET |
/categories |
All categories with product counts | No |
Response 200 OK:
[
{ "id": "CAT-01", "name": "Laptops", "slug": "laptops", "icon": "💻", "displayOrder": 1, "count": 48 }
]| Method | Endpoint | Description | Auth Required |
|---|---|---|---|
GET |
/orders |
Paginated order list (optional status filter) | Bearer token |
GET |
/orders/{id} |
Single order detail | Bearer token |
POST |
/orders |
Place a new order | Bearer token |
| Parameter | Type | Default | Description |
|---|---|---|---|
status |
string | - | Filter: PENDING, PROCESSING, SHIPPED, DELIVERED, CANCELLED |
page |
int | 0 |
Zero-based page index |
size |
int | 20 |
Page size (max: 100) |
After persisting the order via OrderProcessorBean, an OrderMessage is enqueued to jms/OrderQueue for asynchronous processing (confirmation email, invoice generation).
Request body:
{
"userId": 42,
"warehouseId": "WH-NYC",
"items": [
{ "productId": "PROD-001", "quantity": 2 }
],
"shippingAddress": "123 Main St, New York, NY 10001",
"payment": "CARD"
}Response 201 Created:
{
"id": "ORD-10241",
"userId": 42,
"status": "PENDING",
"total": 1999.98,
"createdAt": "2025-07-02T14:00:00Z"
}| Method | Endpoint | Description | Auth Required |
|---|---|---|---|
GET |
/inventory |
All inventory rows (optional warehouse filter) | No |
GET |
/inventory/{productId} |
Stock levels across all warehouses for a product | No |
GET |
/inventory/events |
50 most recent inventory change events | No |
| Parameter | Type | Description |
|---|---|---|
warehouseId |
string | Filter to a single warehouse |
Response 200 OK:
[
{
"productId": "PROD-001",
"productName": "MacBook Pro 14\"",
"warehouseId": "WH-NYC",
"warehouseName": "New York Central",
"quantity": 120,
"reservedQuantity": 15,
"reorderLevel": 25,
"available": 105,
"updatedAt": "2025-07-02T12:00:00Z"
}
]| Method | Endpoint | Description | Auth Required |
|---|---|---|---|
GET |
/warehouses |
All warehouses with capacity and sync metadata | No |
Response 200 OK:
[
{
"id": "WH-NYC",
"name": "New York Central",
"city": "New York",
"region": "Northeast",
"status": "ACTIVE",
"capacity": 10000,
"used": 7430,
"latencyMs": 12,
"lastSyncAt": "2025-07-02T13:45:00Z"
}
]| Method | Endpoint | Description | Auth Required |
|---|---|---|---|
GET |
/notifications |
User notifications (+ global broadcasts) | No |
PUT |
/notifications/{id}/read |
Mark a notification as read | No |
| Parameter | Type | Default | Description |
|---|---|---|---|
userId |
long | 0 |
Target user ID; 0 returns only global notifications |
Admin role required for all
/admin/**endpoints.
| Method | Endpoint | Description |
|---|---|---|
GET |
/admin/dashboard |
KPI summary: revenue, orders, users, inventory alerts |
Real-time insights into JMS queue health and throughput.
| Method | Endpoint | Description |
|---|---|---|
GET |
/admin/jms |
All queue metrics (depth, throughput, error rate) |
GET |
/admin/jms/{queueName} |
Single queue detail |
POST |
/admin/jms/test |
Publish a test message to a queue |
| Method | Endpoint | Description |
|---|---|---|
GET |
/admin/ejb |
EJB invocation counts, avg latency, error counts |
| Method | Endpoint | Description |
|---|---|---|
GET |
/admin/async |
List of all async task records with status |
POST |
/admin/async |
Trigger a new async task for testing |
The EJB layer is the heart of the enterprise architecture, demonstrating all four session bean types as required by the Jakarta EE specification.
Best for: Shared, reusable business logic with no client-specific state. Managed by a container pool for high throughput.
| Bean | Responsibility |
|---|---|
ProductServiceBean |
Product catalogue queries, filtering, pagination via ProductRepository. Delegates to InventoryCacheBean for stock info. |
OrderProcessorBean |
Complete order lifecycle: validation, inventory reservation, persistence via OrderRepository, DTO mapping via OrderMapper. |
PricingEngineBean |
Dynamic pricing rules: base price, discount application, bulk/seasonal adjustments. |
ShippingQuoteBean |
Shipping cost estimation based on warehouse distance, weight, and delivery tier. |
Best for: Conversational state bound to a single client session (shopping cart).
| Bean | Responsibility |
|---|---|
CartSessionBean |
Maintains per-user cart state across multiple requests. Holds cart items, subtotal, and applied promo codes. Destroyed on checkout or session timeout. |
Best for: Application-scoped shared resources that must exist exactly once in the JVM.
| Bean | Responsibility |
|---|---|
DatabaseStartupBean |
@Startup + @PostConstruct validates DB connection on deploy (executes SELECT 1). Logs fatal error if DB is unreachable - fail-fast pattern. |
InventoryCacheBean |
In-memory inventory cache populated at startup, invalidated on stock updates. Dramatically reduces DB load for stock queries. |
SystemMetricsBean |
@Schedule-driven collector that periodically captures JVM heap, active EJB counts, queue depths, and throughput into EjbMetric entities. |
Best for: Long-running operations that should not block the HTTP request thread.
| Bean | Responsibility |
|---|---|
AsyncOrderServiceBean |
@Asynchronous methods for post-order tasks: invoice generation, loyalty point calculation, returns Future<> for status tracking. |
InventorySyncServiceBean |
@Asynchronous inventory reconciliation across warehouses, updates Inventory entities and publishes InventorySyncMessage to JMS. |
The application uses Java Message Service (JMS) for decoupled, asynchronous processing, ensuring order placement returns immediately while heavyweight tasks run in the background.
Order Placement (HTTP POST /orders)
│
▼
OrderMessageProducer.send()
│
▼
╔═══════════════════╗
║ jms/OrderQueue ║ ─────► OrderProcessingMDB ─► Persist status update
║ (order.queue) ║ ─► Trigger email via EmailQueue
╚═══════════════════╝
Inventory Update Event
│
▼
╔═════════════════════════╗
║ jms/InventoryQueue ║ ─────► InventorySyncMDB ─► Update stock levels
║ (inventory.queue) ║ ─► Invalidate InventoryCacheBean
╚═════════════════════════╝
System Events
│
▼
╔═══════════════════════════╗
║ jms/NotificationQueue ║ ─────► NotificationMDB ─► Persist Notification entity
║ (notification.queue) ║ ─► Broadcast to user
╚═══════════════════════════╝
Email Requests
│
▼
╔══════════════════════╗
║ jms/EmailQueue ║ ─────► (EmailMDB) ─► SMTP / third-party email service
║ (email.queue) ║
╚══════════════════════╝
| Resource Type | JNDI Name | Description |
|---|---|---|
| Connection Factory | jms/TechMartCF |
Shared JMS connection factory |
| Queue | jms/OrderQueue |
New order processing queue |
| Queue | jms/InventoryQueue |
Inventory synchronisation queue |
| Queue | jms/NotificationQueue |
User notification delivery queue |
| Queue | jms/EmailQueue |
Outbound email queue |
| MDB | Listens On | Action |
|---|---|---|
OrderProcessingMDB |
jms/OrderQueue |
Updates order status, triggers email/invoice |
InventorySyncMDB |
jms/InventoryQueue |
Reconciles stock levels, invalidates cache |
NotificationMDB |
jms/NotificationQueue |
Persists notifications, delivers to users |
| Setting | Value |
|---|---|
| JDBC URL | jdbc:postgresql://localhost:5432/techmart_online |
| Username | techmart_user |
| Password | TechMart@123 |
| Schema | techmart |
| JNDI Resource | jdbc/techmart |
| Connection Pool | TechMartPool (Payara managed) |
The persistence unit TechMartPU (META-INF/persistence.xml) is configured with:
- Transaction type:
JTA(managed by Payara) - DDL mode:
validate(schema must exist; does not modify DB) - Dialect:
org.hibernate.dialect.PostgreSQLDialect - Default schema:
techmart - Connection pool size: 10
- Second-level cache: Disabled (handled by
InventoryCacheBean)
| Entity | Table | Key Fields |
|---|---|---|
User |
techmart.users |
id, full_name, email, password_hash, role (ADMIN/CUSTOMER/STAFF), status |
Category |
techmart.categories |
id, name, slug, icon, display_order |
Product |
techmart.products |
id, name, slug, brand, price, image_url, category_id |
ProductImage |
techmart.product_images |
id, product_id, url, sort_order |
ProductSpec |
techmart.product_specs |
id, product_id, spec_key, spec_value |
Warehouse |
techmart.warehouses |
id, name, city, region, status, capacity, used_units, latency_ms |
Inventory |
techmart.inventory |
(product_id, warehouse_id) PK, quantity, reserved_quantity, reorder_level |
InventoryEvent |
techmart.inventory_events |
id, product_id, warehouse_id, event_type, quantity_delta, message |
Order |
techmart.orders |
id, user_id, warehouse_id, status, total, payment, shipping_address |
OrderItem |
techmart.order_items |
id, order_id, product_id, quantity, unit_price |
Notification |
techmart.notifications |
id, user_id, type, title, body, unread |
JmsQueue |
techmart.jms_queues |
name, depth, throughput, error_rate |
EjbMetric |
techmart.ejb_metrics |
id, bean_name, invocations, avg_latency_ms, errors |
AsyncTask |
techmart.async_tasks |
id, task_type, status, result, created_at, completed_at |
RevenueDaily |
techmart.revenue_daily (view) |
date, revenue, order_count |
Authentication uses a Base64-encoded prototype token (id:role:timestamp) for the prototype phase. The token is:
- Generated in
AuthResource.buildResponse()after successful BCrypt password verification. - Stored by the frontend in
localStorageunder the keytm-auth. - Sent in subsequent requests as
Authorization: Bearer <token>. - Decoded in
UserResource.resolveUser()to look up the user ID.
Note: For production deployment, replace the prototype token with a full JWT (JSON Web Token) implementation using a proper signing key.
- Algorithm: BCrypt with work factor 12.
- Passwords are never stored in plaintext - only the BCrypt hash is persisted.
- Library:
org.mindrot:jbcrypt:0.4.
| Role | Constant | Access |
|---|---|---|
ADMIN |
Constants.ROLE_ADMIN |
Full access including all /admin/** endpoints |
CUSTOMER |
Constants.ROLE_CUSTOMER |
Standard shopping experience |
STAFF |
Constants.ROLE_STAFF |
Inventory management operations |
All REST resources are mounted under /api/v1 via the @ApplicationPath("api/v1") annotation in ApplicationConfig. No web.xml servlet mapping is needed - Payara discovers this automatically via CDI.
CorsFilter implements both ContainerRequestFilter (@PreMatching) and ContainerResponseFilter. It:
- Short-circuits
OPTIONSpreflight requests immediately with HTTP 200 (prevents 405 Method Not Allowed errors). - Injects CORS headers on every response:
| Header | Value |
|---|---|
Access-Control-Allow-Origin |
* (replace with frontend domain in production) |
Access-Control-Allow-Headers |
Origin, Content-Type, Accept, Authorization, X-Requested-With |
Access-Control-Allow-Methods |
GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD |
Access-Control-Allow-Credentials |
true |
Access-Control-Max-Age |
86400 (24 hours preflight cache) |
// JMS JNDI names
JNDI_ORDER_QUEUE = "jms/OrderQueue"
JNDI_INVENTORY_QUEUE = "jms/InventoryQueue"
JNDI_NOTIFICATION_QUEUE = "jms/NotificationQueue"
JNDI_EMAIL_QUEUE = "jms/EmailQueue"
JNDI_JMS_CF = "jms/TechMartCF"
// User roles
ROLE_ADMIN = "ADMIN"
ROLE_CUSTOMER = "CUSTOMER"
ROLE_STAFF = "STAFF"
// Pagination
DEFAULT_PAGE_SIZE = 20
MAX_PAGE_SIZE = 100
// Default warehouse
DEFAULT_WAREHOUSE = "WH-NYC"ApiExceptionMapper is a global JAX-RS ExceptionMapper<Throwable> that converts all exceptions into consistent JSON error responses:
| Exception Type | HTTP Status | Notes |
|---|---|---|
NotFoundException |
404 Not Found |
Resource not found by ID/slug |
ValidationException |
400 Bad Request |
Invalid input, missing fields, business rule violations |
TransactionalException (unwrapped) |
Depends on cause | CDI @Transactional wrapping is transparently unwrapped |
Any other Throwable |
500 Internal Server Error |
Logged at SEVERE level |
Standard error response shape:
{
"error": "An account with this email already exists.",
"status": 400
}The backend includes built-in performance monitoring capabilities:
PerformanceTimerutility (util/PerformanceTimer.java) - nanosecond-precision stopwatch for instrumenting code paths.SystemMetricsBean-@Singleton+@Schedulebean that periodically captures:- JVM heap usage
- Active EJB invocation counts
- JMS queue depths and throughput
- Average request latency per endpoint
- Writes snapshots to
EjbMetricentities in the DB.
/admin/ejbendpoint - exposes EJB invocation counts, average latency (ms), and error rates./admin/jmsendpoint - exposes per-queue depth, messages processed per minute, and dead-letter counts./admin/asyncendpoint - lists all async task records with status and completion times.
External testing: Use JMeter for load tests (Graph plugin recommended) and Chrome DevTools → Network tab for login/page load timing.
| Tool | Version | Download |
|---|---|---|
| Java JDK | 17+ | Adoptium |
| Apache Maven | 3.8+ | maven.apache.org |
| PostgreSQL | 18 | postgresql.org |
| Payara Server | 6.2025.11 | payara.fish |
| PostgreSQL JDBC Driver | 42.7.3 | mvnrepository.com |
-- 1. Create the database and user
CREATE DATABASE techmart_online;
CREATE USER techmart_user WITH PASSWORD 'TechMart@123';
GRANT ALL PRIVILEGES ON DATABASE techmart_online TO techmart_user;
-- 2. Create the schema
\c techmart_online
CREATE SCHEMA techmart;
GRANT ALL ON SCHEMA techmart TO techmart_user;Run the SQL schema and seed scripts from the database/ folder (if present):
psql -U techmart_user -d techmart_online -f database/00_create_database.sql
psql -U techmart_user -d techmart_online -f database/01_schema_and_mock_data.sql-
Place the PostgreSQL JDBC driver in
<PAYARA_HOME>/glassfish/domains/<domain>/lib/:postgresql-42.7.3.jar -
Create the JDBC Connection Pool via Payara Admin Console (
http://localhost:4848) or CLI:asadmin create-jdbc-connection-pool \ --datasourceclassname org.postgresql.ds.PGSimpleDataSource \ --restype javax.sql.DataSource \ --property "ServerName=localhost:PortNumber=5432:DatabaseName=techmart_online:User=techmart_user:Password=TechMart@123" \ TechMartPool -
Create the JDBC Resource:
asadmin create-jdbc-resource --connectionpoolid TechMartPool jdbc/techmart
-
Create the JMS resources (Connection Factory + 4 Queues):
asadmin create-jms-resource --restype jakarta.jms.ConnectionFactory jms/TechMartCF asadmin create-jms-resource --restype jakarta.jms.Queue jms/OrderQueue asadmin create-jms-resource --restype jakarta.jms.Queue jms/InventoryQueue asadmin create-jms-resource --restype jakarta.jms.Queue jms/NotificationQueue asadmin create-jms-resource --restype jakarta.jms.Queue jms/EmailQueue
Alternatively, include a payara-resources.xml in src/main/resources/ for automated resource creation on deployment.
The frontend API base URL can be overridden at runtime via the VITE_API_BASE_URL environment variable (handled by the frontend Vite build). The backend itself is stateless - all config is in Payara JNDI resources.
mvn clean packageThis produces target/TechMartOnline-backend.war.
Option A - Auto-deploy (dev mode):
# Copy the WAR to the Payara autodeploy directory
cp target/TechMartOnline-backend.war <PAYARA_HOME>/glassfish/domains/<domain>/autodeploy/Option B - Payara Admin CLI:
asadmin deploy target/TechMartOnline-backend.warOption C - Admin Console:
Navigate to http://localhost:4848 → Applications → Deploy → select the WAR file.
Once deployed, the API is reachable at:
http://localhost:8080/TechMartOnline-backend/api/v1/categories
The DatabaseStartupBean logs a confirmation message on successful DB connection:
INFO: Database connection is successful! Verified with 'SELECT 1'.
mvn testsrc/test/java/com/cusaldev/bcd/techmart/
├── api/ # JAX-RS endpoint tests (MockitoExtension)
├── repository/ # Repository unit tests with in-memory stubs
└── service/ # Business-logic unit tests
| Tool | Purpose |
|---|---|
| JUnit 5 | Unit and integration test runner |
| Mockito | Mocking dependencies (@Mock, @InjectMocks) |
| JMeter | Load testing (Graph plugin for throughput/response-time charts) |
| Chrome DevTools | Login and page load timing verification |
mvn clean package -DskipTests| Concern | Jakarta EE (chosen) | Spring Boot |
|---|---|---|
| Assignment requirement | ✅ Mandated by spec | ❌ Not in scope |
| EJB lifecycle management | ✅ Native (Stateless pool, Singleton, Stateful) | ❌ Manual with @Bean scopes |
| JMS / MDB | ✅ First-class @MessageDriven |
❌ Requires additional broker config |
| JTA Transactions | ✅ Container-managed by default | Manual @Transactional |
| Performance monitoring | ✅ Built-in JMX + custom metrics beans | External Actuator dependency |
- JPA entities must not be directly serialised to JSON - this exposes internal DB structure and can cause Jackson infinite-recursion on bidirectional relationships.
ProductMapperandOrderMapperconvert entity graphs to clean, stable JSON shapes, insulating the API contract from DB schema changes.
Work factor 12 requires ~250ms per hash on modern hardware - strong enough to resist brute-force attacks while acceptable for a user-facing login endpoint (sub-second response at p99).
validate confirms the existing schema matches the entity mappings on every deploy without making structural changes. This prevents accidental data loss and enforces schema migrations through proper SQL scripts.
This project was developed as part of the Enterprise Java Development assignment at Birmingham City University (BCU). For educational use.