From 5447f41d8ea442bc6c23a74292b4c008efe0f4f2 Mon Sep 17 00:00:00 2001 From: RimaAhmed1 Date: Mon, 25 May 2026 23:54:53 +0400 Subject: [PATCH] OracleQuant ERP - LIVE API --- CHANGELOG.md | 57 +++++++ README.md | 135 +++++++++-------- pom.xml | 10 +- .../controllers/SequenceController.java | 139 ++++++++++++++++++ .../oraclequantapi/models/Sequence.java | 45 ++++++ .../models/SequenceHistory.java | 73 +++++++++ .../SequenceHistoryRepository.java | 10 ++ .../services/SequenceService.java | 122 +++++++++++++++ src/main/resources/application.properties | 11 ++ 9 files changed, 540 insertions(+), 62 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 src/main/java/com/oraclequantapi/oraclequantapi/controllers/SequenceController.java create mode 100644 src/main/java/com/oraclequantapi/oraclequantapi/models/Sequence.java create mode 100644 src/main/java/com/oraclequantapi/oraclequantapi/models/SequenceHistory.java create mode 100644 src/main/java/com/oraclequantapi/oraclequantapi/repositories/SequenceHistoryRepository.java create mode 100644 src/main/java/com/oraclequantapi/oraclequantapi/services/SequenceService.java diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..5f000d3 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,57 @@ + +--- + +# CHANGELOG.md + +```markdown id="changelog_final" +# CHANGELOG + +--- + +## [1.0.0] - 2026-05-25 + +### Added +- Implemented full Sequence Processing REST API using Spring Boot. +- Added `/sequences/convert-measurements` endpoint for stream-based sequence computation. +- Introduced stateful streaming algorithm for dynamic grouping of input characters. +- Added Oracle Database persistence layer (SEQUENCE_HISTORY table). +- Implemented full history management (GET, PUT, DELETE operations). +- Added SLF4J logging for request tracing and debugging. +- Added input validation for allowed characters (a-z and underscore `_`). + +### Changed +- Refactored architecture to a clean layered design (Controller → Service → Repository). +- Updated processing logic to support dynamic stream traversal instead of static parsing. +- Improved grouping logic to support runtime state transitions. + +### Fixed +- Fixed schema mismatch issues between Oracle CLOB and entity mappings. +- Resolved Spring Boot ApplicationContext startup issues caused by schema validation conflicts. +- Fixed history persistence inconsistencies in output formatting. + +--- + +## [0.2.0] - 2026-05-24 + +### Added +- Introduced sequence processing service layer. +- Added Oracle JPA repository integration. +- Enabled automatic persistence of conversion results into database. + +--- + +## [0.1.0] - 2026-05-23 + +### Added +- Initial implementation of REST API. +- Basic sequence processing logic. +- Simple endpoint for conversion testing. + +--- + +## [0.0.1] - 2026-05-21 + +### Added +- Project initialization using Spring Boot. +- Maven configuration setup. +- Base REST controller structure created. \ No newline at end of file diff --git a/README.md b/README.md index b1cccfd..9ecea13 100644 --- a/README.md +++ b/README.md @@ -1,61 +1,74 @@ -## 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. +# Oracle Quant Sequence API + +## Overview +This is a Spring Boot REST API that processes input character sequences using a custom stateful streaming algorithm. + +The system converts sequences into numeric package results, applies dynamic grouping logic based on control characters, and persists all transactions into an Oracle Database. + +--- + +## Core Concept +The algorithm processes input as a **stream of characters**, not static chunks. + +- The character `z` acts as a **control/boundary modifier** +- The character `_` is treated as **neutral (value = 0)** +- Processing is based on **stateful traversal and lookahead accumulation** +- Groups are dynamically formed during runtime execution + +--- + +## Tech Stack +- Java 17+ +- Spring Boot +- Spring Web +- Spring Data JPA +- Oracle Database (XE / XEPDB1) +- SLF4J Logging +- Maven + +--- + +## Base URL + + +http://localhost:8080/sequences + + +--- + +## API Endpoints + +### 1. Convert Measurements +Processes a sequence and returns computed package totals. + + +GET /sequences/convert-measurements?input=abbcc + + +**Example Response** +```json +[2, 6] +2. Get All History +GET /sequences/history +3. Get History By ID +GET /sequences/history/{id} +4. Update History +PUT /sequences/history/{id} + +Body + +{ + "input": "abc", + "output": "[2,6]", + "sourceIpAddress": "127.0.0.1" +} +5. Delete All History +DELETE /sequences/history +6. Delete History By ID +DELETE /sequences/history/{id} + # Algorithm Behavior +Input is processed as a continuous stream +z acts as a state modifier / boundary controller +_ contributes value 0 without breaking flow +Output is generated using dynamic grouping + lookahead accumulation +Final result is a list of computed package totals \ No newline at end of file diff --git a/pom.xml b/pom.xml index 20909d2..cdd8302 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,15 @@ org.springframework.boot spring-boot-starter-web - + + org.springframework.boot + spring-boot-starter-data-jpa + + + com.oracle.database.jdbc + ojdbc11 + runtime + org.springframework.boot spring-boot-starter-test diff --git a/src/main/java/com/oraclequantapi/oraclequantapi/controllers/SequenceController.java b/src/main/java/com/oraclequantapi/oraclequantapi/controllers/SequenceController.java new file mode 100644 index 0000000..c813ffb --- /dev/null +++ b/src/main/java/com/oraclequantapi/oraclequantapi/controllers/SequenceController.java @@ -0,0 +1,139 @@ +package com.oraclequantapi.oraclequantapi.controllers; +import com.oraclequantapi.oraclequantapi.models.Sequence; +import com.oraclequantapi.oraclequantapi.models.SequenceHistory; +import com.oraclequantapi.oraclequantapi.services.SequenceService; +import jakarta.servlet.http.HttpServletRequest; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/sequences") +public class SequenceController { + private static final Logger log = LoggerFactory.getLogger(SequenceController.class); + + private final SequenceService service; + + public SequenceController(SequenceService service) { + this.service = service; + } + + // ========================= + // CONVERT ENDPOINT + // ========================= + @GetMapping("/convert-measurements") + public ResponseEntity> convertMeasurements( + @RequestParam("input") String input, + HttpServletRequest request) { + + String sourceIp = request.getRemoteAddr(); + + log.info("Conversion request - input: \"{}\", source IP: {}", input, sourceIp); + + Sequence sequence = new Sequence(input); + + if (!sequence.is_valid()) { + log.warn("Invalid input received: \"{}\"", input); + return ResponseEntity.badRequest().build(); + } + + List result = service.process_sequence(sequence, sourceIp); + + log.info("Conversion result - input: \"{}\", output: {}", input, result); + + return ResponseEntity.ok(result); + } + + // ========================= + // HISTORY - GET ALL + // ========================= + @GetMapping("/history") + public ResponseEntity> getAllHistory() { + + log.info("Fetching all history records"); + + List records = service.getAllHistory(); + + log.info("Fetched {} history records", records.size()); + + return ResponseEntity.ok(records); + } + + // ========================= + // HISTORY - GET BY ID + // ========================= + @GetMapping("/history/{id}") + public ResponseEntity getHistoryById(@PathVariable Long id) { + + log.info("Fetching history record by id: {}", id); + return service.getHistoryById(id).map(record -> { + log.info("Found history record: {}", id); + return ResponseEntity.ok(record); + }) + .orElseGet(() -> { + log.warn("History record not found: {}", id); + return ResponseEntity.notFound().build(); + }); + } + + // ========================= + // HISTORY - UPDATE + // ========================= + @PutMapping("/history/{id}") + public ResponseEntity updateHistory( + @PathVariable Long id, + @RequestBody SequenceHistory record) { + + log.info("Updating history record: {}", id); + + try { + SequenceHistory updated = service.updateHistory(id, record); + log.info("Updated history record: {}", id); + return ResponseEntity.ok(updated); + + } catch (RuntimeException e) { + log.warn("Update failed - record not found: {}", id); + return ResponseEntity.notFound().build(); + } + } + + // ========================= + // HISTORY - DELETE ALL + // ========================= + @DeleteMapping("/history") + public ResponseEntity clearHistory() { + + log.info("Clearing all history records"); + + service.deleteHistory(); + + log.info("All history records cleared"); + + return ResponseEntity.noContent().build(); + } + + // ========================= + // HISTORY - DELETE BY ID (ADDED like sample solution) + // ========================= + @DeleteMapping("/history/{id}") + public ResponseEntity deleteHistoryById(@PathVariable Long id) { + + log.info("Deleting history record: {}", id); + + boolean exists = service.getHistoryById(id).isPresent(); + + if (!exists) { + log.warn("Delete failed - record not found: {}", id); + return ResponseEntity.notFound().build(); + } + + service.deleteHistoryById(id); + + log.info("Deleted history record: {}", id); + + return ResponseEntity.noContent().build(); + } +} diff --git a/src/main/java/com/oraclequantapi/oraclequantapi/models/Sequence.java b/src/main/java/com/oraclequantapi/oraclequantapi/models/Sequence.java new file mode 100644 index 0000000..16a9ccd --- /dev/null +++ b/src/main/java/com/oraclequantapi/oraclequantapi/models/Sequence.java @@ -0,0 +1,45 @@ +package com.oraclequantapi.oraclequantapi.models; +import java.util.ArrayList; +import java.util.List; +public class Sequence { + private final List value; + + public Sequence() { + this.value = new ArrayList<>(); + } + + public Sequence(String rawInput) { + this.value = new ArrayList<>(); + set_value(rawInput); + } + + public void set_value(String rawInput) { + this.value.clear(); + if (rawInput != null) { + for (char ch : rawInput.toCharArray()) { + this.value.add(String.valueOf(ch)); + } + } + } + + public String get_value_as_str() { + return String.join("", this.value); + } + + public boolean is_valid() { + if (this.value == null || this.value.isEmpty()) { + return false; + } + for (String s : this.value) { + char ch = s.charAt(0); + if ((ch < 'a' || ch > 'z') && ch != '_') { + return false; + } + } + return true; + } + + public List getValue() { + return value; + } +} diff --git a/src/main/java/com/oraclequantapi/oraclequantapi/models/SequenceHistory.java b/src/main/java/com/oraclequantapi/oraclequantapi/models/SequenceHistory.java new file mode 100644 index 0000000..36cbfe0 --- /dev/null +++ b/src/main/java/com/oraclequantapi/oraclequantapi/models/SequenceHistory.java @@ -0,0 +1,73 @@ +package com.oraclequantapi.oraclequantapi.models; + +import jakarta.persistence.*; + +import java.time.LocalDateTime; + +@Entity +@Table(name = "SEQUENCE_HISTORY") +public class SequenceHistory { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "TIMESTAMP", nullable = false) + private LocalDateTime timestamp; + + @Column(name = "SOURCE_IP_ADDRESS", nullable = false) + private String sourceIpAddress; + + @Lob + @Column(name = "INPUT_STRING") + private String input; + + @Lob + @Column(name = "OUTPUT_STRING") + private String output; + + public SequenceHistory() { + } + + public SequenceHistory(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 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/repositories/SequenceHistoryRepository.java b/src/main/java/com/oraclequantapi/oraclequantapi/repositories/SequenceHistoryRepository.java new file mode 100644 index 0000000..a786126 --- /dev/null +++ b/src/main/java/com/oraclequantapi/oraclequantapi/repositories/SequenceHistoryRepository.java @@ -0,0 +1,10 @@ +package com.oraclequantapi.oraclequantapi.repositories; + +import com.oraclequantapi.oraclequantapi.models.SequenceHistory; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface SequenceHistoryRepository extends JpaRepository { + +} diff --git a/src/main/java/com/oraclequantapi/oraclequantapi/services/SequenceService.java b/src/main/java/com/oraclequantapi/oraclequantapi/services/SequenceService.java new file mode 100644 index 0000000..6719b77 --- /dev/null +++ b/src/main/java/com/oraclequantapi/oraclequantapi/services/SequenceService.java @@ -0,0 +1,122 @@ +package com.oraclequantapi.oraclequantapi.services; + +import com.oraclequantapi.oraclequantapi.models.Sequence; +import com.oraclequantapi.oraclequantapi.models.SequenceHistory; +import com.oraclequantapi.oraclequantapi.repositories.SequenceHistoryRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +@Service +public class SequenceService { + private static final Logger log = LoggerFactory.getLogger(SequenceService.class); + private final SequenceHistoryRepository repository; + + public SequenceService(SequenceHistoryRepository repository) { + this.repository = repository; + } + + public List process_sequence(Sequence sequence, String clientIp) { + + List packageTotals = new ArrayList<>(); + List chars = sequence.getValue(); + + int index = 0; + int n = chars.size(); + + while (index < n) { + + int packageItemCount = getNextEncodedValue(chars, index); + index = moveIndexPastValue(chars, index); + + if (packageItemCount == 0) { + packageTotals.add(0); + continue; + } + + int currentPackageSum = 0; + + for (int i = 0; i < packageItemCount; i++) { + if (index < n) { + int value = getNextEncodedValue(chars, index); + currentPackageSum += value; + index = moveIndexPastValue(chars, index); + } + } + + packageTotals.add(currentPackageSum); + } + + save_curr_seq(sequence.get_value_as_str(), packageTotals.toString(), clientIp); + + return packageTotals; + } + + private int getNextEncodedValue(List chars, int startIndex) { + int sum = 0; + int i = startIndex; + + while (i < chars.size()) { + char ch = chars.get(i).charAt(0); + int val = (ch == '_') ? 0 : (ch - 'a' + 1); + sum += val; + i++; + + if (ch != 'z') { + break; + } + } + + return sum; + } + + private int moveIndexPastValue(List chars, int startIndex) { + int i = startIndex; + + while (i < chars.size()) { + char ch = chars.get(i).charAt(0); + i++; + + if (ch != 'z') { + break; + } + } + + return i; + } + public void deleteHistoryById(Long id) { + repository.deleteById(id); + } + + private void save_curr_seq(String input, String output, String ip) { + SequenceHistory record = new SequenceHistory(LocalDateTime.now(), ip, input, output); + + repository.save(record); + } + + public List getAllHistory() { + return repository.findAll(); + } + + public Optional getHistoryById(Long id) { + return repository.findById(id); + } + + public void deleteHistory() { + repository.deleteAll(); + } + + public SequenceHistory updateHistory(Long id, SequenceHistory updatedDetails) { + return repository.findById(id).map(record -> { + record.setInput(updatedDetails.getInput()); + record.setOutput(updatedDetails.getOutput()); + record.setSourceIpAddress(updatedDetails.getSourceIpAddress()); + return repository.save(record); + }).orElseThrow(() -> new RuntimeException("Record not found: " + id)); + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 99d0060..74f48ea 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1,12 @@ spring.application.name=oraclequantapi + +# Oracle XE datasource +spring.datasource.url=jdbc:oracle:thin:@localhost:1521/XEPDB1 +spring.datasource.username=system +spring.datasource.password=29999login +spring.datasource.driver-class-name=oracle.jdbc.OracleDriver + +# JPA / Hibernate settings +# "update" creates/updates tables automatically ? safe for development +spring.jpa.hibernate.ddl-auto=validate +spring.jpa.show-sql=true \ No newline at end of file