Skip to content

Latest commit

 

History

48 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 

Repository files navigation

TechMart Online - Backend

Java Jakarta EE Payara PostgreSQL Maven JUnit 5

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


📋 Table of Contents

  1. Project Overview
  2. Tech Stack
  3. Architecture Overview
  4. Project Structure
  5. API Reference
  6. EJB Design
  7. JMS Messaging Architecture
  8. Database & Persistence
  9. Security & Authentication
  10. Configuration & CORS
  11. Exception Handling
  12. Performance Monitoring
  13. Getting Started
  14. Build & Deploy
  15. Testing
  16. Design Decisions

🏪 Project Overview

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.

Business Context & Goals

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 OrderQueueOrderProcessingMDB pipeline
No real-time customer notifications JMS NotificationQueue + NotificationMDB
Poor third-party integration Dedicated ShippingQuoteBean + PricingEngineBean

Key Non-Functional Requirements

  • 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

🚀 Tech Stack

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

🏛️ Architecture Overview

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               │
└─────────────────────────────────────────────────────────────────┘

📁 Project Structure

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

📡 API Reference

All endpoints are served under the base path:

http://<host>:8080/TechMartOnline-backend/api/v1

All requests and responses use Content-Type: application/json.

Authentication (/api/v1/auth)

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

POST /auth/login

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.

POST /auth/register

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 400 if already registered).

Products (/api/v1/products)

Method Endpoint Description Auth Required
GET /products Paginated + filtered product catalogue No
GET /products/{slug} Full product detail by URL slug No

GET /products - Query Parameters

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
}

GET /products/{slug}

Returns full product detail including images, specifications, inventory summary, and related products.


Categories (/api/v1/categories)

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 }
]

Orders (/api/v1/orders)

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

GET /orders - Query Parameters

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)

POST /orders - Place Order

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"
}

Inventory (/api/v1/inventory)

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

GET /inventory - Query Parameters

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"
  }
]

Warehouses (/api/v1/warehouses)

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"
  }
]

Notifications (/api/v1/notifications)

Method Endpoint Description Auth Required
GET /notifications User notifications (+ global broadcasts) No
PUT /notifications/{id}/read Mark a notification as read No

GET /notifications - Query Parameters

Parameter Type Default Description
userId long 0 Target user ID; 0 returns only global notifications

Admin Dashboard (/api/v1/admin)

Admin role required for all /admin/** endpoints.

Method Endpoint Description
GET /admin/dashboard KPI summary: revenue, orders, users, inventory alerts

JMS Monitor (/api/v1/admin/jms)

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

EJB Monitor (/api/v1/admin/ejb)

Method Endpoint Description
GET /admin/ejb EJB invocation counts, avg latency, error counts

Async Tasks (/api/v1/admin/async)

Method Endpoint Description
GET /admin/async List of all async task records with status
POST /admin/async Trigger a new async task for testing

☕ EJB (Enterprise JavaBeans) Design

The EJB layer is the heart of the enterprise architecture, demonstrating all four session bean types as required by the Jakarta EE specification.

Stateless Session Beans (ejb/stateless/)

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.

Stateful Session Bean (ejb/stateful/)

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.

Singleton Session Beans (ejb/singleton/)

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.

Async EJBs (ejb/async/)

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.

📨 JMS Messaging Architecture

The application uses Java Message Service (JMS) for decoupled, asynchronous processing, ensuring order placement returns immediately while heavyweight tasks run in the background.

Queue Topology

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)       ║
╚══════════════════════╝

JMS Resource JNDI Names

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

Message-Driven Beans (MDBs)

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

🗃️ Database & Persistence

Connection Details

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)

Persistence Unit

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 Model

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

🔒 Security & Authentication

Authentication Mechanism

Authentication uses a Base64-encoded prototype token (id:role:timestamp) for the prototype phase. The token is:

  1. Generated in AuthResource.buildResponse() after successful BCrypt password verification.
  2. Stored by the frontend in localStorage under the key tm-auth.
  3. Sent in subsequent requests as Authorization: Bearer <token>.
  4. 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.

Password Security

  • Algorithm: BCrypt with work factor 12.
  • Passwords are never stored in plaintext - only the BCrypt hash is persisted.
  • Library: org.mindrot:jbcrypt:0.4.

User Roles

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

⚙️ Configuration & CORS

JAX-RS Application Path

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.

CORS Filter

CorsFilter implements both ContainerRequestFilter (@PreMatching) and ContainerResponseFilter. It:

  1. Short-circuits OPTIONS preflight requests immediately with HTTP 200 (prevents 405 Method Not Allowed errors).
  2. 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)

Application Constants (Constants.java)

// 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"

🚨 Exception Handling

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
}

📊 Performance Monitoring

The backend includes built-in performance monitoring capabilities:

  • PerformanceTimer utility (util/PerformanceTimer.java) - nanosecond-precision stopwatch for instrumenting code paths.
  • SystemMetricsBean - @Singleton + @Schedule bean that periodically captures:
    • JVM heap usage
    • Active EJB invocation counts
    • JMS queue depths and throughput
    • Average request latency per endpoint
    • Writes snapshots to EjbMetric entities in the DB.
  • /admin/ejb endpoint - exposes EJB invocation counts, average latency (ms), and error rates.
  • /admin/jms endpoint - exposes per-queue depth, messages processed per minute, and dead-letter counts.
  • /admin/async endpoint - 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.


🛠️ Getting Started

Prerequisites

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

Step 1 - Database Setup

-- 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

Step 2 - Payara Server Setup

  1. Place the PostgreSQL JDBC driver in <PAYARA_HOME>/glassfish/domains/<domain>/lib/:

    postgresql-42.7.3.jar
    
  2. 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
  3. Create the JDBC Resource:

    asadmin create-jdbc-resource --connectionpoolid TechMartPool jdbc/techmart
  4. 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.


Step 3 - Environment Variable (optional)

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.


🔨 Build & Deploy

Build the WAR

mvn clean package

This produces target/TechMartOnline-backend.war.

Deploy to Payara

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.war

Option C - Admin Console: Navigate to http://localhost:4848 → Applications → Deploy → select the WAR file.

Verify Deployment

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'.

🧪 Testing

Running Unit Tests

mvn test

Test Structure

src/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

Testing Tools

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

Skip Tests (build-only)

mvn clean package -DskipTests

🧠 Design Decisions

Why Jakarta EE / Payara over Spring Boot?

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

Why DTOs + Mappers?

  • JPA entities must not be directly serialised to JSON - this exposes internal DB structure and can cause Jackson infinite-recursion on bidirectional relationships.
  • ProductMapper and OrderMapper convert entity graphs to clean, stable JSON shapes, insulating the API contract from DB schema changes.

Why BCrypt work factor 12?

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).

Why hbm2ddl.auto=validate?

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.


📄 License

This project was developed as part of the Enterprise Java Development assignment at Birmingham City University (BCU). For educational use.

About

Enterprise-grade Jakarta EE 10 REST API powering the TechMart Online e-commerce platform.

Topics

Resources

Stars

8 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages