Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .claude/scheduled_tasks.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"sessionId":"9a40df83-1a1a-48b8-9360-9005af8dc622","pid":32748,"acquiredAt":1780899400896}
8 changes: 8 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
target/
logs/
.git/
.idea/
*.iml
.mvn/
mvnw
mvnw.cmd
128 changes: 128 additions & 0 deletions DATABASE_SETUP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# Database Setup & Connection Guide

This project uses an **Oracle Database XE 21c** instance running in Docker. The
application user/schema (`oraclequant`) has already been created inside the
`XEPDB1` pluggable database.

## 1. Running database container

A container named `oracle-xe` is already running:

| Setting | Value |
|----------------|-----------------------------------------------------|
| Image | `container-registry.oracle.com/database/express:latest` |
| Container name | `oracle-xe` |
| Host port | `1521` → container `1521` (DB listener) |
| Host port | `5500` → container `5500` (EM Express, optional) |
| ORACLE_SID | `XE` |
| PDB | `XEPDB1` |
| SYS/SYSTEM pwd | `29999login` |

If the container isn't running, start it with:

```bash
docker start oracle-xe
```

## 2. Application schema (already created)

The application connects as the `oraclequant` user inside the `XEPDB1` PDB —
matching `src/main/resources/application.properties`:

```properties
spring.datasource.url=jdbc:oracle:thin:@localhost:1521/XEPDB1
spring.datasource.username=oraclequant
spring.datasource.password=oraclequant
```

This user was created with:

```sql
ALTER SESSION SET CONTAINER = XEPDB1;

CREATE USER oraclequant IDENTIFIED BY oraclequant
DEFAULT TABLESPACE USERS
TEMPORARY TABLESPACE TEMP
QUOTA UNLIMITED ON USERS;

GRANT CONNECT, RESOURCE TO oraclequant;
GRANT CREATE SESSION, CREATE TABLE, CREATE SEQUENCE, CREATE VIEW, CREATE PROCEDURE TO oraclequant;
```

(Spring's `spring.jpa.hibernate.ddl-auto=update` will create/update the actual
tables on first application startup.)

## 3. Connection details (for any DB GUI tool)

| Field | Value |
|-------------------|-----------------------------|
| Host | `localhost` |
| Port | `1521` |
| Connection type | **Service Name** (not SID) |
| Service name | `XEPDB1` |
| Username | `oraclequant` |
| Password | `oraclequant` |
| JDBC URL | `jdbc:oracle:thin:@localhost:1521/XEPDB1` |

> Note: connect using the **service name** `XEPDB1`, not the SID `XE` — `XE`
> is the container database (CDB), and the application schema lives in the
> pluggable database `XEPDB1`.

## 4. Connecting with DbVisualizer (Oracle DataDirect driver)

You already have a connection saved in DbVisualizer called **`Oracle-xe`**
(driver: *Oracle (DataDirect)*), but it currently points at the wrong
service name (`ORCL`) and has no username/password set — that's why it
fails. Fix it like this:

1. Open DbVisualizer → in the **Databases** tab on the left, select the
**`Oracle-xe`** connection.
2. Open its **Properties** tab (or right-click → **Properties**) and check
the driver is **Oracle (DataDirect)**.
3. On the connection's **Database** / connection settings, set:
- **Server**: `localhost`
- **Port**: `1521`
- **Service Name**: `XEPDB1` ← change this from `ORCL` to `XEPDB1`
- **Database Userid**: `oraclequant`
- **Database Password**: `oraclequant`
4. Click **Connect** (the plug icon, or right-click → **Connect**).
5. Once connected, expand `Oracle-xe` → **Schemas** → **ORACLEQUANT** to
browse tables. Tables appear after the Spring Boot app has run at least
once (Hibernate creates them via `ddl-auto=update`).

### If you'd rather create a fresh connection from scratch

1. **Connection → Create Connection...**
2. Pick **Oracle** as the database type, then choose driver
**Oracle (DataDirect)** when prompted.
3. Name it (e.g. `oraclequant-xepdb1`), then on the connection settings tab
fill in:
- **Server**: `localhost`
- **Port**: `1521`
- **Service Name**: `XEPDB1` *(NOT `XE` / `ORCL` — that's the CDB SID, the
app schema lives in the pluggable database `XEPDB1`)*
- **Database Userid**: `oraclequant`
- **Database Password**: `oraclequant`
4. Click **Ping Server** to verify connectivity, then **Connect**.

> The Oracle DataDirect driver builds the JDBC URL from these fields itself
> — you don't need to type a URL manually. If DbVisualizer asks you to
> download/install the driver the first time, allow it.

## 5. Connecting via SQL*Plus (CLI, inside the container)

```bash
docker exec -it oracle-xe sqlplus oraclequant/oraclequant@localhost:1521/XEPDB1
```

## 6. Troubleshooting

