Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
**/bin
**/target/

103 changes: 103 additions & 0 deletions java-migration/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.5</version>
<relativePath/>
</parent>

<groupId>com.example</groupId>
<artifactId>cobol-migration</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>cobol-migration</name>
<description>Java Spring Boot migration of COBOL Examples</description>

<properties>
<java.version>17</java.version>
</properties>

<dependencies>
<!-- Spring Boot Starters -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>

<!-- PostgreSQL JDBC Driver -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>

<!-- Flyway for database migrations -->
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
</dependency>


<!-- Jackson XML for XML serialization (replacing XML GENERATE) -->
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>

<!-- JAXB for XML generation with attributes -->
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
</dependency>
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
</dependency>

<!-- Apache POI for report generation (replacing Report Writer) -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>5.2.5</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.5</version>
</dependency>

<!-- Testing -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.example.cobolmigration;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class CobolMigrationApplication {

public static void main(String[] args) {
SpringApplication.run(CobolMigrationApplication.class, args);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.example.cobolmigration.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.dao.DataAccessException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

/**
* Application configuration and global exception handling.
*
* The {@link GlobalExceptionHandler} replaces the COBOL check-sql-state
* paragraph (sql/sql_example.cbl lines 443-472) which checks SQLCODE /
* SQLSTATE and terminates on error. Instead of terminating, we return
* appropriate HTTP error responses.
*/
@Configuration
public class AppConfig {

@RestControllerAdvice
public static class GlobalExceptionHandler {

/**
* Handles database access errors, replacing the COBOL pattern of
* checking SQLCODE after every EXEC SQL statement.
*/
@ExceptionHandler(DataAccessException.class)
public ResponseEntity<ErrorResponse> handleDataAccessException(
DataAccessException ex) {
ErrorResponse error = new ErrorResponse(
"Database error",
ex.getMostSpecificCause().getMessage());
return ResponseEntity
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(error);
}

@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<ErrorResponse> handleIllegalArgument(
IllegalArgumentException ex) {
ErrorResponse error = new ErrorResponse(
"Invalid request", ex.getMessage());
return ResponseEntity
.status(HttpStatus.BAD_REQUEST)
.body(error);
}

@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleGenericException(
Exception ex) {
ErrorResponse error = new ErrorResponse(
"Internal server error", ex.getMessage());
return ResponseEntity
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(error);
}
}

public record ErrorResponse(String error, String detail) {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.example.cobolmigration.controller;

import com.example.cobolmigration.entity.Account;
import com.example.cobolmigration.service.AccountService;
import java.util.List;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
* REST controller replacing the terminal-based menu system from
* sql/sql_example.cbl (lines 155-186).
*
* COBOL menu option mapping:
* 1) Display all accounts -> GET /api/accounts
* 2) Display disabled accounts -> GET /api/accounts/disabled
* 3) Query accounts -> GET /api/accounts/search?q={value}
* 4) Exit -> (not applicable for REST)
*/
@RestController
@RequestMapping("/api/accounts")
public class AccountController {

private final AccountService accountService;

public AccountController(AccountService accountService) {
this.accountService = accountService;
}

/**
* Replaces menu option "1) Display all accounts".
*/
@GetMapping
public ResponseEntity<List<Account>> getAllAccounts() {
return ResponseEntity.ok(accountService.getAllAccounts());
}

/**
* Replaces menu option "2) Display disabled accounts".
*/
@GetMapping("/disabled")
public ResponseEntity<List<Account>> getDisabledAccounts() {
return ResponseEntity.ok(accountService.getDisabledAccounts());
}

/**
* Replaces menu option "3) Query accounts".
*
* The COBOL implementation wraps the trimmed search value with '%'
* wildcards and queries across FIRST_NAME, LAST_NAME, PHONE, and ADDRESS.
*/
@GetMapping("/search")
public ResponseEntity<List<Account>> searchAccounts(
@RequestParam("q") String searchValue) {
return ResponseEntity.ok(accountService.searchAccounts(searchValue));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package com.example.cobolmigration.controller;

import com.example.cobolmigration.dto.JsonRecord;
import com.example.cobolmigration.dto.SearchItem;
import com.example.cobolmigration.service.ReportService;
import com.example.cobolmigration.service.ReportService.StudentRecord;
import com.example.cobolmigration.service.SerializationService;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
* REST controller demonstrating the non-SQL COBOL module equivalents:
* JSON generation, XML generation, report writing, search table, and
* CLI argument handling.
*/
@RestController
@RequestMapping("/api/demo")
public class DemoController {

private final SerializationService serializationService;
private final ReportService reportService;

public DemoController(SerializationService serializationService,
ReportService reportService) {
this.serializationService = serializationService;
this.reportService = reportService;
}

/**
* Demonstrates JSON generation replacing json_generate/json_generate.cbl.
*/
@GetMapping("/json")
public ResponseEntity<JsonRecord> jsonDemo() {
JsonRecord sample = serializationService.createSampleJsonRecord();
return ResponseEntity.ok(sample);
}

/**
* Demonstrates XML generation replacing xml_generate/xml_generate.cbl.
* Returns the raw XML string with the XML declaration.
*/
@GetMapping(value = "/xml", produces = MediaType.APPLICATION_XML_VALUE)
public ResponseEntity<String> xmlDemo() {
String xml = serializationService.generateXml(
serializationService.createSampleXmlRecord());
return ResponseEntity.ok(xml);
}

/**
* Generates a text report replacing report_writer/report_test.cbl.
* Accepts an optional format parameter (text or csv).
*/
@GetMapping(value = "/report", produces = MediaType.TEXT_PLAIN_VALUE)
public ResponseEntity<String> reportDemo(
@RequestParam(value = "format", defaultValue = "text") String format) {
List<StudentRecord> sampleData = List.of(
new StudentRecord(334500, "Alice Johnson", "CSC", 5),
new StudentRecord(334501, "Bob Smith", "PHY", 12),
new StudentRecord(334502, "Carol White", "MAT", 8),
new StudentRecord(334503, "David Brown", "ENG", 3));

String report;
if ("csv".equalsIgnoreCase(format)) {
report = reportService.generateCsvReport(sampleData);
} else {
report = reportService.generateTextReport(sampleData);
}
return ResponseEntity.ok(report);
}

/**
* Demonstrates binary search on a sorted table, replacing
* search/search.cbl SEARCH ALL behaviour.
*
* Populates a list with the same test data as the COBOL program's
* setup-test-data paragraph and performs a binary search by id1.
*/
@GetMapping("/search")
public ResponseEntity<Object> searchDemo(
@RequestParam(value = "id1", defaultValue = "0") int id1) {
List<SearchItem> table = new ArrayList<>();
table.add(new SearchItem(1000, 2000, 9000, "First Item", "2021/08/30"));
table.add(new SearchItem(2000, 3000, 8000, "Second Item", "2021/09/15"));
table.add(new SearchItem(3000, 4000, 7000, "Third Item", "2021/10/01"));
Collections.sort(table);

SearchItem key = new SearchItem(id1, 0, 0, null, null);
int index = Collections.binarySearch(table, key,
(a, b) -> Integer.compare(a.getId1(), b.getId1()));

if (index >= 0) {
return ResponseEntity.ok(table.get(index));
}
return ResponseEntity.ok("Item not found.");
}

/**
* Demonstrates CLI argument handling replacing read_command_args/ module.
* In a REST context the "arguments" are query parameters.
* The COBOL program checks for '--test' in the command line args.
*/
@GetMapping("/args")
public ResponseEntity<String> argsDemo(
@RequestParam(value = "args", defaultValue = "") String args) {
StringBuilder response = new StringBuilder();
response.append("Full command line args: ").append(args).append("\n");

if (args.toLowerCase().contains("--test")) {
response.append("You entered the '--test' cmd arg!\n");
}
return ResponseEntity.ok(response.toString());
}
}
Loading