From a56d35e3abfd51b10590c94505dece5d33976067 Mon Sep 17 00:00:00 2001 From: Sulaiman Al-Farsi Date: Thu, 21 May 2026 10:46:39 +0400 Subject: [PATCH 01/16] init project --- .../pkc_api/PackageMeasurementApiApplication.java | 12 ++++++++++++ .../oraclequantapi/OraclequantapiApplication.java | 13 ------------- .../pkc_api}/OraclequantapiApplicationTests.java | 2 +- 3 files changed, 13 insertions(+), 14 deletions(-) create mode 100644 src/main/java/com/example/pkc_api/PackageMeasurementApiApplication.java delete mode 100644 src/main/java/com/oraclequantapi/oraclequantapi/OraclequantapiApplication.java rename src/test/java/com/{oraclequantapi/oraclequantapi => example/pkc_api}/OraclequantapiApplicationTests.java (81%) diff --git a/src/main/java/com/example/pkc_api/PackageMeasurementApiApplication.java b/src/main/java/com/example/pkc_api/PackageMeasurementApiApplication.java new file mode 100644 index 0000000..acc1c39 --- /dev/null +++ b/src/main/java/com/example/pkc_api/PackageMeasurementApiApplication.java @@ -0,0 +1,12 @@ +package com.example.pkc_api; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class PackageMeasurementApiApplication { + + public static void main(String[] args) { + SpringApplication.run(PackageMeasurementApiApplication.class, args); + } +} diff --git a/src/main/java/com/oraclequantapi/oraclequantapi/OraclequantapiApplication.java b/src/main/java/com/oraclequantapi/oraclequantapi/OraclequantapiApplication.java deleted file mode 100644 index 5e28689..0000000 --- a/src/main/java/com/oraclequantapi/oraclequantapi/OraclequantapiApplication.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.oraclequantapi.oraclequantapi; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -@SpringBootApplication -public class OraclequantapiApplication { - - public static void main(String[] args) { - SpringApplication.run(OraclequantapiApplication.class, args); - } - -} diff --git a/src/test/java/com/oraclequantapi/oraclequantapi/OraclequantapiApplicationTests.java b/src/test/java/com/example/pkc_api/OraclequantapiApplicationTests.java similarity index 81% rename from src/test/java/com/oraclequantapi/oraclequantapi/OraclequantapiApplicationTests.java rename to src/test/java/com/example/pkc_api/OraclequantapiApplicationTests.java index 2de285b..a991606 100644 --- a/src/test/java/com/oraclequantapi/oraclequantapi/OraclequantapiApplicationTests.java +++ b/src/test/java/com/example/pkc_api/OraclequantapiApplicationTests.java @@ -1,4 +1,4 @@ -package com.oraclequantapi.oraclequantapi; +package com.example.pkc_api; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; From 5c96d434dd28235b59af61d872e79b027913f05c Mon Sep 17 00:00:00 2001 From: Sulaiman Al-Farsi Date: Thu, 21 May 2026 10:48:10 +0400 Subject: [PATCH 02/16] Add encoded number parser contract --- .../example/pkc_api/parser/ParsedNumber.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 src/main/java/com/example/pkc_api/parser/ParsedNumber.java diff --git a/src/main/java/com/example/pkc_api/parser/ParsedNumber.java b/src/main/java/com/example/pkc_api/parser/ParsedNumber.java new file mode 100644 index 0000000..75e96a1 --- /dev/null +++ b/src/main/java/com/example/pkc_api/parser/ParsedNumber.java @@ -0,0 +1,20 @@ +package com.example.pkc_api.parser; + +public class ParsedNumber { + + private final int value; + private final int nextIndex; + + public ParsedNumber(int value, int nextIndex) { + this.value = value; + this.nextIndex = nextIndex; + } + + public int getValue() { + return value; + } + + public int getNextIndex() { + return nextIndex; + } +} \ No newline at end of file From cfae383a24d3e2a3943e8e3b88501a28ee874479 Mon Sep 17 00:00:00 2001 From: Sulaiman Al-Farsi Date: Thu, 21 May 2026 10:49:10 +0400 Subject: [PATCH 03/16] Add encoded number parser contract --- .../pkc_api/parser/EncodedNumberParser.java | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 src/main/java/com/example/pkc_api/parser/EncodedNumberParser.java diff --git a/src/main/java/com/example/pkc_api/parser/EncodedNumberParser.java b/src/main/java/com/example/pkc_api/parser/EncodedNumberParser.java new file mode 100644 index 0000000..f3ffbf8 --- /dev/null +++ b/src/main/java/com/example/pkc_api/parser/EncodedNumberParser.java @@ -0,0 +1,53 @@ +package com.example.pkc_api.parser; + +import org.springframework.stereotype.Component; + +@Component +public class EncodedNumberParser implements NumberParser { + + @Override + public ParsedNumber parse(String input, int startIndex) { + if (input == null || startIndex >= input.length()) { + return new ParsedNumber(0, startIndex); + } + + char current = input.charAt(startIndex); + + if (isContinuationCharacter(current)) { + return parseMultiCharacterNumber(input, startIndex); + } + + int value = getCharacterValue(current); + return new ParsedNumber(value, startIndex + 1); + } + + private ParsedNumber parseMultiCharacterNumber(String input, int index) { + int total = 0; + + while (index < input.length() && isContinuationCharacter(input.charAt(index))) { + total += 26; + index++; + } + + if (index < input.length()) { + total += getCharacterValue(input.charAt(index)); + index++; + } + + return new ParsedNumber(total, index); + } + + private int getCharacterValue(char character) { + character = Character.toLowerCase(character); + + if (character >= 'a' && character <= 'z') { + return character - 'a' + 1; + } + + return 0; + } + + private boolean isContinuationCharacter(char character) { + return Character.toLowerCase(character) == 'z'; + } +} From dfe0a8610537b909ffa44137a41afdf646cce79f Mon Sep 17 00:00:00 2001 From: Sulaiman Al-Farsi Date: Thu, 21 May 2026 10:50:29 +0400 Subject: [PATCH 04/16] Add measurement converter interface --- .../com/example/pkc_api/service/MeasurementConverter.java | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 src/main/java/com/example/pkc_api/service/MeasurementConverter.java diff --git a/src/main/java/com/example/pkc_api/service/MeasurementConverter.java b/src/main/java/com/example/pkc_api/service/MeasurementConverter.java new file mode 100644 index 0000000..1a0f37b --- /dev/null +++ b/src/main/java/com/example/pkc_api/service/MeasurementConverter.java @@ -0,0 +1,8 @@ +package com.example.pkc_api.service; + +import java.util.List; + +public interface MeasurementConverter { + + List convertMeasurements(String input); +} From bc633ba9232d7ee974c2e544fe01bb54b207338b Mon Sep 17 00:00:00 2001 From: Sulaiman Al-Farsi Date: Thu, 21 May 2026 10:52:30 +0400 Subject: [PATCH 05/16] Add sequence measurement conversion service --- .../pkc_api/service/SequenceService.java | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 src/main/java/com/example/pkc_api/service/SequenceService.java diff --git a/src/main/java/com/example/pkc_api/service/SequenceService.java b/src/main/java/com/example/pkc_api/service/SequenceService.java new file mode 100644 index 0000000..74f3657 --- /dev/null +++ b/src/main/java/com/example/pkc_api/service/SequenceService.java @@ -0,0 +1,54 @@ +package com.example.pkc_api.service; + + +import com.example.pkc_api.parser.NumberParser; +import com.example.pkc_api.parser.ParsedNumber; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; + +@Service +public class SequenceService implements MeasurementConverter { + + private final NumberParser numberParser; + + public SequenceService(NumberParser numberParser) { + this.numberParser = numberParser; + } + + @Override + public List convertMeasurements(String input) { + List result = new ArrayList<>(); + + if (input == null || input.isEmpty()) { + return result; + } + + int index = 0; + + while (index < input.length()) { + ParsedNumber countNumber = numberParser.parse(input, index); + + int count = countNumber.getValue(); + index = countNumber.getNextIndex(); + + int packageTotal = 0; + + for (int i = 0; i < count; i++) { + if (index >= input.length()) { + break; + } + + ParsedNumber measurementNumber = numberParser.parse(input, index); + + packageTotal += measurementNumber.getValue(); + index = measurementNumber.getNextIndex(); + } + + result.add(packageTotal); + } + + return result; + } +} From cc3db5b8b1dda4814b143d07e747697dfe4d9160 Mon Sep 17 00:00:00 2001 From: Sulaiman Al-Farsi Date: Thu, 21 May 2026 10:53:31 +0400 Subject: [PATCH 06/16] Add sequence measurement conversion service --- .../controller/MeasurementController.java | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 src/main/java/com/example/pkc_api/controller/MeasurementController.java diff --git a/src/main/java/com/example/pkc_api/controller/MeasurementController.java b/src/main/java/com/example/pkc_api/controller/MeasurementController.java new file mode 100644 index 0000000..fce1af6 --- /dev/null +++ b/src/main/java/com/example/pkc_api/controller/MeasurementController.java @@ -0,0 +1,38 @@ +package com.example.pkc_api.controller; + + +import com.example.pkc_api.service.HistoryService; +import com.example.pkc_api.service.MeasurementConverter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +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; + +@RestController +public class MeasurementController { + + private static final Logger log = LoggerFactory.getLogger(MeasurementController.class); + + private final MeasurementConverter measurementConverter; + private final HistoryService historyService; + + public MeasurementController(MeasurementConverter measurementConverter, HistoryService historyService) { + this.measurementConverter = measurementConverter; + this.historyService = historyService; + } + + @GetMapping("/convert-measurements") + public List convertMeasurements( + @RequestParam String input + ) { + List output = measurementConverter.convertMeasurements(input); + + historyService.saveHistory(input, output.toString()); + log.info("Converted measurement inputLength={} output={}", input.length(), output); + + return output; + } +} From dbf28370e05cde3c9e3c3af601b00a7f8f6a4826 Mon Sep 17 00:00:00 2001 From: Sulaiman Al-Farsi Date: Thu, 21 May 2026 10:54:19 +0400 Subject: [PATCH 07/16] Add sequence history repository --- .../pkc_api/controller/HistoryController.java | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 src/main/java/com/example/pkc_api/controller/HistoryController.java diff --git a/src/main/java/com/example/pkc_api/controller/HistoryController.java b/src/main/java/com/example/pkc_api/controller/HistoryController.java new file mode 100644 index 0000000..bd202f8 --- /dev/null +++ b/src/main/java/com/example/pkc_api/controller/HistoryController.java @@ -0,0 +1,53 @@ +package com.example.pkc_api.controller; + + +import com.example.pkc_api.dto.UpdateHistoryRequest; +import com.example.pkc_api.entity.SequenceHistory; +import com.example.pkc_api.service.HistoryService; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.UUID; + +@RestController +@RequestMapping("/history") +public class HistoryController { + + private final HistoryService historyService; + + public HistoryController(HistoryService historyService) { + this.historyService = historyService; + } + + @GetMapping + public List getAllHistory() { + return historyService.getAllHistory(); + } + + @GetMapping("/{id}") + public SequenceHistory getHistoryById(@PathVariable UUID id) { + return historyService.getHistoryById(id); + } + + @PatchMapping("/{id}") + public SequenceHistory updateHistory( + @PathVariable UUID id, + @RequestBody UpdateHistoryRequest request + ) { + return historyService.updateHistory(id, request); + } + + @PutMapping("/{id}") + public SequenceHistory replaceHistory( + @PathVariable UUID id, + @RequestBody UpdateHistoryRequest request + ) { + return historyService.updateHistory(id, request); + } + + @DeleteMapping + public String clearHistory() { + historyService.clearHistory(); + return "History cleared successfully."; + } +} From 85859040657435cc58f5f5f15e286318552946ef Mon Sep 17 00:00:00 2001 From: Sulaiman Al-Farsi Date: Thu, 21 May 2026 10:54:56 +0400 Subject: [PATCH 08/16] Add history update request DTO --- .../pkc_api/dto/UpdateHistoryRequest.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 src/main/java/com/example/pkc_api/dto/UpdateHistoryRequest.java diff --git a/src/main/java/com/example/pkc_api/dto/UpdateHistoryRequest.java b/src/main/java/com/example/pkc_api/dto/UpdateHistoryRequest.java new file mode 100644 index 0000000..b9f4c51 --- /dev/null +++ b/src/main/java/com/example/pkc_api/dto/UpdateHistoryRequest.java @@ -0,0 +1,23 @@ +package com.example.pkc_api.dto; + +public class UpdateHistoryRequest { + + private String input; + private String output; + + public String getInput() { + return input; + } + + public String getOutput() { + return output; + } + + public void setInput(String input) { + this.input = input; + } + + public void setOutput(String output) { + this.output = output; + } +} From 99b698583682957d77ddcdaa72f6c51f548ce6bb Mon Sep 17 00:00:00 2001 From: Sulaiman Al-Farsi Date: Thu, 21 May 2026 10:55:25 +0400 Subject: [PATCH 09/16] Add history record not found exception --- .../exception/HistoryRecordNotFoundException.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 src/main/java/com/example/pkc_api/exception/HistoryRecordNotFoundException.java diff --git a/src/main/java/com/example/pkc_api/exception/HistoryRecordNotFoundException.java b/src/main/java/com/example/pkc_api/exception/HistoryRecordNotFoundException.java new file mode 100644 index 0000000..899dbbe --- /dev/null +++ b/src/main/java/com/example/pkc_api/exception/HistoryRecordNotFoundException.java @@ -0,0 +1,14 @@ +package com.example.pkc_api.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +import java.util.UUID; + +@ResponseStatus(HttpStatus.NOT_FOUND) +public class HistoryRecordNotFoundException extends RuntimeException { + + public HistoryRecordNotFoundException(UUID id) { + super("History record not found with id: " + id); + } +} From 9001a6458d52df12bf100d9f2634dc04d174ab82 Mon Sep 17 00:00:00 2001 From: Sulaiman Al-Farsi Date: Thu, 21 May 2026 10:56:00 +0400 Subject: [PATCH 10/16] Add sequence history service --- .../pkc_api/service/HistoryService.java | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 src/main/java/com/example/pkc_api/service/HistoryService.java diff --git a/src/main/java/com/example/pkc_api/service/HistoryService.java b/src/main/java/com/example/pkc_api/service/HistoryService.java new file mode 100644 index 0000000..f1a0a68 --- /dev/null +++ b/src/main/java/com/example/pkc_api/service/HistoryService.java @@ -0,0 +1,69 @@ +package com.example.pkc_api.service; + + +import com.example.pkc_api.dto.UpdateHistoryRequest; +import com.example.pkc_api.entity.SequenceHistory; +import com.example.pkc_api.exception.HistoryRecordNotFoundException; +import com.example.pkc_api.repository.SequenceHistoryRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.UUID; + +@Service +public class HistoryService { + + private static final Logger log = LoggerFactory.getLogger(HistoryService.class); + + private final SequenceHistoryRepository historyRepository; + + public HistoryService(SequenceHistoryRepository historyRepository) { + this.historyRepository = historyRepository; + } + + public SequenceHistory saveHistory(String input, String output) { + SequenceHistory history = new SequenceHistory( + LocalDateTime.now(), + input, + output + ); + + SequenceHistory savedHistory = historyRepository.save(history); + log.info("Saved history record id={}", savedHistory.getId()); + return savedHistory; + } + + public List getAllHistory() { + return historyRepository.findAll(Sort.by(Sort.Direction.ASC, "id")); + } + + public SequenceHistory getHistoryById(UUID id) { + return historyRepository.findById(id) + .orElseThrow(() -> new HistoryRecordNotFoundException(id)); + } + + public SequenceHistory updateHistory(UUID id, UpdateHistoryRequest request) { + SequenceHistory history = getHistoryById(id); + + if (request.getInput() != null) { + history.setInput(request.getInput()); + } + + if (request.getOutput() != null) { + history.setOutput(request.getOutput()); + } + + SequenceHistory savedHistory = historyRepository.save(history); + log.info("Updated history record id={}", id); + return savedHistory; + } + + public void clearHistory() { + historyRepository.deleteAll(); + log.info("Cleared all history records"); + } +} From 2104111a9e8326114bf38f2450d3fbcd56b64145 Mon Sep 17 00:00:00 2001 From: Sulaiman Al-Farsi Date: Thu, 21 May 2026 10:57:58 +0400 Subject: [PATCH 11/16] Add SequenceHistory --- .../pkc_api/entity/SequenceHistory.java | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 src/main/java/com/example/pkc_api/entity/SequenceHistory.java diff --git a/src/main/java/com/example/pkc_api/entity/SequenceHistory.java b/src/main/java/com/example/pkc_api/entity/SequenceHistory.java new file mode 100644 index 0000000..014735b --- /dev/null +++ b/src/main/java/com/example/pkc_api/entity/SequenceHistory.java @@ -0,0 +1,65 @@ +package com.example.pkc_api.entity; + + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; + +import java.time.LocalDateTime; +import java.util.UUID; + +@Entity +@Table(name = "sequence_history") +@SuppressWarnings("unused") +public class SequenceHistory { + + @Id + @Column(nullable = false, updatable = false) + private UUID id = UUID.randomUUID(); + + private LocalDateTime timestamp; + + @Column(length = 2000) + private String input; + + @Column(length = 2000) + private String output; + + public SequenceHistory() { + } + + public SequenceHistory(LocalDateTime timestamp, String input, String output) { + this.timestamp = timestamp; + this.input = input; + this.output = output; + } + + public UUID getId() { + return id; + } + + public LocalDateTime getTimestamp() { + return timestamp; + } + + public String getInput() { + return input; + } + + public String getOutput() { + return output; + } + + public void setTimestamp(LocalDateTime timestamp) { + this.timestamp = timestamp; + } + + public void setInput(String input) { + this.input = input; + } + + public void setOutput(String output) { + this.output = output; + } +} From e07a693959de347dd6e2cf0892b288692e65f9a8 Mon Sep 17 00:00:00 2001 From: Sulaiman Al-Farsi Date: Thu, 21 May 2026 10:58:33 +0400 Subject: [PATCH 12/16] Add SequenceHistoryRepository --- .../pkc_api/repository/SequenceHistoryRepository.java | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 src/main/java/com/example/pkc_api/repository/SequenceHistoryRepository.java diff --git a/src/main/java/com/example/pkc_api/repository/SequenceHistoryRepository.java b/src/main/java/com/example/pkc_api/repository/SequenceHistoryRepository.java new file mode 100644 index 0000000..11292b0 --- /dev/null +++ b/src/main/java/com/example/pkc_api/repository/SequenceHistoryRepository.java @@ -0,0 +1,10 @@ +package com.example.pkc_api.repository; + + +import com.example.pkc_api.entity.SequenceHistory; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.UUID; + +public interface SequenceHistoryRepository extends JpaRepository { +} From c5addc24c399e2841ad0245374fd235b19d172e4 Mon Sep 17 00:00:00 2001 From: Sulaiman Al-Farsi Date: Thu, 21 May 2026 11:00:01 +0400 Subject: [PATCH 13/16] Add NumberParser --- src/main/java/com/example/pkc_api/parser/NumberParser.java | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 src/main/java/com/example/pkc_api/parser/NumberParser.java diff --git a/src/main/java/com/example/pkc_api/parser/NumberParser.java b/src/main/java/com/example/pkc_api/parser/NumberParser.java new file mode 100644 index 0000000..451a6e0 --- /dev/null +++ b/src/main/java/com/example/pkc_api/parser/NumberParser.java @@ -0,0 +1,6 @@ +package com.example.pkc_api.parser; + +public interface NumberParser { + + ParsedNumber parse(String input, int startIndex); +} From 2ff48f209a0d5601a7fbe5a37a4fb83def39a4f6 Mon Sep 17 00:00:00 2001 From: Sulaiman Al-Farsi Date: Thu, 21 May 2026 11:06:40 +0400 Subject: [PATCH 14/16] add dependency --- pom.xml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/pom.xml b/pom.xml index 20909d2..07e40a2 100644 --- a/pom.xml +++ b/pom.xml @@ -35,6 +35,17 @@ spring-boot-starter-web + + org.springframework.boot + spring-boot-starter-data-jpa + + + + com.h2database + h2 + runtime + + org.springframework.boot spring-boot-starter-test From f6429d6e620006b53c5dc371f28619ef6c78a18a Mon Sep 17 00:00:00 2001 From: Sulaiman Al-Farsi Date: Thu, 21 May 2026 12:06:30 +0400 Subject: [PATCH 15/16] add deploy md --- ORACLE_LINUX_DEPLOYMENT.md | 482 +++++++++++++++++++++++++++++++++++++ README.md | 417 +++++++++++++++++++++++++++----- pom.xml | 8 +- 3 files changed, 845 insertions(+), 62 deletions(-) create mode 100644 ORACLE_LINUX_DEPLOYMENT.md diff --git a/ORACLE_LINUX_DEPLOYMENT.md b/ORACLE_LINUX_DEPLOYMENT.md new file mode 100644 index 0000000..a7e5e18 --- /dev/null +++ b/ORACLE_LINUX_DEPLOYMENT.md @@ -0,0 +1,482 @@ +# Deploy `oraclequantapi.jar` on Oracle Linux with Oracle Database + +This guide assumes the JAR is already copied to the Oracle Linux server as: + +```bash +/home/sulaiman/oraclequantapi.jar +``` + +The application will be reachable at: + +```text +http://192.168.100.246:8080 +``` + +Replace usernames, passwords, paths, and database connection details with your real values. + +## Local and free setup + +This guide is for a local/free setup: + +- The Spring Boot app runs on your Oracle Linux machine. +- The database runs on the same Oracle Linux machine, using `localhost`. +- The app is accessed from your local network at `http://192.168.100.246:8080`. +- You do not need Oracle Cloud Infrastructure for this guide. +- You do not need a paid hosting service for this guide. +- Use Oracle Database Express Edition, also called Oracle XE, if you want a free Oracle Database for local development/testing. + +Important: `192.168.100.246` is a private local network IP address. It works inside your home/lab network, but it is not a public internet deployment. + +## 1. SSH into Oracle Linux + +From your local machine: + +```bash +ssh sulaiman@192.168.100.246 +``` + +If you use a private key: + +```bash +ssh -i /path/to/private-key sulaiman@192.168.100.246 +``` + +Confirm the JAR exists: + +```bash +ls -lh /home/sulaiman/oraclequantapi.jar +``` + +Because your terminal shows the file in your current home directory, this command should also work: + +```bash +ls -lh ~/oraclequantapi.jar +``` + +## 2. Install Java 17 + +Spring Boot 3 requires Java 17 or newer. + +```bash +sudo dnf install -y java-17-openjdk +java -version +``` + +Expected result should show Java 17. + +## 3. Important build note for Oracle Database + +The application must include the Oracle JDBC driver if you want to connect to Oracle Database. + +In `pom.xml`, add this dependency before building the JAR: + +```xml + + com.oracle.database.jdbc + ojdbc11 + runtime + +``` + +Then rebuild locally: + +```bash +./mvnw clean package +``` + +On Windows: + +```powershell +.\mvnw.cmd clean package +``` + +Copy the rebuilt JAR to Oracle Linux again: + +```bash +scp target/oraclequantapi-0.0.1-SNAPSHOT.jar sulaiman@192.168.100.246:/home/sulaiman/oraclequantapi.jar +``` + +If the JAR was built before adding the Oracle JDBC dependency, it may start with H2 but fail when configured for Oracle Database. + +## 4. Create Oracle Database user/schema + +Run these steps on the machine where Oracle Database is installed. + +In your case, because you are deploying on Oracle Linux, start from the Oracle Linux terminal after SSH: + +```bash +ssh sulaiman@192.168.100.246 +``` + +You should see a prompt like this: + +```text +[sulaiman@localhost ~]$ +``` + +The `sqlplus` commands below run in the Oracle Linux terminal. The `CREATE USER` and `GRANT` commands run inside SQL*Plus after you connect. + +First, check if SQL*Plus is installed: + +```bash +sqlplus -v +``` + +If you see a SQL*Plus version, continue. + +Connect to Oracle Database as an admin user. For Oracle XE, try: + +```bash +sqlplus system@localhost:1521/XEPDB1 +``` + +It will ask for the `system` password that was set when Oracle Database was installed. + +If that does not work and you have sudo/root access on the database server, try: + +```bash +sudo su - oracle +sqlplus / as sysdba +``` + +After connecting, your prompt will change to: + +```text +SQL> +``` + +From this point, commands are SQL commands. Type them at the `SQL>` prompt. + +If you connected using `sqlplus / as sysdba`, switch to the pluggable database: + +```sql +ALTER SESSION SET CONTAINER = XEPDB1; +``` + +Create the application user. In Oracle, the user is also the schema. + +```sql +CREATE USER ORACLEQUANTAPI IDENTIFIED BY "StrongPassword123"; +``` + +Give the user permission to connect and create tables: + +```sql +GRANT CREATE SESSION TO ORACLEQUANTAPI; +GRANT CREATE TABLE TO ORACLEQUANTAPI; +GRANT CREATE SEQUENCE TO ORACLEQUANTAPI; +GRANT CREATE VIEW TO ORACLEQUANTAPI; +GRANT UNLIMITED TABLESPACE TO ORACLEQUANTAPI; +``` + +Exit SQL*Plus: + +```sql +EXIT; +``` + +Summary of where to type commands: + +- Windows PowerShell: only use this for SSH or copying the JAR with `scp` +- Oracle Linux terminal: use this for `sqlplus`, `java -jar`, `systemctl`, and firewall commands +- SQL*Plus `SQL>` prompt: use this for `CREATE USER`, `GRANT`, `CREATE TABLE`, and `SELECT` + +## 5. Create the application table + +Still on Oracle Linux, connect as the new application user: + +```bash +sqlplus ORACLEQUANTAPI/"StrongPassword123"@localhost:1521/XEPDB1 +``` + +You should now see: + +```text +SQL> +``` + +Create the table by typing this at the `SQL>` prompt: + +```sql +CREATE TABLE sequence_history ( + id RAW(16) PRIMARY KEY, + timestamp TIMESTAMP, + input VARCHAR2(2000), + output VARCHAR2(2000) +); +``` + +Check that the table exists: + +```sql +SELECT table_name FROM user_tables WHERE table_name = 'SEQUENCE_HISTORY'; +``` + +You should see: + +```text +SEQUENCE_HISTORY +``` + +Exit SQL*Plus: + +```sql +EXIT; +``` + +## 6. Configure the app for Oracle Database + +You can configure Spring Boot from environment variables when starting the JAR. + +Use this JDBC URL format: + +```text +jdbc:oracle:thin:@//HOST:PORT/SERVICE_NAME +``` + +Example: + +```bash +export SPRING_DATASOURCE_URL='jdbc:oracle:thin:@//localhost:1521/XEPDB1' +export SPRING_DATASOURCE_USERNAME='ORACLEQUANTAPI' +export SPRING_DATASOURCE_PASSWORD='StrongPassword123' +export SPRING_DATASOURCE_DRIVER_CLASS_NAME='oracle.jdbc.OracleDriver' +export SPRING_JPA_DATABASE_PLATFORM='org.hibernate.dialect.OracleDialect' +export SPRING_JPA_HIBERNATE_DDL_AUTO='validate' +export SPRING_JPA_OPEN_IN_VIEW='false' +``` + +Use `validate` after you create the schema manually. If you want Hibernate to create/update tables automatically during development, use: + +```bash +export SPRING_JPA_HIBERNATE_DDL_AUTO='update' +``` + +For production, prefer `validate` and manage schema changes with SQL migrations. + +## 7. Run the JAR manually + +Start the application: + +```bash +java -jar /home/sulaiman/oraclequantapi.jar +``` + +If you want it to listen specifically on port `8080`: + +```bash +java -jar /home/sulaiman/oraclequantapi.jar --server.port=8080 +``` + +Run it in the background: + +```bash +nohup java -jar /home/sulaiman/oraclequantapi.jar --server.port=8080 > /home/sulaiman/oraclequantapi.log 2>&1 & +``` + +Check logs: + +```bash +tail -f /home/sulaiman/oraclequantapi.log +``` + +Check the process: + +```bash +ps -ef | grep oraclequantapi.jar +``` + +Stop the process: + +```bash +pkill -f oraclequantapi.jar +``` + +## 8. Open port 8080 on Oracle Linux firewall + +Check firewall status: + +```bash +sudo firewall-cmd --state +``` + +Open port `8080`: + +```bash +sudo firewall-cmd --permanent --add-port=8080/tcp +sudo firewall-cmd --reload +sudo firewall-cmd --list-ports +``` + +For this local/free setup, this is normally enough. You only need Oracle Cloud network rules if you later move the server to Oracle Cloud Infrastructure. + +## 9. Test from the server + +On the Oracle Linux server: + +```bash +curl http://localhost:8080 +``` + +Or test your API endpoints directly, for example: + +```bash +curl http://localhost:8080/actuator/health +``` + +If actuator is not enabled, test one of your real controller endpoints instead. + +## 10. Test from another machine + +From your browser or terminal: + +```text +http://192.168.100.246:8080 +``` + +Using curl: + +```bash +curl http://192.168.100.246:8080 +``` + +If it works locally on the server but not from another machine, check: + +- Oracle Linux firewall allows `8080/tcp` +- Your other machine is on the same local network as `192.168.100.246` +- The app is still running +- The IP address is correct +- No other process is using port `8080` + +Cloud network security rules are only needed if this server is later deployed in a cloud VM. They are not required for the local/free setup described here. + +Check port usage: + +```bash +sudo ss -lntp | grep 8080 +``` + +## 11. Run as a systemd service + +Create a service file: + +```bash +sudo vi /etc/systemd/system/oraclequantapi.service +``` + +Paste this: + +```ini +[Unit] +Description=Oracle Quant API +After=network.target + +[Service] +User=sulaiman +WorkingDirectory=/home/sulaiman +ExecStart=/usr/bin/java -jar /home/sulaiman/oraclequantapi.jar --server.port=8080 +Restart=always +RestartSec=10 + +Environment=SPRING_DATASOURCE_URL=jdbc:oracle:thin:@//localhost:1521/XEPDB1 +Environment=SPRING_DATASOURCE_USERNAME=ORACLEQUANTAPI +Environment=SPRING_DATASOURCE_PASSWORD=StrongPassword123 +Environment=SPRING_DATASOURCE_DRIVER_CLASS_NAME=oracle.jdbc.OracleDriver +Environment=SPRING_JPA_DATABASE_PLATFORM=org.hibernate.dialect.OracleDialect +Environment=SPRING_JPA_HIBERNATE_DDL_AUTO=validate +Environment=SPRING_JPA_OPEN_IN_VIEW=false + +[Install] +WantedBy=multi-user.target +``` + +Reload systemd: + +```bash +sudo systemctl daemon-reload +``` + +Start the service: + +```bash +sudo systemctl start oraclequantapi +``` + +Enable it on boot: + +```bash +sudo systemctl enable oraclequantapi +``` + +Check status: + +```bash +sudo systemctl status oraclequantapi +``` + +View logs: + +```bash +journalctl -u oraclequantapi -f +``` + +Restart after replacing the JAR: + +```bash +sudo systemctl restart oraclequantapi +``` + +## 12. Common problems + +### `ClassNotFoundException: oracle.jdbc.OracleDriver` + +The JAR does not include the Oracle JDBC driver. Add `ojdbc11` to `pom.xml`, rebuild, and upload the new JAR. + +### `ORA-01017: invalid username/password` + +The database username or password is wrong. Test manually: + +```bash +sqlplus ORACLEQUANTAPI/"StrongPassword123"@localhost:1521/XEPDB1 +``` + +### `ORA-12514` or `ORA-12154` + +The Oracle service name or connection URL is wrong. Check available services: + +```bash +lsnrctl status +``` + +Use the service name shown by the listener, for example `XEPDB1`. + +### App starts locally but browser cannot access it + +Check that port `8080` is open: + +```bash +sudo firewall-cmd --list-ports +sudo ss -lntp | grep 8080 +``` + +Also check VM or cloud network rules. + +### Port `8080` already in use + +Find the process: + +```bash +sudo ss -lntp | grep 8080 +``` + +Run on a different port: + +```bash +java -jar /home/sulaiman/oraclequantapi.jar --server.port=8081 +``` + +Then access: + +```text +http://192.168.100.246:8081 +``` diff --git a/README.md b/README.md index b1cccfd..32b7b62 100644 --- a/README.md +++ b/README.md @@ -1,61 +1,356 @@ -## 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. +# OracleQuant ERP - Package Measurement Conversion API + +Spring Boot API for converting encoded package measurement strings into total measured inflows per package. + +## Requirements + +- Oracle OpenJDK 17 +- Maven wrapper included in this repository +- Oracle XE Database for production/runtime persistence + +## Build And Test + +```powershell +.\mvnw.cmd test +.\mvnw.cmd package +``` + +The runnable artifact is created at: + +```text +target/pkc-api.jar +``` + +## Oracle XE Configuration + +The application reads database settings from environment variables: + +```bash +export PKC_DB_URL="jdbc:oracle:thin:@localhost:1521/XEPDB1" +export PKC_DB_USERNAME="pkc_user" +export PKC_DB_PASSWORD="pkc_password" +export PKC_JPA_DDL_AUTO="update" +``` + +Create the Oracle user before running the application: + +```sql +CREATE USER pkc_user IDENTIFIED BY pkc_password; +GRANT CONNECT, RESOURCE TO pkc_user; +ALTER USER pkc_user QUOTA UNLIMITED ON USERS; +``` + +## Run Locally + +For quick local Postman testing without Oracle XE, run the application with the `local` profile. This uses an in-memory H2 database: + +```powershell +.\mvnw.cmd spring-boot:run "-Dspring-boot.run.profiles=local" +``` + +Or run the packaged jar with the local profile: + +```powershell +.\mvnw.cmd package +java -jar target/pkc-api.jar --spring.profiles.active=local +``` + +Use the default profile when Oracle XE is running and you want history persisted in Oracle: + +```powershell +$env:PKC_DB_URL="jdbc:oracle:thin:@localhost:1521/XEPDB1" +$env:PKC_DB_USERNAME="pkc_user" +$env:PKC_DB_PASSWORD="pkc_password" +.\mvnw.cmd spring-boot:run +``` + +Or run the packaged jar with Oracle: + +```bash +java -jar target/pkc-api.jar +``` + +The API listens on `http://localhost:8080` by default. Override with `SERVER_PORT`. + +## Conversion API + +`GET /convert-measurements?input={encoded-input}` + +Examples: + +```bash +curl "http://localhost:8080/convert-measurements?input=aa" +# [1] + +curl "http://localhost:8080/convert-measurements?input=abbcc" +# [2,6] + +curl "http://localhost:8080/convert-measurements?input=dz_a_aazzaaa" +# [28,53,1] +``` + +Encoding rules: + +- `a` through `z` represent `1` through `26`. +- Numbers higher than `26` start with one or more `z` characters and end at the first non-`z` character. +- Non-letter characters such as `_` contribute `0` and can terminate a multi-character number. +- Each package starts with a count, followed by that many measured values. The response contains the sum for each package. + +## Postman Test Cases + +Start the application first. If Oracle XE is not running locally, use: + +```powershell +.\mvnw.cmd spring-boot:run "-Dspring-boot.run.profiles=local" +``` + +Create a Postman environment variable: + +```text +base_url = http://localhost:8080 +``` + +Before testing history, clear old records: + +```text +DELETE {{base_url}}/history +``` + +### Conversion Tests + +For each row, create a `GET` request and verify the response body exactly matches the expected JSON array. + +| Case | Method | URL | Expected status | Expected body | +| --- | --- | --- | --- | --- | +| 1 | GET | `{{base_url}}/convert-measurements?input=aa` | `200 OK` | `[1]` | +| 2 | GET | `{{base_url}}/convert-measurements?input=abbcc` | `200 OK` | `[2,6]` | +| 3 | GET | `{{base_url}}/convert-measurements?input=dz_a_aazzaaa` | `200 OK` | `[28,53,1]` | +| 4 | GET | `{{base_url}}/convert-measurements?input=a_` | `200 OK` | `[0]` | +| 5 | GET | `{{base_url}}/convert-measurements?input=abcdabcdab` | `200 OK` | `[2,7,7]` | +| 6 | GET | `{{base_url}}/convert-measurements?input=abcdabcdab_` | `200 OK` | `[2,7,7,0]` | +| 7 | GET | `{{base_url}}/convert-measurements?input=zdaaaaaaaabaaaaaaaabaaaaaaaabbaa` | `200 OK` | `[34]` | +| 8 | GET | `{{base_url}}/convert-measurements?input=za_a_a_a_a_a_a_a_a_a_a_a_a_azaaa` | `200 OK` | `[40,1]` | + +Use this Postman `Tests` script for each conversion request. Change `expected` for each case: + +```javascript +const expected = [1]; + +pm.test("status is 200", function () { + pm.response.to.have.status(200); +}); + +pm.test("response is expected JSON array", function () { + pm.expect(pm.response.json()).to.eql(expected); +}); +``` + +Example for case 3: + +```javascript +const expected = [28, 53, 1]; + +pm.test("status is 200", function () { + pm.response.to.have.status(200); +}); + +pm.test("response is expected JSON array", function () { + pm.expect(pm.response.json()).to.eql(expected); +}); +``` + +### History Tests In Postman + +After running any conversion request, the API saves a history record. + +#### Get All History + +```text +GET {{base_url}}/history +``` + +Expected: + +```json +[ + { + "id": "550e8400-e29b-41d4-a716-446655440000", + "timestamp": "2026-05-20T11:00:00.000000", + "input": "aa", + "output": "[1]" + } +] +``` + +Postman `Tests` script: + +```javascript +pm.test("status is 200", function () { + pm.response.to.have.status(200); +}); + +pm.test("history contains required fields", function () { + const records = pm.response.json(); + pm.expect(records.length).to.be.above(0); + + const record = records[0]; + pm.expect(record).to.have.property("id"); + pm.expect(record).to.have.property("timestamp"); + pm.expect(record).to.have.property("input"); + pm.expect(record).to.have.property("output"); + + pm.collectionVariables.set("history_id", record.id); +}); +``` + +#### Get One History Record + +```text +GET {{base_url}}/history/{{history_id}} +``` + +Postman `Tests` script: + +```javascript +pm.test("status is 200", function () { + pm.response.to.have.status(200); +}); + +pm.test("history record id matches", function () { + const record = pm.response.json(); + pm.expect(record.id).to.eql(pm.collectionVariables.get("history_id")); +}); +``` + +#### Update History Record + +```text +PATCH {{base_url}}/history/{{history_id}} +Content-Type: application/json +``` + +Body: + +```json +{ + "input": "a_", + "output": "[0]" +} +``` + +Postman `Tests` script: + +```javascript +pm.test("status is 200", function () { + pm.response.to.have.status(200); +}); + +pm.test("history record was updated", function () { + const record = pm.response.json(); + pm.expect(record.input).to.eql("a_"); + pm.expect(record.output).to.eql("[0]"); +}); +``` + +#### Clear History + +```text +DELETE {{base_url}}/history +``` + +Postman `Tests` script: + +```javascript +pm.test("status is 200", function () { + pm.response.to.have.status(200); +}); +``` + +Then confirm history is empty: + +```text +GET {{base_url}}/history +``` + +Expected body: + +```json +[] +``` + +## History API + +History records contain `id`, `timestamp`, `input`, and `output`. + +```bash +curl "http://localhost:8080/history" +curl "http://localhost:8080/history/550e8400-e29b-41d4-a716-446655440000" + +curl -X PATCH "http://localhost:8080/history/550e8400-e29b-41d4-a716-446655440000" \ + -H "Content-Type: application/json" \ + -d '{"input":"a_","output":"[0]"}' + +curl -X PUT "http://localhost:8080/history/550e8400-e29b-41d4-a716-446655440000" \ + -H "Content-Type: application/json" \ + -d '{"input":"aa","output":"[1]"}' + +curl -X DELETE "http://localhost:8080/history" +``` + +## Logging + +Logs are written to the console and to rolling files under `logs/`. +The application keeps seven days of compressed log files. + +## Deploy On Oracle Linux Via SSH + +Build the jar locally: + +```powershell +.\mvnw.cmd package +``` + +Copy it to the Oracle Linux host: + +```bash +scp target/pkc-api.jar opc@YOUR_HOST:/home/opc/pkc-api.jar +``` + +SSH into the host and run it: + +```bash +ssh opc@YOUR_HOST +export PKC_DB_URL="jdbc:oracle:thin:@localhost:1521/XEPDB1" +export PKC_DB_USERNAME="pkc_user" +export PKC_DB_PASSWORD="pkc_password" +java -jar /home/opc/pkc-api.jar +``` + +For a long-running service, create `/etc/systemd/system/pkc-api.service`: + +```ini +[Unit] +Description=OracleQuant Package Measurement Conversion API +After=network.target + +[Service] +User=opc +Environment=PKC_DB_URL=jdbc:oracle:thin:@localhost:1521/XEPDB1 +Environment=PKC_DB_USERNAME=pkc_user +Environment=PKC_DB_PASSWORD=pkc_password +ExecStart=/usr/bin/java -jar /home/opc/pkc-api.jar +Restart=always +RestartSec=5 + +[Install] +WantedBy=multi-user.target +``` + +Enable and start: + +```bash +sudo systemctl daemon-reload +sudo systemctl enable pkc-api +sudo systemctl start pkc-api +sudo systemctl status pkc-api +``` diff --git a/pom.xml b/pom.xml index 07e40a2..426ecb6 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.oraclequantapi oraclequantapi - 0.0.1-SNAPSHOT + 0.0.1 @@ -46,6 +46,12 @@ runtime + + com.oracle.database.jdbc + ojdbc11 + runtime + + org.springframework.boot spring-boot-starter-test From f1b5a69ca2ecd3c0028957137a6b6e668c603071 Mon Sep 17 00:00:00 2001 From: Sulaiman Al-Farsi Date: Mon, 25 May 2026 16:15:13 +0400 Subject: [PATCH 16/16] add CORS and update deploy setup --- ORACLE_LINUX_DEPLOYMENT.md | 671 ++++++++++++------ .../example/pkc_api/config/CorsConfig.java | 17 + 2 files changed, 472 insertions(+), 216 deletions(-) create mode 100644 src/main/java/com/example/pkc_api/config/CorsConfig.java diff --git a/ORACLE_LINUX_DEPLOYMENT.md b/ORACLE_LINUX_DEPLOYMENT.md index a7e5e18..df83285 100644 --- a/ORACLE_LINUX_DEPLOYMENT.md +++ b/ORACLE_LINUX_DEPLOYMENT.md @@ -1,167 +1,249 @@ -# Deploy `oraclequantapi.jar` on Oracle Linux with Oracle Database +# OracleQuant API Deployment Guide -This guide assumes the JAR is already copied to the Oracle Linux server as: +This guide explains how to run **OracleQuant API** on **Oracle Linux VirtualBox** and connect it to an **Oracle Database Free Docker container running on Windows**. -```bash -/home/sulaiman/oraclequantapi.jar -``` +This setup is for a **local development / training deployment**, not public production hosting. -The application will be reachable at: +--- -```text -http://192.168.100.246:8080 -``` +## 1. Final Working Setup -Replace usernames, passwords, paths, and database connection details with your real values. +| Part | Machine | Value | +|---|---|---| +| Oracle Database | Windows Docker | `container-registry.oracle.com/database/free:latest` | +| Oracle DB Container Name | Windows | `oracle-db` | +| Oracle DB Port | Windows | `1521` | +| Oracle DB Service Name | Docker Oracle DB | `FREEPDB1` | +| Oracle DB Admin User | Docker Oracle DB | `SYSTEM` | +| Oracle DB Admin Password | Docker Oracle DB | `OracleAdmin7191` | +| App DB User | Oracle DB | `ORACLEQUANTAPI` | +| App DB Password | Oracle DB | `StrongPassword123` | +| Spring Boot JAR | Oracle Linux | `/home/sulaiman/oraclequantapi.jar` | +| API Port | Oracle Linux | `8080` | +| Windows IP | Windows Wi-Fi | `172.20.10.2` | +| Oracle Linux IP | VirtualBox Bridged Adapter | `172.20.10.3` | +| API Base URL | Browser/Postman | `http://172.20.10.3:8080` | -## Local and free setup +> Important: Your IPs can change after restarting Wi-Fi, Windows hotspot, or the VM. Always check them again if something stops working. -This guide is for a local/free setup: +--- -- The Spring Boot app runs on your Oracle Linux machine. -- The database runs on the same Oracle Linux machine, using `localhost`. -- The app is accessed from your local network at `http://192.168.100.246:8080`. -- You do not need Oracle Cloud Infrastructure for this guide. -- You do not need a paid hosting service for this guide. -- Use Oracle Database Express Edition, also called Oracle XE, if you want a free Oracle Database for local development/testing. +## 2. Where to Run Each Command -Important: `192.168.100.246` is a private local network IP address. It works inside your home/lab network, but it is not a public internet deployment. +| Command Type | Run It In | +|---|---| +| `docker ps`, `docker exec`, `docker logs` | Windows CMD | +| `netsh advfirewall ...` | Windows CMD as Administrator | +| `java -jar ...` | Oracle Linux terminal | +| `ip addr` | Oracle Linux terminal | +| `timeout 5 bash -c ...` | Oracle Linux terminal | +| SQL commands | Inside `SQL>` prompt | +| API testing | Windows browser or Postman | -## 1. SSH into Oracle Linux +--- -From your local machine: +## 3. Start Oracle Database on Windows -```bash -ssh sulaiman@192.168.100.246 +Open **Windows CMD**. + +Check Docker: + +```cmd +docker ps ``` -If you use a private key: +If the container already exists but is stopped, start it: -```bash -ssh -i /path/to/private-key sulaiman@192.168.100.246 +```cmd +docker start oracle-db ``` -Confirm the JAR exists: +If the container does not exist, create it: -```bash -ls -lh /home/sulaiman/oraclequantapi.jar +```cmd +docker run -d --name oracle-db -p 1521:1521 -e ORACLE_PWD=OracleAdmin7191 container-registry.oracle.com/database/free:latest ``` -Because your terminal shows the file in your current home directory, this command should also work: +Check that it is running: -```bash -ls -lh ~/oraclequantapi.jar +```cmd +docker ps ``` -## 2. Install Java 17 +Expected: -Spring Boot 3 requires Java 17 or newer. +```text +oracle-db +0.0.0.0:1521->1521/tcp +healthy +``` -```bash -sudo dnf install -y java-17-openjdk -java -version +Check logs: + +```cmd +docker logs -f oracle-db +``` + +Wait until the database is ready. When done, press: + +```text +CTRL + C ``` -Expected result should show Java 17. +This only stops viewing logs. It does not stop the database. -## 3. Important build note for Oracle Database +--- -The application must include the Oracle JDBC driver if you want to connect to Oracle Database. +## 4. Test Oracle Database Inside Docker -In `pom.xml`, add this dependency before building the JAR: +Run in **Windows CMD**: -```xml - - com.oracle.database.jdbc - ojdbc11 - runtime - +```cmd +docker exec -it oracle-db sqlplus system/OracleAdmin7191@localhost:1521/FREEPDB1 ``` -Then rebuild locally: +If connected, you will see: -```bash -./mvnw clean package +```sql +SQL> ``` -On Windows: +Exit: -```powershell -.\mvnw.cmd clean package +```sql +EXIT; ``` -Copy the rebuilt JAR to Oracle Linux again: +--- -```bash -scp target/oraclequantapi-0.0.1-SNAPSHOT.jar sulaiman@192.168.100.246:/home/sulaiman/oraclequantapi.jar +## 5. Open Windows Firewall Port 1521 + +Open **CMD as Administrator**. + +Run: + +```cmd +netsh advfirewall firewall add rule name="Oracle DB 1521" dir=in action=allow protocol=TCP localport=1521 +``` + +Expected: + +```text +Ok. ``` -If the JAR was built before adding the Oracle JDBC dependency, it may start with H2 but fail when configured for Oracle Database. +This allows Oracle Linux to reach the Oracle Database running on Windows Docker. + +--- -## 4. Create Oracle Database user/schema +## 6. Configure VirtualBox Network -Run these steps on the machine where Oracle Database is installed. +In **VirtualBox Manager**: -In your case, because you are deploying on Oracle Linux, start from the Oracle Linux terminal after SSH: +1. Shut down Oracle Linux first: ```bash -ssh sulaiman@192.168.100.246 +sudo poweroff ``` -You should see a prompt like this: +2. Select your Oracle Linux VM. +3. Go to: ```text -[sulaiman@localhost ~]$ +Settings -> Network ``` -The `sqlplus` commands below run in the Oracle Linux terminal. The `CREATE USER` and `GRANT` commands run inside SQL*Plus after you connect. +4. Adapter 1: -First, check if SQL*Plus is installed: +```text +Attached to: Bridged Adapter +Name: Intel(R) Dual Band Wireless-AC 3165 +Cable Connected: checked +``` + +5. Click **OK**. +6. Start Oracle Linux again. + +--- + +## 7. Check Oracle Linux IP + +Run inside **Oracle Linux**: ```bash -sqlplus -v +ip addr ``` -If you see a SQL*Plus version, continue. +Look for `enp0s3`. -Connect to Oracle Database as an admin user. For Oracle XE, try: +Working example: -```bash -sqlplus system@localhost:1521/XEPDB1 +```text +inet 172.20.10.3/28 ``` -It will ask for the `system` password that was set when Oracle Database was installed. +So your Linux API IP is: -If that does not work and you have sudo/root access on the database server, try: +```text +172.20.10.3 +``` + +Your Windows IP from `ipconfig` is: + +```text +172.20.10.2 +``` + +Both must be on the same network: + +```text +Windows: 172.20.10.2 +Oracle Linux: 172.20.10.3 +``` + +--- + +## 8. Test Windows Oracle DB Port From Oracle Linux + +Run this in **Oracle Linux**: ```bash -sudo su - oracle -sqlplus / as sysdba +timeout 5 bash -c ' +PORT OPEN ``` -From this point, commands are SQL commands. Type them at the `SQL>` prompt. +If it says `PORT CLOSED`, check: -If you connected using `sqlplus / as sysdba`, switch to the pluggable database: +1. Docker container is running on Windows: -```sql -ALTER SESSION SET CONTAINER = XEPDB1; +```cmd +docker ps ``` -Create the application user. In Oracle, the user is also the schema. +2. Windows firewall rule exists. +3. VirtualBox network is set to Bridged Adapter. +4. Windows IP is still `172.20.10.2`. -```sql -CREATE USER ORACLEQUANTAPI IDENTIFIED BY "StrongPassword123"; +--- + +## 9. Create Oracle App User + +Run this in **Windows CMD**: + +```cmd +docker exec -it oracle-db sqlplus system/OracleAdmin7191@localhost:1521/FREEPDB1 ``` -Give the user permission to connect and create tables: +Inside `SQL>` run: ```sql +CREATE USER ORACLEQUANTAPI IDENTIFIED BY "StrongPassword123"; + GRANT CREATE SESSION TO ORACLEQUANTAPI; GRANT CREATE TABLE TO ORACLEQUANTAPI; GRANT CREATE SEQUENCE TO ORACLEQUANTAPI; @@ -169,33 +251,25 @@ GRANT CREATE VIEW TO ORACLEQUANTAPI; GRANT UNLIMITED TABLESPACE TO ORACLEQUANTAPI; ``` -Exit SQL*Plus: +If `CREATE USER` says the user already exists, continue with the grants. + +Exit: ```sql EXIT; ``` -Summary of where to type commands: +--- -- Windows PowerShell: only use this for SSH or copying the JAR with `scp` -- Oracle Linux terminal: use this for `sqlplus`, `java -jar`, `systemctl`, and firewall commands -- SQL*Plus `SQL>` prompt: use this for `CREATE USER`, `GRANT`, `CREATE TABLE`, and `SELECT` +## 10. Create Application Table -## 5. Create the application table +Connect as the app user: -Still on Oracle Linux, connect as the new application user: - -```bash -sqlplus ORACLEQUANTAPI/"StrongPassword123"@localhost:1521/XEPDB1 -``` - -You should now see: - -```text -SQL> +```cmd +docker exec -it oracle-db sqlplus ORACLEQUANTAPI/StrongPassword123@localhost:1521/FREEPDB1 ``` -Create the table by typing this at the `SQL>` prompt: +Create the table: ```sql CREATE TABLE sequence_history ( @@ -206,165 +280,202 @@ CREATE TABLE sequence_history ( ); ``` -Check that the table exists: +If it says table already exists, that is okay. + +Check the table: ```sql -SELECT table_name FROM user_tables WHERE table_name = 'SEQUENCE_HISTORY'; +SELECT table_name +FROM user_tables +WHERE table_name = 'SEQUENCE_HISTORY'; ``` -You should see: +Expected: ```text SEQUENCE_HISTORY ``` -Exit SQL*Plus: +Exit: ```sql EXIT; ``` -## 6. Configure the app for Oracle Database +--- -You can configure Spring Boot from environment variables when starting the JAR. +## 11. Copy JAR to Oracle Linux -Use this JDBC URL format: +If the JAR is not already on Oracle Linux, copy it from **Windows CMD or PowerShell**: -```text -jdbc:oracle:thin:@//HOST:PORT/SERVICE_NAME +```cmd +scp "C:\Users\Codeline\Documents\GitHub\oraclequantapi\target\oraclequantapi-0.0.1.jar" sulaiman@172.20.10.3:/home/sulaiman/oraclequantapi.jar ``` -Example: +Check on Oracle Linux: ```bash -export SPRING_DATASOURCE_URL='jdbc:oracle:thin:@//localhost:1521/XEPDB1' +ls -lh /home/sulaiman/oraclequantapi.jar +``` + +--- + +## 12. Check Java 17 on Oracle Linux + +Run: + +```bash +java -version +``` + +If Java is missing: + +```bash +sudo dnf install -y java-17-openjdk +java -version +``` + +--- + +## 13. Run Spring Boot API Manually + +Run this in **Oracle Linux**: + +```bash +export SPRING_DATASOURCE_URL='jdbc:oracle:thin:@//172.20.10.2:1521/FREEPDB1' export SPRING_DATASOURCE_USERNAME='ORACLEQUANTAPI' export SPRING_DATASOURCE_PASSWORD='StrongPassword123' export SPRING_DATASOURCE_DRIVER_CLASS_NAME='oracle.jdbc.OracleDriver' -export SPRING_JPA_DATABASE_PLATFORM='org.hibernate.dialect.OracleDialect' export SPRING_JPA_HIBERNATE_DDL_AUTO='validate' export SPRING_JPA_OPEN_IN_VIEW='false' + +java -jar /home/sulaiman/oraclequantapi.jar --server.address=0.0.0.0 --server.port=8080 ``` -Use `validate` after you create the schema manually. If you want Hibernate to create/update tables automatically during development, use: +Successful startup should include something like: -```bash -export SPRING_JPA_HIBERNATE_DDL_AUTO='update' +```text +HikariPool-1 - Added connection +Tomcat started on port 8080 +Started PackageMeasurementApiApplication ``` -For production, prefer `validate` and manage schema changes with SQL migrations. +Keep this terminal open while testing. -## 7. Run the JAR manually +--- -Start the application: +## 14. Open Oracle Linux Firewall Port 8080 + +In another Oracle Linux terminal: ```bash -java -jar /home/sulaiman/oraclequantapi.jar +sudo firewall-cmd --permanent --add-port=8080/tcp +sudo firewall-cmd --reload +sudo firewall-cmd --list-ports ``` -If you want it to listen specifically on port `8080`: +--- -```bash -java -jar /home/sulaiman/oraclequantapi.jar --server.port=8080 -``` +## 15. Test API From Windows -Run it in the background: +Open browser or Postman on Windows. -```bash -nohup java -jar /home/sulaiman/oraclequantapi.jar --server.port=8080 > /home/sulaiman/oraclequantapi.log 2>&1 & +Base URL: + +```text +http://172.20.10.3:8080 ``` -Check logs: +Test history: -```bash -tail -f /home/sulaiman/oraclequantapi.log +```text +http://172.20.10.3:8080/history ``` -Check the process: +Test conversion: -```bash -ps -ef | grep oraclequantapi.jar +```text +http://172.20.10.3:8080/convert-measurements?input=1kg ``` -Stop the process: +--- -```bash -pkill -f oraclequantapi.jar -``` +## 16. Check Saved Data in Oracle Database + +Run in **Windows CMD**: -## 8. Open port 8080 on Oracle Linux firewall +```cmd +docker exec -it oracle-db sqlplus ORACLEQUANTAPI/StrongPassword123@localhost:1521/FREEPDB1 +``` -Check firewall status: +Inside `SQL>`: -```bash -sudo firewall-cmd --state +```sql +SELECT * FROM sequence_history ORDER BY timestamp DESC; ``` -Open port `8080`: +Exit: -```bash -sudo firewall-cmd --permanent --add-port=8080/tcp -sudo firewall-cmd --reload -sudo firewall-cmd --list-ports +```sql +EXIT; ``` -For this local/free setup, this is normally enough. You only need Oracle Cloud network rules if you later move the server to Oracle Cloud Infrastructure. +--- -## 9. Test from the server +## 17. Run API in Background Temporarily -On the Oracle Linux server: +For quick background testing on Oracle Linux: ```bash -curl http://localhost:8080 +nohup java -jar /home/sulaiman/oraclequantapi.jar --server.address=0.0.0.0 --server.port=8080 > /home/sulaiman/oraclequantapi.log 2>&1 & ``` -Or test your API endpoints directly, for example: +Check logs: ```bash -curl http://localhost:8080/actuator/health +tail -f /home/sulaiman/oraclequantapi.log ``` -If actuator is not enabled, test one of your real controller endpoints instead. +Stop it: -## 10. Test from another machine +```bash +pkill -f oraclequantapi.jar +``` -From your browser or terminal: +--- -```text -http://192.168.100.246:8080 -``` +## 18. Run API as a systemd Service -Using curl: +Create environment file: ```bash -curl http://192.168.100.246:8080 +sudo vi /etc/oraclequantapi.env ``` -If it works locally on the server but not from another machine, check: - -- Oracle Linux firewall allows `8080/tcp` -- Your other machine is on the same local network as `192.168.100.246` -- The app is still running -- The IP address is correct -- No other process is using port `8080` +Paste: -Cloud network security rules are only needed if this server is later deployed in a cloud VM. They are not required for the local/free setup described here. +```bash +SPRING_DATASOURCE_URL=jdbc:oracle:thin:@//172.20.10.2:1521/FREEPDB1 +SPRING_DATASOURCE_USERNAME=ORACLEQUANTAPI +SPRING_DATASOURCE_PASSWORD=StrongPassword123 +SPRING_DATASOURCE_DRIVER_CLASS_NAME=oracle.jdbc.OracleDriver +SPRING_JPA_HIBERNATE_DDL_AUTO=validate +SPRING_JPA_OPEN_IN_VIEW=false +``` -Check port usage: +Protect it: ```bash -sudo ss -lntp | grep 8080 +sudo chmod 600 /etc/oraclequantapi.env ``` -## 11. Run as a systemd service - -Create a service file: +Create service file: ```bash sudo vi /etc/systemd/system/oraclequantapi.service ``` -Paste this: +Paste: ```ini [Unit] @@ -374,37 +485,20 @@ After=network.target [Service] User=sulaiman WorkingDirectory=/home/sulaiman -ExecStart=/usr/bin/java -jar /home/sulaiman/oraclequantapi.jar --server.port=8080 +EnvironmentFile=/etc/oraclequantapi.env +ExecStart=/usr/bin/java -jar /home/sulaiman/oraclequantapi.jar --server.address=0.0.0.0 --server.port=8080 Restart=always RestartSec=10 -Environment=SPRING_DATASOURCE_URL=jdbc:oracle:thin:@//localhost:1521/XEPDB1 -Environment=SPRING_DATASOURCE_USERNAME=ORACLEQUANTAPI -Environment=SPRING_DATASOURCE_PASSWORD=StrongPassword123 -Environment=SPRING_DATASOURCE_DRIVER_CLASS_NAME=oracle.jdbc.OracleDriver -Environment=SPRING_JPA_DATABASE_PLATFORM=org.hibernate.dialect.OracleDialect -Environment=SPRING_JPA_HIBERNATE_DDL_AUTO=validate -Environment=SPRING_JPA_OPEN_IN_VIEW=false - [Install] WantedBy=multi-user.target ``` -Reload systemd: +Start service: ```bash sudo systemctl daemon-reload -``` - -Start the service: - -```bash sudo systemctl start oraclequantapi -``` - -Enable it on boot: - -```bash sudo systemctl enable oraclequantapi ``` @@ -420,63 +514,208 @@ View logs: journalctl -u oraclequantapi -f ``` -Restart after replacing the JAR: +Restart after replacing JAR: ```bash sudo systemctl restart oraclequantapi ``` -## 12. Common problems +Stop service: -### `ClassNotFoundException: oracle.jdbc.OracleDriver` +```bash +sudo systemctl stop oraclequantapi +``` + +--- + +## 19. Daily Start Checklist -The JAR does not include the Oracle JDBC driver. Add `ojdbc11` to `pom.xml`, rebuild, and upload the new JAR. +### On Windows -### `ORA-01017: invalid username/password` +Start Docker Desktop. -The database username or password is wrong. Test manually: +Check Oracle DB: + +```cmd +docker ps +``` + +If stopped: + +```cmd +docker start oracle-db +``` + +### On Oracle Linux + +Check IP: ```bash -sqlplus ORACLEQUANTAPI/"StrongPassword123"@localhost:1521/XEPDB1 +ip addr ``` -### `ORA-12514` or `ORA-12154` +Test DB port: -The Oracle service name or connection URL is wrong. Check available services: +```bash +timeout 5 bash -c ' +``` + +Not from: + +```text +[sulaiman@localhost ~]$ +``` -Check that port `8080` is open: +--- + +### Error: `NoRouteToHostException` + +Linux cannot reach Windows DB port. + +Fix: + +1. Use Bridged Adapter in VirtualBox. +2. Make sure Windows and Linux are on same network. +3. Open Windows firewall port `1521`. +4. Test: ```bash -sudo firewall-cmd --list-ports -sudo ss -lntp | grep 8080 +timeout 5 bash -c '