From a4909596c0cdf405b1972fcb65d116e8ea59889c Mon Sep 17 00:00:00 2001 From: Alharith Ahmed Date: Mon, 25 May 2026 13:00:31 +0400 Subject: [PATCH 1/2] Alharith Submission Branch --- CHANGELOG.md | 23 ++ README.md | 216 +++++++++++++----- dbvis/README-DbVisualizer.md | 120 ++++++++++ dbvis/README-Docker-DbVisualizer.md | 13 ++ dbvis/README-Mac-DbVisualizer.md | 81 +++++++ dbvis/connection-oracle-linux.properties | 12 + docker/docker-compose.yml | 25 ++ docker/oracle-init/01-pkc-user.sql | 20 ++ docker/setup-pkc-user-grants-only.sql | 30 +++ docker/setup-pkc-user-manual.sql | 38 +++ pom.xml | 56 +++-- scripts/JdbcPing.class | Bin 0 -> 1544 bytes scripts/docker-setup-db-only.sh | 32 +++ scripts/docker-start-all.sh | 50 ++++ scripts/fix-pkc-user-FREEPDB1.sql | 25 ++ scripts/fix-pkc-user-complete.sql | 46 ++++ scripts/fix-pkc-user-grants.sql | 16 ++ scripts/mac-send-jar-to-linux.sh | 28 +++ scripts/mac-start-docker.sh | 71 ++++++ scripts/mac-start-local.sh | 4 + scripts/oracle-linux-all-in-one.sh | 185 +++++++++++++++ scripts/oracle-linux-setup.sql | 60 +++++ scripts/setup-oracle-xe.sql | 41 ++++ scripts/start-pkc-api-oracle-linux.sh | 25 ++ scripts/stop-pkc-api.sh | 26 +++ .../OraclequantapiApplication.java | 5 + .../controller/HistoryController.java | 66 ++++++ .../controller/MeasurementController.java | 50 ++++ .../model/HistoryRecord.java | 79 +++++++ .../repository/HistoryRepository.java | 12 + .../service/ConversionEngine.java | 70 ++++++ .../service/HistoryService.java | 26 +++ .../service/HistoryServiceImpl.java | 91 ++++++++ .../service/MeasurementService.java | 13 ++ .../service/MeasurementServiceImpl.java | 64 ++++++ .../application-docker-internal.properties | 14 ++ ...pplication-docker-local.properties.example | 8 + .../resources/application-docker.properties | 24 ++ src/main/resources/application-h2.properties | 9 + .../resources/application-local.properties | 16 ++ ...pplication-oracle-local.properties.example | 15 ++ .../resources/application-oracle.properties | 22 ++ src/main/resources/application.properties | 12 +- src/main/resources/logback-spring.xml | 35 +++ .../OraclequantapiApplicationTests.java | 2 + .../service/ConversionEngineTest.java | 83 +++++++ 46 files changed, 1878 insertions(+), 81 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 dbvis/README-DbVisualizer.md create mode 100644 dbvis/README-Docker-DbVisualizer.md create mode 100644 dbvis/README-Mac-DbVisualizer.md create mode 100644 dbvis/connection-oracle-linux.properties create mode 100644 docker/docker-compose.yml create mode 100644 docker/oracle-init/01-pkc-user.sql create mode 100644 docker/setup-pkc-user-grants-only.sql create mode 100644 docker/setup-pkc-user-manual.sql create mode 100644 scripts/JdbcPing.class create mode 100755 scripts/docker-setup-db-only.sh create mode 100755 scripts/docker-start-all.sh create mode 100644 scripts/fix-pkc-user-FREEPDB1.sql create mode 100644 scripts/fix-pkc-user-complete.sql create mode 100644 scripts/fix-pkc-user-grants.sql create mode 100755 scripts/mac-send-jar-to-linux.sh create mode 100755 scripts/mac-start-docker.sh create mode 100755 scripts/mac-start-local.sh create mode 100755 scripts/oracle-linux-all-in-one.sh create mode 100644 scripts/oracle-linux-setup.sql create mode 100644 scripts/setup-oracle-xe.sql create mode 100755 scripts/start-pkc-api-oracle-linux.sh create mode 100755 scripts/stop-pkc-api.sh create mode 100644 src/main/java/com/oraclequantapi/oraclequantapi/alharith_alsubhi/packageconversion/controller/HistoryController.java create mode 100644 src/main/java/com/oraclequantapi/oraclequantapi/alharith_alsubhi/packageconversion/controller/MeasurementController.java create mode 100644 src/main/java/com/oraclequantapi/oraclequantapi/alharith_alsubhi/packageconversion/model/HistoryRecord.java create mode 100644 src/main/java/com/oraclequantapi/oraclequantapi/alharith_alsubhi/packageconversion/repository/HistoryRepository.java create mode 100644 src/main/java/com/oraclequantapi/oraclequantapi/alharith_alsubhi/packageconversion/service/ConversionEngine.java create mode 100644 src/main/java/com/oraclequantapi/oraclequantapi/alharith_alsubhi/packageconversion/service/HistoryService.java create mode 100644 src/main/java/com/oraclequantapi/oraclequantapi/alharith_alsubhi/packageconversion/service/HistoryServiceImpl.java create mode 100644 src/main/java/com/oraclequantapi/oraclequantapi/alharith_alsubhi/packageconversion/service/MeasurementService.java create mode 100644 src/main/java/com/oraclequantapi/oraclequantapi/alharith_alsubhi/packageconversion/service/MeasurementServiceImpl.java create mode 100644 src/main/resources/application-docker-internal.properties create mode 100644 src/main/resources/application-docker-local.properties.example create mode 100644 src/main/resources/application-docker.properties create mode 100644 src/main/resources/application-h2.properties create mode 100644 src/main/resources/application-local.properties create mode 100644 src/main/resources/application-oracle-local.properties.example create mode 100644 src/main/resources/application-oracle.properties create mode 100644 src/main/resources/logback-spring.xml create mode 100644 src/test/java/com/oraclequantapi/oraclequantapi/alharith_alsubhi/packageconversion/service/ConversionEngineTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..5a7033b --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,23 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +--- + +## [1.0.0] - 2026-05-20 + +### Added +- **Core Conversion Engine**: Implemented `z*[^z]` decoder logic to convert measurement input strings into package inflow total lists. Supports standard characters (`a-y`), multiplier prefixes (`z`), empty/zero values (`_`), and case insensitivity. +- **Persistent REST History Endpoints**: Exposes standard endpoints to interact with history database tables: + - `GET /convert-measurements` (accepts `input` / `convert-measurements` query parameter, converts, logs, and persists details). + - `GET /history` (returns all records). + - `GET /history/{id}` (returns a specific record). + - `PUT /history/{id}` (replaces/updates record). + - `PATCH /history/{id}` (patches specific fields). + - `DELETE /history` (clears history record table). +- **Dual Persistence Architecture**: Setup standard H2 in-memory configuration by default (for out-of-the-box local testing) and added a dedicated `prod` profile using `ojdbc11` to persist history records on production Oracle XE databases. +- **7-day Rolling Log System**: Configured custom Logback rolling file appenders that generate structured daily logs under `logs/pkc-api.log` with a strict 7-day retention policy. +- **Full Automated Testing**: Added `ConversionEngineTest` to verify correctness on all requirements and edge cases. diff --git a/README.md b/README.md index b1cccfd..29cb51b 100644 --- a/README.md +++ b/README.md @@ -1,61 +1,155 @@ -## Submission Instructions - -To submit your Oracle JAVA Spring Boot Maven project as a solution, please follow these steps: - -### Step 1: Install git on your PC -- Install "git" as shown in this tutorial: [How to install git](https://youtu.be/iYkLrXobBbA?si=_l0haibv_X9NpIjJ) -- Open command prompt and run - ```bash - git version - ``` -- If you see the version, then git is successfully installed. - -### Step 2: Fork the Repository -- Navigate to [this repository](https://github.com/CodelineAtyab/oraclequantapi) provided by Codeline. -- Click on the "Fork" button at the top-right corner of the page to create a copy of the repository under your own GitHub account. - -### Step 3: Clone the Forked Repository -- Open your terminal or command prompt. -- Clone the forked repository to your local machine using the following command: - ```bash - git clone https://github.com/your-username/repo-name.git - ``` - -### Step 4: Create a new branch -- Navigate to the cloned repository directory - ```bash - cd repo-name - ``` -- Create a new branch for your code submissions (Replace your-name with your name in your-name-submission-branch): - ```bash - git checkout -b your-name-submission-branch - ``` - - -### Step 5: Add Your Code -- Implement the API - -### Step 6: Commit your changes -- Run the following commands in order to commit your changes: - ```bash - git add * - git commit -m "Meaningful commit message here" - ``` - -### Step 7: Push Your Branch to GitHub -- Run the following commands to upload the changes to the forked github repository (Replace your-name with your name in your-name-submission-branch): - ```bash - git push origin your-name-submission-branch - ``` - -### Step 8: Create a Pull Request -- Go to your forked repository on GitHub. -- You should see a prompt to create a pull request. Click on "Compare & pull request". -- Provide a title and description for your pull request, then click "Create pull request". - -### Step 9: Notify Codeline -- Notify on slack that you have created a PR for your solution. - -## Note: If you face any issues in the process above, Please do the following: -- Watch [this youtube tutorial](https://www.youtube.com/watch?v=a_FLqX3vGR4) -- Contact Ikhlas or Atyab. +# My OracleQuant API notes + +This is my small project for the evaluation. +You send text like `aa` → you get `[1]`. It saves everything in **MEASUREMENT_HISTORY**. +Jar: **pkc-api.jar** · runs on port **8081** + +I am still learning so this readme is just my story day by day. + +--- + +## Day 1 · Tuesday 20 May 2026 + +**What I tried to do** +Start the task. Read the PDF. I did not understand everything at first (packages, letters a-z = numbers, etc). + +**What I did** +- Opened the project in IntelliJ +- Made the conversion code (`ConversionEngine`) +- Ran unit tests — the 8 examples from PDF passed (I was happy here) +- Started Spring Boot with **H2** database (teacher said easier for first step) + +**Challenges** +- PDF is long and has many rules +- I confused where to put my files in the package folder +- First time using Spring Boot + Maven together + +**What worked at end of day** +Tests green. I could think the math part is OK. + +--- + +## Day 2 · Wednesday 21 May 2026 + +**What I tried to do** +Make the API real — controller, history save, try Postman. + +**What I did** +- Added `/convert-measurements` and `/history` endpoints +- Every Postman call should save a row (input, output, IP, time) +- Fixed a stupid mistake: class name must match file name (`PackageConversionApplication`) +- Connected **DbVisualizer** to H2 file database +- Saw table **MEASUREMENT_HISTORY** with my test data + +**Challenges** +- App would not compile sometimes (JPA package missing in IDE) +- Had to run `mvn compile` to see real errors +- Did not know JDBC URL for H2 at first + +**What worked at end of day** +Postman: `http://localhost:8081/convert-measurements?input=aa` → `[1]` and row in DbVisualizer. + +--- + +## Day 3 · Thursday 22 May 2026 + +**What I tried to do** +Move from H2 to **real Oracle** (because assignment wants Oracle XE). + +**What I did** +- Installed Oracle XE on Mac +- Wrote SQL scripts for user **pkc_user** password **PkcPassword1** +- Made `oracle` profile in `application.properties` +- Tried DbVisualizer with `localhost:1521` and service **XEPDB1** + +**Challenges** +- **ORA-01017** — login failed (I typed wrong password many times) +- **ORA-01045** — user exists but cannot connect (needed GRANT CREATE SESSION) +- App started very slow or failed when Oracle was off +- I did not understand SYS vs SYSTEM vs pkc_user at first + +**What worked at end of day** +User `pkc_user` can login in SQL*Plus / DbVisualizer. Table created. + +--- + +## Day 4 · Friday 23 May 2026 + +**What I tried to do** +Run the app on **Oracle Linux VM** (assignment also talks about Linux server). + +**What I did** +- VM name: **AlharithLinux**, user **alharith** +- Built jar: `mvn clean package` → copied **pkc-api.jar** to `/home/alharith/` +- Tried SSH from Mac (port 2222) — did not work, used VM screen instead +- Ran `java -jar pkc-api.jar` on Linux + +**Challenges** +- I ran `pkc-api.out` once by mistake (that is not the app file) +- Tried `mvn spring-boot:run` on VM → error **no plugin spring-boot** (no pom.xml there, only jar) +- DbVisualizer from Mac to VM IP `10.0.2.15` → **timeout** (NAT network, I learned later) +- Port 8081 sometimes already used from old run +- Connection refused in Postman because app stuck on database + +**What worked at end of day** +I understand: on VM you only need `java -jar`, not Maven. Scripts saved in `scripts/` folder. + +--- + +## Day 5 · Saturday 24 May 2026 + +**What I tried to do** +Make everything work on **my Mac** with **Docker + DbVisualizer + Postman** (my main setup now). + +**What I did** +- Oracle in Docker container **oracle-xe** +- Script `docker-setup-db-only.sh` for pkc_user +- Script `mac-start-docker.sh` to start DB + API +- API runs **inside Docker** on network **pkc-net** with profile **docker-internal** +- DbVisualizer on Mac still uses `127.0.0.1:1521` + +**Challenges** +- Docker said container name already exists — had to `docker start oracle-xe` not create new one +- `mvn spring-boot:run` with docker profile on Mac **hangs** forever (HikariPool) — big confusion +- Postman **ECONNREFUSED** — app never really started +- Port **8081 in use** — needed `scripts/stop-pkc-api.sh` first + +**What worked at end of day (finally)** +```bash +bash scripts/stop-pkc-api.sh +bash scripts/mac-start-docker.sh +``` +Postman OK. DbVisualizer shows **MEASUREMENT_HISTORY**. I can show my trainer. + +--- + +## Quick run (what I use now) + +```bash +cd /Users/mac/IdeaProjects/oraclequantapi +bash scripts/stop-pkc-api.sh +bash scripts/mac-start-docker.sh +``` + +- Postman: `http://127.0.0.1:8081/convert-measurements?input=aa` +- History: `http://127.0.0.1:8081/history` +- DbVisualizer: `jdbc:oracle:thin:@//127.0.0.1:1521/XEPDB1` · user `pkc_user` · pass `PkcPassword1` + +Build jar: `mvn clean package` + +--- + +## Small hints (for me later) + +- docker profile + mvn on Mac = bad (hangs) +- use `.jar` not `.out` +- stop old app before start (8081) +- VM = `java -jar` only if no project folder + +--- + +## Submit + +branch `alharith-alsubhi-submission-branch` → push → PR → Slack trainer + +*last update 24 May 2026* diff --git a/dbvis/README-DbVisualizer.md b/dbvis/README-DbVisualizer.md new file mode 100644 index 0000000..72315ff --- /dev/null +++ b/dbvis/README-DbVisualizer.md @@ -0,0 +1,120 @@ +# DbVisualizer — connect to your program's data on Oracle Linux + +Your API stores every conversion in **your own table**: + +| Item | Value | +|------|--------| +| **Schema (owner)** | `PKC_USER` | +| **Table** | `MEASUREMENT_HISTORY` | +| **Columns** | `ID`, `TIMESTAMP`, `SOURCE_IP_ADDRESS`, `INPUT_VALUE`, `OUTPUT_VALUE` | + +--- + +## Part 1 — On Oracle Linux VM (one time) + +### 1. Run database setup + +As **SYSTEM** on the VM (SQL*Plus or SQL Developer connected to **XEPDB1**): + +```bash +sqlplus system@localhost:1521/XEPDB1 +``` + +Then: + +```sql +@/home/YOUR_USER/oraclequantapi/scripts/oracle-linux-setup.sql +``` + +(Or copy/paste the file contents.) + +### 2. Open firewall for DbVisualizer (from your Mac) + +```bash +sudo firewall-cmd --add-port=1521/tcp --permanent +sudo firewall-cmd --add-port=8081/tcp --permanent +sudo firewall-cmd --reload +``` + +### 3. Start the API on the VM + +```bash +chmod +x scripts/start-pkc-api-oracle-linux.sh +./scripts/start-pkc-api-oracle-linux.sh /home/YOUR_USER/pkc-api.jar +``` + +Send a test request on the VM: + +```bash +curl "http://localhost:8081/convert-measurements?input=aa" +``` + +--- + +## Part 2 — DbVisualizer on your Mac + +### New connection + +1. **Database** → **Create Database Connection** +2. **Name:** `OracleQuant PKC API (Linux VM)` +3. **Database type:** Oracle +4. **Driver:** Thin + +| Field | Value | +|-------|--------| +| **Server** | Your VM IP (e.g. `192.168.56.101`) — **not** `localhost` | +| **Port** | `1521` | +| **Database (Service)** | `XEPDB1` | +| **Userid** | `pkc_user` | +| **Password** | `PkcPassword1` | + +**Database URL:** + +```text +jdbc:oracle:thin:@//YOUR_VM_IP:1521/XEPDB1 +``` + +Replace `YOUR_VM_IP` with the Oracle Linux VM address from VirtualBox/VMware. + +5. **Connect** + +### Find your data + +In the object tree: + +```text +OracleQuant PKC API (Linux VM) + └── PKC_USER + └── Tables + └── MEASUREMENT_HISTORY ← double-click → Data tab → Refresh +``` + +### Sample query + +```sql +SELECT id, timestamp, source_ip_address, input_value, output_value +FROM pkc_user.measurement_history +ORDER BY id DESC; +``` + +--- + +## If connection fails + +| Error | Fix | +|-------|-----| +| Connection refused | Oracle listener not running on VM; open port 1521 | +| ORA-01017 | Wrong password — re-run `oracle-linux-setup.sql` | +| ORA-01045 | User missing grants — re-run `oracle-linux-setup.sql` | +| ORA-12514 | Wrong service — try `FREEPDB1` instead of `XEPDB1` | +| Table empty | Start `pkc-api.jar` with `oracle` profile and call the API | + +--- + +## Oracle 23c Free on Linux + +Use service **FREEPDB1** and run `scripts/fix-pkc-user-FREEPDB1.sql` instead, then: + +```text +jdbc:oracle:thin:@//YOUR_VM_IP:1521/FREEPDB1 +``` diff --git a/dbvis/README-Docker-DbVisualizer.md b/dbvis/README-Docker-DbVisualizer.md new file mode 100644 index 0000000..4631190 --- /dev/null +++ b/dbvis/README-Docker-DbVisualizer.md @@ -0,0 +1,13 @@ +# DbVisualizer → Docker Oracle (same DB as Spring Boot) + +| Setting | Value | +|---------|--------| +| **URL** | `jdbc:oracle:thin:@//localhost:1521/XEPDB1` | +| **User** | `pkc_user` | +| **Password** | `PkcPassword1` | + +**Table:** `MEASUREMENT_HISTORY` under schema `PKC_USER` + +Start **Docker** and **Spring Boot** (`docker` profile) before connecting. + +After `GET http://localhost:8081/convert-measurements?input=aa`, refresh the Data tab. diff --git a/dbvis/README-Mac-DbVisualizer.md b/dbvis/README-Mac-DbVisualizer.md new file mode 100644 index 0000000..e39c03e --- /dev/null +++ b/dbvis/README-Mac-DbVisualizer.md @@ -0,0 +1,81 @@ +# Mac — Spring Boot + DbVisualizer (no Oracle Linux) + +Your app stores data in a **file on your Mac**: + +``` +/Users/mac/IdeaProjects/oraclequantapi/data/pkcdb.mv.db +``` + +Table: **`MEASUREMENT_HISTORY`** + +--- + +## 1) Start Spring Boot on Mac + +**IntelliJ:** Run **Local Mac DbVisualizer** + +**Terminal:** +```bash +cd /Users/mac/IdeaProjects/oraclequantapi +bash scripts/mac-start-local.sh +``` + +Or: +```bash +mvn spring-boot:run -Dspring-boot.run.profiles=local +``` + +Wait for: `Started OraclequantapiApplication` and profile **`local`**. + +--- + +## 2) Send test data (Postman or browser) + +``` +GET http://localhost:8081/convert-measurements?input=aa +GET http://localhost:8081/history +``` + +--- + +## 3) DbVisualizer connection + +1. **Database** → **Create Database Connection** +2. **Driver:** H2 +3. Settings: + +| Field | Value | +|-------|--------| +| **Database URL** | `jdbc:h2:file:/Users/mac/IdeaProjects/oraclequantapi/data/pkcdb;AUTO_SERVER=TRUE` | +| **User** | `sa` | +| **Password** | *(empty)* | + +4. **Connect** (while Spring Boot is running) + +--- + +## 4) Open your table + +``` +Connection → PUBLIC → Tables → MEASUREMENT_HISTORY → Data → Refresh +``` + +SQL: +```sql +SELECT * FROM measurement_history ORDER BY id DESC; +``` + +After each Postman call, **Refresh** — new rows should appear. + +--- + +## Linked? + +| Step | OK? | +|------|-----| +| App runs with profile `local` | ☐ | +| `curl /history` shows data | ☐ | +| DbVisualizer connects to H2 URL above | ☐ | +| New row after API call + Refresh | ☐ | + +All yes = Spring Boot and DbVisualizer share the same database on your Mac. diff --git a/dbvis/connection-oracle-linux.properties b/dbvis/connection-oracle-linux.properties new file mode 100644 index 0000000..d9c3cff --- /dev/null +++ b/dbvis/connection-oracle-linux.properties @@ -0,0 +1,12 @@ +# Copy these values into DbVisualizer → Create Database Connection +# Replace YOUR_VM_IP with your Oracle Linux VM address (e.g. 192.168.56.101) + +ConnectionName=OracleQuant PKC API (Linux VM) +Driver=Oracle Thin +URL=jdbc:oracle:thin:@//YOUR_VM_IP:1521/XEPDB1 +User=pkc_user +Password=PkcPassword1 + +# Your table after setup + API runs: +# Schema: PKC_USER +# Table: MEASUREMENT_HISTORY diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 0000000..96bcca9 --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,25 @@ +# Oracle XE for Spring Boot + DbVisualizer on Mac (Docker Desktop) +# Start: docker compose -f docker/docker-compose.yml up -d +# Stop: docker compose -f docker/docker-compose.yml down + +services: + oracle-xe: + image: container-registry.oracle.com/database/express:21.3.0-xe + container_name: oracle-xe + ports: + - "1521:1521" + - "5500:5500" + environment: + ORACLE_PWD: ${ORACLE_PWD:-29999login} + volumes: + - oracle-data:/opt/oracle/oradata + - ./oracle-init:/opt/oracle/scripts/setup + healthcheck: + test: ["CMD", "healthcheck.sh"] + interval: 30s + timeout: 10s + retries: 10 + start_period: 120s + +volumes: + oracle-data: diff --git a/docker/oracle-init/01-pkc-user.sql b/docker/oracle-init/01-pkc-user.sql new file mode 100644 index 0000000..959f0d6 --- /dev/null +++ b/docker/oracle-init/01-pkc-user.sql @@ -0,0 +1,20 @@ +-- Runs once on first DB creation (Docker setup scripts) +ALTER SESSION SET CONTAINER = XEPDB1; + +CREATE USER pkc_user IDENTIFIED BY PkcPassword1 + DEFAULT TABLESPACE USERS + TEMPORARY TABLESPACE TEMP + QUOTA UNLIMITED ON USERS; + +GRANT CREATE SESSION, CONNECT, RESOURCE TO pkc_user; +GRANT CREATE TABLE, CREATE SEQUENCE, UNLIMITED TABLESPACE TO pkc_user; + +ALTER SESSION SET CURRENT_SCHEMA = pkc_user; + +CREATE TABLE measurement_history ( + id NUMBER(19,0) GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + timestamp TIMESTAMP NOT NULL, + source_ip_address VARCHAR2(255) NOT NULL, + input_value VARCHAR2(4000) NOT NULL, + output_value VARCHAR2(4000) NOT NULL +); diff --git a/docker/setup-pkc-user-grants-only.sql b/docker/setup-pkc-user-grants-only.sql new file mode 100644 index 0000000..a0ba9fa --- /dev/null +++ b/docker/setup-pkc-user-grants-only.sql @@ -0,0 +1,30 @@ +-- Safe re-run: grants + table (no DROP USER) +ALTER SESSION SET CONTAINER = XEPDB1; + +BEGIN + EXECUTE IMMEDIATE 'CREATE USER pkc_user IDENTIFIED BY PkcPassword1 DEFAULT TABLESPACE USERS TEMPORARY TABLESPACE TEMP QUOTA UNLIMITED ON USERS'; +EXCEPTION + WHEN OTHERS THEN + IF SQLCODE != -1920 THEN RAISE; END IF; +END; +/ + +GRANT CREATE SESSION, CONNECT, RESOURCE TO pkc_user; +GRANT CREATE TABLE, CREATE SEQUENCE, UNLIMITED TABLESPACE TO pkc_user; + +ALTER SESSION SET CURRENT_SCHEMA = pkc_user; + +BEGIN + EXECUTE IMMEDIATE 'CREATE TABLE measurement_history ( + id NUMBER(19,0) GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + timestamp TIMESTAMP NOT NULL, + source_ip_address VARCHAR2(255) NOT NULL, + input_value VARCHAR2(4000) NOT NULL, + output_value VARCHAR2(4000) NOT NULL)'; +EXCEPTION + WHEN OTHERS THEN + IF SQLCODE != -955 THEN RAISE; END IF; +END; +/ + +COMMIT; diff --git a/docker/setup-pkc-user-manual.sql b/docker/setup-pkc-user-manual.sql new file mode 100644 index 0000000..5625363 --- /dev/null +++ b/docker/setup-pkc-user-manual.sql @@ -0,0 +1,38 @@ +-- Run if DB already existed before init script (manual fix) +-- docker exec -i oracle-xe sqlplus -s system/29999login@//localhost:1521/XEPDB1 @/tmp/setup.sql + +ALTER SESSION SET CONTAINER = XEPDB1; + +BEGIN + EXECUTE IMMEDIATE 'DROP USER pkc_user CASCADE'; +EXCEPTION + WHEN OTHERS THEN + IF SQLCODE NOT IN (-1918, -01918) THEN RAISE; END IF; +END; +/ + +CREATE USER pkc_user IDENTIFIED BY PkcPassword1 + DEFAULT TABLESPACE USERS TEMPORARY TABLESPACE TEMP QUOTA UNLIMITED ON USERS; + +GRANT CREATE SESSION, CONNECT, RESOURCE TO pkc_user; +GRANT CREATE TABLE, CREATE SEQUENCE, UNLIMITED TABLESPACE TO pkc_user; + +ALTER SESSION SET CURRENT_SCHEMA = pkc_user; + +BEGIN + EXECUTE IMMEDIATE 'DROP TABLE measurement_history CASCADE CONSTRAINTS'; +EXCEPTION + WHEN OTHERS THEN + IF SQLCODE != -942 THEN RAISE; END IF; +END; +/ + +CREATE TABLE measurement_history ( + id NUMBER(19,0) GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + timestamp TIMESTAMP NOT NULL, + source_ip_address VARCHAR2(255) NOT NULL, + input_value VARCHAR2(4000) NOT NULL, + output_value VARCHAR2(4000) NOT NULL +); + +COMMIT; diff --git a/pom.xml b/pom.xml index 20909d2..1b4930f 100644 --- a/pom.xml +++ b/pom.xml @@ -5,36 +5,50 @@ org.springframework.boot spring-boot-starter-parent - 3.5.14 - + 3.2.5 + com.oraclequantapi oraclequantapi - 0.0.1-SNAPSHOT - - - - - - - - - - - - - - - + 1.0 + oraclequantapi + OracleQuant ERP Package Measurement Conversion API + 17 + 17 + 17 + UTF-8 + org.springframework.boot spring-boot-starter-web - + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-validation + + + com.h2database + h2 + runtime + + + com.oracle.database.jdbc + ojdbc11 + runtime + + + com.oracle.database.jdbc + ojdbc11 + 23.3.0.23.09 + org.springframework.boot spring-boot-starter-test @@ -43,10 +57,14 @@ + pkc-api org.springframework.boot spring-boot-maven-plugin + + com.oraclequantapi.oraclequantapi.OraclequantapiApplication + diff --git a/scripts/JdbcPing.class b/scripts/JdbcPing.class new file mode 100644 index 0000000000000000000000000000000000000000..49b1b7b95549bdfcf09f6fdbed166df87ab9eb68 GIT binary patch literal 1544 zcma)6O?TQx6un~u0wKo^sh!ZeDM_3Zh=ahblNR!2W89d4O%K#fPZv33F~^n>5|sw0 z{wG~^n{~QK(=5_m7ySqMDcLlAl89|id~yOun$ep(@80(@^Y4EzUjbOh_bMhZsUV^u ziWtMpK0n~P#qC{vYiC~=k|B26v`u-JVKSTBjw8-c-|y}iWyj@)CCYNow9EH(y|i?z zP^5pUTv}c#>5poywbjzS5LB>*EL68PH*3|-n;-2@!al0iIpI}zO zoQCtbz%UmC=KW&nYp!`9+$OjAu5cNqc7?1uwoS2`j?K`_HjZ=9mab{y?XP76|&XCeM8{$kyZ;@|F41)J7DXPXr4PPL`aK6ubf_xZU{%FcQnt77j zlH#7t)<-l!g+}UBT*egz3mUHC8pF9j{Kr?<#4s^G&0&8>xEsc(g*Owlj1Mv2OZ`rrxP8_$ep4RmYK@bornu!0oOjyoHA2^oD~EckI~4&f!3u0za9f@Fu97Y`u|-Dz~h*DMf`2-v*r_w`$S( z6O_Rw!`1goo~JmWbo zr6d0!pN{;6FBzU9Q;zzExg&#Cj-{jN*mD%p(Z8P&(L5c63O%dXK!e7KK+a{v15rs5 zaE9h75;jja#3GV}PT&zVd_#zvh$UAPeESwR5K&N}DGgaL1&ePngXpo1EP*-xhB)rf h-Ux+!7n8V8^9ihw0uU2Jjh-qV(w9oJ1~$<``hN{3hi3o) literal 0 HcmV?d00001 diff --git a/scripts/docker-setup-db-only.sh b/scripts/docker-setup-db-only.sh new file mode 100755 index 0000000..d8c607b --- /dev/null +++ b/scripts/docker-setup-db-only.sh @@ -0,0 +1,32 @@ +#!/bin/bash +# Create pkc_user + MEASUREMENT_HISTORY in running oracle-xe container +# Uses SYSDBA inside container (no SYSTEM password needed) +set -u + +PROJECT_DIR="$(cd "$(dirname "$0")/.." && pwd)" +CONTAINER="${ORACLE_CONTAINER:-oracle-xe}" + +if ! docker ps --format '{{.Names}}' | grep -q "^${CONTAINER}$"; then + echo "Container ${CONTAINER} is not running." + echo "Start it with:" + echo " cd ~/Desktop/oracle-xe-db-hosting && docker compose up -d" + echo " OR" + echo " cd ${PROJECT_DIR} && docker compose -f docker/docker-compose.yml up -d" + exit 1 +fi + +echo "Setting up pkc_user via SYSDBA in container ${CONTAINER}..." +docker cp "${PROJECT_DIR}/docker/setup-pkc-user-grants-only.sql" "${CONTAINER}:/tmp/setup-pkc-user.sql" + +if docker exec -i "${CONTAINER}" bash -c 'sqlplus -s / as sysdba @/tmp/setup-pkc-user.sql'; then + echo "" + echo "DB setup OK." + echo " DbVisualizer URL: jdbc:oracle:thin:@//localhost:1521/XEPDB1" + echo " User / Password: pkc_user / PkcPassword1" + echo " Table: PKC_USER.MEASUREMENT_HISTORY" +else + echo "" + echo "DB setup failed. Last lines from container:" + docker logs "${CONTAINER}" --tail 20 + exit 1 +fi diff --git a/scripts/docker-start-all.sh b/scripts/docker-start-all.sh new file mode 100755 index 0000000..5991f0f --- /dev/null +++ b/scripts/docker-start-all.sh @@ -0,0 +1,50 @@ +#!/bin/bash +# Mac: Docker Oracle XE + DB user + Spring Boot (profile docker) +# Run from: /Users/mac/IdeaProjects/oraclequantapi +set -u + +PROJECT_DIR="$(cd "$(dirname "$0")/.." && pwd)" +cd "${PROJECT_DIR}" + +CONTAINER="oracle-xe" + +echo "=== [1/4] Oracle XE container ===" +if docker ps -a --format '{{.Names}}' | grep -qx "${CONTAINER}"; then + echo "Container '${CONTAINER}' already exists — using it (no conflict)." + docker start "${CONTAINER}" 2>/dev/null || true +else + echo "Creating new container..." + docker compose -f docker/docker-compose.yml up -d +fi + +if ! docker ps --format '{{.Names}}' | grep -qx "${CONTAINER}"; then + echo "ERROR: ${CONTAINER} is not running. Start Docker Desktop, then:" + echo " cd ~/Desktop/oracle-xe-db-hosting && docker compose up -d" + exit 1 +fi + +echo "=== [2/4] Waiting for Oracle ===" +for i in $(seq 1 60); do + if docker exec "${CONTAINER}" healthcheck.sh >/dev/null 2>&1; then + echo "Oracle is healthy." + break + fi + if nc -z localhost 1521 2>/dev/null; then + echo "Port 1521 is open." + sleep 15 + break + fi + echo " waiting... ($i/60)" + sleep 5 +done + +echo "=== [3/4] Creating pkc_user + MEASUREMENT_HISTORY (SYSDBA — no SYSTEM password) ===" +bash "${PROJECT_DIR}/scripts/docker-setup-db-only.sh" || exit 1 + +echo "=== [4/4] Starting Spring Boot (profile: docker) ===" +echo " API: http://localhost:8081/convert-measurements?input=aa" +echo " DbVisualizer: jdbc:oracle:thin:@//localhost:1521/XEPDB1" +echo " User: pkc_user / PkcPassword1" +echo "" + +mvn spring-boot:run -Dspring-boot.run.profiles=docker diff --git a/scripts/fix-pkc-user-FREEPDB1.sql b/scripts/fix-pkc-user-FREEPDB1.sql new file mode 100644 index 0000000..dc1ed04 --- /dev/null +++ b/scripts/fix-pkc-user-FREEPDB1.sql @@ -0,0 +1,25 @@ +-- Use this file ONLY if XEPDB1 does not exist (Oracle Database 23c Free on Mac). +-- Run as SYSTEM with Service name = FREEPDB1 + +ALTER SESSION SET CONTAINER = FREEPDB1; + +BEGIN + EXECUTE IMMEDIATE 'DROP USER pkc_user CASCADE'; +EXCEPTION + WHEN OTHERS THEN + IF SQLCODE NOT IN (-1918, -01918) THEN + RAISE; + END IF; +END; +/ + +CREATE USER pkc_user IDENTIFIED BY PkcPassword1 + DEFAULT TABLESPACE USERS + TEMPORARY TABLESPACE TEMP + QUOTA UNLIMITED ON USERS; + +GRANT CREATE SESSION, CONNECT, RESOURCE TO pkc_user; +GRANT CREATE TABLE, CREATE SEQUENCE, UNLIMITED TABLESPACE TO pkc_user; + +SELECT privilege FROM dba_sys_privs WHERE grantee = 'PKC_USER'; +COMMIT; diff --git a/scripts/fix-pkc-user-complete.sql b/scripts/fix-pkc-user-complete.sql new file mode 100644 index 0000000..3119964 --- /dev/null +++ b/scripts/fix-pkc-user-complete.sql @@ -0,0 +1,46 @@ +-- ============================================================================= +-- FIX ORA-01045 — run as SYSTEM in SQL Developer +-- Connection: Service name = XEPDB1 (or FREEPDB1 for Oracle 23c Free — see below) +-- Press F5 (Run Script) on the WHOLE file +-- ============================================================================= + +SELECT SYS_CONTEXT('USERENV', 'CON_NAME') AS current_container FROM DUAL; + +ALTER SESSION SET CONTAINER = XEPDB1; + +SELECT SYS_CONTEXT('USERENV', 'CON_NAME') AS after_switch FROM DUAL; + +-- Clean start inside the pluggable database +BEGIN + EXECUTE IMMEDIATE 'DROP USER pkc_user CASCADE'; +EXCEPTION + WHEN OTHERS THEN + IF SQLCODE NOT IN (-1918, -01918) THEN + RAISE; + END IF; +END; +/ + +CREATE USER pkc_user IDENTIFIED BY PkcPassword1 + DEFAULT TABLESPACE USERS + TEMPORARY TABLESPACE TEMP + QUOTA UNLIMITED ON USERS; + +GRANT CREATE SESSION TO pkc_user; +GRANT CONNECT TO pkc_user; +GRANT RESOURCE TO pkc_user; +GRANT CREATE TABLE TO pkc_user; +GRANT CREATE SEQUENCE TO pkc_user; +GRANT UNLIMITED TABLESPACE TO pkc_user; + +-- MUST return at least one row: CREATE SESSION +SELECT privilege +FROM dba_sys_privs +WHERE grantee = 'PKC_USER' +ORDER BY privilege; + +SELECT username, account_status, default_tablespace +FROM dba_users +WHERE username = 'PKC_USER'; + +COMMIT; diff --git a/scripts/fix-pkc-user-grants.sql b/scripts/fix-pkc-user-grants.sql new file mode 100644 index 0000000..fb32dd1 --- /dev/null +++ b/scripts/fix-pkc-user-grants.sql @@ -0,0 +1,16 @@ +-- Run as SYSTEM connected to XEPDB1 (F5 = Run Script) +-- Fixes: ORA-01045 user PKC_USER lacks CREATE SESSION privilege + +ALTER SESSION SET CONTAINER = XEPDB1; + +GRANT CREATE SESSION TO pkc_user; +GRANT CONNECT TO pkc_user; +GRANT RESOURCE TO pkc_user; +GRANT CREATE TABLE TO pkc_user; +GRANT CREATE SEQUENCE TO pkc_user; +GRANT UNLIMITED TABLESPACE TO pkc_user; + +-- Must show OPEN +SELECT username, account_status FROM dba_users WHERE username = 'PKC_USER'; + +COMMIT; diff --git a/scripts/mac-send-jar-to-linux.sh b/scripts/mac-send-jar-to-linux.sh new file mode 100755 index 0000000..a40b6d5 --- /dev/null +++ b/scripts/mac-send-jar-to-linux.sh @@ -0,0 +1,28 @@ +#!/bin/bash +# ============================================================================= +# Run on your MAC — builds JAR and copies to Oracle Linux VM +# Usage: bash scripts/mac-send-jar-to-linux.sh +# bash scripts/mac-send-jar-to-linux.sh alharith@127.0.0.1 +# ============================================================================= + +set -e +PROJECT_DIR="$(cd "$(dirname "$0")/.." && pwd)" +cd "${PROJECT_DIR}" + +# Default: SSH via VirtualBox port-forward (set in VM Network -> Port Forwarding: 2222 -> 22) +VM_SSH_TARGET="${1:-alharith@127.0.0.1}" +VM_SSH_PORT="${VM_SSH_PORT:-2222}" + +echo "Building pkc-api.jar..." +mvn -q clean package -DskipTests + +echo "Copying to Linux (${VM_SSH_TARGET})..." +scp -P "${VM_SSH_PORT}" "${PROJECT_DIR}/target/pkc-api.jar" "${VM_SSH_TARGET}:/home/alharith/pkc-api.jar" + +echo "Copying setup script..." +scp -P "${VM_SSH_PORT}" "${PROJECT_DIR}/scripts/oracle-linux-all-in-one.sh" "${VM_SSH_TARGET}:/home/alharith/" + +echo "" +echo "Done. Now SSH to Linux and run:" +echo " ssh -p ${VM_SSH_PORT} alharith@127.0.0.1" +echo " bash /home/alharith/oracle-linux-all-in-one.sh" diff --git a/scripts/mac-start-docker.sh b/scripts/mac-start-docker.sh new file mode 100755 index 0000000..e8a0d37 --- /dev/null +++ b/scripts/mac-start-docker.sh @@ -0,0 +1,71 @@ +#!/bin/bash +# Mac: Oracle (existing container) + Spring Boot in Docker + DbVisualizer +set -u +PROJECT_DIR="$(cd "$(dirname "$0")/.." && pwd)" +cd "${PROJECT_DIR}" +NETWORK="pkc-net" +CONTAINER_ORACLE="oracle-xe" +CONTAINER_API="pkc-api" + +echo "=== [1/6] Stop old API ===" +bash "${PROJECT_DIR}/scripts/stop-pkc-api.sh" 2>/dev/null || true +docker rm -f "${CONTAINER_API}" 2>/dev/null || true + +echo "=== [2/6] Build JAR ===" +mvn -q clean package -DskipTests +test -f target/pkc-api.jar || { echo "Missing target/pkc-api.jar"; exit 1; } + +echo "=== [3/6] Start Oracle container ===" +if ! docker ps --format '{{.Names}}' | grep -qx "${CONTAINER_ORACLE}"; then + if docker ps -a --format '{{.Names}}' | grep -qx "${CONTAINER_ORACLE}"; then + docker start "${CONTAINER_ORACLE}" + else + echo "Start Oracle first:" + echo " cd ~/Desktop/oracle-xe-db-hosting && docker compose up -d" + exit 1 + fi +fi + +echo "=== [4/6] Docker network ${NETWORK} (so API can reach oracle-xe by name) ===" +docker network create "${NETWORK}" 2>/dev/null || true +docker network connect "${NETWORK}" "${CONTAINER_ORACLE}" 2>/dev/null || true + +echo "=== [5/6] Wait for Oracle + setup pkc_user ===" +for i in $(seq 1 40); do + if docker exec "${CONTAINER_ORACLE}" healthcheck.sh >/dev/null 2>&1; then + echo "Oracle healthy." + break + fi + sleep 5 +done +bash "${PROJECT_DIR}/scripts/docker-setup-db-only.sh" + +echo "=== [6/6] Start API container on ${NETWORK} ===" +docker run -d --name "${CONTAINER_API}" \ + --network "${NETWORK}" \ + -p 8081:8081 \ + -v "${PROJECT_DIR}/target/pkc-api.jar:/app/pkc-api.jar:ro" \ + -e SPRING_PROFILES_ACTIVE=docker-internal \ + eclipse-temurin:17-jre \ + java -jar /app/pkc-api.jar + +echo "Waiting for API..." +for i in $(seq 1 40); do + if curl -sf "http://127.0.0.1:8081/convert-measurements?input=aa" >/dev/null 2>&1; then + echo "" + echo "==============================================" + echo " READY" + echo " Postman: http://127.0.0.1:8081/convert-measurements?input=aa" + echo " DbVisualizer: jdbc:oracle:thin:@//127.0.0.1:1521/XEPDB1" + echo " pkc_user / PkcPassword1" + echo " Table: PKC_USER.MEASUREMENT_HISTORY" + echo " API logs: docker logs -f pkc-api" + echo "==============================================" + exit 0 + fi + sleep 3 +done + +echo "API not ready. Logs:" +docker logs "${CONTAINER_API}" --tail 60 +exit 1 diff --git a/scripts/mac-start-local.sh b/scripts/mac-start-local.sh new file mode 100755 index 0000000..9c2d7f8 --- /dev/null +++ b/scripts/mac-start-local.sh @@ -0,0 +1,4 @@ +#!/bin/bash +# Start pkc-api on Mac with persistent H2 (DbVisualizer can connect) +cd "$(dirname "$0")/.." +mvn -q spring-boot:run -Dspring-boot.run.profiles=local diff --git a/scripts/oracle-linux-all-in-one.sh b/scripts/oracle-linux-all-in-one.sh new file mode 100755 index 0000000..f2fb22e --- /dev/null +++ b/scripts/oracle-linux-all-in-one.sh @@ -0,0 +1,185 @@ +#!/bin/bash +# ============================================================================= +# Oracle Linux — full setup for alharith@AlharithLinux VM +# Run on the VM: bash oracle-linux-all-in-one.sh +# ============================================================================= + +set -u + +LINUX_USER="${LINUX_USER:-alharith}" +HOME_DIR="/home/${LINUX_USER}" +JAR="${HOME_DIR}/pkc-api.jar" +LOG="${HOME_DIR}/pkc-api.out" +ORACLE_SERVICE="${ORACLE_SERVICE:-XEPDB1}" +ORACLE_USER="${ORACLE_USER:-pkc_user}" +ORACLE_PASSWORD="${ORACLE_PASSWORD:-PkcPassword1}" +ORACLE_JDBC_URL="jdbc:oracle:thin:@//localhost:1521/${ORACLE_SERVICE}" +API_PORT="${PORT:-8081}" + +echo "==============================================" +echo " PKC-API Oracle Linux setup" +echo " User: ${LINUX_USER} Service: ${ORACLE_SERVICE}" +echo "==============================================" + +# --- 1) Java --- +if ! command -v java &>/dev/null; then + echo "[1] Installing Java 17..." + sudo dnf install -y java-17-openjdk-devel || sudo yum install -y java-17-openjdk-devel +fi +echo "[1] Java: $(java -version 2>&1 | head -1)" + +# --- 2) JAR --- +if [[ ! -f "${JAR}" ]]; then + echo "[2] ERROR: Missing ${JAR}" + echo " Copy from Mac:" + echo " scp /Users/mac/IdeaProjects/oraclequantapi/target/pkc-api.jar ${LINUX_USER}@:${JAR}" + exit 1 +fi +echo "[2] JAR OK: $(ls -lh "${JAR}" | awk '{print $5, $9}')" + +# --- 3) Oracle listener --- +echo "[3] Checking Oracle listener..." +if command -v lsnrctl &>/dev/null; then + lsnrctl status | head -20 || true +else + echo " lsnrctl not in PATH (Oracle may still run)" +fi + +# --- 4) Database user + table --- +SETUP_SQL="${HOME_DIR}/oracle-linux-setup.sql" +cat > "${SETUP_SQL}" <<'SQL' +ALTER SESSION SET CONTAINER = XEPDB1; + +BEGIN + EXECUTE IMMEDIATE 'DROP USER pkc_user CASCADE'; +EXCEPTION + WHEN OTHERS THEN + IF SQLCODE NOT IN (-1918, -01918) THEN RAISE; END IF; +END; +/ + +CREATE USER pkc_user IDENTIFIED BY PkcPassword1 + DEFAULT TABLESPACE USERS + TEMPORARY TABLESPACE TEMP + QUOTA UNLIMITED ON USERS; + +GRANT CREATE SESSION, CONNECT, RESOURCE TO pkc_user; +GRANT CREATE TABLE, CREATE SEQUENCE, UNLIMITED TABLESPACE TO pkc_user; + +ALTER SESSION SET CURRENT_SCHEMA = pkc_user; + +BEGIN + EXECUTE IMMEDIATE 'DROP TABLE measurement_history CASCADE CONSTRAINTS'; +EXCEPTION + WHEN OTHERS THEN + IF SQLCODE != -942 THEN RAISE; END IF; +END; +/ + +CREATE TABLE measurement_history ( + id NUMBER(19,0) GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + timestamp TIMESTAMP NOT NULL, + source_ip_address VARCHAR2(255) NOT NULL, + input_value VARCHAR2(4000) NOT NULL, + output_value VARCHAR2(4000) NOT NULL +); + +SELECT privilege FROM dba_sys_privs WHERE grantee = 'PKC_USER' ORDER BY privilege; +COMMIT; +SQL + +# Allow FREEPDB1 +if [[ "${ORACLE_SERVICE}" == "FREEPDB1" ]]; then + sed -i 's/XEPDB1/FREEPDB1/g' "${SETUP_SQL}" +fi + +SQLPLUS="" +for p in $(find /opt /u01 /home/oracle 2>/dev/null -name sqlplus -type f 2>/dev/null); do + SQLPLUS="$p" + break +done +[[ -z "${SQLPLUS}" ]] && command -v sqlplus &>/dev/null && SQLPLUS="sqlplus" + +echo "[4] Database setup..." +if [[ -n "${SQLPLUS}" ]]; then + if [[ -z "${ORACLE_SYSTEM_PASSWORD:-}" ]]; then + echo " Enter SYSTEM password for Oracle (install password):" + read -rs ORACLE_SYSTEM_PASSWORD + echo + fi + echo "${ORACLE_SYSTEM_PASSWORD}" | "${SQLPLUS}" -S "system@localhost:1521/${ORACLE_SERVICE}" @"${SETUP_SQL}" \ + && echo " DB setup OK" \ + || echo " DB setup FAILED — run ${SETUP_SQL} manually in SQL Developer as SYSTEM" +else + echo " sqlplus not found — run this file in SQL Developer as SYSTEM@${ORACLE_SERVICE}:" + echo " ${SETUP_SQL}" +fi + +# --- 5) Firewall --- +echo "[5] Opening ports 1521 and ${API_PORT}..." +if command -v firewall-cmd &>/dev/null; then + sudo firewall-cmd --add-port=1521/tcp --permanent 2>/dev/null || true + sudo firewall-cmd --add-port=${API_PORT}/tcp --permanent 2>/dev/null || true + sudo firewall-cmd --reload 2>/dev/null || true +fi + +# --- 6) Stop old app --- +echo "[6] Stopping old pkc-api processes..." +pkill -f "pkc-api.jar" 2>/dev/null || true +sleep 2 + +# --- 7) Start app --- +echo "[7] Starting pkc-api.jar..." +export SPRING_PROFILES_ACTIVE=oracle +export ORACLE_JDBC_URL="${ORACLE_JDBC_URL}" +export ORACLE_USER="${ORACLE_USER}" +export ORACLE_PASSWORD="${ORACLE_PASSWORD}" + +cd "${HOME_DIR}" +nohup java -Dspring.profiles.active=oracle -jar "${JAR}" > "${LOG}" 2>&1 & +APP_PID=$! +echo " PID: ${APP_PID}" +echo " Log: ${LOG}" + +# --- 8) Wait for startup --- +echo "[8] Waiting for port ${API_PORT}..." +for i in $(seq 1 30); do + if ss -tlnp 2>/dev/null | grep -q ":${API_PORT} "; then + echo " Port ${API_PORT} is OPEN" + break + fi + if ! kill -0 "${APP_PID}" 2>/dev/null; then + echo " Process died. Last log lines:" + tail -40 "${LOG}" + exit 1 + fi + sleep 2 +done + +if ! ss -tlnp 2>/dev/null | grep -q ":${API_PORT} "; then + echo " Port ${API_PORT} still closed. Log:" + tail -50 "${LOG}" + echo "" + echo " If you see ORA-01045 or ORA-01017: fix DB with ${SETUP_SQL}" + echo " If ORA-12514: try: ORACLE_SERVICE=FREEPDB1 bash $0" + exit 1 +fi + +# --- 9) Test API --- +echo "[9] Testing API..." +curl -sf "http://localhost:${API_PORT}/convert-measurements?input=aa" && echo "" +curl -sf "http://localhost:${API_PORT}/history" | head -c 500 && echo "" + +echo "" +echo "==============================================" +echo " DONE — API is running on Oracle Linux" +echo "==============================================" +echo " VM test: curl http://localhost:${API_PORT}/convert-measurements?input=aa" +echo " Table: PKC_USER.MEASUREMENT_HISTORY" +echo "" +echo " DbVisualizer on Mac (set VirtualBox port forward 1521 first):" +echo " URL: jdbc:oracle:thin:@//127.0.0.1:1521/${ORACLE_SERVICE}" +echo " User: ${ORACLE_USER}" +echo " Password: ${ORACLE_PASSWORD}" +echo " Tree: PKC_USER -> Tables -> MEASUREMENT_HISTORY" +echo "==============================================" diff --git a/scripts/oracle-linux-setup.sql b/scripts/oracle-linux-setup.sql new file mode 100644 index 0000000..d6f2ba9 --- /dev/null +++ b/scripts/oracle-linux-setup.sql @@ -0,0 +1,60 @@ +-- ============================================================================= +-- Run ON ORACLE LINUX (as SYSTEM) — SQL*Plus or SQL Developer on the VM +-- Service: XEPDB1 (or FREEPDB1 for Oracle 23c Free — change below) +-- Creates: user PKC_USER + table MEASUREMENT_HISTORY (your program data) +-- ============================================================================= + +ALTER SESSION SET CONTAINER = XEPDB1; + +BEGIN + EXECUTE IMMEDIATE 'DROP USER pkc_user CASCADE'; +EXCEPTION + WHEN OTHERS THEN + IF SQLCODE NOT IN (-1918, -01918) THEN + RAISE; + END IF; +END; +/ + +CREATE USER pkc_user IDENTIFIED BY PkcPassword1 + DEFAULT TABLESPACE USERS + TEMPORARY TABLESPACE TEMP + QUOTA UNLIMITED ON USERS; + +GRANT CREATE SESSION, CONNECT, RESOURCE TO pkc_user; +GRANT CREATE TABLE, CREATE SEQUENCE, UNLIMITED TABLESPACE TO pkc_user; + +ALTER SESSION SET CURRENT_SCHEMA = pkc_user; + +-- Your application's history table (Hibernate also maintains this) +BEGIN + EXECUTE IMMEDIATE 'DROP TABLE measurement_history CASCADE CONSTRAINTS'; +EXCEPTION + WHEN OTHERS THEN + IF SQLCODE != -942 THEN + RAISE; + END IF; +END; +/ + +CREATE TABLE measurement_history ( + id NUMBER(19, 0) GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + timestamp TIMESTAMP NOT NULL, + source_ip_address VARCHAR2(255) NOT NULL, + input_value VARCHAR2(4000) NOT NULL, + output_value VARCHAR2(4000) NOT NULL +); + +COMMENT ON TABLE measurement_history IS 'Package conversion API request history (pkc-api)'; + +SELECT 'User OK' AS step, username, account_status +FROM dba_users WHERE username = 'PKC_USER'; + +SELECT 'Privileges' AS step, privilege +FROM dba_sys_privs WHERE grantee = 'PKC_USER' ORDER BY privilege; + +SELECT 'Table OK' AS step, table_name +FROM all_tables +WHERE owner = 'PKC_USER' AND table_name = 'MEASUREMENT_HISTORY'; + +COMMIT; diff --git a/scripts/setup-oracle-xe.sql b/scripts/setup-oracle-xe.sql new file mode 100644 index 0000000..2d8db83 --- /dev/null +++ b/scripts/setup-oracle-xe.sql @@ -0,0 +1,41 @@ +-- ============================================================================= +-- Run in Oracle SQL Developer as SYSTEM (password from your Oracle install) +-- Connection MUST use Service name: XEPDB1 (not just "OracleXE" CDB only) +-- ============================================================================= +-- How to connect in SQL Developer: +-- 1. New Connection → User: SYSTEM, Role: default +-- 2. Connection Type: Basic → Host: localhost, Port: 1521 +-- 3. Service name: XEPDB1 (important!) +-- 4. Test → Connect +-- 5. Open this file → press F5 (Run Script) to run ALL lines +-- ============================================================================= + +ALTER SESSION SET CONTAINER = XEPDB1; + +-- Remove old user if a previous attempt failed halfway +BEGIN + EXECUTE IMMEDIATE 'DROP USER pkc_user CASCADE'; +EXCEPTION + WHEN OTHERS THEN + IF SQLCODE != -1918 THEN + RAISE; + END IF; +END; +/ + +CREATE USER pkc_user IDENTIFIED BY "PkcPassword1" + DEFAULT TABLESPACE USERS + QUOTA UNLIMITED ON USERS; + +GRANT CREATE SESSION TO pkc_user; +GRANT CONNECT TO pkc_user; +GRANT RESOURCE TO pkc_user; +GRANT CREATE TABLE TO pkc_user; +GRANT CREATE SEQUENCE TO pkc_user; + +-- Verify (should show 1 row: OPEN) +SELECT username, account_status, default_tablespace +FROM dba_users +WHERE username = 'PKC_USER'; + +COMMIT; diff --git a/scripts/start-pkc-api-oracle-linux.sh b/scripts/start-pkc-api-oracle-linux.sh new file mode 100755 index 0000000..6eedd6a --- /dev/null +++ b/scripts/start-pkc-api-oracle-linux.sh @@ -0,0 +1,25 @@ +#!/bin/bash +# Run on Oracle Linux VM (same machine as Oracle XE) +set -e +cd "$(dirname "$0")/.." + +export SPRING_PROFILES_ACTIVE=oracle +export ORACLE_JDBC_URL="${ORACLE_JDBC_URL:-jdbc:oracle:thin:@//localhost:1521/XEPDB1}" +export ORACLE_USER="${ORACLE_USER:-pkc_user}" +export ORACLE_PASSWORD="${ORACLE_PASSWORD:-PkcPassword1}" + +JAR="${1:-./pkc-api.jar}" +if [[ ! -f "$JAR" ]]; then + echo "Missing JAR: $JAR" + echo "Usage: ./scripts/start-pkc-api-oracle-linux.sh /path/to/pkc-api.jar" + exit 1 +fi + +echo "Starting pkc-api with Oracle XE..." +echo " URL: $ORACLE_JDBC_URL" +echo " User: $ORACLE_USER" +nohup java -Dspring.profiles.active=oracle \ + -jar "$JAR" > pkc-api.out 2>&1 & +echo "PID: $!" +echo "Log: tail -f pkc-api.out" +echo "API: http://$(hostname -I | awk '{print $1}'):8081/convert-measurements?input=aa" diff --git a/scripts/stop-pkc-api.sh b/scripts/stop-pkc-api.sh new file mode 100755 index 0000000..c1285d0 --- /dev/null +++ b/scripts/stop-pkc-api.sh @@ -0,0 +1,26 @@ +#!/bin/bash +# Stop anything using port 8081 and old pkc-api / spring-boot runs +echo "Stopping processes on port 8081..." +for pid in $(lsof -ti :8081 2>/dev/null); do + echo " kill PID $pid" + kill "$pid" 2>/dev/null || kill -9 "$pid" 2>/dev/null +done + +pkill -f "oraclequantapi.*spring-boot:run" 2>/dev/null || true +pkill -f "pkc-api.jar" 2>/dev/null || true +pkill -f "OraclequantapiApplication" 2>/dev/null || true +docker stop pkc-api 2>/dev/null || true + +sleep 2 +if lsof -i :8081 >/dev/null 2>&1; then + echo "Port 8081 still in use — force kill:" + lsof -ti :8081 | xargs kill -9 2>/dev/null || true +fi + +if lsof -i :8081 >/dev/null 2>&1; then + echo "ERROR: port 8081 still busy" + lsof -i :8081 + exit 1 +fi + +echo "Port 8081 is free. You can start Spring Boot now." diff --git a/src/main/java/com/oraclequantapi/oraclequantapi/OraclequantapiApplication.java b/src/main/java/com/oraclequantapi/oraclequantapi/OraclequantapiApplication.java index 5e28689..9fb794a 100644 --- a/src/main/java/com/oraclequantapi/oraclequantapi/OraclequantapiApplication.java +++ b/src/main/java/com/oraclequantapi/oraclequantapi/OraclequantapiApplication.java @@ -3,10 +3,15 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; + // Application entry point. + // components under this package + @SpringBootApplication public class OraclequantapiApplication { public static void main(String[] args) { + + // Bootstraps Spring context then listens on server port SpringApplication.run(OraclequantapiApplication.class, args); } diff --git a/src/main/java/com/oraclequantapi/oraclequantapi/alharith_alsubhi/packageconversion/controller/HistoryController.java b/src/main/java/com/oraclequantapi/oraclequantapi/alharith_alsubhi/packageconversion/controller/HistoryController.java new file mode 100644 index 0000000..ace47aa --- /dev/null +++ b/src/main/java/com/oraclequantapi/oraclequantapi/alharith_alsubhi/packageconversion/controller/HistoryController.java @@ -0,0 +1,66 @@ +package com.oraclequantapi.oraclequantapi.alharith_alsubhi.packageconversion.controller; + +import com.oraclequantapi.oraclequantapi.alharith_alsubhi.packageconversion.model.HistoryRecord; +import com.oraclequantapi.oraclequantapi.alharith_alsubhi.packageconversion.service.HistoryService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.server.ResponseStatusException; + +import java.sql.SQLOutput; +import java.util.List; + + + // Web layer — CRUD-style REST API for persisted conversion history. +@RestController +@RequestMapping("/history") +public class HistoryController { + + private final HistoryService historyService; + + @Autowired + public HistoryController(HistoryService historyService) { + this.historyService = historyService; + } + + // GET /history — list every saved request (newest order depends on DB default). */ + @GetMapping + public ResponseEntity> getAllHistory() { + List history = historyService.getAllRecords(); + return ResponseEntity.ok(history); + } + + // GET /history/{id} — fetch one record; 404 if id does not exist. */ + @GetMapping("/{id}") + public ResponseEntity getHistoryById(@PathVariable Long id) { + HistoryRecord record = historyService.getRecordById(id) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "History record not found with ID " + id)); + return ResponseEntity.ok(record); + } + + // PUT /history/{id} — replace fields on an existing row (full update). */ + @PutMapping("/{id}") + public ResponseEntity updateHistory( + @PathVariable Long id, + @RequestBody HistoryRecord updatedRecord) { + HistoryRecord saved = historyService.updateRecord(id, updatedRecord); + return ResponseEntity.ok(saved); + } + + // PATCH /history/{id} — change only fields sent in JSON body (partial update). */ + @PatchMapping("/{id}") + public ResponseEntity patchHistory( + @PathVariable Long id, + @RequestBody HistoryRecord partialRecord) { + HistoryRecord saved = historyService.patchRecord(id, partialRecord); + return ResponseEntity.ok(saved); + } + + // DELETE /history — wipe entire history table (204 No Content). */ + @DeleteMapping + public ResponseEntity clearAllHistory() { + historyService.clearHistory(); + return ResponseEntity.noContent().build(); + } +} diff --git a/src/main/java/com/oraclequantapi/oraclequantapi/alharith_alsubhi/packageconversion/controller/MeasurementController.java b/src/main/java/com/oraclequantapi/oraclequantapi/alharith_alsubhi/packageconversion/controller/MeasurementController.java new file mode 100644 index 0000000..6da6d35 --- /dev/null +++ b/src/main/java/com/oraclequantapi/oraclequantapi/alharith_alsubhi/packageconversion/controller/MeasurementController.java @@ -0,0 +1,50 @@ +package com.oraclequantapi.oraclequantapi.alharith_alsubhi.packageconversion.controller; + +import com.oraclequantapi.oraclequantapi.alharith_alsubhi.packageconversion.service.MeasurementService; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + + // Web layer exposes the conversion REST endpoint. +@RestController +public class MeasurementController { + + private final MeasurementService measurementService; + + @Autowired + public MeasurementController(MeasurementService measurementService) { + this.measurementService = measurementService; + } + + // Returns JSON array of package totals + @GetMapping("/convert-measurements") + public ResponseEntity> convertMeasurements( + @RequestParam(name = "input", required = false) String input, + @RequestParam(name = "convert-measurements", required = false) String convertMeasurements, + HttpServletRequest request) { + + // Read query string + String targetInput = input != null ? input : convertMeasurements; + if (targetInput == null) { + targetInput = ""; // Hint: empty input → empty result list, not an error. + } + + // Capture client IP for history + String clientIp = request.getRemoteAddr(); + String xForwardedFor = request.getHeader("X-Forwarded-For"); + if (xForwardedFor != null && !xForwardedFor.isEmpty()) { + clientIp = xForwardedFor.split(",")[0].trim(); + } + + // Delegate: convert, log, and save row in database. + List result = measurementService.convertAndLog(targetInput, clientIp); + + // Return 200 OK + return ResponseEntity.ok(result); + } +} diff --git a/src/main/java/com/oraclequantapi/oraclequantapi/alharith_alsubhi/packageconversion/model/HistoryRecord.java b/src/main/java/com/oraclequantapi/oraclequantapi/alharith_alsubhi/packageconversion/model/HistoryRecord.java new file mode 100644 index 0000000..5cb6924 --- /dev/null +++ b/src/main/java/com/oraclequantapi/oraclequantapi/alharith_alsubhi/packageconversion/model/HistoryRecord.java @@ -0,0 +1,79 @@ +package com.oraclequantapi.oraclequantapi.alharith_alsubhi.packageconversion.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import jakarta.persistence.*; +import java.time.LocalDateTime; + + // Domain and database entity gives one row per conversion request. +@Entity +@Table(name = "MEASUREMENT_HISTORY") +public class HistoryRecord { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; // auto-increment primary key + + @Column(name = "TIMESTAMP", nullable = false) + private LocalDateTime timestamp; // when the request was processed + + @JsonProperty("source_ip_address") + @Column(name = "SOURCE_IP_ADDRESS", nullable = false) + private String sourceIpAddress; + + @Column(name = "INPUT_VALUE", length = 4000, nullable = false) + private String input; // raw query string + + @Column(name = "OUTPUT_VALUE", length = 4000, nullable = false) + private String output; // JSON array as text + + public HistoryRecord() { + } + + // Measurement Service Impl when saving a new history row + public HistoryRecord(LocalDateTime timestamp, String sourceIpAddress, String input, String output) { + this.timestamp = timestamp; + this.sourceIpAddress = sourceIpAddress; + this.input = input; + this.output = output; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public LocalDateTime getTimestamp() { + return timestamp; + } + + public void setTimestamp(LocalDateTime timestamp) { + this.timestamp = timestamp; + } + + public String getSourceIpAddress() { + return sourceIpAddress; + } + + public void setSourceIpAddress(String sourceIpAddress) { + this.sourceIpAddress = sourceIpAddress; + } + + public String getInput() { + return input; + } + + public void setInput(String input) { + this.input = input; + } + + public String getOutput() { + return output; + } + + public void setOutput(String output) { + this.output = output; + } +} diff --git a/src/main/java/com/oraclequantapi/oraclequantapi/alharith_alsubhi/packageconversion/repository/HistoryRepository.java b/src/main/java/com/oraclequantapi/oraclequantapi/alharith_alsubhi/packageconversion/repository/HistoryRepository.java new file mode 100644 index 0000000..e3f8fd8 --- /dev/null +++ b/src/main/java/com/oraclequantapi/oraclequantapi/alharith_alsubhi/packageconversion/repository/HistoryRepository.java @@ -0,0 +1,12 @@ +package com.oraclequantapi.oraclequantapi.alharith_alsubhi.packageconversion.repository; + +import com.oraclequantapi.oraclequantapi.alharith_alsubhi.packageconversion.model.HistoryRecord; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + + // Data access layer — talks to the database. + // JpaRepository provides save, findAll, findById and deleteAll with no SQL code. +@Repository +public interface HistoryRepository extends JpaRepository { + // Long is type of @Id field on HistoryRecord +} diff --git a/src/main/java/com/oraclequantapi/oraclequantapi/alharith_alsubhi/packageconversion/service/ConversionEngine.java b/src/main/java/com/oraclequantapi/oraclequantapi/alharith_alsubhi/packageconversion/service/ConversionEngine.java new file mode 100644 index 0000000..5f6aceb --- /dev/null +++ b/src/main/java/com/oraclequantapi/oraclequantapi/alharith_alsubhi/packageconversion/service/ConversionEngine.java @@ -0,0 +1,70 @@ +package com.oraclequantapi.oraclequantapi.alharith_alsubhi.packageconversion.service; + +import java.util.ArrayList; +import java.util.List; + + // Core algorithm — no Spring dependencies (easy to unit test). + // Two phases for decode string to numbers and group numbers into packages. +public class ConversionEngine { + + // Converts encoded measurement text into package inflow totals. + public static List convert(String input) { + // Guard is null + if (input == null || input.trim().isEmpty()) { + return List.of(); + } + + String cleanInput = input.trim(); + List parsedNumbers = new ArrayList<>(); + int i = 0; + int n = cleanInput.length(); + + // Scan left to right + while (i < n) { + int currentSum = 0; + + while (i < n && Character.toLowerCase(cleanInput.charAt(i)) == 'z') { + currentSum += 26; + i++; + } + + if (i < n) { + char ch = Character.toLowerCase(cleanInput.charAt(i)); + int val = 0; + if (ch == '_') { + val = 0; + } else if (ch >= 'a' && ch <= 'y') { + val = ch - 'a' + 1; + } else { + val = 0; // for unknown chars replace 0 + } + currentSum += val; + i++; + } + parsedNumbers.add(currentSum); + } + + // Build packages + List packageTotals = new ArrayList<>(); + int index = 0; + int numCount = parsedNumbers.size(); + + while (index < numCount) { + int count = parsedNumbers.get(index); // package size (how many values to read) + index++; + + int totalValue = 0; + for (int k = 0; k < count; k++) { + if (index < numCount) { + totalValue += parsedNumbers.get(index); + index++; + } else { + break; + } + } + packageTotals.add(totalValue); + } + + return packageTotals; + } +} diff --git a/src/main/java/com/oraclequantapi/oraclequantapi/alharith_alsubhi/packageconversion/service/HistoryService.java b/src/main/java/com/oraclequantapi/oraclequantapi/alharith_alsubhi/packageconversion/service/HistoryService.java new file mode 100644 index 0000000..a028174 --- /dev/null +++ b/src/main/java/com/oraclequantapi/oraclequantapi/alharith_alsubhi/packageconversion/service/HistoryService.java @@ -0,0 +1,26 @@ +package com.oraclequantapi.oraclequantapi.alharith_alsubhi.packageconversion.service; + +import com.oraclequantapi.oraclequantapi.alharith_alsubhi.packageconversion.model.HistoryRecord; + +import java.util.List; +import java.util.Optional; + + // Service contract for history CRUD. + // HistoryController talks to this interface not the repository directly +public interface HistoryService { + + // All rows + List getAllRecords(); + + // One row by primary key + Optional getRecordById(Long id); + + // Full update + HistoryRecord updateRecord(Long id, HistoryRecord updatedRecord); + + // Partial update + HistoryRecord patchRecord(Long id, HistoryRecord partialRecord); + + // Delete all rows + void clearHistory(); +} diff --git a/src/main/java/com/oraclequantapi/oraclequantapi/alharith_alsubhi/packageconversion/service/HistoryServiceImpl.java b/src/main/java/com/oraclequantapi/oraclequantapi/alharith_alsubhi/packageconversion/service/HistoryServiceImpl.java new file mode 100644 index 0000000..0874941 --- /dev/null +++ b/src/main/java/com/oraclequantapi/oraclequantapi/alharith_alsubhi/packageconversion/service/HistoryServiceImpl.java @@ -0,0 +1,91 @@ +package com.oraclequantapi.oraclequantapi.alharith_alsubhi.packageconversion.service; + +import com.oraclequantapi.oraclequantapi.alharith_alsubhi.packageconversion.model.HistoryRecord; +import com.oraclequantapi.oraclequantapi.alharith_alsubhi.packageconversion.repository.HistoryRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; +import org.springframework.web.server.ResponseStatusException; + +import java.util.List; +import java.util.Optional; + + // Business logic for history endpoints + // Sits between HistoryController and HistoryRepository +@Service +public class HistoryServiceImpl implements HistoryService { + + private static final Logger logger = LoggerFactory.getLogger(HistoryServiceImpl.class); + + private final HistoryRepository historyRepository; + + @Autowired + public HistoryServiceImpl(HistoryRepository historyRepository) { + this.historyRepository = historyRepository; + } + + @Override + public List getAllRecords() { + logger.info("Fetching all history records from database."); + return historyRepository.findAll(); + } + + @Override + public Optional getRecordById(Long id) { + logger.info("Fetching history record by ID: {}", id); + return historyRepository.findById(id); + } + + @Override + public HistoryRecord updateRecord(Long id, HistoryRecord updatedRecord) { + logger.info("Completely updating history record with ID: {}", id); + return historyRepository.findById(id) + .map(existing -> { + + // overwrite each field when provided and others keep old value + existing.setTimestamp(updatedRecord.getTimestamp() != null ? updatedRecord.getTimestamp() : existing.getTimestamp()); + existing.setSourceIpAddress(updatedRecord.getSourceIpAddress() != null ? updatedRecord.getSourceIpAddress() : existing.getSourceIpAddress()); + existing.setInput(updatedRecord.getInput() != null ? updatedRecord.getInput() : existing.getInput()); + existing.setOutput(updatedRecord.getOutput() != null ? updatedRecord.getOutput() : existing.getOutput()); + HistoryRecord saved = historyRepository.save(existing); + logger.debug("Successfully updated history record: {}", id); + return saved; + }) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "History record not found with ID " + id)); + } + + @Override + public HistoryRecord patchRecord(Long id, HistoryRecord partialRecord) { + logger.info("Partially patching history record with ID: {}", id); + return historyRepository.findById(id) + .map(existing -> { + + // only not null fields from JSON body are applied + if (partialRecord.getTimestamp() != null) { + existing.setTimestamp(partialRecord.getTimestamp()); + } + if (partialRecord.getSourceIpAddress() != null) { + existing.setSourceIpAddress(partialRecord.getSourceIpAddress()); + } + if (partialRecord.getInput() != null) { + existing.setInput(partialRecord.getInput()); + } + if (partialRecord.getOutput() != null) { + existing.setOutput(partialRecord.getOutput()); + } + HistoryRecord saved = historyRepository.save(existing); + logger.debug("Successfully patched history record: {}", id); + return saved; + }) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "History record not found with ID " + id)); + } + + @Override + public void clearHistory() { + logger.warn("Clearing all history records from the database!"); + historyRepository.deleteAll(); // removes every row and table structure remains + logger.info("All history records cleared successfully."); + } +} diff --git a/src/main/java/com/oraclequantapi/oraclequantapi/alharith_alsubhi/packageconversion/service/MeasurementService.java b/src/main/java/com/oraclequantapi/oraclequantapi/alharith_alsubhi/packageconversion/service/MeasurementService.java new file mode 100644 index 0000000..6f0f0f2 --- /dev/null +++ b/src/main/java/com/oraclequantapi/oraclequantapi/alharith_alsubhi/packageconversion/service/MeasurementService.java @@ -0,0 +1,13 @@ +package com.oraclequantapi.oraclequantapi.alharith_alsubhi.packageconversion.service; + +import java.util.List; + + + // Service contract for conversion requests + // Interface keeps controller decoupled from MeasurementServiceImpl (polymorphism /testing) + // Run conversion, write logs, and persist one history row + +public interface MeasurementService { + + List convertAndLog(String input, String sourceIpAddress); +} diff --git a/src/main/java/com/oraclequantapi/oraclequantapi/alharith_alsubhi/packageconversion/service/MeasurementServiceImpl.java b/src/main/java/com/oraclequantapi/oraclequantapi/alharith_alsubhi/packageconversion/service/MeasurementServiceImpl.java new file mode 100644 index 0000000..313dd80 --- /dev/null +++ b/src/main/java/com/oraclequantapi/oraclequantapi/alharith_alsubhi/packageconversion/service/MeasurementServiceImpl.java @@ -0,0 +1,64 @@ +package com.oraclequantapi.oraclequantapi.alharith_alsubhi.packageconversion.service; + +import com.oraclequantapi.oraclequantapi.alharith_alsubhi.packageconversion.model.HistoryRecord; +import com.oraclequantapi.oraclequantapi.alharith_alsubhi.packageconversion.repository.HistoryRepository; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.List; + + // Orchestrates conversion and audit trail. +@Service +public class MeasurementServiceImpl implements MeasurementService { + + private static final Logger logger = LoggerFactory.getLogger(MeasurementServiceImpl.class); + + private final HistoryRepository historyRepository; + private final ObjectMapper objectMapper; + + @Autowired + public MeasurementServiceImpl(HistoryRepository historyRepository, ObjectMapper objectMapper) { + this.historyRepository = historyRepository; + this.objectMapper = objectMapper; + } + + @Override + public List convertAndLog(String input, String sourceIpAddress) { + // Log incoming request + logger.info("Incoming measurement conversion request. Source IP: {}, Input: '{}'", sourceIpAddress, input); + + // Run pure conversion logic + List result = ConversionEngine.convert(input); + + // Serialize result as JSON string for the history + String jsonOutput = "[]"; + try { + jsonOutput = objectMapper.writeValueAsString(result); + } catch (Exception e) { + logger.error("Error serializing conversion result to JSON: {}", e.getMessage(), e); + jsonOutput = result.toString(); // fallback so API still returns numbers to client + } + + // Persist history + try { + HistoryRecord record = new HistoryRecord( + LocalDateTime.now(), + sourceIpAddress, + input != null ? input : "", + jsonOutput + ); + historyRepository.save(record); + logger.debug("Successfully saved history record to database for Input: '{}'", input); + } catch (Exception e) { + // conversion still succeeds even if DB save fails + logger.error("Failed to persist history record to database: {}", e.getMessage(), e); + } + + logger.info("Successfully processed conversion request. Output: {}", jsonOutput); + return result; + } +} diff --git a/src/main/resources/application-docker-internal.properties b/src/main/resources/application-docker-internal.properties new file mode 100644 index 0000000..33082bb --- /dev/null +++ b/src/main/resources/application-docker-internal.properties @@ -0,0 +1,14 @@ +# Spring Boot running INSIDE Docker (same network as oracle-xe container) +spring.datasource.url=jdbc:oracle:thin:@//oracle-xe:1521/XEPDB1 +spring.datasource.username=pkc_user +spring.datasource.password=PkcPassword1 +spring.datasource.driver-class-name=oracle.jdbc.OracleDriver + +spring.datasource.hikari.connection-timeout=30000 +spring.datasource.hikari.maximum-pool-size=5 + +spring.jpa.database-platform=org.hibernate.dialect.OracleDialect +spring.jpa.hibernate.ddl-auto=update +spring.jpa.show-sql=false + +spring.h2.console.enabled=false diff --git a/src/main/resources/application-docker-local.properties.example b/src/main/resources/application-docker-local.properties.example new file mode 100644 index 0000000..5815327 --- /dev/null +++ b/src/main/resources/application-docker-local.properties.example @@ -0,0 +1,8 @@ +# cp application-docker-local.properties.example application-docker-local.properties + +ORACLE_JDBC_URL=jdbc:oracle:thin:@//localhost:1521/XEPDB1 +ORACLE_USER=pkc_user +ORACLE_PASSWORD=PkcPassword1 + +# Docker SYSTEM password (only for manual SQL setup script) +# ORACLE_SYSTEM_PASSWORD=29999login diff --git a/src/main/resources/application-docker.properties b/src/main/resources/application-docker.properties new file mode 100644 index 0000000..3372ab6 --- /dev/null +++ b/src/main/resources/application-docker.properties @@ -0,0 +1,24 @@ +# ========================================== +# DOCKER profile — Oracle XE in Docker Desktop + DbVisualizer on Mac +# Start Docker first, then Spring Boot with profile: docker +# ========================================== + +# Use 127.0.0.1 (faster/more reliable than localhost on Mac) +spring.datasource.url=${ORACLE_JDBC_URL:jdbc:oracle:thin:@//127.0.0.1:1521/XEPDB1} +spring.datasource.username=${ORACLE_USER:pkc_user} +spring.datasource.password=${ORACLE_PASSWORD:PkcPassword1} +spring.datasource.driver-class-name=oracle.jdbc.OracleDriver + +# Fail fast if Docker Oracle is not ready (avoids hanging at HikariPool) +spring.datasource.hikari.connection-timeout=20000 +spring.datasource.hikari.maximum-pool-size=5 +spring.datasource.hikari.initialization-fail-timeout=1 + +spring.jpa.database-platform=org.hibernate.dialect.OracleDialect +spring.jpa.hibernate.ddl-auto=update +spring.jpa.show-sql=false +spring.jpa.properties.hibernate.jdbc.time_zone=UTC + +spring.h2.console.enabled=false + +spring.config.import=optional:classpath:application-docker-local.properties diff --git a/src/main/resources/application-h2.properties b/src/main/resources/application-h2.properties new file mode 100644 index 0000000..c9c077a --- /dev/null +++ b/src/main/resources/application-h2.properties @@ -0,0 +1,9 @@ +# In-memory H2 — quick dev and unit tests (data lost on restart) +spring.datasource.url=jdbc:h2:mem:pkcdb;DB_CLOSE_DELAY=-1 +spring.datasource.driver-class-name=org.h2.Driver +spring.datasource.username=sa +spring.datasource.password= +spring.jpa.database-platform=org.hibernate.dialect.H2Dialect +spring.jpa.hibernate.ddl-auto=update +spring.h2.console.enabled=true +spring.h2.console.path=/h2-console diff --git a/src/main/resources/application-local.properties b/src/main/resources/application-local.properties new file mode 100644 index 0000000..7c30a2b --- /dev/null +++ b/src/main/resources/application-local.properties @@ -0,0 +1,16 @@ +# ========================================== +# LOCAL profile — Mac: Spring Boot + DbVisualizer (persistent H2 file DB) +# Run: -Dspring.profiles.active=local +# Data file: ./data/pkcdb.mv.db +# ========================================== + +spring.datasource.url=jdbc:h2:file:./data/pkcdb;AUTO_SERVER=TRUE;DB_CLOSE_DELAY=-1 +spring.datasource.driver-class-name=org.h2.Driver +spring.datasource.username=sa +spring.datasource.password= +spring.jpa.database-platform=org.hibernate.dialect.H2Dialect +spring.jpa.hibernate.ddl-auto=update +spring.jpa.show-sql=false + +spring.h2.console.enabled=true +spring.h2.console.path=/h2-console diff --git a/src/main/resources/application-oracle-local.properties.example b/src/main/resources/application-oracle-local.properties.example new file mode 100644 index 0000000..c2393dc --- /dev/null +++ b/src/main/resources/application-oracle-local.properties.example @@ -0,0 +1,15 @@ +# Copy to application-oracle-local.properties and set your real Oracle password. +# cp application-oracle-local.properties.example application-oracle-local.properties + +# Mac Oracle XE 21c+ (pluggable DB — most common): +# ORACLE_JDBC_URL=jdbc:oracle:thin:@//localhost:1521/XEPDB1 + +# Older Oracle XE (SID XE) — uncomment if XEPDB1 does not work: +# ORACLE_JDBC_URL=jdbc:oracle:thin:@localhost:1521:XE + +ORACLE_USER=pkc_user +ORACLE_PASSWORD=PkcPassword1 + +# Or use SYSTEM while testing (not recommended for production): +# ORACLE_USER=system +# ORACLE_PASSWORD=the_password_you_set_at_install diff --git a/src/main/resources/application-oracle.properties b/src/main/resources/application-oracle.properties new file mode 100644 index 0000000..a5ec84f --- /dev/null +++ b/src/main/resources/application-oracle.properties @@ -0,0 +1,22 @@ +# ========================================== +# Oracle XE profile (Mac or Oracle Linux VM) +# Run: -Dspring.profiles.active=oracle +# On Linux VM: ORACLE_JDBC_URL=jdbc:oracle:thin:@//localhost:1521/XEPDB1 +# DbVisualizer from Mac: use VM IP — see dbvis/README-DbVisualizer.md +# ========================================== + +# XEPDB1 = Oracle 21c XE | FREEPDB1 = Oracle 23c Free on Mac +spring.datasource.url=${ORACLE_JDBC_URL:jdbc:oracle:thin:@//localhost:1521/XEPDB1} +spring.datasource.username=${ORACLE_USER:pkc_user} +spring.datasource.password=${ORACLE_PASSWORD:PkcPassword1} +spring.datasource.driver-class-name=oracle.jdbc.OracleDriver + +spring.jpa.database-platform=org.hibernate.dialect.OracleDialect +spring.jpa.hibernate.ddl-auto=update +spring.jpa.show-sql=true +spring.jpa.properties.hibernate.format_sql=true + +spring.h2.console.enabled=false + +# Optional: override credentials without editing this file (not committed) +spring.config.import=optional:classpath:application-oracle-local.properties diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 99d0060..d1531ce 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1,11 @@ -spring.application.name=oraclequantapi +# ========================================== +# Core — shared by all profiles +# ========================================== +spring.application.name=pkc-api +server.port=${PORT:8081} +logging.config=classpath:logback-spring.xml + +# Default: local = Spring Boot on Mac + H2 file (DbVisualizer H2) +# Full Docker stack: run scripts/mac-start-docker.sh (Oracle + API in containers) +# Profiles: docker-internal (API container), docker (legacy), h2 (tests), oracle (Linux VM) +spring.profiles.default=local diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..5215df8 --- /dev/null +++ b/src/main/resources/logback-spring.xml @@ -0,0 +1,35 @@ + + + + + + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + ${LOG_DIR}/${LOG_FILE}.log + + ${LOG_DIR}/${LOG_FILE}.%d{yyyy-MM-dd}.log.gz + + 7 + true + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + + diff --git a/src/test/java/com/oraclequantapi/oraclequantapi/OraclequantapiApplicationTests.java b/src/test/java/com/oraclequantapi/oraclequantapi/OraclequantapiApplicationTests.java index 2de285b..ca03da1 100644 --- a/src/test/java/com/oraclequantapi/oraclequantapi/OraclequantapiApplicationTests.java +++ b/src/test/java/com/oraclequantapi/oraclequantapi/OraclequantapiApplicationTests.java @@ -2,8 +2,10 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; @SpringBootTest +@ActiveProfiles("h2") class OraclequantapiApplicationTests { @Test diff --git a/src/test/java/com/oraclequantapi/oraclequantapi/alharith_alsubhi/packageconversion/service/ConversionEngineTest.java b/src/test/java/com/oraclequantapi/oraclequantapi/alharith_alsubhi/packageconversion/service/ConversionEngineTest.java new file mode 100644 index 0000000..4b0ad08 --- /dev/null +++ b/src/test/java/com/oraclequantapi/oraclequantapi/alharith_alsubhi/packageconversion/service/ConversionEngineTest.java @@ -0,0 +1,83 @@ +package com.oraclequantapi.oraclequantapi.alharith_alsubhi.packageconversion.service; + +import org.junit.jupiter.api.Test; +import java.util.List; +import static org.junit.jupiter.api.Assertions.*; + +/** + * Verifies ConversionEngine against all PDF example inputs. + * Hint: Run with mvn test — no Spring context needed (fast unit tests). + */ +public class ConversionEngineTest { + + // --- Official evaluation examples (must match auto-grader expected output) --- + + @Test + public void testExample1_aa() { + List result = ConversionEngine.convert("aa"); + assertEquals(List.of(1), result); + } + + @Test + public void testExample2_abbcc() { + List result = ConversionEngine.convert("abbcc"); + assertEquals(List.of(2, 6), result); + } + + @Test + public void testExample3_dz_a_aazzaaa() { + List result = ConversionEngine.convert("dz_a_aazzaaa"); + assertEquals(List.of(28, 53, 1), result); + } + + @Test + public void testExample4_a_() { + List result = ConversionEngine.convert("a_"); + assertEquals(List.of(0), result); + } + + @Test + public void testExample5_abcdabcdab() { + List result = ConversionEngine.convert("abcdabcdab"); + assertEquals(List.of(2, 7, 7), result); + } + + @Test + public void testExample6_abcdabcdab_() { + List result = ConversionEngine.convert("abcdabcdab_"); + assertEquals(List.of(2, 7, 7, 0), result); + } + + @Test + public void testExample7_zdaaaaaaaabaaaaaaaabaaaaaaaabbaa() { + List result = ConversionEngine.convert("zdaaaaaaaabaaaaaaaabaaaaaaaabbaa"); + assertEquals(List.of(34), result); + } + + @Test + public void testExample8_za_a_a_a_a_a_a_a_a_a_a_a_a_azaaa() { + List result = ConversionEngine.convert("za_a_a_a_a_a_a_a_a_a_a_a_a_azaaa"); + assertEquals(List.of(40, 1), result); + } + + // --- Extra edge cases (robustness, not all in PDF) --- + + @Test + public void testEdgeCases() { + // Null and empty strings + assertTrue(ConversionEngine.convert(null).isEmpty()); + assertTrue(ConversionEngine.convert("").isEmpty()); + assertTrue(ConversionEngine.convert(" ").isEmpty()); + + // Case insensitivity + assertEquals(List.of(1), ConversionEngine.convert("AA")); + assertEquals(List.of(2, 6), ConversionEngine.convert("aBBcc")); + + // Truncated package counts/values gracefully handled + // Count is 2, but no values follow (sums available = 0) + assertEquals(List.of(0), ConversionEngine.convert("b")); + + // Count is 2, but only 1 value follows (sums available = 1) + assertEquals(List.of(1), ConversionEngine.convert("ba")); + } +} From a45f171b04e6716dfc98c7be5c18438f416a1502 Mon Sep 17 00:00:00 2001 From: Alharith Ahmed Date: Mon, 25 May 2026 13:14:16 +0400 Subject: [PATCH 2/2] Ready to Push --- .DS_Store | Bin 0 -> 8196 bytes .gitignore | 4 ++++ .run/Docker Oracle DbVisualizer.run.xml | 10 ++++++++++ .run/Local Mac DbVisualizer.run.xml | 10 ++++++++++ .run/Oracle XE.run.xml | 10 ++++++++++ 5 files changed, 34 insertions(+) create mode 100644 .DS_Store create mode 100644 .run/Docker Oracle DbVisualizer.run.xml create mode 100644 .run/Local Mac DbVisualizer.run.xml create mode 100644 .run/Oracle XE.run.xml diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..9aba56d1619c2bdf4e3445835f392e32010d55eb GIT binary patch literal 8196 zcmeHMYitx%6u#fIlo>m~v=%7HWVbG_V6od$D-XeCTVB!%vMt?~V%^;t+L7r@-I?9; zNKK9LZKCm&_>1`b*{IQoF(~@W#8)&8i6j!DCgLA|@dp#oGjnH&U8sqPF`#pkxo6I~ z=bV{y_B;3XOc`TH7PMx@>KS7qT^?1cX}Zer*1hYBAq0|2q9A+5bmnHqoYAalXRpu> z1z`rl41^g7GZ1DV%)qsf0Xnn2A{<@eQZ#vYqu>oV_16LH!w7vR0ettOS8w4cEQpd-!{b; zL`jqemHMMco7>tNqb)5hssQC(bhF>Y?yrHmu`pfb;)z;+P3JFgchpx&Hq z7Dg;*P^rmf%v{0DxcX+(^pr_9&&lO&)7))4hHKbXU)t3Vxg|%_meT!>op;-PXM>7T zaP&Po`7(-n2YDn{wcgWqje2gJy?Wju8cW1^h|=WTg^{HzSGU}LM`Fv4u6w5DDK)in zoxGs8XgEgJ)O*GZ%^mK`I62+Y3~Q*LvTM1<0YlGAHE5K&t;~o%Ra?Jk@l8uqwOqU8 zC0)oG4-^dduo4qR{)`4SCMoZU0~)<#m#ujoHGf5IP*;fJK2P4B*J(oOI8ygPf&e#dmD@x`lX}cdRyVn5oxDL*c1pcP9yu4*$#;rUI%kiJP%sXc zCDjg5`AodmdvIsQad(aCR_T?J>UL4=cZM^0eb*QPuw>CGivABva?fOoX1I3V&&J&i zs@mtd+N^21MnNyGUsS86)E-g!mTVNmDjEcN`Ykx}#aIb*vBWBV8#}3;_3!)~OLZlM zfO-d<%9gXWbf9&!1I%U*v8UNF_AWcYK4M?8Q|t`;iT%nhu;184b_o@bQHv#5iq&XB z9L-pd4cLfH*okiRU@x*b2on};xERNSn7~7L6wlyUJcsA;GG4`Ncmr=@67Sqx_0B{%P{1q zEdlk<1>j?TTBZ1y_i<5+sTbrU*bQF^l#W{ zc9xxE=h+2Rp&AMzSczNFO32=d?S$@5bP>AK=*K<`K*taa+)r3{QN$q}#t}S>NAMUP z#}kC~7w{rp!ZEx;Xn&pXK8d&RHr~O9IDwCG5})|EpThU}0Y8=@xobL-alRbM#Z=xh zZEJ|+CG<}>xvsei{`U1=|G#DC@Bi0zjN!|N83;4**BL-%N2()9Iy3EYUauXc>pr@? z;pt6q>YLEyx8p?pcAV(te;CqzlvF*NL_jAvB?*mx{X@Xt`Exrw|HJeDKYITE0f7W` A>i_@% literal 0 HcmV?d00001 diff --git a/.gitignore b/.gitignore index 667aaef..2deb6e7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,9 @@ HELP.md target/ +logs/ +data/ +src/main/resources/application-oracle-local.properties +src/main/resources/application-docker-local.properties .mvn/wrapper/maven-wrapper.jar !**/src/main/**/target/ !**/src/test/**/target/ diff --git a/.run/Docker Oracle DbVisualizer.run.xml b/.run/Docker Oracle DbVisualizer.run.xml new file mode 100644 index 0000000..98e796e --- /dev/null +++ b/.run/Docker Oracle DbVisualizer.run.xml @@ -0,0 +1,10 @@ + + + + diff --git a/.run/Local Mac DbVisualizer.run.xml b/.run/Local Mac DbVisualizer.run.xml new file mode 100644 index 0000000..93a377a --- /dev/null +++ b/.run/Local Mac DbVisualizer.run.xml @@ -0,0 +1,10 @@ + + + + diff --git a/.run/Oracle XE.run.xml b/.run/Oracle XE.run.xml new file mode 100644 index 0000000..1cb8b9d --- /dev/null +++ b/.run/Oracle XE.run.xml @@ -0,0 +1,10 @@ + + + +