|
|
||
|---|---|---|
| .gitea/workflows | ||
| admin | ||
| common | ||
| db | ||
| storefront | ||
| .gitignore | ||
| Containerfile | ||
| pom.xml | ||
| README.md | ||
Classic Shop
A deliberately legacy-styled Java web application: a small e-commerce store split
into two WARs deployed on a single Tomcat 9, backed by PostgreSQL via a
container-managed JNDI datasource. It is a realistic 2015-era enterprise app
(Spring MVC 5.3, Hibernate ORM 5.6, JSP views, javax.* Servlet 4 / Java EE 8
APIs, logback file logging, commons-fileupload) that happens to compile and run
on Java 17.
It exists as a migration / discovery test fixture: the multi-WAR layout, the
JNDI datasource with credentials embedded in context.xml, the host filesystem
paths for uploads and logs, and the server-side session cart are all things a
VM-to-OpenShift containerisation pass has to notice and externalise.
What it does
- storefront.war -- customer facing:
- session-based login against the
userstable (unsalted SHA-256 hex hashes) - product catalog (read side) with list + detail pages
- a multi-step session cart wizard: add items -> review -> checkout, where
checkout writes an
orders+order_itemsrow set - product image upload via commons-fileupload, saved to a configurable
uploads dir (default
/var/lib/classic-shop/uploads) - a
GET /healthservlet returning{"status":"UP","db":true|false}with a realSELECT 1ping against the JNDI datasource
- session-based login against the
- admin.war -- back office:
- product CRUD (create / update / delete)
- order list + order detail
- a dashboard with live counts (products / orders / users) from the DB
Module layout
classic-shop/
pom.xml parent (dependency + plugin management)
common/ entities, DAOs, services, shared persistence config
com/example/shop/
domain/ User, Product, Order, OrderItem (JPA/Hibernate)
dao/ UserDao, ProductDao, OrderDao (Hibernate Session)
service/ UserService, CatalogService, OrderService (@Transactional)
util/PasswordUtil legacy SHA-256 hashing
PersistenceConfig JNDI DataSource + Hibernate SessionFactory + tx mgr
storefront/ -> storefront.war
web/ Login, Catalog, Cart, Upload controllers
servlet/HealthServlet /health
config/StorefrontMvcConfig
webapp/WEB-INF/views/*.jsp, web.xml, spring/root-context.xml
webapp/META-INF/context.xml JNDI datasource (with credentials)
admin/ -> admin.war
web/ Dashboard, AdminProduct, AdminOrder controllers
config/AdminMvcConfig
webapp/WEB-INF/views/*.jsp, web.xml, spring/root-context.xml
webapp/META-INF/context.xml
db/init.sql idempotent schema + seed data
Both WARs share the common jar and each bootstraps Spring with a classic
XML-plus-annotation mix: an XML root-context.xml loaded by
ContextLoaderListener simply enables annotation processing and registers the
annotated @Configuration classes; the DispatcherServlet uses an
AnnotationConfigWebApplicationContext.
Build
Requires Maven and Java 17 (Spring 5.3 / Hibernate 5.6 run fine on 17).
mvn -B -DskipTests package
Produces storefront/target/storefront.war and admin/target/admin.war.
If you only have podman, build in the UBI9 OpenJDK 17 image:
podman run --rm -v "$PWD":/src:Z --user 0 \
registry.access.redhat.com/ubi9/openjdk-17 \
bash -c 'cp -r /src /tmp/b && cd /tmp/b && mvn -B -DskipTests package \
&& cp */target/*.war /src/$(dirname)/target/'
Run locally (Tomcat 9 + PostgreSQL)
1. PostgreSQL
createdb classicshop
createuser shopapp
psql -d classicshop -f db/init.sql
psql -d classicshop -c "ALTER USER shopapp WITH PASSWORD 's3cr3t-shop-pw';"
psql -d classicshop -c "GRANT ALL ON ALL TABLES IN SCHEMA public TO shopapp;"
psql -d classicshop -c "GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO shopapp;"
2. Tomcat 9
Create the host directories the app writes to:
sudo mkdir -p /var/lib/classic-shop/uploads /var/log/classic-shop
sudo chown -R tomcat:tomcat /var/lib/classic-shop /var/log/classic-shop
The uploads directory is overridable with -Dshop.uploads.dir=... (or the
SHOP_UPLOADS_DIR env var); the log directory with SHOP_LOG_DIR.
Drop the PostgreSQL JDBC driver into $CATALINA_HOME/lib/ (e.g.
postgresql-42.7.4.jar), copy both WARs into $CATALINA_HOME/webapps/, and
start Tomcat. The apps will be available at:
- storefront:
http://localhost:8080/storefront/(health:/storefront/health) - admin:
http://localhost:8080/admin/
Each WAR ships its own META-INF/context.xml defining the datasource, so no
server-wide config is strictly required. If you prefer a server-wide datasource,
put the <Resource> block in $CATALINA_HOME/conf/context.xml instead (see
below) and remove it from the WARs.
Datasource snippet (Tomcat conf/context.xml)
This is the JNDI datasource the application looks up at
java:comp/env/jdbc/shopDS. The database username and password are embedded
in plain text -- this is the classic "secrets baked into the deployment
descriptor" pattern, and on a real VM it lives in conf/context.xml:
<Context>
<Resource name="jdbc/shopDS"
auth="Container"
type="javax.sql.DataSource"
driverClassName="org.postgresql.Driver"
url="jdbc:postgresql://localhost:5432/classicshop"
username="shopapp"
password="s3cr3t-shop-pw"
maxTotal="20"
maxIdle="5"
maxWaitMillis="10000"
validationQuery="SELECT 1"
testOnBorrow="true"/>
</Context>
Migration note: a containerisation pass should externalise
username,password, andurlto environment variables / a Kubernetes Secret rather than shipping them inside the image.
Default credentials
Passwords are stored as unsalted SHA-256 hex digests (legacy scheme; see
PasswordUtil).
| Username | Password | Role | Notes |
|---|---|---|---|
| admin | admin123 | ADMIN | site administrator |
| alice | password | CUSTOMER | seeded with sample orders |
| bob | letmein | CUSTOMER | seeded with sample orders |
The storefront uses the customer accounts; the admin WAR has no auth gate of its own in this sample (it is assumed to sit behind a network / reverse-proxy restriction, another migration discovery point).
Seed data
db/init.sql is idempotent (drops and recreates the four tables, then
reseeds). It loads:
- 3 users (1 admin + 2 customers)
- 12 products (SKU-1001 .. SKU-1012)
- 5 orders with 12 order-item lines across the two customer accounts, in statuses NEW / PLACED / SHIPPED