- **ORA-12514 "TNS:listener does not currently know of service"**: the
**Service Name** field has the wrong value (e.g. `ORCL` or `XE`). Set it
to `XEPDB1`.
- **ORA-01017 "invalid username/password"**: double check **Database
Userid**/**Database Password** are filled in (`oraclequant`/`oraclequant`)
and that you're targeting `XEPDB1` — the `oraclequant` user only exists
inside `XEPDB1`, not in the root `XE` container.
- **Connection refused**: make sure the container is running and healthy —
`docker ps` should show `oracle-xe` as `Up ... (healthy)`.
106 changes: 106 additions & 0 deletions DOCKER.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# Running Everything in Docker (Database + App)

`docker-compose.yml` spins up **both** the Oracle XE database and the
Spring Boot app as containers, networked together — no local Java/Maven/JDK
setup required.

## 1. Files involved

| File | Purpose |
|-----------------------------------|----------------------------------------------------------------|
| `Dockerfile` | Multi-stage build: compiles the jar with Maven+JDK17, runs it on a slim JRE 17 |
| `docker-compose.yml` | Defines the `oracle-xe` and `app` services + shared network |
| `db/setup/01_create_oraclequant_user.sql` | Auto-creates the `oraclequant` schema in `XEPDB1` on first DB init |
| `.dockerignore` | Keeps `target/`, `.git/`, `.idea/`, `logs/` out of the build context |

## 2. IMPORTANT: stop any existing standalone Oracle XE container first

You already have a separate Oracle XE container (`oracle-xe`, started from
`Documents/oracle-xe-db-hosting/docker-compose.yaml`) bound to host port
`1521`. It will conflict with the one in this stack. Stop it first:

```bash
docker compose -f "C:\Users\kinda\OneDrive - yay app\Documents\oracle-xe-db-hosting\docker-compose.yaml" down
```
*(This only stops that container — its data volume is untouched.)*

## 3. Start the full stack

From the project root:

```bash
docker compose up --build
```

What happens:
1. **`oracle-xe`** starts from a fresh `oracle-data` volume. On its very
first initialization (this can take several minutes — Oracle XE creates
the database from scratch), it automatically executes
`db/setup/01_create_oraclequant_user.sql`, creating the `oraclequant`
user/schema inside `XEPDB1` — exactly what the app needs.
2. Once `oracle-xe` reports **healthy**, the **`app`** service builds (Maven
compiles the jar inside a build stage) and starts, connecting to
`jdbc:oracle:thin:@oracle-xe:1521/XEPDB1` as `oraclequant`/`oraclequant`
(passed via `DB_URL`/`DB_USERNAME`/`DB_PASSWORD` env vars — see
`application.properties`, which reads these with `oraclequant` as default).
3. Hibernate (`ddl-auto=update`) creates `HISTORY_RECORD` automatically on
first startup.

To run in the background:
```bash
docker compose up --build -d
```

## 4. Verify it's running

```bash
docker compose ps
# both `oraclequant-db` and `oraclequantapi` should show as Up/healthy

curl -s -G "http://localhost:8080/convert-measurements" --data-urlencode "input=aa"
# -> [1]

curl -s http://localhost:8080/history
```

See `TESTING.md` for a full set of endpoint test cases.

## 5. Logs

```bash
docker compose logs -f app # Spring Boot / app logs
docker compose logs -f oracle-xe # Database logs
```

The app also writes to `./logs/oraclequantapi.log` on the host (mounted via
`volumes: - ./logs:/app/logs`).

## 6. Connecting a DB GUI tool (DbVisualizer, etc.)

Same connection details as before — Docker still publishes port `1521` on
`localhost`:
- Host: `localhost`, Port: `1521`, Service Name: `XEPDB1`
- User: `oraclequant` / Password: `oraclequant`

See `DATABASE_SETUP.md` for full GUI connection steps.

## 7. Stopping / cleaning up

```bash
docker compose down # stop + remove containers (keeps the data volume)
docker compose down -v # also delete the database volume (full reset)
```

## 8. Troubleshooting

- **"port is already allocated" for 1521/8080**: another container or local
process is using the port. Check `docker ps` and stop the conflicting
container (see step 2), or stop a locally-running instance of the app.
- **`app` keeps restarting / can't connect to DB**: the database can take
several minutes to initialize on first run. Watch
`docker compose logs -f oracle-xe` until it reports healthy before the
app's `depends_on: condition: service_healthy` lets it start.
- **`oraclequant` user missing after first run**: the setup script in
`db/setup/` only runs against an *empty* `oracle-data` volume. If you've
run the stack before with old data, either `docker compose down -v` for a
clean slate, or create the user manually (see `DATABASE_SETUP.md` §2).
19 changes: 19 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# ---- Build stage: compile the Spring Boot jar with Maven + JDK 17 ----
FROM maven:3.9.6-eclipse-temurin-17 AS build
WORKDIR /build

COPY pom.xml .
RUN mvn -q dependency:go-offline

COPY src ./src
RUN mvn -q clean package -DskipTests \
&& find target -maxdepth 1 -name "*.jar" ! -name "*.original" -exec cp {} app.jar \;

# ---- Runtime stage: run the jar on a slim JRE 17 ----
FROM eclipse-temurin:17-jre-jammy
WORKDIR /app

COPY --from=build /build/app.jar app.jar

EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
110 changes: 110 additions & 0 deletions TESTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# Testing the `/convert-measurements` Endpoint

The app exposes a single `GET` endpoint that converts a measurement string
into a list of integer totals, and records every request in the
`HISTORY_RECORD` table (browsable via the `/history` endpoints).

Base URL (when running locally or via Docker Compose, see `DOCKER.md`):
```
http://localhost:8080
```

## 1. Endpoint

```
GET /convert-measurements?input=<string>
```

Returns a JSON array of integers, e.g. `[1]`, `[2,2]`, or `[]`.

## 2. Quick test via browser

Just open in your browser (spaces must be URL-encoded as `%20`):

```
http://localhost:8080/convert-measurements?input=aa
http://localhost:8080/convert-measurements?input=ab%20ab
```

## 3. Quick test via curl

Use `curl -G --data-urlencode` so spaces/special characters are encoded
correctly:

```bash
curl -s -G "http://localhost:8080/convert-measurements" --data-urlencode "input=aa"
# -> [1]

curl -s -G "http://localhost:8080/convert-measurements" --data-urlencode "input=ab ab"
# -> [2,2]
```

## 4. Verified test cases (run against the live app)

Parsing rules recap: letters `a`-`z` carry values 1-26, `_` carries 0. A
package = one COUNT token followed by that many VALUE tokens; the package's
total is the sum of its values. A run of `z`s extends the next token's value
by `26 * (number of z's)`.

| Input | Output | Why |
|------------|------------|---------------------------------------------------------------------|
| `aa` | `[1]` | count=`a`(1), 1 value `a`(1) → sum 1 |
| `ab` | `[2]` | count=`a`(1), 1 value `b`(2) → sum 2 |
| `abc` | `[2]` | first package sums to 2; `c`(3) starts a package needing 3 values but input ends → incomplete, stop with totals so far |
| `_a` | `[0]` | count=`_`(0) → empty package, total 0; `_` followed immediately by a letter stops processing |
| `_b` | `[0]` | same rule as above |
| `za` | `[]` | `z`+`a` → count = 26·1+1 = 27, needs 27 values but input ends → incomplete, no totals collected yet |
| `zza` | `[]` | `zz`+`a` → count = 26·2+1 = 53 → incomplete |
| `z_` | `[]` | `z`+`_` → count = 26·1+0 = 26 → incomplete |
| `ab ab` | `[2,2]` | two packages separated by a single space, each totalling 2 |
| `aab aaa` | `[]` | a space appears mid-token (after reading count `b`) → invalid input → empty list |
| `aa bb` | `[1]` | first package totals 1; two consecutive spaces stop processing, returning totals collected so far |
| `ba` | `[]` | count=`b`(2) needs 2 values, only 1 available before input ends → incomplete |
| `ca_` | `[]` | count=`c`(3) needs 3 values, only 2 available → incomplete |
| `1a`, `a1` | `[]` | digits aren't valid characters (only `a`-`z`, `_`, space) → invalid input |

Run them all in one go:

```bash
for input in "aa" "ab" "abc" "_a" "za" "zza" "z_" "ab ab" "aab aaa" "aa bb" "ba" "ca_"; do
printf "input=[%s] -> " "$input"
curl -s -G "http://localhost:8080/convert-measurements" --data-urlencode "input=$input"
echo
done
```

## 5. Inspecting recorded history

Every call (valid or not) is persisted. Browse it via:

```bash
# list all recorded requests
curl -s http://localhost:8080/history | jq

# get one record by id
curl -s http://localhost:8080/history/1 | jq

# delete all history records
curl -s -X DELETE http://localhost:8080/history -o /dev/null -w "%{http_code}\n"
```

Example record shape:
```json
{
"id": 1,
"timestamp": "2026-06-08T15:49:32.774915",
"sourceIpAddress": "0:0:0:0:0:0:0:1",
"input": "2ab",
"output": "[]"
}
```

You can also browse the `HISTORY_RECORD` table directly with DbVisualizer —
see `DATABASE_SETUP.md` for connection steps.

## 6. Postman / HTTP client

If you prefer a GUI client, import this as a request:
- Method: `GET`
- URL: `http://localhost:8080/convert-measurements`
- Query param: `input` = `aa` (or any of the test strings above)
9 changes: 9 additions & 0 deletions db/setup/01_create_oraclequant_user.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
ALTER SESSION SET CONTAINER = XEPDB1;

CREATE USER oraclequant IDENTIFIED BY oraclequant
DEFAULT TABLESPACE USERS
TEMPORARY TABLESPACE TEMP
QUOTA UNLIMITED ON USERS;

GRANT CONNECT, RESOURCE TO oraclequant;
GRANT CREATE SESSION, CREATE TABLE, CREATE SEQUENCE, CREATE VIEW, CREATE PROCEDURE TO oraclequant;
Loading