2 Commits

113 changed files with 3061 additions and 661 deletions

47
cygnus-lib/pom.xml Normal file
View File

@@ -0,0 +1,47 @@
<?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>com.cygnus</groupId>
<artifactId>cygnus-parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>cygnus-lib</artifactId>
<packaging>jar</packaging>
<name>Cygnus Shared Library</name>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,71 @@
package lib.constants;
import java.util.Arrays;
/** Canonical user-facing errors shared by Cygnus applications. */
public enum ApplicationError {
BAD_REQUEST(400, "HTTP-400", "Invalid request",
"Cygnus could not process the submitted request.",
"Review the supplied information and try again."),
SESSION_REQUIRED(401, "AUTH-401", "Sign in required",
"Your session is missing or has expired.",
"Sign in again to continue securely."),
ACCESS_DENIED(403, "AUTH-403", "Access denied",
"You do not have permission to open this page.",
"Contact your administrator if this feature should be available to you."),
PAGE_NOT_FOUND(404, "HTTP-404", "Page not found",
"The requested page could not be found.",
"Check the address or return to the application dashboard."),
METHOD_NOT_ALLOWED(405, "HTTP-405", "Action not allowed",
"This page does not support the requested action.",
"Return to the previous page and try an available action."),
CONFLICT(409, "HTTP-409", "Request conflict",
"The request conflicts with the current state of the data.",
"Refresh the page and try again."),
PAYLOAD_TOO_LARGE(413, "HTTP-413", "File is too large",
"The submitted content exceeds the permitted size.",
"Reduce the file size and try again."),
TOO_MANY_REQUESTS(429, "HTTP-429", "Too many requests",
"Cygnus has received too many requests in a short period.",
"Wait briefly before trying again."),
INTERNAL_SERVER_ERROR(500, "HTTP-500", "Something went wrong",
"Cygnus could not complete your request.",
"Try again. If the issue continues, share the reference ID with support."),
BAD_GATEWAY(502, "HTTP-502", "Service response unavailable",
"A required service returned an invalid response.",
"Try again shortly. If the issue continues, contact support."),
SERVICE_UNAVAILABLE(503, "HTTP-503", "Service temporarily unavailable",
"A required Cygnus service is currently unavailable.",
"Wait briefly and try again."),
GATEWAY_TIMEOUT(504, "HTTP-504", "Service response timed out",
"A required service took too long to respond.",
"Try the request again shortly.");
private final int httpStatus;
private final String code;
private final String title;
private final String message;
private final String description;
ApplicationError(
int httpStatus, String code, String title, String message, String description) {
this.httpStatus = httpStatus;
this.code = code;
this.title = title;
this.message = message;
this.description = description;
}
public static ApplicationError fromHttpStatus(int status) {
return Arrays.stream(values())
.filter(error -> error.httpStatus == status)
.findFirst()
.orElse(INTERNAL_SERVER_ERROR);
}
public int getHttpStatus() { return httpStatus; }
public String getCode() { return code; }
public String getTitle() { return title; }
public String getMessage() { return message; }
public String getDescription() { return description; }
}

View File

@@ -0,0 +1,23 @@
package lib.models;
import java.util.List;
import java.util.Map;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class CasePunching {
private ErrorDetails errorDetails;
private Map<String, String> optionsl;
private Map<String, List<Option>> options;
private List<String> visibleSections;
private Integer portfolioId;
private Integer formMode;
private String userId;
private String dynamicFields;
private String dynamicHtml;
private String verificationCaseId;
private String documentCaseId;
}

View File

@@ -0,0 +1,58 @@
package lib.models;
import lib.constants.ApplicationError;
import java.io.Serial;
import java.io.Serializable;
import java.time.Instant;
import java.util.Objects;
import java.util.UUID;
import lombok.Getter;
/** Dependency-free error information shared across Cygnus modules. */
@Getter
public final class ErrorDetails implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private final int httpStatus;
private final String errorCode;
private final String title;
private final String message;
private final String description;
private final String referenceId;
private final Instant timestamp;
public ErrorDetails(ApplicationError error) {
this(error.getHttpStatus(), error.getCode(), error.getTitle(),
error.getMessage(), error.getDescription(),
UUID.randomUUID().toString(), Instant.now());
}
private ErrorDetails(
int httpStatus,
String errorCode,
String title,
String message,
String description,
String referenceId,
Instant timestamp) {
if (httpStatus < 400 || httpStatus > 599) {
throw new IllegalArgumentException("HTTP error status must be between 400 and 599");
}
this.httpStatus = httpStatus;
this.errorCode = required(errorCode, "errorCode");
this.title = required(title, "title");
this.message = required(message, "message");
this.description = description == null ? "" : description.trim();
this.referenceId = required(referenceId, "referenceId");
this.timestamp = Objects.requireNonNull(timestamp, "timestamp");
}
private static String required(String value, String name) {
if (value == null || value.isBlank()) {
throw new IllegalArgumentException(name + " is required");
}
return value.trim();
}
}

View File

@@ -0,0 +1,12 @@
package lib.models;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class Option {
private Object value;
private String label;
private String group;
}

View File

@@ -0,0 +1,22 @@
package lib.models;
import lib.constants.ApplicationError;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
class ErrorDetailsTest {
@Test
void createsAReusableErrorContract() {
ErrorDetails details = new ErrorDetails(ApplicationError.ACCESS_DENIED);
assertEquals(403, details.getHttpStatus());
assertEquals("AUTH-403", details.getErrorCode());
}
@Test
void mapsHttpStatusToCanonicalError() {
assertEquals(ApplicationError.PAGE_NOT_FOUND, ApplicationError.fromHttpStatus(404));
assertEquals(ApplicationError.INTERNAL_SERVER_ERROR,
ApplicationError.fromHttpStatus(599));
}
}

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,3 @@
artifactId=cygnus-lib
groupId=com.cygnus
version=1.0.0-SNAPSHOT

View File

@@ -0,0 +1,3 @@
lib/models/ErrorDetails.class
lib/models/CasePunching.class
lib/constants/ApplicationError.class

View File

@@ -0,0 +1,4 @@
/Users/maddy/Projects/cygnus/matrix/cygnus-lib/src/main/java/lib/constants/ApplicationError.java
/Users/maddy/Projects/cygnus/matrix/cygnus-lib/src/main/java/lib/models/CasePunching.java
/Users/maddy/Projects/cygnus/matrix/cygnus-lib/src/main/java/lib/models/ErrorDetails.java
/Users/maddy/Projects/cygnus/matrix/cygnus-lib/src/main/java/lib/models/Option.java

View File

@@ -0,0 +1 @@
lib/models/ErrorDetailsTest.class

View File

@@ -0,0 +1 @@
/Users/maddy/Projects/cygnus/matrix/cygnus-lib/src/test/java/lib/models/ErrorDetailsTest.java

View File

@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8"?>
<testsuite xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://maven.apache.org/surefire/maven-surefire-plugin/xsd/surefire-test-report.xsd" version="3.0.2" name="lib.models.ErrorDetailsTest" time="0.024" tests="2" errors="0" skipped="0" failures="0">
<properties>
<property name="java.specification.version" value="25"/>
<property name="sun.jnu.encoding" value="UTF-8"/>
<property name="java.class.path" value="/Users/maddy/Projects/cygnus/matrix/cygnus-lib/target/test-classes:/Users/maddy/Projects/cygnus/matrix/cygnus-lib/target/classes:/Users/maddy/.m2/repository/org/projectlombok/lombok/1.18.46/lombok-1.18.46.jar:/Users/maddy/.m2/repository/org/junit/jupiter/junit-jupiter/5.12.2/junit-jupiter-5.12.2.jar:/Users/maddy/.m2/repository/org/junit/jupiter/junit-jupiter-api/5.12.2/junit-jupiter-api-5.12.2.jar:/Users/maddy/.m2/repository/org/opentest4j/opentest4j/1.3.0/opentest4j-1.3.0.jar:/Users/maddy/.m2/repository/org/junit/platform/junit-platform-commons/1.12.2/junit-platform-commons-1.12.2.jar:/Users/maddy/.m2/repository/org/apiguardian/apiguardian-api/1.1.2/apiguardian-api-1.1.2.jar:/Users/maddy/.m2/repository/org/junit/jupiter/junit-jupiter-params/5.12.2/junit-jupiter-params-5.12.2.jar:/Users/maddy/.m2/repository/org/junit/jupiter/junit-jupiter-engine/5.12.2/junit-jupiter-engine-5.12.2.jar:/Users/maddy/.m2/repository/org/junit/platform/junit-platform-engine/1.12.2/junit-platform-engine-1.12.2.jar:"/>
<property name="java.vm.vendor" value="Homebrew"/>
<property name="sun.arch.data.model" value="64"/>
<property name="java.vendor.url" value="https://github.com/Homebrew/homebrew-core/issues"/>
<property name="os.name" value="Mac OS X"/>
<property name="java.vm.specification.version" value="25"/>
<property name="sun.java.launcher" value="SUN_STANDARD"/>
<property name="user.country" value="US"/>
<property name="sun.boot.library.path" value="/opt/homebrew/Cellar/openjdk/25.0.2/libexec/openjdk.jdk/Contents/Home/lib"/>
<property name="sun.java.command" value="/Users/maddy/Projects/cygnus/matrix/cygnus-lib/target/surefire/surefirebooter-20260801204253825_3.jar /Users/maddy/Projects/cygnus/matrix/cygnus-lib/target/surefire 2026-08-01T20-42-53_787-jvmRun1 surefire-20260801204253825_1tmp surefire_0-20260801204253825_2tmp"/>
<property name="http.nonProxyHosts" value="local|*.local|169.254/16|*.169.254/16"/>
<property name="jdk.debug" value="release"/>
<property name="surefire.test.class.path" value="/Users/maddy/Projects/cygnus/matrix/cygnus-lib/target/test-classes:/Users/maddy/Projects/cygnus/matrix/cygnus-lib/target/classes:/Users/maddy/.m2/repository/org/projectlombok/lombok/1.18.46/lombok-1.18.46.jar:/Users/maddy/.m2/repository/org/junit/jupiter/junit-jupiter/5.12.2/junit-jupiter-5.12.2.jar:/Users/maddy/.m2/repository/org/junit/jupiter/junit-jupiter-api/5.12.2/junit-jupiter-api-5.12.2.jar:/Users/maddy/.m2/repository/org/opentest4j/opentest4j/1.3.0/opentest4j-1.3.0.jar:/Users/maddy/.m2/repository/org/junit/platform/junit-platform-commons/1.12.2/junit-platform-commons-1.12.2.jar:/Users/maddy/.m2/repository/org/apiguardian/apiguardian-api/1.1.2/apiguardian-api-1.1.2.jar:/Users/maddy/.m2/repository/org/junit/jupiter/junit-jupiter-params/5.12.2/junit-jupiter-params-5.12.2.jar:/Users/maddy/.m2/repository/org/junit/jupiter/junit-jupiter-engine/5.12.2/junit-jupiter-engine-5.12.2.jar:/Users/maddy/.m2/repository/org/junit/platform/junit-platform-engine/1.12.2/junit-platform-engine-1.12.2.jar:"/>
<property name="sun.cpu.endian" value="little"/>
<property name="user.home" value="/Users/maddy"/>
<property name="user.language" value="en"/>
<property name="java.specification.vendor" value="Oracle Corporation"/>
<property name="java.version.date" value="2026-01-20"/>
<property name="java.home" value="/opt/homebrew/Cellar/openjdk/25.0.2/libexec/openjdk.jdk/Contents/Home"/>
<property name="file.separator" value="/"/>
<property name="basedir" value="/Users/maddy/Projects/cygnus/matrix/cygnus-lib"/>
<property name="java.vm.compressedOopsMode" value="Zero based"/>
<property name="line.separator" value="&#10;"/>
<property name="java.vm.specification.vendor" value="Oracle Corporation"/>
<property name="java.specification.name" value="Java Platform API Specification"/>
<property name="apple.awt.application.name" value="ForkedBooter"/>
<property name="surefire.real.class.path" value="/Users/maddy/Projects/cygnus/matrix/cygnus-lib/target/surefire/surefirebooter-20260801204253825_3.jar"/>
<property name="sun.management.compiler" value="HotSpot 64-Bit Tiered Compilers"/>
<property name="ftp.nonProxyHosts" value="local|*.local|169.254/16|*.169.254/16"/>
<property name="java.runtime.version" value="25.0.2"/>
<property name="user.name" value="maddy"/>
<property name="stdout.encoding" value="UTF-8"/>
<property name="path.separator" value=":"/>
<property name="os.version" value="26.5.2"/>
<property name="java.runtime.name" value="OpenJDK Runtime Environment"/>
<property name="file.encoding" value="UTF-8"/>
<property name="java.vm.name" value="OpenJDK 64-Bit Server VM"/>
<property name="java.vendor.version" value="Homebrew"/>
<property name="localRepository" value="/Users/maddy/.m2/repository"/>
<property name="java.vendor.url.bug" value="https://github.com/Homebrew/homebrew-core/issues"/>
<property name="java.io.tmpdir" value="/var/folders/1l/36214rdn79755j30lcnmgsqh0000gn/T/"/>
<property name="java.version" value="25.0.2"/>
<property name="user.dir" value="/Users/maddy/Projects/cygnus/matrix/cygnus-lib"/>
<property name="os.arch" value="aarch64"/>
<property name="java.vm.specification.name" value="Java Virtual Machine Specification"/>
<property name="native.encoding" value="UTF-8"/>
<property name="java.library.path" value="/Users/maddy/Library/Java/Extensions:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java:."/>
<property name="java.vm.info" value="mixed mode, sharing"/>
<property name="stderr.encoding" value="UTF-8"/>
<property name="java.vendor" value="Homebrew"/>
<property name="java.vm.version" value="25.0.2"/>
<property name="stdin.encoding" value="UTF-8"/>
<property name="sun.io.unicode.encoding" value="UnicodeBig"/>
<property name="socksNonProxyHosts" value="local|*.local|169.254/16|*.169.254/16"/>
<property name="java.class.version" value="69.0"/>
</properties>
<testcase name="mapsHttpStatusToCanonicalError" classname="lib.models.ErrorDetailsTest" time="0.008"/>
<testcase name="createsAReusableErrorContract" classname="lib.models.ErrorDetailsTest" time="0.007"/>
</testsuite>

View File

@@ -0,0 +1,4 @@
-------------------------------------------------------------------------------
Test set: lib.models.ErrorDetailsTest
-------------------------------------------------------------------------------
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.024 s -- in lib.models.ErrorDetailsTest

View File

@@ -10,7 +10,7 @@
<mvc:annotation-driven/>
<!-- Scan for annotation based controllers -->
<context:component-scan base-package="matrix.nimble" />
<context:component-scan base-package="matrix.nimble,matrix.services" />
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView" />
<property name="prefix" value="/WEB-INF/app/" />

View File

@@ -33,7 +33,7 @@
<link href="/matrix/css/matrix-shell-v2.css?v=7" rel="stylesheet" type="text/css" />
<link href="/matrix/css/tool-workspace-v3.css?v=2" rel="stylesheet" type="text/css" />
<link href="/matrix/css/punch-workspace-v3.css?v=1" rel="stylesheet" type="text/css" />
<link href="/matrix/css/add-cases-v4.css?v=1" rel="stylesheet" type="text/css" />
<link href="/matrix/css/add-cases-v4.css?v=5" rel="stylesheet" type="text/css" />
<script src="/matrix/js/matrix-dialog-child.js" type="text/javascript"></script>
<script src="/matrix/js/bootstrap-5.3.8.bundle.min.js" defer></script>
<script src="/matrix/js/matrix-shell-v2.js" defer></script>
@@ -591,7 +591,7 @@
<div id="section6">
<!-- Title -->
<div class="title">
<img class="matrix-title-icon" src="/matrix/images/othdet.png" />
<img class="matrix-title-icon" src="/matrix/images/edit_form.png" alt="" />
<span class="matrix-title-text">Other Details</span>
</div>
<!-- -->

View File

@@ -3,6 +3,12 @@
<%@page language="java" session="true" %>
<%@taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
<c:set var="pageFormMode" value="${not empty model ? model.formMode : formmode}" />
<c:set var="pagePortfolioId" value="${not empty model ? model.portfolioId : portfolio_id}" />
<c:set var="pageDynamicFields" value="${not empty model ? model.dynamicFields : dynamicfields}" />
<c:set var="pageDocumentCaseId" value="${not empty model ? model.documentCaseId : duuid}" />
<c:set var="pageUserId" value="${not empty model ? model.userId : usid}" />
<!DOCTYPE html>
<html>
<head>
@@ -14,8 +20,8 @@
<script language="javascript" src="/matrix/js/lib/jsfuncs.js" type="text/javascript"></script>
<script language="javascript" src="/matrix/js/ui/msgdialog.js" type="text/javascript"></script>
<script language="javascript" src="/matrix/js/ui/select.js" type="text/javascript"></script>
<script language="javascript" src="/matrix/js/appjs/punching.js?ver=4" type="text/javascript"></script>
<script language="javascript" src="/matrix/js/validator.js" type="text/javascript"></script>
<script language="javascript" src="/matrix/js/appjs/punching.js?ver=6" type="text/javascript"></script>
<script language="javascript" src="/matrix/js/validator.js?ver=2" type="text/javascript"></script>
<script language="javascript" src="/matrix/js/ajax.js" type="text/javascript"></script>
<script language="javascript" src="/matrix/js/ajax-dynamic-list.js?ver=1" type="text/javascript"></script>
@@ -30,7 +36,7 @@
<link href="/matrix/css/matrix-shell-v2.css?v=7" rel="stylesheet" type="text/css" />
<link href="/matrix/css/tool-workspace-v3.css?v=2" rel="stylesheet" type="text/css" />
<link href="/matrix/css/punch-workspace-v3.css?v=1" rel="stylesheet" type="text/css" />
<link href="/matrix/css/add-cases-v4.css?v=1" rel="stylesheet" type="text/css" />
<link href="/matrix/css/add-cases-v4.css?v=5" rel="stylesheet" type="text/css" />
<script src="/matrix/js/bootstrap-5.3.8.bundle.min.js" defer></script>
<script src="/matrix/js/matrix-shell-v2.js" defer></script>
<link href="/matrix/css/matrix-frame-dialog.css?v=6" rel="stylesheet" type="text/css" />
@@ -44,7 +50,7 @@
<!-- Title Bar -->
<%@ include file="/WEB-INF/app/fragments/app-title.jspf" %>
<!-- -->
<c:if test="${formmode!=2}">
<c:if test="${pageFormMode!=2}">
<!-- Dynamic Menu -->
<nav class="matrix-shell__navigation" aria-label="Primary navigation">
<c:out value="${Sessvals.menuHtml}" escapeXml="false" />
@@ -59,21 +65,30 @@
<div class="title matrix-form-title">
<img class="matrix-title-icon" src="/matrix/images/commondet.png" align="absmiddle" />
<span class="matrix-title-text matrix-form-title__heading">Common Details <span id='recfound'></span></span>
<label class="matrix-form-title__field">Skip To <input type="text" class="textbox" style="width:30px" onkeypress="return NumberOnly(event)" onblur="GotoRecord(this,'recno')"></label>
<label class="matrix-form-title__field">Search By File No <input type="text" class="textbox" style="width:100px" onblur="GotoRecord(this,'query')"></label>
<label class="matrix-form-title__field">Skip To <input type="text" class="textbox" style="width:50px" onkeypress="return NumberOnly(event)" onblur="GotoRecord(this,'recno')"></label>
<label class="matrix-form-title__field">Search By File No <input type="text" class="textbox" style="width:150px" onblur="GotoRecord(this,'query')"></label>
<!-- Checkbox for AUTO CUT diabled for now -->
<label class="matrix-form-title__check"><input type="checkbox" name="autocut" checked id="autocut" /> Auto Cut-Off</label>
<c:if test="${formmode==2}">
<input type="hidden" id="portfolio_id" name="portfolio_id" value="-1" />
<c:if test="${pageFormMode==2}">
<input type="hidden" id="portfolio_id" name="portfolioId" value="${pagePortfolioId}" />
</c:if>
<c:if test="${formmode!=2}">
<div class="divselect matrix-form-title__end" id="divportfolio" style="width:217px;">
<select id="portfolio_id" name="portfolio_id" style="width:235px" onchange="SubmitForm('caseadd','_parent','casedetails')">
<c:if test="${pageFormMode!=2}">
<div class="divselect matrix-form-title__end" id="divportfolio" style="width:297px;">
<select id="portfolio_id" name="portfolioId" style="width:315px" onchange="SubmitForm('caseadd','_parent','casedetails')">
<option value="-1" selected >SELECT PORTFOLIO</option>
<c:forEach items="${portlist}" var="port">
<option value="${port[0]}">${port[1]}</option>
</c:forEach>
<c:choose>
<c:when test="${not empty model}">
<c:forEach items="${model.options['portfolio.']}" var="port">
<option value="${port.value}"><c:out value="${port.label}" /></option>
</c:forEach>
</c:when>
<c:otherwise>
<c:forEach items="${portlist}" var="port">
<option value="${port[0]}"><c:out value="${port[1]}" /></option>
</c:forEach>
</c:otherwise>
</c:choose>
</select>
</div>
</c:if>
@@ -116,9 +131,14 @@
<div class="divselect" id="divbranch" style="width:135px;">
<select id="bank_branch_id" name="bank_branch_id" style="width:153px" onblur="validate(this,'t','');">
<option value="-1" selected >SELECT</option>
<c:forEach items="${branchlist}" var="brnch">
<option value="${brnch[0]}">${brnch[1]}</option>
</c:forEach>
<c:choose>
<c:when test="${not empty model}">
<c:forEach items="${model.options}" var="option"><c:if test="${fn:startsWith(option.key, 'branch.')}">
<option value="${fn:substringAfter(option.key, 'branch.')}"><c:out value="${option.value}" /></option>
</c:if></c:forEach>
</c:when>
<c:otherwise><c:forEach items="${branchlist}" var="brnch"><option value="${brnch[0]}"><c:out value="${brnch[1]}" /></option></c:forEach></c:otherwise>
</c:choose>
</select>
</div>
</div>
@@ -131,9 +151,10 @@
<div class="divselect" id="divproduct" style="width:132px;">
<select id="product" name="product" style="width:150px" onblur="validate(this,'t','');">
<option value="-1" selected >SELECT</option>
<c:forEach items="${prodlist}" var="prod">
<option value="${prod[0]}">${prod[1]}</option>
</c:forEach>
<c:choose>
<c:when test="${not empty model}"><c:forEach items="${model.options}" var="option"><c:if test="${fn:startsWith(option.key, 'product.')}"><option value="${fn:substringAfter(option.key, 'product.')}"><c:out value="${option.value}" /></option></c:if></c:forEach></c:when>
<c:otherwise><c:forEach items="${prodlist}" var="prod"><option value="${prod[0]}"><c:out value="${prod[1]}" /></option></c:forEach></c:otherwise>
</c:choose>
</select>
</div>
</div>
@@ -162,9 +183,10 @@
<div class="divselect" id="divapptype" style="width:135px;">
<select id="apptype" name="apptype" style="width:153px" onchange="ToggleChks((this.value.toUpperCase()!='APPLICANT' && this.value.toUpperCase()!='-1'),'sradd!C0L!soadd!C0L!spadd!C0L!','!C0L!')" onblur="validate(this,'t','');">
<option value="-1" selected>SELECT</option>
<c:forEach items="${typelist}" var="type">
<option value="${type[0]}">${type[1]}</option>
</c:forEach>
<c:choose>
<c:when test="${not empty model}"><c:forEach items="${model.options}" var="option"><c:if test="${fn:startsWith(option.key, 'applicationType.')}"><option value="${fn:substringAfter(option.key, 'applicationType.')}"><c:out value="${option.value}" /></option></c:if></c:forEach></c:when>
<c:otherwise><c:forEach items="${typelist}" var="type"><option value="${type[0]}"><c:out value="${type[1]}" /></option></c:forEach></c:otherwise>
</c:choose>
</select>
</div>
</div>
@@ -177,9 +199,10 @@
<div class="divselect" id="divcategory" style="width:115px;">
<select id="category" name="category" style="width:133px">
<option value="-1" selected>SELECT</option>
<c:forEach items="${catlist}" var="cat">
<option value="${cat[0]}">${cat[1]}</option>
</c:forEach>
<c:choose>
<c:when test="${not empty model}"><c:forEach items="${model.options}" var="option"><c:if test="${fn:startsWith(option.key, 'category.')}"><option value="${fn:substringAfter(option.key, 'category.')}"><c:out value="${option.value}" /></option></c:if></c:forEach></c:when>
<c:otherwise><c:forEach items="${catlist}" var="cat"><option value="${cat[0]}"><c:out value="${cat[1]}" /></option></c:forEach></c:otherwise>
</c:choose>
</select>
</div>
</div>
@@ -287,9 +310,10 @@
<select id="rcity" name="colr_citylist" style="width:128px" onblur="HandleBlur(this)" >
<option value="-1">Select</option>
<option value="Unknown">Unknown</option>
<c:forEach items="${citylist}" var="city">
<option value="${city[0]}">${city[1]}</option>
</c:forEach>
<c:choose>
<c:when test="${not empty model}"><c:forEach items="${model.options}" var="option"><c:if test="${fn:startsWith(option.key, 'city.')}"><option value="${fn:substringAfter(option.key, 'city.')}"><c:out value="${option.value}" /></option></c:if></c:forEach></c:when>
<c:otherwise><c:forEach items="${citylist}" var="city"><option value="${city[0]}"><c:out value="${city[1]}" /></option></c:forEach></c:otherwise>
</c:choose>
</select>
</div>
</div>
@@ -347,7 +371,7 @@
<span class="matrix-title-text">Office Details</span>
<div class="divchk" id="ctrldiv">
<input type="checkbox" id="ov" onclick="HandleClick(this)" />OVR&nbsp;
<input type="checkbox" id="rco" style="display:none" onclick="HandleClick(this)" /><span id="rco">RCO</span>&nbsp;
<input type="checkbox" id="rco" style="display:none" onclick="HandleClick(this)" /><span id="rco">RCO&nbsp;</span>
<input type="checkbox" id="soadd" onclick="return FindSameDet(this,'O','ophone')" /><span id="soadd">Same Office</span>
<input type="checkbox" id="otv" onclick="HandleClick(this)" /><span id="otv">OTV</span>
</div>
@@ -404,9 +428,10 @@
<select id="ocity" name="colo_citylist" style="width:128px" onblur="HandleBlur(this)" >
<option value="-1">Select</option>
<option value="Unknown">Unknown</option>
<c:forEach items="${citylist}" var="city">
<option value="${city[0]}">${city[1]}</option>
</c:forEach>
<c:choose>
<c:when test="${not empty model}"><c:forEach items="${model.options}" var="option"><c:if test="${fn:startsWith(option.key, 'city.')}"><option value="${fn:substringAfter(option.key, 'city.')}"><c:out value="${option.value}" /></option></c:if></c:forEach></c:when>
<c:otherwise><c:forEach items="${citylist}" var="city"><option value="${city[0]}"><c:out value="${city[1]}" /></option></c:forEach></c:otherwise>
</c:choose>
</select>
</div>
</div>
@@ -482,8 +507,8 @@
<span class="matrix-title-text">Property Details</span>
<div class="divchk" id="ctrldiv">
<input type="checkbox" id="pv" onclick="HandleClick(this)" />PVR&nbsp;
<input type="checkbox" id="rcp" style="display:none" onclick="HandleClick(this)" /><span id="rcp">RCP</span>&nbsp;
<input type="checkbox" id="ocp" style="display:none" onclick="HandleClick(this)" /><span id="ocp">OCP</span>&nbsp;
<input type="checkbox" id="rcp" style="display:none" onclick="HandleClick(this)" /><span id="rcp">RCP&nbsp;</span>
<input type="checkbox" id="ocp" style="display:none" onclick="HandleClick(this)" /><span id="ocp">OCP&nbsp;</span>
<input type="checkbox" id="spadd" onclick="return FindSameDet(this,'P','ppincode')" /><span id="spadd">Same Property</span>
</div>
</div>
@@ -530,9 +555,10 @@
<select id="pcity" name="colp_citylist" style="width:128px" onblur="HandleBlur(this)" >
<option value="-1">Select</option>
<option value="Unknown">Unknown</option>
<c:forEach items="${citylist}" var="city">
<option value="${city[0]}">${city[1]}</option>
</c:forEach>
<c:choose>
<c:when test="${not empty model}"><c:forEach items="${model.options}" var="option"><c:if test="${fn:startsWith(option.key, 'city.')}"><option value="${fn:substringAfter(option.key, 'city.')}"><c:out value="${option.value}" /></option></c:if></c:forEach></c:when>
<c:otherwise><c:forEach items="${citylist}" var="city"><option value="${city[0]}"><c:out value="${city[1]}" /></option></c:forEach></c:otherwise>
</c:choose>
</select>
</div>
</div>
@@ -621,12 +647,15 @@
<div id="section6">
<!-- Title -->
<div class="title">
<img class="matrix-title-icon" src="/matrix/images/othdet.png" />
<img class="matrix-title-icon" src="/matrix/images/edit_form.png" alt="" />
<span class="matrix-title-text">Other Details</span>
</div>
<!-- -->
<div id="FormPanel6" class="FormPanel">
${dynamichtml}
<c:choose>
<c:when test="${not empty model}"><c:out value="${model.dynamicHtml}" escapeXml="false" /></c:when>
<c:otherwise><c:out value="${dynamichtml}" escapeXml="false" /></c:otherwise>
</c:choose>
<br/>
<br/>
<br/>
@@ -650,16 +679,22 @@
<br />
<br />
<br />
<input type="hidden" id="dynamicfields" name="dynamicfields" value="${dynamicfields}" />
<input type="hidden" id="dynamicfields" name="dynamicfields" value="${pageDynamicFields}" />
<input type="hidden" id="visiblecontents" name="visiblecontents" value="${visiblecontents}" />
<input type="hidden" id="uuid" name="uuid" value="${duuid}" />
<input type="hidden" id="initial-portfolio-id" value="${pagePortfolioId}" />
<c:if test="${not empty model}">
<c:forEach items="${model.visibleSections}" var="section">
<span hidden class="model-visible-section"><c:out value="${section}" /></span>
</c:forEach>
</c:if>
<input type="hidden" id="uuid" name="uuid" value="${pageDocumentCaseId}" />
<input type="hidden" id="case_id" name="case_id" value="" />
<input type="hidden" id="company_id" name="company_id" value="${Sessvals.getCompanyID()}" />
<input type="hidden" id="branch_id" name="branch_id" value="${Sessvals.getBranchID()}" />
<input type="hidden" id="status" name="status" value="" />
<!-- -->
<input type="hidden" id="invalidfields" value="0" />
<c:if test="${formmode gt 0}">
<c:if test="${pageFormMode gt 0}">
<div style="position:fixed;text-align:right;bottom:0px;width:980px;" class="buttonbar">
<input type="hidden" id="invalidfields" value="0" />
<input type="button" class="button" name="btnfirst" style="margin-left:5px;margin-top:-2px;float:left;width:80px" value="" id="btnfirst" accesskey="F" onclick="findRecord(1)" />
@@ -667,25 +702,40 @@
<input type="button" class="button" name="btnnext" style="margin-top:-2px;float:left;width:80px" value="" id="btnnext" accesskey="N" onclick="findRecord(3)" />
<input type="button" class="button" name="btnlast" style="margin-top:-2px;float:left;width:80px" value="" id="btnlast" accesskey="L" onclick="findRecord(4)" />
<input type="button" class="button" name="btnsave" style="float:right" value="Save" id="btnsave" accesskey="S" onclick="return ValidateSubmitForm('casedetails');" />
<c:if test="${formmode!=2}">
<c:if test="${pageFormMode!=2}">
<input type="button" class="button" name="btnadd" style="float:right" value="Add" id="btnadd" accesskey="A" onclick="return AddNewRecord();" />
</c:if>
</div>
</c:if>
</form>
<input type="hidden" id="formmode" name="formmode" value="${formmode}" />
<input type="hidden" id="usid" name="usid" value="${usid}" />
<input type="hidden" id="formmode" name="formmode" value="${pageFormMode}" />
<input type="hidden" id="usid" name="usid" value="${pageUserId}" />
<c:if test="${not empty model.errorDetails}">
<span id="page-error-code" hidden><c:out value="${model.errorDetails.errorCode}" /></span>
<span id="page-error-message" hidden><c:out value="${model.errorDetails.message}" /></span>
</c:if>
</div>
</div>
</body>
<!-- Page Load Javascript -->
<script language="javascript" type="text/javascript">
InitPage('${portfolio_id}','${visiblecontents}');
(function () {
var sectionFields = document.querySelectorAll(".model-visible-section");
if (sectionFields.length > 0) {
document.getElementById("visiblecontents").value = Array.prototype.map.call(
sectionFields, function (field) { return field.textContent; }
).join(ColDelim);
}
InitPage();
var errorCode = document.getElementById("page-error-code");
var errorMessage = document.getElementById("page-error-message");
if (errorCode && errorMessage) {
CallMessage(errorCode.textContent + ":error:" + errorMessage.textContent, 3000, 200, 300);
}
}());
</script>
<!-- -->
<!-- Process Message -->
<c:if test="${not empty msg}">
<script language="javascript" type="text/javascript"> CallMessage('${msg}',3000,200,300); </script>
</c:if>
<!-- -->
</html>

View File

@@ -17,7 +17,7 @@
<script language="javascript" src="/matrix/js/ajax.js" type="text/javascript"></script>
<script language="javascript" src="/matrix/js/ajax-dynamic-list.js" type="text/javascript"></script>
<script language="javascript" src="/matrix/js/appjs/punchingdoc.js" type="text/javascript"></script>
<script language="javascript" src="/matrix/js/validator.js" type="text/javascript"></script>
<script language="javascript" src="/matrix/js/validator.js?ver=2" type="text/javascript"></script>
<link href="/matrix/css/button.css" rel="stylesheet" type="text/css" />
<link href="/matrix/css/div.css" rel="stylesheet" type="text/css" />
@@ -30,7 +30,7 @@
<link href="/matrix/css/matrix-shell-v2.css?v=7" rel="stylesheet" type="text/css" />
<link href="/matrix/css/tool-workspace-v3.css?v=2" rel="stylesheet" type="text/css" />
<link href="/matrix/css/punch-workspace-v3.css?v=1" rel="stylesheet" type="text/css" />
<link href="/matrix/css/add-cases-v4.css?v=1" rel="stylesheet" type="text/css" />
<link href="/matrix/css/add-cases-v4.css?v=5" rel="stylesheet" type="text/css" />
<script src="/matrix/js/bootstrap-5.3.8.bundle.min.js" defer></script>
<script src="/matrix/js/matrix-shell-v2.js" defer></script>
<link href="/matrix/css/matrix-frame-dialog.css?v=6" rel="stylesheet" type="text/css" />

View File

@@ -1,29 +1,67 @@
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<!DOCTYPE html>
<html>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!doctype html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<script language="javascript" src="/matrix/js/lib/constants.js" type="text/javascript"></script>
<script language="javascript" src="/matrix/js/lib/jsfuncs.js" type="text/javascript"></script>
<script language="javascript" src="/matrix/js/jquery1.7.2.js" type="text/javascript"></script>
<script language="javascript" src="/matrix/js/ui/uifuncs.js" type="text/javascript"></script>
<script language="javascript" src="/matrix/js/validator.js" type="text/javascript"></script>
<link href="/matrix/css/matrix.css" rel="stylesheet" type="text/css" />
<link href="/matrix/css/div.css" rel="stylesheet" type="text/css" />
<title>Cygnus 1.0 | 404 Error</title>
<script src="/matrix/js/matrix-accessibility-v1.js?v=1" defer></script>
<link href="/matrix/css/matrix-theme-v2.css?v=4" rel="stylesheet" type="text/css" />
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link href="<c:url value='/css/bootstrap-5.3.8.min.css' />" rel="stylesheet" />
<link href="<c:url value='/css/matrix-v2.css' />" rel="stylesheet" />
<link href="<c:url value='/css/matrix-shell-v2.css' />" rel="stylesheet" />
<link href="<c:url value='/css/matrix-theme-v2.css' />" rel="stylesheet" />
<script src="<c:url value='/js/matrix-shell-v2.js' />" defer></script>
<title>Cygnus 1.0 | <c:out value="${empty errorDetails.title ? 'Page unavailable' : errorDetails.title}" /></title>
</head>
<body class="matrix-error-page">
<form method="post" name="unaccess" id="unaccess">
<main class="matrix-error-card" role="alert" aria-labelledby="errorTitle">
<div class="matrix-error-card__code">404</div>
<h1 id="errorTitle">Page unavailable</h1>
<p>Unauthorized access or the requested page could not be found.</p>
<button type="button" class="matrix-error-card__action" onclick="SubmitForm('logout', '_parent', 'unaccess');">Return to sign in</button>
</main>
</form>
<body class="matrix-v2 matrix-shell matrix-error-page matrix-error-page--${empty errorDetails.httpStatus ? 404 : errorDetails.httpStatus}">
<c:choose>
<c:when test="${not empty Sessvals and not empty Sessvals.menuHtml}">
<%@ include file="/WEB-INF/app/fragments/app-shell-header.jspf" %>
</c:when>
<c:otherwise>
<%@ include file="/WEB-INF/app/fragments/app-title.jspf" %>
</c:otherwise>
</c:choose>
<main class="matrix-error-layout" id="mainContent">
<section class="matrix-error-card" role="alert" aria-labelledby="errorTitle">
<div class="matrix-error-card__status" aria-hidden="true">
<span><c:out value="${empty errorDetails.httpStatus ? 404 : errorDetails.httpStatus}" /></span>
</div>
<div class="matrix-error-card__content">
<span class="matrix-error-card__eyebrow">
Error <c:out value="${empty errorDetails.errorCode ? 'HTTP-404' : errorDetails.errorCode}" />
</span>
<h1 id="errorTitle">
<c:out value="${empty errorDetails.title ? 'Page unavailable' : errorDetails.title}" />
</h1>
<p class="matrix-error-card__message">
<c:out value="${empty errorDetails.message ? 'The requested page is unavailable.' : errorDetails.message}" />
</p>
<c:if test="${not empty errorDetails.description}">
<p class="matrix-error-card__description"><c:out value="${errorDetails.description}" /></p>
</c:if>
<div class="matrix-error-card__actions">
<c:choose>
<c:when test="${not empty Sessvals and not empty Sessvals.menuHtml}">
<form method="post" action="<c:url value='/ver/dashboard' />">
<button type="submit" class="matrix-error-card__action">Return to dashboard</button>
</form>
<button type="button" class="matrix-error-card__action matrix-error-card__action--secondary" onclick="history.back()">Go back</button>
</c:when>
<c:otherwise>
<a class="matrix-error-card__action" href="<c:url value='/ver/login' />">Return to sign in</a>
</c:otherwise>
</c:choose>
</div>
<c:if test="${not empty errorDetails.referenceId}">
<p class="matrix-error-card__reference">
Reference ID: <code><c:out value="${errorDetails.referenceId}" /></code>
</p>
</c:if>
</div>
</section>
</main>
</body>
</html>

View File

@@ -38,6 +38,14 @@
</session-config>
<!-- Error Page -->
<error-page>
<error-code>404</error-code>
<location>/ver/error</location>
</error-page>
<error-page>
<error-code>500</error-code>
<location>/ver/error</location>
</error-page>
<error-page>
<exception-type>org.springframework.web.HttpSessionRequiredException</exception-type>
<location>/matrix/index.jsp</location>

View File

@@ -31,6 +31,12 @@
margin: 0;
}
/* Legacy forms separate labels and controls with a BR. In the modern flex
layout that BR becomes an extra flex row and creates an unintended gap. */
.matrix-add-cases .matrix-form-controls .widget > br {
display: none;
}
.matrix-add-cases .matrix-form-controls .widget[style*="display:none"] {
display: none !important;
}
@@ -89,3 +95,87 @@
.matrix-add-cases .matrix-form-controls .widget:has(#splist),
.matrix-add-cases .matrix-form-controls .widget:has(textarea) { grid-column: span 6; }
}
.matrix-add-cases .inputcontainer {
position: relative;
}
.matrix-add-cases .inputcontainer > input.errtxt,
.matrix-add-cases .inputcontainer > textarea.errtxt {
padding-right: 24px !important;
}
.matrix-add-cases .matrix-validation-error-icon {
display: block;
margin: 0 !important;
object-fit: contain;
}
.matrix-add-cases #section2 > .title,
.matrix-add-cases #section3 > .title,
.matrix-add-cases #section4 > .title,
.matrix-add-cases #section5 > .title,
.matrix-add-cases #section6 > .title,
.matrix-add-cases #section7 > .title {
box-sizing: border-box;
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 6px 8px;
min-height: 38px;
height: auto !important;
padding: 5px 10px !important;
}
.matrix-add-cases #section2 > .title > .matrix-title-icon,
.matrix-add-cases #section3 > .title > .matrix-title-icon,
.matrix-add-cases #section4 > .title > .matrix-title-icon,
.matrix-add-cases #section5 > .title > .matrix-title-icon,
.matrix-add-cases #section6 > .title > .matrix-title-icon,
.matrix-add-cases #section7 > .title > .matrix-title-icon {
display: block;
flex: 0 0 16px;
width: 16px;
height: 16px;
margin: 0 !important;
object-fit: contain;
vertical-align: middle;
}
.matrix-add-cases #section2 > .title > .matrix-title-text,
.matrix-add-cases #section3 > .title > .matrix-title-text,
.matrix-add-cases #section4 > .title > .matrix-title-text,
.matrix-add-cases #section5 > .title > .matrix-title-text,
.matrix-add-cases #section6 > .title > .matrix-title-text,
.matrix-add-cases #section7 > .title > .matrix-title-text {
line-height: 16px;
}
.matrix-add-cases #section2 > .title > .divchk,
.matrix-add-cases #section3 > .title > .divchk,
.matrix-add-cases #section4 > .title > .divchk,
.matrix-add-cases #section5 > .title > .divchk,
.matrix-add-cases #section7 > .title > .divchk {
position: static !important;
right: auto !important;
float: none !important;
display: inline-flex;
align-items: center;
align-self: center;
gap: 4px;
min-height: 18px;
margin: 0 0 0 auto !important;
padding: 0 !important;
line-height: 18px !important;
white-space: nowrap;
}
.matrix-add-cases #section2 > .title > .divchk input[type="checkbox"],
.matrix-add-cases #section3 > .title > .divchk input[type="checkbox"],
.matrix-add-cases #section4 > .title > .divchk input[type="checkbox"],
.matrix-add-cases #section5 > .title > .divchk input[type="checkbox"],
.matrix-add-cases #section7 > .title > .divchk input[type="checkbox"] {
flex: 0 0 auto;
margin-top: 0 !important;
margin-bottom: 0 !important;
vertical-align: middle;
}

View File

@@ -119,45 +119,103 @@ body .nav-menu {
}
body.matrix-error-page {
display: grid;
min-height: 100vh;
margin: 0;
padding: 24px;
place-items: center;
color: #263746;
background: var(--matrix-theme-canvas) !important;
font-family: Arial, sans-serif;
}
.matrix-error-card {
width: min(100%, 480px);
.matrix-error-layout {
min-height: calc(100vh - 84px);
display: grid;
place-items: center;
box-sizing: border-box;
padding: clamp(24px, 5vw, 64px) 20px;
}
.matrix-error-card {
width: min(100%, 760px);
display: grid;
grid-template-columns: 150px minmax(0, 1fr);
overflow: hidden;
box-sizing: border-box;
padding: 30px;
text-align: center;
border: 1px solid var(--matrix-theme-border);
border-radius: 8px;
border-radius: 10px;
background: var(--matrix-theme-panel);
box-shadow: 0 8px 24px rgba(31, 52, 72, .12);
}
.matrix-error-card__code {
color: #b42318;
font-size: 38px;
.matrix-error-card__status {
min-height: 290px;
display: grid;
place-items: center;
color: #fff;
background: #a4473f;
}
.matrix-error-page--401 .matrix-error-card__status,
.matrix-error-page--403 .matrix-error-card__status {
background: #a66a19;
}
.matrix-error-page--404 .matrix-error-card__status,
.matrix-error-page--405 .matrix-error-card__status {
background: #376b94;
}
.matrix-error-card__status span {
font-size: 42px;
font-weight: 800;
line-height: 1;
}
.matrix-error-card h1 {
margin: 10px 0 6px;
font-size: 21px;
.matrix-error-card__content {
padding: 34px 38px 28px;
}
.matrix-error-card p {
margin: 0 0 20px;
.matrix-error-card__eyebrow {
color: #607587;
font-size: 12px;
font-weight: 700;
letter-spacing: .08em;
text-transform: uppercase;
}
.matrix-error-card h1 {
margin: 8px 0 10px;
font-size: 25px;
}
.matrix-error-card__message {
margin: 0 0 8px;
color: #344b5e;
font-size: 15px;
font-weight: 600;
}
.matrix-error-card__description {
margin: 0;
color: #536779;
line-height: 1.55;
}
.matrix-error-card__actions {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 9px;
margin-top: 24px;
}
.matrix-error-card__actions form {
margin: 0;
}
.matrix-error-card__action {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 34px;
padding: 6px 16px;
color: #fff;
@@ -166,6 +224,55 @@ body.matrix-error-page {
background: #245a91;
font-weight: 700;
cursor: pointer;
text-decoration: none;
}
.matrix-error-card__action:hover,
.matrix-error-card__action:focus {
color: #fff;
background: #1d4e7d;
}
.matrix-error-card__action--secondary {
color: #334b5e;
border-color: #aebdca;
background: transparent;
}
.matrix-error-card__action--secondary:hover,
.matrix-error-card__action--secondary:focus {
color: #263746;
background: #e4edf4;
}
.matrix-error-card__reference {
margin: 22px 0 0;
padding-top: 14px;
color: #6b7e8e;
border-top: 1px solid #d5e0e8;
font-size: 11px;
}
.matrix-error-card__reference code {
color: inherit;
}
@media (max-width: 620px) {
.matrix-error-card {
grid-template-columns: 1fr;
}
.matrix-error-card__status {
min-height: 82px;
}
.matrix-error-card__status span {
font-size: 30px;
}
.matrix-error-card__content {
padding: 26px 24px;
}
}
/* Compact toolbars for legacy forms that place controls in their title bar. */

View File

@@ -12,14 +12,25 @@ AddrElem[1]=["ov","companyname","oaddr1","oaddr2","oaddr3","olandmark","Ocolony"
AddrElem[2]=["pv","paddr1","paddr2","paddr3","plandmark","Pcolony","colp","pcity","ppincode"];
AddrElem[3]=["bankcode","bankcode","bank_branch_id","product","loanamount","apptype","contactperson","specialinst"];
function InitPage(port,viscontents)
function InitPage()
{
var portField = document.getElementById("portfolio_id");
var initialPortField = document.getElementById("initial-portfolio-id");
var visibleContentsField = document.getElementById("visiblecontents");
var port = initialPortField ? initialPortField.value : (portField ? portField.value : "-1");
var viscontents = visibleContentsField ? visibleContentsField.value : "";
if (viscontents && viscontents.slice(-ColDelim.length) !== ColDelim) {
viscontents += ColDelim;
}
centerDivBoth("formcontainer","H");
HideSections('rtv');
HideSections('otv');
HideSections('section');
UnhideSections(viscontents,ColDelim);
$("#portfolio_id").val(port);
if (portField) {
$(portField).val(port);
}
if(port>0)
{
if(document.getElementById("ajaxmsg") !== null)

View File

@@ -91,28 +91,33 @@ function highlightField(el)
else
{
invalidFields=invalidFields+1;
var offset=null;
var width=null;
var iconHost=el.parentNode;
var current=el.parentNode;
while(current && current!==document.body)
{
var classes=" "+(current.className || "")+" ";
if(classes.indexOf(" inputcontainer ")!==-1)
{
iconHost=current;
break;
}
current=current.parentNode;
}
if(el.type=='text' || el.type=='textarea' || el.type=='password')
{
el.setAttribute("class", "textbox errtxt");
offset=$(el).offset();
width=el.offsetWidth;
}
else if(el.type=='select-one')
{
el.parentNode.setAttribute("class", "divselect errdiv");
tmpel=el.parentNode;
offset=$(tmpel).offset();
width=tmpel.offsetWidth;
}
var top=parseInt(offset.top)-5;
var left=parseInt(offset.left)+(width-5);
iconHost.style.position="relative";
var img=document.createElement("img");
img.setAttribute("src","/matrix/images/exclamation.png");
img.setAttribute("style","position:absolute;margin-left:"+left+"px;margin-top:"+top+"px;z-index:3");
img.setAttribute("class", "matrix-validation-error-icon"+(el.type=='select-one' ? " matrix-validation-error-icon--select" : ""));
img.setAttribute("style","position:absolute;right:"+(el.type=='select-one' ? "24px" : "5px")+";top:50%;width:14px;height:14px;transform:translateY(-50%);z-index:3;pointer-events:none");
img.setAttribute("id","err"+el.id);
document.body.appendChild(img);
iconHost.appendChild(img);
}
}

View File

@@ -23,6 +23,16 @@
</properties>
<dependencies>
<dependency>
<groupId>com.cygnus</groupId>
<artifactId>cygnus-lib</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.cygnus</groupId>
<artifactId>cygnus-onprem-db</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.cygnus</groupId>
<artifactId>cygnus-cloud-client</artifactId>

View File

@@ -0,0 +1,63 @@
package matrix.nimble.controller;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import lib.constants.ApplicationError;
import lib.models.ErrorDetails;
import matrix.nimble.model.Session;
import matrix.services.commons.CommonErrorService;
import matrix.services.commons.CommonService;
import org.springframework.ui.ModelMap;
/** Shared session and page-authorization workflow for authenticated controllers. */
public abstract class AbstractAuthenticatedController {
private final CommonService commonService;
private final CommonErrorService errorService;
protected AbstractAuthenticatedController(
CommonService commonService,
CommonErrorService errorService) {
this.commonService = commonService;
this.errorService = errorService;
}
protected final PageAuthorization authorizePage(
String pageRoute,
ModelMap model,
HttpSession httpSession,
HttpServletResponse response) {
Session session = commonService.getSession(httpSession);
if (session == null) {
return denied(model, response, httpSession, ApplicationError.SESSION_REQUIRED);
}
if (!commonService.hasPageAccess(session, pageRoute)) {
return denied(model, response, httpSession, ApplicationError.ACCESS_DENIED);
}
model.addAttribute(CommonService.SESSION_ATTRIBUTE, session);
return PageAuthorization.granted(session);
}
private PageAuthorization denied(
ModelMap model,
HttpServletResponse response,
HttpSession httpSession,
ApplicationError error) {
String viewName = errorService.render(
model, response, httpSession, new ErrorDetails(error));
return PageAuthorization.denied(viewName);
}
protected record PageAuthorization(Session session, String viewName) {
private static PageAuthorization granted(Session session) {
return new PageAuthorization(session, null);
}
private static PageAuthorization denied(String viewName) {
return new PageAuthorization(null, viewName);
}
public boolean isGranted() {
return session != null;
}
}
}

View File

@@ -0,0 +1,43 @@
package matrix.nimble.controller;
import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import lib.models.ErrorDetails;
import lib.constants.ApplicationError;
import matrix.services.commons.CommonErrorService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class ApplicationErrorController {
private final CommonErrorService errorService;
public ApplicationErrorController(CommonErrorService errorService) {
this.errorService = errorService;
}
@RequestMapping("error")
public String error(
ModelMap model,
HttpServletRequest request,
HttpServletResponse response,
HttpSession httpSession) {
int status = status(request);
return errorService.render(model, response, httpSession, details(status));
}
private int status(HttpServletRequest request) {
Object value = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
if (value instanceof Integer status && status >= 400 && status <= 599) {
return status;
}
return HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
}
private ErrorDetails details(int status) {
return new ErrorDetails(ApplicationError.fromHttpStatus(status));
}
}

View File

@@ -4,6 +4,7 @@ import matrix.nimble.cloud.identity.CloudAuthenticationException;
import matrix.nimble.cloud.identity.CloudAuthenticationGateway;
import matrix.nimble.model.Login;
import matrix.nimble.model.Session;
import matrix.services.commons.CommonService;
//servlet libraries
import jakarta.servlet.http.HttpServletRequest;
@@ -21,9 +22,13 @@ import org.springframework.web.bind.annotation.SessionAttributes;
@SessionAttributes("Sessvals")
public class SessionController {
private final CloudAuthenticationGateway cloudAuthenticationGateway;
private final CommonService commonService;
public SessionController(CloudAuthenticationGateway cloudAuthenticationGateway) {
public SessionController(
CloudAuthenticationGateway cloudAuthenticationGateway,
CommonService commonService) {
this.cloudAuthenticationGateway = cloudAuthenticationGateway;
this.commonService = commonService;
}
@RequestMapping(value="login", method={RequestMethod.GET, RequestMethod.POST})
public String LoginPage(ModelMap model)
@@ -42,13 +47,17 @@ public class SessionController {
return "login";
}
@RequestMapping(value="authenticatelogin",method=RequestMethod.POST )
public String Authenticate(ModelMap model,@ModelAttribute(value="login") Login login)
public String Authenticate(
ModelMap model,
@ModelAttribute(value="login") Login login,
HttpSession httpSession)
{
try {
Session cloudSession = cloudAuthenticationGateway.authenticate(
login.getLoginid(), login.getPassword());
model.remove("login");
model.addAttribute("Sessvals", cloudSession);
commonService.storeSession(httpSession, cloudSession);
return "home";
} catch (CloudAuthenticationException exception) {
login.setErrMsg(exception.reason()

View File

@@ -1,161 +0,0 @@
package matrix.nimble.edp.punching.controller;
//servlet libraries
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
//spring libraries
import matrix.nimble.edp.punching.model.CaseDetails;
import matrix.nimble.model.Session;
import matrix.nimble.utilities.ModuleFunctions;
import matrix.nimble.utilities.GlobalClass;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.SessionAttributes;
@Controller
@SessionAttributes({"Sessvals"})
public class CasePucnhing {
@RequestMapping(value="caseadd1",method=RequestMethod.POST )
public String OpenAddCases(ModelMap model,HttpServletRequest request,@ModelAttribute(value="Sessvals") Session Sessvals)
{
CaseDetails caseDetails=new CaseDetails();
caseDetails.setFormMode(1);
caseDetails.setErrMsg("");
model.addAttribute("visibleContents","");
model.addAttribute("caseDetails",caseDetails);
model.addAttribute("branchlist",FillBranchList("0"));
model.addAttribute("prodlist",FillProductList("0"));
model.addAttribute("citylist",FillCityList("0"));
model.addAttribute("typelist",FillApptypeList("0"));
model.addAttribute("doclist",FillDocList("0"));
model.addAttribute("catlist",FillCatList("0"));
model.addAttribute("Sessvals",Sessvals);
model.addAttribute("portlist",FillPortList(Sessvals.getBranchID()));
return "edp/punching/addcases";
}
@RequestMapping(value="casesave",method=RequestMethod.POST )
public String SaveCase(ModelMap model,HttpServletRequest request,@ModelAttribute(value="Sessvals") Session Sessvals,@ModelAttribute(value="caseDetails") CaseDetails caseDet,HttpSession session)
{
String DynamicNsessVals="";
CaseDetails caseDetails=new CaseDetails();
caseDet.setErrCode("1001");
if(caseDet.getDynamicFields()!="")
{
String [] DynamicFields=caseDet.getDynamicFields().split(GlobalClass.ColDelim);
for(int indx=0;indx<DynamicFields.length;indx++)
{
//DynamicNsessVals=DynamicNsessVals+request.getParameter(DynamicFields[indx])+Delimeter.ColDelim;
caseDet.InvokeSetter("set"+DynamicFields[indx], request.getParameter(DynamicFields[indx]));
}
}
if(caseDet.getFormMode()==1 || caseDet.getFormMode()==2)
{
DynamicNsessVals=DynamicNsessVals+""+Sessvals.getUserID()+GlobalClass.ColDelim;
DynamicNsessVals=DynamicNsessVals+""+Sessvals.getCompanyID()+GlobalClass.ColDelim;
DynamicNsessVals=DynamicNsessVals+""+Sessvals.getBranchID();
}
if(!session.getAttribute("UUID").toString().equals(caseDet.getUUID()))
{
caseDetails.setProcessFlag(true);
caseDetails.setErrMsg("");
}
else
{
caseDet.SaveDetails(DynamicNsessVals);
caseDetails.setProcessFlag(caseDet.isProcessFlag());
caseDetails.setErrMsg(caseDet.getErrMsg());
}
String VisibleContents=caseDet.getVisibleContents();
if(caseDetails.isProcessFlag())
{
String UUID=GlobalClass.GenerateUUID();
caseDetails.setUUID(UUID);
session.setAttribute("UUID", UUID);
caseDetails.setErrCode("1001");
caseDetails.setFormMode(caseDet.getFormMode());
caseDetails.setVisibleContents(VisibleContents);
caseDetails.setPortfolioId(caseDet.getPortfolioId());
caseDetails=caseDetails.DynamicHtml(caseDetails,caseDet.getPortfolioId());
caseDetails.setDynamicFields(caseDet.getDynamicFields());
VisibleContents=VisibleContents+""+caseDetails.getDynamicSection();
model.addAttribute("caseDetails",caseDetails);
}
else
{
caseDet=caseDet.DynamicHtml(caseDet,caseDet.getPortfolioId());
VisibleContents=VisibleContents+""+caseDet.getDynamicSection();
model.addAttribute("caseDetails",caseDet);
}
model.addAttribute("visibleContents",VisibleContents);
model.addAttribute("branchlist",FillBranchList(caseDet.getPortfolioId()));
model.addAttribute("prodlist",FillProductList(caseDet.getPortfolioId()));
model.addAttribute("citylist",FillCityList(Sessvals.getBranchID()));
model.addAttribute("typelist",FillApptypeList(Sessvals.getBranchID()));
model.addAttribute("doclist",FillDocList(caseDet.getPortfolioId()));
model.addAttribute("catlist",FillCatList(caseDet.getPortfolioId()));
model.addAttribute("Sessvals",Sessvals);
model.addAttribute("portlist",FillPortList(Sessvals.getBranchID()));
return "edp/punching/addcases";
}
@RequestMapping(value="casedet",method=RequestMethod.POST )
public String GetRequiredData(ModelMap model,HttpServletRequest request,@ModelAttribute(value="Sessvals") Session Sessvals,@ModelAttribute(value="caseDetails") CaseDetails caseDet,HttpSession session)
{
CaseDetails caseDetails = new CaseDetails();
caseDetails.setPortfolioId(caseDet.getPortfolioId());
caseDetails.setErrCode("1001");
String UUID=GlobalClass.GenerateUUID();
caseDetails.setUUID(UUID);
session.setAttribute("UUID", UUID);
caseDetails.setFormMode(caseDet.getFormMode());
caseDetails=caseDetails.DynamicHtml(caseDetails,caseDet.getPortfolioId());
String VisibleContents=(new ModuleFunctions("1001").GetDDHashString(10, ("10"+GlobalClass.ColDelim+caseDet.getPortfolioId()).split(GlobalClass.ColDelim))).replace(GlobalClass.RowDelim, "");
caseDetails.setVisibleContents(VisibleContents);
VisibleContents=(VisibleContents+""+caseDetails.getDynamicSection()).replace(GlobalClass.ColDelim+"null", "");
model.addAttribute("visibleContents",VisibleContents);
model.addAttribute("caseDetails",caseDetails);
model.addAttribute("portlist",Sessvals.getBranchID());
model.addAttribute("branchlist",FillBranchList(caseDet.getPortfolioId()));
model.addAttribute("prodlist",FillProductList(caseDet.getPortfolioId()));
model.addAttribute("citylist",FillCityList(Sessvals.getBranchID()));
model.addAttribute("typelist",FillApptypeList(Sessvals.getBranchID()));
model.addAttribute("doclist",FillDocList(caseDet.getPortfolioId()));
model.addAttribute("catlist",FillCatList(caseDet.getPortfolioId()));
model.addAttribute("Sessvals",Sessvals);
model.addAttribute("portlist",FillPortList(Sessvals.getBranchID()));
return "edp/punching/addcases";
}
public String [][] FillPortList(String BranchId)
{
return new ModuleFunctions("1001").GetResultArray(3,(BranchId+GlobalClass.ColDelim).split(GlobalClass.ColDelim));
}
public String [][] FillCityList(String BranchID)
{
return new ModuleFunctions("1001").GetResultArray(6,BranchID.split(GlobalClass.ColDelim));
}
public String [][] FillApptypeList(String BranchID)
{
return new ModuleFunctions("1001").GetResultArray(8,BranchID.split(GlobalClass.ColDelim));
}
public String [][] FillCatList(String PortfolioID)
{
return new ModuleFunctions("1001").GetResultArray(9,PortfolioID.split(GlobalClass.ColDelim));
}
public String [][] FillDocList(String PortfolioID)
{
return new ModuleFunctions("1001").GetResultArray(7,PortfolioID.split(GlobalClass.ColDelim));
}
public String [][] FillBranchList(String PortfolioID)
{
return new ModuleFunctions("1001").GetResultArray(4,PortfolioID.split(GlobalClass.ColDelim));
}
public String [][] FillProductList(String PortfolioID)
{
return new ModuleFunctions("1001").GetResultArray(5,PortfolioID.split(GlobalClass.ColDelim));
}
}

View File

@@ -1,60 +0,0 @@
package matrix.nimble.edp.punching.controller;
import java.util.HashMap;
import jakarta.servlet.http.HttpServletRequest;
import matrix.nimble.edp.punching.model.CaseGrid;
import matrix.nimble.model.Session;
import matrix.nimble.utilities.GlobalClass;
import matrix.nimble.utilities.ModuleFunctions;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.SessionAttributes;
@Controller
@SessionAttributes({"Sessvals"})
public class EditCasesGrid {
@RequestMapping(value="editgrid",method=RequestMethod.POST )
public String OpenEditGrid(ModelMap model,HttpServletRequest request,@ModelAttribute(value="Sessvals") Session Sessvals)
{
String [][]CaseList=null;
CaseGrid CG=new CaseGrid();
model.addAttribute("Sessvals",Sessvals);
model.addAttribute("caseList",CaseList);
model.addAttribute("caseGrid",CG);
model.addAttribute("portlist",FillPortList(Sessvals.getBranchID(),Sessvals.getUserID()));
return "edp/punching/editcases";
}
@RequestMapping(value="caselist",method=RequestMethod.POST)
public String ListCases(ModelMap model,HttpServletRequest request,@ModelAttribute(value="Sessvals") Session Sessvals,@ModelAttribute(value="caseGrid") CaseGrid caseGrid)
{
CaseGrid CG=new CaseGrid();
CG.setErrCode("1002");
CG.setPortfolioId(caseGrid.getPortfolioId());
String [][]CaseList=CG.FillCaseList(caseGrid.getPortfolioId(), Sessvals.getBranchID());
if(CG.isProcessFlag())
{
model.addAttribute("caseList",CaseList);
model.addAttribute("caseGrid",CG);
}
else
{
caseGrid.setErrMsg(CG.getErrMsg());
model.addAttribute("caseList",CaseList);
model.addAttribute("caseGrid",caseGrid);
}
model.addAttribute("Sessvals",Sessvals);
model.addAttribute("portlist",FillPortList(Sessvals.getBranchID(),Sessvals.getUserID()));
return "edp/punching/editcases";
}
public String [][] FillPortList(String BranchId,String userId)
{
return new ModuleFunctions("1002").GetResultArray(249,(BranchId+GlobalClass.ColDelim+userId+GlobalClass.ColDelim).split(GlobalClass.ColDelim));
}
}

View File

@@ -1,278 +0,0 @@
package matrix.nimble.edp.punching.controller;
//servlet libraries
import java.net.URLDecoder;
import jakarta.servlet.http.HttpServletRequest;
//spring libraries
import matrix.nimble.edp.punching.model.PunchingHandler;
import matrix.nimble.model.Session;
import matrix.nimble.utilities.CommonFunctions;
import matrix.nimble.utilities.GlobalClass;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.SessionAttributes;
@Controller
@SessionAttributes({"Sessvals"})
public class Punching {
@RequestMapping(value="initview",method=RequestMethod.POST )
public String InitializeScreen(ModelMap model,HttpServletRequest request,@ModelAttribute(value="Sessvals") Session Sessvals)
{
PunchingHandler PH=new PunchingHandler();
PH.setErrCode("1001");
model.addAttribute("visiblecontents","");
model.addAttribute("branchlist",null);
model.addAttribute("prodlist",null);
model.addAttribute("citylist",null);
model.addAttribute("typelist",null);
model.addAttribute("doclist",null);
model.addAttribute("catlist",null);
model.addAttribute("portfolio_id","-1");
model.addAttribute("Sessvals",Sessvals);
PH.GetDDValues(3, Sessvals.getBranchID().split(GlobalClass.ColDelim));
model.addAttribute("portlist",PH.getOptionvals());
model.addAttribute("formmode",0);
model.addAttribute("stat",true);
model.addAttribute("duuid","xxx");
model.addAttribute("msg","");
return "edp/punching/initcase";
}
@RequestMapping(value="initdocview",method=RequestMethod.POST )
public String InitDocScreen(ModelMap model,HttpServletRequest request,@ModelAttribute(value="Sessvals") Session Sessvals)
{
PunchingHandler PH=new PunchingHandler();
PH.setErrCode("1901");
model.addAttribute("branchlist",null);
model.addAttribute("prodlist",null);
model.addAttribute("typelist",null);
model.addAttribute("doclist",null);
model.addAttribute("catlist",null);
model.addAttribute("portfolio_id","-1");
model.addAttribute("Sessvals",Sessvals);
PH.GetDDValues(240, Sessvals.getBranchID().split(GlobalClass.ColDelim));
model.addAttribute("portlist",PH.getOptionvals());
model.addAttribute("formmode",0);
model.addAttribute("stat",true);
model.addAttribute("msg","");
return "edp/punching/initdocs";
}
@RequestMapping(value="docadd",method=RequestMethod.POST )
public String PunchDocs(ModelMap model,HttpServletRequest request,@ModelAttribute(value="Sessvals") Session Sessvals)
{
String msg="";
PunchingHandler PH=new PunchingHandler();
CommonFunctions comFunc=new CommonFunctions();
PH.setErrCode("1901");
PH.setPortfolioid(request.getParameter("portfolio_id"));
PH.setProcessFlag(true);
PH.DocDynamicHtml();
if(PH.isProcessFlag())
{
model.addAttribute("dynamichtml",PH.getDynamicpanel());
model.addAttribute("dynamicfields",PH.getDynamicfields());
model.addAttribute("docdynamichtml",PH.getDocdynamicpanel());
model.addAttribute("docdynamicfields",PH.getDocdynamicfields());
}
else
{
msg=PH.getErrMsg();
}
PH.GetDDValues(240, Sessvals.getBranchID().split(GlobalClass.ColDelim));
model.addAttribute("portlist",PH.getOptionvals());
PH.GetDDValues(4, PH.getPortfolioid().split(GlobalClass.ColDelim));
model.addAttribute("branchlist",PH.getOptionvals());
PH.GetDDValues(5, (Sessvals.getBranchID()+GlobalClass.ColDelim+"op.description='DOCUMENT' or op.description='APPTYPE'"+GlobalClass.ColDelim).split(GlobalClass.ColDelim));
if(PH.getOptionvals()!=null)
{
model.addAttribute("typelist",comFunc.GetSubArray("APPTYPE", 2, PH.getOptionvals()));
}
else
{
model.addAttribute("typelist",null);
}
PH.GetDDValues(241, PH.getPortfolioid().split(GlobalClass.ColDelim));
model.addAttribute("doclist",PH.getOptionvals());
PH.GetDDValues(70, (PH.getPortfolioid()+GlobalClass.ColDelim+"op.description='CATEGORY' or op.description='PRODUCT'"+GlobalClass.ColDelim).split(GlobalClass.ColDelim));
if(PH.getOptionvals()!=null)
{
model.addAttribute("prodlist",comFunc.GetSubArray("PRODUCT", 2, PH.getOptionvals()));
model.addAttribute("catlist",comFunc.GetSubArray("CATEGORY", 2, PH.getOptionvals()));
}
else
{
model.addAttribute("prodlist",null);
model.addAttribute("catlist",null);
}
model.addAttribute("portfolio_id",PH.getPortfolioid());
model.addAttribute("usid",Sessvals.getUserID());
model.addAttribute("Sessvals",Sessvals);
model.addAttribute("formmode",1);
model.addAttribute("stat",PH.isProcessFlag());
model.addAttribute("msg",msg);
return "edp/punching/initdocs";
}
@RequestMapping(value="addeditdoc",method=RequestMethod.POST )
public String AddEditDocs(@RequestParam String val0,@RequestParam String val1,@RequestParam String val2,@RequestParam String val3,@RequestParam String val4,@RequestParam String val5,@RequestParam String val6,@RequestParam String val7,@RequestParam String val8,@RequestParam String val9,@RequestParam String val10)
{
String retval="success";
try
{
//System.out.println(URLDecoder.decode(val0,"UTF-8"));
//System.out.println(URLDecoder.decode(val1,"UTF-8"));
//System.out.println(URLDecoder.decode(val2,"UTF-8"));
//System.out.println(URLDecoder.decode(val3,"UTF-8"));
//System.out.println(URLDecoder.decode(val4,"UTF-8"));
//System.out.println(URLDecoder.decode(val6,"UTF-8"));
//System.out.println(URLDecoder.decode(val7,"UTF-8"));
//System.out.println(URLDecoder.decode(val8,"UTF-8"));
//System.out.println(URLDecoder.decode(val9,"UTF-8"));
//System.out.println(URLDecoder.decode(val10,"UTF-8"));
PunchingHandler PH=new PunchingHandler();
PH.setErrCode("1902");
PH.setProcessFlag(true);
PH.AddEditDocs(val0, val1, val2, val3, val4, val6, val7, val8, val9, val10);
}catch(Exception exce)
{
retval=exce.getMessage();
}
return retval;
}
@RequestMapping(value="caseadd",method=RequestMethod.POST )
public String PunchCase(ModelMap model,HttpServletRequest request,@ModelAttribute(value="Sessvals") Session Sessvals)
{
String msg="";
PunchingHandler PH=new PunchingHandler();
CommonFunctions comFunc=new CommonFunctions();
PH.setErrCode("1001");
PH.setPortfolioid(request.getParameter("portfolio_id"));
PH.setProcessFlag(true);
PH.DynamicHtml("10");
if(PH.isProcessFlag())
{
model.addAttribute("dynamicfields",PH.getDynamicfields());
if(PH.isProcessFlag())
{
model.addAttribute("visiblecontents",(PH.GetVisibleSections()+""+PH.getDynamicsection()).replace("null", ""));
model.addAttribute("dynamichtml",PH.getDynamicpanel());
}
else
{
msg=PH.getErrMsg();
}
}
else
{
msg=PH.getErrMsg();
}
PH.GetDDValues(3, Sessvals.getBranchID().split(GlobalClass.ColDelim));
model.addAttribute("portlist",PH.getOptionvals());
PH.GetDDValues(4, PH.getPortfolioid().split(GlobalClass.ColDelim));
model.addAttribute("branchlist",PH.getOptionvals());
PH.GetDDValues(5, (Sessvals.getBranchID()+GlobalClass.ColDelim+"op.description='CITY' or op.description='APPTYPE'"+GlobalClass.ColDelim).split(GlobalClass.ColDelim));
if(PH.getOptionvals()!=null)
{
model.addAttribute("citylist",comFunc.GetSubArray("CITY", 2, PH.getOptionvals()));
model.addAttribute("typelist",comFunc.GetSubArray("APPTYPE", 2, PH.getOptionvals()));
}
else
{
model.addAttribute("citylist",null);
model.addAttribute("typelist",null);
}
PH.GetDDValues(70, (PH.getPortfolioid()+GlobalClass.ColDelim+"op.description='CATEGORY' or op.description='PRODUCT'"+GlobalClass.ColDelim).split(GlobalClass.ColDelim));
if(PH.getOptionvals()!=null)
{
model.addAttribute("prodlist",comFunc.GetSubArray("PRODUCT", 2, PH.getOptionvals()));
model.addAttribute("catlist",comFunc.GetSubArray("CATEGORY", 2, PH.getOptionvals()));
}
else
{
model.addAttribute("prodlist",null);
model.addAttribute("catlist",null);
}
model.addAttribute("portfolio_id",PH.getPortfolioid());
model.addAttribute("doclist",null);
model.addAttribute("usid",Sessvals.getUserID());
model.addAttribute("duuid","xxx");
model.addAttribute("Sessvals",Sessvals);
model.addAttribute("formmode",1);
model.addAttribute("stat",PH.isProcessFlag());
model.addAttribute("msg",msg);
return "edp/punching/initcase";
}
@RequestMapping(value="caseedit",method=RequestMethod.POST )
public String EditCase(ModelMap model,HttpServletRequest request,@ModelAttribute(value="Sessvals") Session Sessvals)
{
String msg="";
PunchingHandler PH=new PunchingHandler();
CommonFunctions comFunc=new CommonFunctions();
PH.setErrCode("1001");
PH.setPortfolioid(request.getParameter("PortfolioId"));
PH.setProcessFlag(true);
PH.DynamicHtml("10");
if(PH.isProcessFlag())
{
model.addAttribute("dynamicfields",PH.getDynamicfields());
if(PH.isProcessFlag())
{
model.addAttribute("visiblecontents",(PH.GetVisibleSections()+""+PH.getDynamicsection()).replace("null", ""));
model.addAttribute("dynamichtml",PH.getDynamicpanel());
}
else
{
msg=PH.getErrMsg();
}
}
else
{
msg=PH.getErrMsg();
}
PH.GetDDValues(3, Sessvals.getBranchID().split(GlobalClass.ColDelim));
model.addAttribute("portlist",PH.getOptionvals());
PH.GetDDValues(4, PH.getPortfolioid().split(GlobalClass.ColDelim));
model.addAttribute("branchlist",PH.getOptionvals());
PH.GetDDValues(5, (Sessvals.getBranchID()+GlobalClass.ColDelim+"op.description='CITY' or op.description='APPTYPE'"+GlobalClass.ColDelim).split(GlobalClass.ColDelim));
if(PH.getOptionvals()!=null)
{
model.addAttribute("citylist",comFunc.GetSubArray("CITY", 2, PH.getOptionvals()));
model.addAttribute("typelist",comFunc.GetSubArray("APPTYPE", 2, PH.getOptionvals()));
}
else
{
model.addAttribute("citylist",null);
model.addAttribute("typelist",null);
}
PH.GetDDValues(70, (PH.getPortfolioid()+GlobalClass.ColDelim+"op.description='CATEGORY' or op.description='PRODUCT'"+GlobalClass.ColDelim).split(GlobalClass.ColDelim));
if(PH.getOptionvals()!=null)
{
model.addAttribute("prodlist",comFunc.GetSubArray("PRODUCT", 2, PH.getOptionvals()));
model.addAttribute("catlist",comFunc.GetSubArray("CATEGORY", 2, PH.getOptionvals()));
}
else
{
model.addAttribute("prodlist",null);
model.addAttribute("catlist",null);
}
model.addAttribute("portfolio_id",PH.getPortfolioid());
model.addAttribute("doclist",null);
model.addAttribute("usid",Sessvals.getUserID());
model.addAttribute("Sessvals",Sessvals);
model.addAttribute("formmode",2);
model.addAttribute("duuid",request.getParameter("uuid"));
model.addAttribute("stat",PH.isProcessFlag());
model.addAttribute("msg",msg);
return "edp/punching/initcase";
}
}

View File

@@ -0,0 +1,160 @@
package matrix.nimble.edp.punching.controllers;
//servlet libraries
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
//spring libraries
import matrix.nimble.edp.punching.model.CaseDetails;
import matrix.nimble.model.Session;
import matrix.nimble.utilities.ModuleFunctions;
import matrix.nimble.utilities.GlobalClass;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.SessionAttributes;
@Controller
@SessionAttributes({ "Sessvals" })
public class CasePucnhing {
@RequestMapping(value = "caseadd1", method = RequestMethod.POST)
public String OpenAddCases(ModelMap model, HttpServletRequest request,
@ModelAttribute(value = "Sessvals") Session Sessvals) {
CaseDetails caseDetails = new CaseDetails();
caseDetails.setFormMode(1);
caseDetails.setErrMsg("");
model.addAttribute("visibleContents", "");
model.addAttribute("caseDetails", caseDetails);
model.addAttribute("branchlist", FillBranchList("0"));
model.addAttribute("prodlist", FillProductList("0"));
model.addAttribute("citylist", FillCityList("0"));
model.addAttribute("typelist", FillApptypeList("0"));
model.addAttribute("doclist", FillDocList("0"));
model.addAttribute("catlist", FillCatList("0"));
model.addAttribute("Sessvals", Sessvals);
model.addAttribute("portlist", FillPortList(Sessvals.getBranchID()));
return "edp/punching/addcases";
}
@RequestMapping(value = "casesave", method = RequestMethod.POST)
public String SaveCase(ModelMap model, HttpServletRequest request,
@ModelAttribute(value = "Sessvals") Session Sessvals,
@ModelAttribute(value = "caseDetails") CaseDetails caseDet, HttpSession session) {
String DynamicNsessVals = "";
CaseDetails caseDetails = new CaseDetails();
caseDet.setErrCode("1001");
if (caseDet.getDynamicFields() != "") {
String[] DynamicFields = caseDet.getDynamicFields().split(GlobalClass.ColDelim);
for (int indx = 0; indx < DynamicFields.length; indx++) {
// DynamicNsessVals=DynamicNsessVals+request.getParameter(DynamicFields[indx])+Delimeter.ColDelim;
caseDet.InvokeSetter("set" + DynamicFields[indx], request.getParameter(DynamicFields[indx]));
}
}
if (caseDet.getFormMode() == 1 || caseDet.getFormMode() == 2) {
DynamicNsessVals = DynamicNsessVals + "" + Sessvals.getUserID() + GlobalClass.ColDelim;
DynamicNsessVals = DynamicNsessVals + "" + Sessvals.getCompanyID() + GlobalClass.ColDelim;
DynamicNsessVals = DynamicNsessVals + "" + Sessvals.getBranchID();
}
if (!session.getAttribute("UUID").toString().equals(caseDet.getUUID())) {
caseDetails.setProcessFlag(true);
caseDetails.setErrMsg("");
} else {
caseDet.SaveDetails(DynamicNsessVals);
caseDetails.setProcessFlag(caseDet.isProcessFlag());
caseDetails.setErrMsg(caseDet.getErrMsg());
}
String VisibleContents = caseDet.getVisibleContents();
if (caseDetails.isProcessFlag()) {
String UUID = GlobalClass.GenerateUUID();
caseDetails.setUUID(UUID);
session.setAttribute("UUID", UUID);
caseDetails.setErrCode("1001");
caseDetails.setFormMode(caseDet.getFormMode());
caseDetails.setVisibleContents(VisibleContents);
caseDetails.setPortfolioId(caseDet.getPortfolioId());
caseDetails = caseDetails.DynamicHtml(caseDetails, caseDet.getPortfolioId());
caseDetails.setDynamicFields(caseDet.getDynamicFields());
VisibleContents = VisibleContents + "" + caseDetails.getDynamicSection();
model.addAttribute("caseDetails", caseDetails);
} else {
caseDet = caseDet.DynamicHtml(caseDet, caseDet.getPortfolioId());
VisibleContents = VisibleContents + "" + caseDet.getDynamicSection();
model.addAttribute("caseDetails", caseDet);
}
model.addAttribute("visibleContents", VisibleContents);
model.addAttribute("branchlist", FillBranchList(caseDet.getPortfolioId()));
model.addAttribute("prodlist", FillProductList(caseDet.getPortfolioId()));
model.addAttribute("citylist", FillCityList(Sessvals.getBranchID()));
model.addAttribute("typelist", FillApptypeList(Sessvals.getBranchID()));
model.addAttribute("doclist", FillDocList(caseDet.getPortfolioId()));
model.addAttribute("catlist", FillCatList(caseDet.getPortfolioId()));
model.addAttribute("Sessvals", Sessvals);
model.addAttribute("portlist", FillPortList(Sessvals.getBranchID()));
return "edp/punching/addcases";
}
@RequestMapping(value = "casedet", method = RequestMethod.POST)
public String GetRequiredData(ModelMap model, HttpServletRequest request,
@ModelAttribute(value = "Sessvals") Session Sessvals,
@ModelAttribute(value = "caseDetails") CaseDetails caseDet, HttpSession session) {
CaseDetails caseDetails = new CaseDetails();
caseDetails.setPortfolioId(caseDet.getPortfolioId());
caseDetails.setErrCode("1001");
String UUID = GlobalClass.GenerateUUID();
caseDetails.setUUID(UUID);
session.setAttribute("UUID", UUID);
caseDetails.setFormMode(caseDet.getFormMode());
caseDetails = caseDetails.DynamicHtml(caseDetails, caseDet.getPortfolioId());
String VisibleContents = (new ModuleFunctions("1001").GetDDHashString(10,
("10" + GlobalClass.ColDelim + caseDet.getPortfolioId()).split(GlobalClass.ColDelim)))
.replace(GlobalClass.RowDelim, "");
caseDetails.setVisibleContents(VisibleContents);
VisibleContents = (VisibleContents + "" + caseDetails.getDynamicSection())
.replace(GlobalClass.ColDelim + "null", "");
model.addAttribute("visibleContents", VisibleContents);
model.addAttribute("caseDetails", caseDetails);
model.addAttribute("portlist", Sessvals.getBranchID());
model.addAttribute("branchlist", FillBranchList(caseDet.getPortfolioId()));
model.addAttribute("prodlist", FillProductList(caseDet.getPortfolioId()));
model.addAttribute("citylist", FillCityList(Sessvals.getBranchID()));
model.addAttribute("typelist", FillApptypeList(Sessvals.getBranchID()));
model.addAttribute("doclist", FillDocList(caseDet.getPortfolioId()));
model.addAttribute("catlist", FillCatList(caseDet.getPortfolioId()));
model.addAttribute("Sessvals", Sessvals);
model.addAttribute("portlist", FillPortList(Sessvals.getBranchID()));
return "edp/punching/addcases";
}
public String[][] FillPortList(String BranchId) {
return new ModuleFunctions("1001").GetResultArray(3,
(BranchId + GlobalClass.ColDelim).split(GlobalClass.ColDelim));
}
public String[][] FillCityList(String BranchID) {
return new ModuleFunctions("1001").GetResultArray(6, BranchID.split(GlobalClass.ColDelim));
}
public String[][] FillApptypeList(String BranchID) {
return new ModuleFunctions("1001").GetResultArray(8, BranchID.split(GlobalClass.ColDelim));
}
public String[][] FillCatList(String PortfolioID) {
return new ModuleFunctions("1001").GetResultArray(9, PortfolioID.split(GlobalClass.ColDelim));
}
public String[][] FillDocList(String PortfolioID) {
return new ModuleFunctions("1001").GetResultArray(7, PortfolioID.split(GlobalClass.ColDelim));
}
public String[][] FillBranchList(String PortfolioID) {
return new ModuleFunctions("1001").GetResultArray(4, PortfolioID.split(GlobalClass.ColDelim));
}
public String[][] FillProductList(String PortfolioID) {
return new ModuleFunctions("1001").GetResultArray(5, PortfolioID.split(GlobalClass.ColDelim));
}
}

View File

@@ -0,0 +1,60 @@
package matrix.nimble.edp.punching.controllers;
import java.util.HashMap;
import jakarta.servlet.http.HttpServletRequest;
import matrix.nimble.edp.punching.model.CaseGrid;
import matrix.nimble.model.Session;
import matrix.nimble.utilities.GlobalClass;
import matrix.nimble.utilities.ModuleFunctions;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.SessionAttributes;
@Controller
@SessionAttributes({ "Sessvals" })
public class EditCasesGrid {
@RequestMapping(value = "editgrid", method = RequestMethod.POST)
public String OpenEditGrid(ModelMap model, HttpServletRequest request,
@ModelAttribute(value = "Sessvals") Session Sessvals) {
String[][] CaseList = null;
CaseGrid CG = new CaseGrid();
model.addAttribute("Sessvals", Sessvals);
model.addAttribute("caseList", CaseList);
model.addAttribute("caseGrid", CG);
model.addAttribute("portlist", FillPortList(Sessvals.getBranchID(), Sessvals.getUserID()));
return "edp/punching/editcases";
}
@RequestMapping(value = "caselist", method = RequestMethod.POST)
public String ListCases(ModelMap model, HttpServletRequest request,
@ModelAttribute(value = "Sessvals") Session Sessvals,
@ModelAttribute(value = "caseGrid") CaseGrid caseGrid) {
CaseGrid CG = new CaseGrid();
CG.setErrCode("1002");
CG.setPortfolioId(caseGrid.getPortfolioId());
String[][] CaseList = CG.FillCaseList(caseGrid.getPortfolioId(), Sessvals.getBranchID());
if (CG.isProcessFlag()) {
model.addAttribute("caseList", CaseList);
model.addAttribute("caseGrid", CG);
} else {
caseGrid.setErrMsg(CG.getErrMsg());
model.addAttribute("caseList", CaseList);
model.addAttribute("caseGrid", caseGrid);
}
model.addAttribute("Sessvals", Sessvals);
model.addAttribute("portlist", FillPortList(Sessvals.getBranchID(), Sessvals.getUserID()));
return "edp/punching/editcases";
}
public String[][] FillPortList(String BranchId, String userId) {
return new ModuleFunctions("1002").GetResultArray(249,
(BranchId + GlobalClass.ColDelim + userId + GlobalClass.ColDelim).split(GlobalClass.ColDelim));
}
}

View File

@@ -0,0 +1,128 @@
package matrix.nimble.edp.punching.controllers;
//servlet libraries
import java.net.URLDecoder;
import jakarta.servlet.http.HttpServletRequest;
//spring libraries
import matrix.nimble.edp.punching.model.PunchingHandler;
import matrix.nimble.model.Session;
import matrix.nimble.utilities.CommonFunctions;
import matrix.nimble.utilities.GlobalClass;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.SessionAttributes;
@Controller
@SessionAttributes({ "Sessvals" })
public class Punching {
@RequestMapping(value = "initdocview", method = RequestMethod.POST)
public String InitDocScreen(ModelMap model, HttpServletRequest request,
@ModelAttribute(value = "Sessvals") Session Sessvals) {
PunchingHandler PH = new PunchingHandler();
PH.setErrCode("1901");
model.addAttribute("branchlist", null);
model.addAttribute("prodlist", null);
model.addAttribute("typelist", null);
model.addAttribute("doclist", null);
model.addAttribute("catlist", null);
model.addAttribute("portfolio_id", "-1");
model.addAttribute("Sessvals", Sessvals);
PH.GetDDValues(240, Sessvals.getBranchID().split(GlobalClass.ColDelim));
model.addAttribute("portlist", PH.getOptionvals());
model.addAttribute("formmode", 0);
model.addAttribute("stat", true);
model.addAttribute("msg", "");
return "edp/punching/initdocs";
}
@RequestMapping(value = "docadd", method = RequestMethod.POST)
public String PunchDocs(ModelMap model, HttpServletRequest request,
@ModelAttribute(value = "Sessvals") Session Sessvals) {
String msg = "";
PunchingHandler PH = new PunchingHandler();
CommonFunctions comFunc = new CommonFunctions();
PH.setErrCode("1901");
PH.setPortfolioid(request.getParameter("portfolio_id"));
PH.setProcessFlag(true);
PH.DocDynamicHtml();
if (PH.isProcessFlag()) {
model.addAttribute("dynamichtml", PH.getDynamicpanel());
model.addAttribute("dynamicfields", PH.getDynamicfields());
model.addAttribute("docdynamichtml", PH.getDocdynamicpanel());
model.addAttribute("docdynamicfields", PH.getDocdynamicfields());
} else {
msg = PH.getErrMsg();
}
PH.GetDDValues(240, Sessvals.getBranchID().split(GlobalClass.ColDelim));
model.addAttribute("portlist", PH.getOptionvals());
PH.GetDDValues(4, PH.getPortfolioid().split(GlobalClass.ColDelim));
model.addAttribute("branchlist", PH.getOptionvals());
PH.GetDDValues(5,
(Sessvals.getBranchID() + GlobalClass.ColDelim + "op.description='DOCUMENT' or op.description='APPTYPE'"
+ GlobalClass.ColDelim).split(GlobalClass.ColDelim));
if (PH.getOptionvals() != null) {
model.addAttribute("typelist", comFunc.GetSubArray("APPTYPE", 2, PH.getOptionvals()));
} else {
model.addAttribute("typelist", null);
}
PH.GetDDValues(241, PH.getPortfolioid().split(GlobalClass.ColDelim));
model.addAttribute("doclist", PH.getOptionvals());
PH.GetDDValues(70,
(PH.getPortfolioid() + GlobalClass.ColDelim + "op.description='CATEGORY' or op.description='PRODUCT'"
+ GlobalClass.ColDelim).split(GlobalClass.ColDelim));
if (PH.getOptionvals() != null) {
model.addAttribute("prodlist", comFunc.GetSubArray("PRODUCT", 2, PH.getOptionvals()));
model.addAttribute("catlist", comFunc.GetSubArray("CATEGORY", 2, PH.getOptionvals()));
} else {
model.addAttribute("prodlist", null);
model.addAttribute("catlist", null);
}
model.addAttribute("portfolio_id", PH.getPortfolioid());
model.addAttribute("usid", Sessvals.getUserID());
model.addAttribute("Sessvals", Sessvals);
model.addAttribute("formmode", 1);
model.addAttribute("stat", PH.isProcessFlag());
model.addAttribute("msg", msg);
return "edp/punching/initdocs";
}
@RequestMapping(value = "addeditdoc", method = RequestMethod.POST)
public String AddEditDocs(@RequestParam String val0, @RequestParam String val1, @RequestParam String val2,
@RequestParam String val3, @RequestParam String val4, @RequestParam String val5, @RequestParam String val6,
@RequestParam String val7, @RequestParam String val8, @RequestParam String val9,
@RequestParam String val10) {
String retval = "success";
try {
// System.out.println(URLDecoder.decode(val0,"UTF-8"));
// System.out.println(URLDecoder.decode(val1,"UTF-8"));
// System.out.println(URLDecoder.decode(val2,"UTF-8"));
// System.out.println(URLDecoder.decode(val3,"UTF-8"));
// System.out.println(URLDecoder.decode(val4,"UTF-8"));
// System.out.println(URLDecoder.decode(val6,"UTF-8"));
// System.out.println(URLDecoder.decode(val7,"UTF-8"));
// System.out.println(URLDecoder.decode(val8,"UTF-8"));
// System.out.println(URLDecoder.decode(val9,"UTF-8"));
// System.out.println(URLDecoder.decode(val10,"UTF-8"));
PunchingHandler PH = new PunchingHandler();
PH.setErrCode("1902");
PH.setProcessFlag(true);
PH.AddEditDocs(val0, val1, val2, val3, val4, val6, val7, val8, val9, val10);
} catch (Exception exce) {
retval = exce.getMessage();
}
return retval;
}
}

View File

@@ -0,0 +1,87 @@
package matrix.nimble.edp.punching.controllers;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestParam;
import jakarta.servlet.http.HttpSession;
import jakarta.servlet.http.HttpServletResponse;
import matrix.nimble.controller.AbstractAuthenticatedController;
import matrix.nimble.model.Session;
import matrix.services.commons.CommonService;
import matrix.services.edp.PunchingService;
import matrix.services.commons.CommonErrorService;
import lib.models.CasePunching;
@Controller
public class PunchingController extends AbstractAuthenticatedController {
private static final String INIT_VIEW_ROUTE = "initview";
private final PunchingService punchingService;
public PunchingController(CommonService commonService, CommonErrorService errorService,
PunchingService punchingService) {
super(commonService, errorService);
this.punchingService = punchingService;
}
@RequestMapping(value = INIT_VIEW_ROUTE, method = RequestMethod.POST)
public String InitializeScreen(
ModelMap model,
HttpSession httpSession,
HttpServletResponse response) {
PageAuthorization authorization = authorizePage(
INIT_VIEW_ROUTE, model, httpSession, response);
if (!authorization.isGranted()) {
return authorization.viewName();
}
Session Sessvals = authorization.session();
model.addAttribute("model", punchingService.init(
Sessvals.getBranchID(), Sessvals.getUserID(), null, "xxx"));
model.addAttribute("Sessvals", Sessvals);
return "edp/punching/initcase";
}
@RequestMapping(value = "caseadd", method = RequestMethod.POST)
public String addCase(
@ModelAttribute("model") CasePunching submitted,
ModelMap model,
HttpSession httpSession,
HttpServletResponse response) {
PageAuthorization authorization = authorizePage(
INIT_VIEW_ROUTE, model, httpSession, response);
if (!authorization.isGranted()) {
return authorization.viewName();
}
Session session = authorization.session();
model.addAttribute("model", punchingService.addCase(
submitted, session.getBranchID(), session.getUserID()));
model.addAttribute("Sessvals", session);
return "edp/punching/initcase";
}
@RequestMapping(value = "caseedit", method = RequestMethod.POST)
public String editCase(
@RequestParam("PortfolioId") Integer portfolioId,
@RequestParam("uuid") String documentCaseId,
ModelMap model,
HttpSession httpSession,
HttpServletResponse response) {
PageAuthorization authorization = authorizePage(
INIT_VIEW_ROUTE, model, httpSession, response);
if (!authorization.isGranted()) {
return authorization.viewName();
}
Session session = authorization.session();
model.addAttribute("model", punchingService.editCase(
portfolioId, documentCaseId, session.getBranchID(), session.getUserID()));
model.addAttribute("Sessvals", session);
return "edp/punching/initcase";
}
}

View File

@@ -0,0 +1,17 @@
package matrix.nimble.query;
import com.cygnus.db.QueryDefinition;
import com.cygnus.db.QueryDefinitionProvider;
public final class CloudQueryDefinitionProvider implements QueryDefinitionProvider {
private final QueryProvider provider;
public CloudQueryDefinitionProvider(QueryProvider provider) {
this.provider = provider;
}
@Override
public QueryDefinition get(int queryId) {
return QueryDefinition.parse(queryId, provider.getQuery(queryId));
}
}

View File

@@ -0,0 +1,34 @@
package matrix.nimble.query;
import com.cygnus.db.CygnusDbExecutor;
import com.cygnus.db.ExecutorOptions;
import com.cygnus.db.JdbcCygnusDbExecutor;
import matrix.nimble.utilities.DatabaseConnectionPool;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ModernDbConfiguration implements DisposableBean {
private CygnusDbExecutor installed;
@Bean
CygnusDbExecutor cygnusDbExecutor(
QueryProvider queryProvider,
@Value("${CYGNUS_DB_FETCH_SIZE:250}") int fetchSize,
@Value("${CYGNUS_DB_QUERY_TIMEOUT_SECONDS:60}") int queryTimeoutSeconds) {
installed = new JdbcCygnusDbExecutor(
DatabaseConnectionPool.dataSource(),
new CloudQueryDefinitionProvider(queryProvider),
new ExecutorOptions(fetchSize, queryTimeoutSeconds));
ModernDbExecutors.install(installed);
return installed;
}
@Override
public void destroy() {
if (installed != null)
ModernDbExecutors.clear(installed);
}
}

View File

@@ -0,0 +1,24 @@
package matrix.nimble.query;
import com.cygnus.db.CygnusDbExecutor;
import java.util.concurrent.atomic.AtomicReference;
public final class ModernDbExecutors {
private static final AtomicReference<CygnusDbExecutor> CURRENT = new AtomicReference<>();
private ModernDbExecutors() {}
public static void install(CygnusDbExecutor executor) {
CURRENT.set(java.util.Objects.requireNonNull(executor, "executor"));
}
public static CygnusDbExecutor current() {
CygnusDbExecutor executor = CURRENT.get();
if (executor == null) throw new IllegalStateException("Modern database executor is not initialized");
return executor;
}
public static void clear(CygnusDbExecutor executor) {
CURRENT.compareAndSet(executor, null);
}
}

View File

@@ -7,7 +7,7 @@ import java.util.logging.Logger;
public final class RedisCachingQueryProvider implements QueryProvider {
public static final Duration QUERY_TTL = Duration.ofHours(12);
public static final Duration QUERY_TTL = Duration.ofHours(3);
private static final Logger LOGGER = Logger.getLogger(RedisCachingQueryProvider.class.getName());
private static final int LOCK_COUNT = 64;

View File

@@ -1,6 +1,14 @@
package matrix.nimble.utilities;
import java.sql.*;
import java.util.Hashtable;
import java.util.List;
import java.util.Optional;
import com.cygnus.db.DbResult;
import com.cygnus.db.RowMapper;
import com.cygnus.db.RowView;
import com.cygnus.db.RowConsumer;
import com.cygnus.db.TransactionCallback;
import matrix.nimble.query.ModernDbExecutors;
public class DBFunctions {
private Connection con;
@@ -368,6 +376,59 @@ public class DBFunctions {
return getErrMsg();
}
}
public DbResult<RowView> execute(int queryId, Object[] parameters) {
return ModernDbExecutors.current().execute(queryId, parameters);
}
public <T> DbResult<T> execute(int queryId, Object[] parameters, Class<T> responseType) {
return ModernDbExecutors.current().execute(queryId, parameters, responseType);
}
public <T> List<T> query(int queryId, Object[] parameters, Class<T> responseType) {
return ModernDbExecutors.current().query(queryId, parameters, responseType);
}
public <T> List<T> query(int queryId, Object[] parameters, RowMapper<T> mapper) {
return ModernDbExecutors.current().query(queryId, parameters, mapper);
}
public <T> Optional<T> queryOne(int queryId, Object[] parameters, Class<T> responseType) {
return ModernDbExecutors.current().queryOne(queryId, parameters, responseType);
}
public int insert(int queryId, Object[] parameters) {
return ModernDbExecutors.current().insert(queryId, parameters);
}
public <K> K insert(int queryId, Object[] parameters, Class<K> generatedKeyType) {
return ModernDbExecutors.current().insert(queryId, parameters, generatedKeyType);
}
public int update(int queryId, Object[] parameters) {
return ModernDbExecutors.current().update(queryId, parameters);
}
public int delete(int queryId, Object[] parameters) {
return ModernDbExecutors.current().delete(queryId, parameters);
}
public <T> T procedure(int queryId, Object[] parameters, Class<T> responseType) {
return ModernDbExecutors.current().procedure(queryId, parameters, responseType);
}
public <T> List<T> procedureRows(int queryId, Object[] parameters, Class<T> responseType) {
return ModernDbExecutors.current().procedureRows(queryId, parameters, responseType);
}
public <T> void stream(
int queryId, Object[] parameters, RowMapper<T> mapper, RowConsumer<T> consumer) {
ModernDbExecutors.current().stream(queryId, parameters, mapper, consumer);
}
public <T> T transaction(TransactionCallback<T> callback) {
return ModernDbExecutors.current().transaction(callback);
}
@SuppressWarnings("finally")
public String QueryBuilder(String Query,String[] Val)
{

View File

@@ -5,6 +5,7 @@ import java.sql.SQLException;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import javax.sql.DataSource;
/** Shared, bounded JDBC connection pool for the legacy database layer. */
public final class DatabaseConnectionPool {
@@ -20,6 +21,10 @@ public final class DatabaseConnectionPool {
return getDataSource().getConnection();
}
public static DataSource dataSource() {
return getDataSource();
}
private static HikariDataSource getDataSource() {
HikariDataSource current = dataSource;
if (current != null) {

View File

@@ -0,0 +1,33 @@
package matrix.services.commons;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import lib.models.ErrorDetails;
import matrix.nimble.model.Session;
import org.springframework.stereotype.Service;
import org.springframework.ui.ModelMap;
@Service
public class CommonErrorService {
public static final String ERROR_ATTRIBUTE = "errorDetails";
private final CommonService commonService;
public CommonErrorService(CommonService commonService) {
this.commonService = commonService;
}
public String render(
ModelMap model,
HttpServletResponse response,
HttpSession httpSession,
ErrorDetails errorDetails) {
response.setStatus(errorDetails.getHttpStatus());
model.addAttribute(ERROR_ATTRIBUTE, errorDetails);
Session session = commonService.getSession(httpSession);
if (session != null) {
model.addAttribute(CommonService.SESSION_ATTRIBUTE, session);
}
return "error";
}
}

View File

@@ -0,0 +1,102 @@
package matrix.services.commons;
import com.cygnus.db.CygnusDbExecutor;
import jakarta.servlet.http.HttpSession;
import lib.models.Option;
import java.util.List;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import matrix.nimble.model.Session;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class CommonService {
public static final String SESSION_ATTRIBUTE = "Sessvals";
private static final Pattern MENU_COMMAND = Pattern.compile(
"SubmitMenuCommand\\(\\s*'([^']+)'\\s*,",
Pattern.CASE_INSENSITIVE);
private final CygnusDbExecutor dbExecutor;
@Autowired
public CommonService(CygnusDbExecutor dbExecutor) {
this.dbExecutor = Objects.requireNonNull(dbExecutor, "dbExecutor");
}
/* Used by the session-only unit tests. Production construction is handled
by Spring through the CygnusDbExecutor constructor above. */
public CommonService() {
this.dbExecutor = null;
}
public Session getSession(HttpSession httpSession) {
if (httpSession == null) {
return null;
}
Object value = httpSession.getAttribute(SESSION_ATTRIBUTE);
return value instanceof Session session ? session : null;
}
public void storeSession(HttpSession httpSession, Session session) {
Objects.requireNonNull(httpSession, "httpSession");
httpSession.setAttribute(SESSION_ATTRIBUTE, Objects.requireNonNull(session, "session"));
}
public boolean hasPageAccess(HttpSession httpSession, String pageRoute) {
return hasPageAccess(getSession(httpSession), pageRoute);
}
public boolean hasPageAccess(Session session, String pageRoute) {
if (session == null || session.getMenuHtml() == null) {
return false;
}
String requiredRoute = normalizeRoute(pageRoute);
if (requiredRoute.isEmpty()) {
return false;
}
Matcher matcher = MENU_COMMAND.matcher(session.getMenuHtml());
while (matcher.find()) {
if (normalizeRoute(matcher.group(1)).equals(requiredRoute)) {
return true;
}
}
return false;
}
private String normalizeRoute(String route) {
if (route == null) {
return "";
}
String normalized = route.trim().replace('\\', '/');
int query = normalized.indexOf('?');
if (query >= 0) {
normalized = normalized.substring(0, query);
}
int fragment = normalized.indexOf('#');
if (fragment >= 0) {
normalized = normalized.substring(0, fragment);
}
while (normalized.endsWith("/")) {
normalized = normalized.substring(0, normalized.length() - 1);
}
int slash = normalized.lastIndexOf('/');
return slash >= 0 ? normalized.substring(slash + 1) : normalized;
}
public List<Option> getOptions(Integer queryId, Object[] queryParams) {
if (queryId == null) {
return List.of();
}
if (dbExecutor == null) {
throw new IllegalStateException("CygnusDbExecutor is not configured");
}
Object[] parameters = queryParams == null ? new Object[0] : queryParams;
return dbExecutor.query(queryId, parameters, Option.class);
}
}

View File

@@ -0,0 +1,195 @@
package matrix.services.edp;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import org.springframework.stereotype.Service;
import lib.models.CasePunching;
import lib.models.ErrorDetails;
import lib.models.Option;
import lib.constants.ApplicationError;
import matrix.nimble.edp.punching.model.PunchingHandler;
import matrix.nimble.utilities.CommonFunctions;
import matrix.nimble.utilities.GlobalClass;
import matrix.services.commons.CommonService;
@Service
public class PunchingService {
private static final String PORTFOLIO = "portfolio.";
private static final String BRANCH = "branch.";
private static final String PRODUCT = "product.";
private static final String CITY = "city.";
private static final String APPLICATION_TYPE = "applicationType.";
private static final String CATEGORY = "category.";
private final CommonService commonService;
public PunchingService(CommonService commonService) {
this.commonService = commonService;
}
public CasePunching init(
String branchId,
String userId,
String verificationCaseId,
String documentCaseId) {
PunchingHandler punchingHandler = new PunchingHandler();
punchingHandler.setErrCode("1001");
CasePunching casePunching = new CasePunching();
Map<String, List<Option>> options = new LinkedHashMap<>();
Short databaseBranchId = Short.valueOf(branchId.trim());
List<Option> portfolios = commonService.getOptions(
3, new Object[] { databaseBranchId, Short.valueOf((short) 1) });
addOptions(options, PORTFOLIO, portfolios);
casePunching.setOptions(Collections.unmodifiableMap(options));
casePunching.setVisibleSections(List.of());
casePunching.setPortfolioId(-1);
casePunching.setFormMode(0);
casePunching.setUserId(userId);
casePunching.setDynamicFields("");
casePunching.setDynamicHtml("");
casePunching.setVerificationCaseId(verificationCaseId);
casePunching.setDocumentCaseId(documentCaseId);
return casePunching;
}
public CasePunching addCase(
CasePunching submitted,
String branchIds,
String userId) {
CasePunching result = new CasePunching();
result.setPortfolioId(submitted.getPortfolioId());
result.setFormMode(1);
result.setUserId(userId);
result.setVerificationCaseId(submitted.getVerificationCaseId());
result.setDocumentCaseId("xxx");
if (submitted.getPortfolioId() == null || submitted.getPortfolioId() <= 0) {
result.setOptions(Collections.emptyMap());
result.setVisibleSections(List.of());
result.setDynamicFields("");
result.setDynamicHtml("");
result.setErrorDetails(new ErrorDetails(ApplicationError.BAD_REQUEST));
return result;
}
String portfolioId = submitted.getPortfolioId().toString();
PunchingHandler punchingHandler = new PunchingHandler();
CommonFunctions commonFunctions = new CommonFunctions();
punchingHandler.setErrCode("1001");
punchingHandler.setPortfolioid(portfolioId);
punchingHandler.setProcessFlag(true);
punchingHandler.DynamicHtml("10");
if (punchingHandler.isProcessFlag()) {
result.setDynamicFields(defaultString(punchingHandler.getDynamicfields()));
result.setDynamicHtml(defaultString(punchingHandler.getDynamicpanel()));
String sections = defaultString(punchingHandler.GetVisibleSections())
+ defaultString(punchingHandler.getDynamicsection());
result.setVisibleSections(toVisibleSections(sections));
} else {
result.setDynamicFields("");
result.setDynamicHtml("");
result.setVisibleSections(List.of());
result.setErrorDetails(new ErrorDetails(ApplicationError.INTERNAL_SERVER_ERROR));
}
Map<String, String> options = new LinkedHashMap<>();
punchingHandler.GetDDValues(3, branchIds.split(GlobalClass.ColDelim));
addOptions(options, PORTFOLIO, punchingHandler.getOptionvals());
punchingHandler.GetDDValues(4, new String[] { portfolioId });
addOptions(options, BRANCH, punchingHandler.getOptionvals());
punchingHandler.GetDDValues(5,
(branchIds + GlobalClass.ColDelim
+ "op.description='CITY' or op.description='APPTYPE'"
+ GlobalClass.ColDelim).split(GlobalClass.ColDelim));
String[][] generalOptions = punchingHandler.getOptionvals();
if (generalOptions != null) {
addOptions(options, CITY,
commonFunctions.GetSubArray("CITY", 2, generalOptions));
addOptions(options, APPLICATION_TYPE,
commonFunctions.GetSubArray("APPTYPE", 2, generalOptions));
}
punchingHandler.GetDDValues(70,
(portfolioId + GlobalClass.ColDelim
+ "op.description='CATEGORY' or op.description='PRODUCT'"
+ GlobalClass.ColDelim).split(GlobalClass.ColDelim));
String[][] portfolioOptions = punchingHandler.getOptionvals();
if (portfolioOptions != null) {
addOptions(options, PRODUCT,
commonFunctions.GetSubArray("PRODUCT", 2, portfolioOptions));
addOptions(options, CATEGORY,
commonFunctions.GetSubArray("CATEGORY", 2, portfolioOptions));
}
result.setOptionsl(Collections.unmodifiableMap(options));
return result;
}
public CasePunching editCase(
Integer portfolioId,
String documentCaseId,
String branchIds,
String userId) {
CasePunching submitted = new CasePunching();
submitted.setPortfolioId(portfolioId);
submitted.setDocumentCaseId(documentCaseId);
CasePunching result = addCase(submitted, branchIds, userId);
result.setFormMode(2);
result.setDocumentCaseId(documentCaseId);
return result;
}
private void addOptions(Map<String, String> options, String prefix, String[][] values) {
if (values == null) {
return;
}
for (String[] value : values) {
if (value != null && value.length >= 2 && value[0] != null) {
options.put(prefix + value[0], value[1] == null ? "" : value[1]);
}
}
}
private void addOptions(Map<String, List<Option>> options, String prefix, List<Option> values) {
if (values == null || values.isEmpty()) {
return;
}
List<Option> groupOptions = options.get(prefix);
for (Option value : values) {
if (value == null || value.getValue() == null) {
continue;
}
if (groupOptions == null) {
groupOptions = new java.util.ArrayList<>(values.size());
options.put(prefix, groupOptions);
}
groupOptions.add(value);
}
}
private List<String> toVisibleSections(String sections) {
if (sections == null || sections.isBlank()) {
return List.of();
}
return List.of(sections.split(Pattern.quote(GlobalClass.ColDelim))).stream()
.map(String::trim)
.filter(section -> !section.isEmpty() && !"null".equalsIgnoreCase(section))
.distinct()
.toList();
}
private String defaultString(String value) {
return value == null ? "" : value;
}
}

View File

@@ -0,0 +1,70 @@
package matrix.nimble.controller;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
import matrix.nimble.model.Session;
import matrix.services.commons.CommonErrorService;
import matrix.services.commons.CommonService;
import org.junit.jupiter.api.Test;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.ui.ModelMap;
class AbstractAuthenticatedControllerTest {
private final CommonService commonService = new CommonService();
private final TestController controller = new TestController(
commonService, new CommonErrorService(commonService));
@Test
void grantsAConfiguredMenuRoute() {
MockHttpSession httpSession = new MockHttpSession();
Session session = sessionWithRoute("initview");
commonService.storeSession(httpSession, session);
AbstractAuthenticatedController.PageAuthorization result = controller.authorize(
"initview", new ModelMap(), httpSession, new MockHttpServletResponse());
assertTrue(result.isGranted());
assertSame(session, result.session());
}
@Test
void returns401ForMissingSessionAnd403ForMissingPage() {
MockHttpServletResponse noSessionResponse = new MockHttpServletResponse();
AbstractAuthenticatedController.PageAuthorization noSession = controller.authorize(
"initview", new ModelMap(), new MockHttpSession(), noSessionResponse);
assertEquals("error", noSession.viewName());
assertEquals(401, noSessionResponse.getStatus());
MockHttpSession httpSession = new MockHttpSession();
commonService.storeSession(httpSession, sessionWithRoute("dashboard"));
MockHttpServletResponse deniedResponse = new MockHttpServletResponse();
AbstractAuthenticatedController.PageAuthorization denied = controller.authorize(
"initview", new ModelMap(), httpSession, deniedResponse);
assertEquals("error", denied.viewName());
assertEquals(403, deniedResponse.getStatus());
}
private Session sessionWithRoute(String route) {
Session session = new Session();
session.setMenuHtml("<a onclick=\"SubmitMenuCommand('" + route
+ "','_parent','MenuForm','')\">Page</a>");
return session;
}
private static final class TestController extends AbstractAuthenticatedController {
private TestController(CommonService commonService, CommonErrorService errorService) {
super(commonService, errorService);
}
private PageAuthorization authorize(
String route,
ModelMap model,
MockHttpSession session,
MockHttpServletResponse response) {
return authorizePage(route, model, session, response);
}
}
}

View File

@@ -0,0 +1,28 @@
package matrix.nimble.query;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import com.cygnus.db.QueryDefinitionException;
import com.cygnus.db.QueryType;
import org.junit.jupiter.api.Test;
class CloudQueryDefinitionProviderTest {
@Test
void adaptsEncryptedCloudQueryProviderValueToDbDefinition() {
CloudQueryDefinitionProvider provider = new CloudQueryDefinitionProvider(
queryId -> "select!C0L!select operation_id from operation where type = ?");
var definition = provider.get(513);
assertEquals(513, definition.queryId());
assertEquals(QueryType.SELECT, definition.type());
assertEquals("select operation_id from operation where type = ?", definition.sql());
}
@Test
void preventsLegacyPlaceholderExecution() {
CloudQueryDefinitionProvider provider = new CloudQueryDefinitionProvider(
queryId -> "select!C0L!select * from operation where type = 'val0'");
assertThrows(QueryDefinitionException.class, () -> provider.get(513));
}
}

View File

@@ -0,0 +1,35 @@
package matrix.services.commons;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertSame;
import lib.models.ErrorDetails;
import lib.constants.ApplicationError;
import matrix.nimble.model.Session;
import org.junit.jupiter.api.Test;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.ui.ModelMap;
class CommonErrorServiceTest {
private final CommonService commonService = new CommonService();
private final CommonErrorService errorService = new CommonErrorService(commonService);
@Test
void appliesHttpStatusAndPreservesAnActiveSessionForTheMenu() {
MockHttpSession httpSession = new MockHttpSession();
Session session = new Session();
session.setMenuHtml("<div id='MenuBar'></div>");
commonService.storeSession(httpSession, session);
ErrorDetails details = new ErrorDetails(ApplicationError.ACCESS_DENIED);
MockHttpServletResponse response = new MockHttpServletResponse();
ModelMap model = new ModelMap();
String view = errorService.render(model, response, httpSession, details);
assertEquals("error", view);
assertEquals(403, response.getStatus());
assertSame(details, model.get(CommonErrorService.ERROR_ATTRIBUTE));
assertSame(session, model.get(CommonService.SESSION_ATTRIBUTE));
}
}

View File

@@ -0,0 +1,45 @@
package matrix.services.commons;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
import matrix.nimble.model.Session;
import org.junit.jupiter.api.Test;
import org.springframework.mock.web.MockHttpSession;
class CommonServiceTest {
private final CommonService service = new CommonService();
@Test
void storesAndReadsTheAuthenticatedSession() {
MockHttpSession httpSession = new MockHttpSession();
Session session = sessionWithMenu("initview");
service.storeSession(httpSession, session);
assertSame(session, service.getSession(httpSession));
}
@Test
void grantsOnlyAnExactMenuCommandRoute() {
Session session = sessionWithMenu("/matrix/ver/initview?source=menu");
assertTrue(service.hasPageAccess(session, "initview"));
assertFalse(service.hasPageAccess(session, "view"));
assertFalse(service.hasPageAccess(session, "initview-admin"));
}
@Test
void deniesMissingSessionAndMissingMenu() {
assertFalse(service.hasPageAccess((Session) null, "initview"));
assertFalse(service.hasPageAccess(new Session(), "initview"));
}
private Session sessionWithMenu(String route) {
Session session = new Session();
session.setMenuHtml("<a onclick=\"SubmitMenuCommand('" + route
+ "','_parent','MenuForm','')\">Punching</a>");
return session;
}
}

View File

@@ -0,0 +1,39 @@
# Cygnus On-Premises Database Core
This module executes cloud-supplied, parameterized query definitions against the
on-premises JDBC `DataSource` without creating delimiter strings or `String[][]`
result buffers.
## Query format
The query catalog value retains the operation prefix during incremental migration:
```text
select!C0L!select id, name from operation where operation_type = ? and operated_by = ?
```
Legacy `val0`, `val1`, ... placeholders are rejected by the modern executor. They
remain available through the existing `DBFunctions.FetchRunQuery` path until each
query is migrated.
## Usage
```java
List<Operation> rows = dbFunctions.query(
600,
new Object[] { operationType, operatedBy },
Operation.class);
```
For maximum throughput, use an explicit mapper:
```java
List<Operation> rows = dbFunctions.query(
600,
parameters,
result -> new Operation(result.getLong("id"), result.getString("name")));
```
Large reports can use `stream(...)` so rows are consumed without retaining the
whole result in memory. PostgreSQL cursor fetching is enabled by running reads with
auto-commit disabled and the configured fetch size.

38
cygnus-onprem-db/pom.xml Normal file
View File

@@ -0,0 +1,38 @@
<?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>com.cygnus</groupId>
<artifactId>cygnus-parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>cygnus-onprem-db</artifactId>
<packaging>jar</packaging>
<name>Cygnus On-Premises Database Core</name>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.3.232</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.5.4</version>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,21 @@
package com.cygnus.db;
import java.util.List;
import java.util.Optional;
public interface CygnusDbExecutor {
<T> DbResult<T> execute(int queryId, Object[] parameters, Class<T> responseType);
DbResult<RowView> execute(int queryId, Object[] parameters);
<T> List<T> query(int queryId, Object[] parameters, Class<T> responseType);
<T> List<T> query(int queryId, Object[] parameters, RowMapper<T> mapper);
List<RowView> query(int queryId, Object[] parameters);
<T> Optional<T> queryOne(int queryId, Object[] parameters, Class<T> responseType);
int insert(int queryId, Object[] parameters);
<K> K insert(int queryId, Object[] parameters, Class<K> generatedKeyType);
int update(int queryId, Object[] parameters);
int delete(int queryId, Object[] parameters);
<T> T procedure(int queryId, Object[] parameters, Class<T> responseType);
<T> List<T> procedureRows(int queryId, Object[] parameters, Class<T> responseType);
<T> void stream(int queryId, Object[] parameters, RowMapper<T> mapper, RowConsumer<T> consumer);
<T> T transaction(TransactionCallback<T> callback);
}

View File

@@ -0,0 +1,12 @@
package com.cygnus.db;
public final class DatabaseExecutionException extends RuntimeException {
private final int queryId;
public DatabaseExecutionException(int queryId, String message, Throwable cause) {
super(message, cause);
this.queryId = queryId;
}
public int queryId() { return queryId; }
}

View File

@@ -0,0 +1,20 @@
package com.cygnus.db;
import java.util.List;
import java.util.Optional;
public record DbResult<T>(QueryType type, List<T> rows, int affectedRows, T value) {
public DbResult {
rows = rows == null ? List.of() : List.copyOf(rows);
}
public Optional<T> optionalValue() { return Optional.ofNullable(value); }
public static <T> DbResult<T> rows(QueryType type, List<T> rows) {
return new DbResult<>(type, rows, 0, rows.isEmpty() ? null : rows.getFirst());
}
public static <T> DbResult<T> affected(QueryType type, int count) {
return new DbResult<>(type, List.of(), count, null);
}
}

View File

@@ -0,0 +1,10 @@
package com.cygnus.db;
public record ExecutorOptions(int fetchSize, int queryTimeoutSeconds) {
public static final ExecutorOptions DEFAULT = new ExecutorOptions(250, 60);
public ExecutorOptions {
if (fetchSize < 0) throw new IllegalArgumentException("fetchSize cannot be negative");
if (queryTimeoutSeconds < 0) throw new IllegalArgumentException("queryTimeoutSeconds cannot be negative");
}
}

View File

@@ -0,0 +1,33 @@
package com.cygnus.db;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
final class ImmutableRowView implements RowView {
private final Map<String, Object> values;
private final Map<String, Object> normalized;
ImmutableRowView(Map<String, Object> values) {
this.values = Collections.unmodifiableMap(new LinkedHashMap<>(values));
Map<String, Object> index = new LinkedHashMap<>();
values.forEach((name, value) -> index.put(name.toLowerCase(Locale.ROOT), value));
this.normalized = Map.copyOf(index);
}
@Override
public Object get(String column) {
Object value = values.get(column);
return value != null || values.containsKey(column)
? value : normalized.get(column.toLowerCase(Locale.ROOT));
}
@Override
public <T> T get(String column, Class<T> type) {
return ValueConverter.convert(get(column), type);
}
@Override
public Map<String, Object> asMap() { return values; }
}

View File

@@ -0,0 +1,316 @@
package com.cygnus.db;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import javax.sql.DataSource;
public final class JdbcCygnusDbExecutor implements CygnusDbExecutor {
private final DataSource dataSource;
private final QueryDefinitionProvider queryProvider;
private final ExecutorOptions options;
private final ParameterBinder binder = new ParameterBinder();
private final ObjectRowMapperFactory mapperFactory = new ObjectRowMapperFactory();
public JdbcCygnusDbExecutor(DataSource dataSource, QueryDefinitionProvider queryProvider) {
this(dataSource, queryProvider, ExecutorOptions.DEFAULT);
}
public JdbcCygnusDbExecutor(
DataSource dataSource, QueryDefinitionProvider queryProvider, ExecutorOptions options) {
this.dataSource = java.util.Objects.requireNonNull(dataSource, "dataSource");
this.queryProvider = java.util.Objects.requireNonNull(queryProvider, "queryProvider");
this.options = java.util.Objects.requireNonNull(options, "options");
}
@Override
public <T> DbResult<T> execute(int queryId, Object[] parameters, Class<T> responseType) {
QueryDefinition definition = queryProvider.get(queryId);
if (definition.type() == QueryType.SELECT) {
return withReadConnection(queryId, connection ->
executeRowsOrCount(connection, definition, parameters, responseType));
}
if (definition.type() == QueryType.PROCEDURE) {
return executeRowsOrCount(definition, parameters, responseType);
}
return DbResult.affected(definition.type(), executeUpdate(definition, parameters));
}
@Override
public DbResult<RowView> execute(int queryId, Object[] parameters) {
return execute(queryId, parameters, RowView.class);
}
@Override
public <T> List<T> query(int queryId, Object[] parameters, Class<T> responseType) {
return withReadConnection(queryId, connection -> query(
connection, require(queryId, QueryType.SELECT), parameters, responseType));
}
@Override
public <T> List<T> query(int queryId, Object[] parameters, RowMapper<T> mapper) {
return withReadConnection(queryId, connection -> query(
connection, require(queryId, QueryType.SELECT), parameters, mapper));
}
@Override
public List<RowView> query(int queryId, Object[] parameters) {
return query(queryId, parameters, RowView.class);
}
@Override
public <T> Optional<T> queryOne(int queryId, Object[] parameters, Class<T> responseType) {
List<T> rows = query(queryId, parameters, responseType);
if (rows.size() > 1) throw new DatabaseExecutionException(
queryId, "Expected one row but received " + rows.size(), null);
return rows.stream().findFirst();
}
@Override
public int insert(int queryId, Object[] parameters) {
return executeUpdate(require(queryId, QueryType.INSERT), parameters);
}
@Override
public <K> K insert(int queryId, Object[] parameters, Class<K> generatedKeyType) {
QueryDefinition definition = require(queryId, QueryType.INSERT);
return withConnection(queryId, connection -> {
try (PreparedStatement statement = prepare(
connection, definition, Statement.RETURN_GENERATED_KEYS)) {
binder.bind(statement, parameters);
statement.executeUpdate();
try (ResultSet keys = statement.getGeneratedKeys()) {
if (!keys.next()) throw new DatabaseExecutionException(
queryId, "Insert did not return a generated key", null);
return ValueConverter.convert(keys.getObject(1), generatedKeyType);
}
}
});
}
@Override
public int update(int queryId, Object[] parameters) {
return executeUpdate(require(queryId, QueryType.UPDATE), parameters);
}
@Override
public int delete(int queryId, Object[] parameters) {
return executeUpdate(require(queryId, QueryType.DELETE), parameters);
}
@Override
public <T> T procedure(int queryId, Object[] parameters, Class<T> responseType) {
List<T> rows = procedureRows(queryId, parameters, responseType);
return rows.isEmpty() ? null : rows.getFirst();
}
@Override
public <T> List<T> procedureRows(int queryId, Object[] parameters, Class<T> responseType) {
DbResult<T> result = executeRowsOrCount(
require(queryId, QueryType.PROCEDURE), parameters, responseType);
return result.rows();
}
@Override
public <T> void stream(
int queryId, Object[] parameters, RowMapper<T> mapper, RowConsumer<T> consumer) {
QueryDefinition definition = require(queryId, QueryType.SELECT);
withReadConnection(queryId, connection -> {
try (PreparedStatement statement = prepare(connection, definition);
ResultSet result = executeQuery(statement, parameters)) {
while (result.next()) consumer.accept(mapper.map(result));
return null;
}
});
}
@Override
public <T> T transaction(TransactionCallback<T> callback) {
try (Connection connection = dataSource.getConnection()) {
boolean autoCommit = connection.getAutoCommit();
connection.setAutoCommit(false);
try {
T result = callback.execute(new TransactionOperationsImpl(connection));
connection.commit();
return result;
} catch (Exception exception) {
try { connection.rollback(); } catch (SQLException rollback) { exception.addSuppressed(rollback); }
if (exception instanceof RuntimeException runtime) throw runtime;
throw new DatabaseExecutionException(0, "Transaction failed", exception);
} finally {
connection.setAutoCommit(autoCommit);
}
} catch (SQLException exception) {
throw new DatabaseExecutionException(0, "Unable to manage transaction", exception);
}
}
private <T> DbResult<T> executeRowsOrCount(
QueryDefinition definition, Object[] parameters, Class<T> responseType) {
return withConnection(definition.queryId(), connection ->
executeRowsOrCount(connection, definition, parameters, responseType));
}
private <T> DbResult<T> executeRowsOrCount(
Connection connection,
QueryDefinition definition,
Object[] parameters,
Class<T> responseType) throws SQLException {
try (PreparedStatement statement = prepare(connection, definition)) {
binder.bind(statement, parameters);
boolean rows = statement.execute();
if (!rows) return DbResult.affected(definition.type(), statement.getUpdateCount());
try (ResultSet result = statement.getResultSet()) {
return DbResult.rows(definition.type(), map(result, responseType));
}
}
}
private int executeUpdate(QueryDefinition definition, Object[] parameters) {
return withConnection(definition.queryId(), connection ->
executeUpdate(connection, definition, parameters));
}
private int executeUpdate(
Connection connection, QueryDefinition definition, Object[] parameters) throws SQLException {
try (PreparedStatement statement = prepare(connection, definition)) {
binder.bind(statement, parameters);
return statement.executeUpdate();
}
}
private <T> List<T> query(
Connection connection,
QueryDefinition definition,
Object[] parameters,
Class<T> responseType) throws SQLException {
try (PreparedStatement statement = prepare(connection, definition);
ResultSet result = executeQuery(statement, parameters)) {
return map(result, responseType);
}
}
private <T> List<T> query(
Connection connection,
QueryDefinition definition,
Object[] parameters,
RowMapper<T> mapper) throws SQLException {
try (PreparedStatement statement = prepare(connection, definition);
ResultSet result = executeQuery(statement, parameters)) {
List<T> rows = new ArrayList<>();
while (result.next()) rows.add(mapper.map(result));
return List.copyOf(rows);
}
}
private <T> List<T> map(ResultSet result, Class<T> responseType) throws SQLException {
RowMapper<T> mapper = mapperFactory.create(responseType, result.getMetaData());
List<T> rows = new ArrayList<>();
while (result.next()) rows.add(mapper.map(result));
return List.copyOf(rows);
}
private ResultSet executeQuery(PreparedStatement statement, Object[] parameters) throws SQLException {
binder.bind(statement, parameters);
return statement.executeQuery();
}
private PreparedStatement prepare(Connection connection, QueryDefinition definition) throws SQLException {
PreparedStatement statement = connection.prepareStatement(
definition.sql(), ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
configure(statement);
return statement;
}
private PreparedStatement prepare(
Connection connection, QueryDefinition definition, int generatedKeys) throws SQLException {
PreparedStatement statement = connection.prepareStatement(definition.sql(), generatedKeys);
configure(statement);
return statement;
}
private void configure(PreparedStatement statement) throws SQLException {
statement.setFetchSize(options.fetchSize());
statement.setQueryTimeout(options.queryTimeoutSeconds());
}
private QueryDefinition require(int queryId, QueryType expected) {
QueryDefinition definition = queryProvider.get(queryId);
if (definition.type() != expected) throw new QueryDefinitionException(
"Query " + queryId + " is " + definition.type() + ", not " + expected);
return definition;
}
private <T> T withConnection(int queryId, SqlWork<T> work) {
try (Connection connection = dataSource.getConnection()) {
return work.execute(connection);
} catch (DatabaseExecutionException exception) {
throw exception;
} catch (Exception exception) {
throw new DatabaseExecutionException(queryId, "Database operation failed for query " + queryId, exception);
}
}
private <T> T withReadConnection(int queryId, SqlWork<T> work) {
try (Connection connection = dataSource.getConnection()) {
boolean autoCommit = connection.getAutoCommit();
connection.setAutoCommit(false); // PostgreSQL requires this for cursor-based fetchSize.
try {
T value = work.execute(connection);
connection.commit();
return value;
} catch (Exception exception) {
try { connection.rollback(); } catch (SQLException rollback) { exception.addSuppressed(rollback); }
if (exception instanceof RuntimeException runtime) throw runtime;
throw new DatabaseExecutionException(queryId,
"Database read failed for query " + queryId, exception);
} finally {
connection.setAutoCommit(autoCommit);
}
} catch (SQLException exception) {
throw new DatabaseExecutionException(queryId,
"Unable to manage database read for query " + queryId, exception);
}
}
@FunctionalInterface
private interface SqlWork<T> { T execute(Connection connection) throws Exception; }
private final class TransactionOperationsImpl implements TransactionOperations {
private final Connection connection;
private TransactionOperationsImpl(Connection connection) { this.connection = connection; }
@Override
public <T> List<T> query(int queryId, Object[] parameters, Class<T> type) {
try { return JdbcCygnusDbExecutor.this.query(
connection, require(queryId, QueryType.SELECT), parameters, type); }
catch (SQLException exception) { throw failure(queryId, exception); }
}
@Override
public <T> Optional<T> queryOne(int queryId, Object[] parameters, Class<T> type) {
List<T> rows = query(queryId, parameters, type);
if (rows.size() > 1) throw new DatabaseExecutionException(
queryId, "Expected one row but received " + rows.size(), null);
return rows.stream().findFirst();
}
@Override public int insert(int id, Object[] values) { return updateType(id, values, QueryType.INSERT); }
@Override public int update(int id, Object[] values) { return updateType(id, values, QueryType.UPDATE); }
@Override public int delete(int id, Object[] values) { return updateType(id, values, QueryType.DELETE); }
private int updateType(int id, Object[] values, QueryType type) {
try { return executeUpdate(connection, require(id, type), values); }
catch (SQLException exception) { throw failure(id, exception); }
}
private DatabaseExecutionException failure(int id, SQLException exception) {
return new DatabaseExecutionException(id, "Transaction operation failed for query " + id, exception);
}
}
}

View File

@@ -0,0 +1,141 @@
package com.cygnus.db;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.RecordComponent;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
final class ObjectRowMapperFactory {
private final ConcurrentMap<Class<?>, TypeMetadata> metadataCache = new ConcurrentHashMap<>();
<T> RowMapper<T> create(Class<T> type, ResultSetMetaData resultMetadata) throws SQLException {
if (type == RowView.class) return result -> type.cast(rowView(result, resultMetadata));
if (isScalar(type)) return result -> ValueConverter.convert(result.getObject(1), type);
TypeMetadata typeMetadata = metadataCache.computeIfAbsent(type, this::inspect);
Map<String, Integer> columns = columns(resultMetadata);
return typeMetadata.mapper(type, columns);
}
private RowView rowView(java.sql.ResultSet result, ResultSetMetaData metadata) throws SQLException {
Map<String, Object> values = new LinkedHashMap<>();
for (int column = 1; column <= metadata.getColumnCount(); column++) {
values.put(metadata.getColumnLabel(column), result.getObject(column));
}
return new ImmutableRowView(values);
}
private TypeMetadata inspect(Class<?> type) {
try {
if (type.isRecord()) {
RecordComponent[] components = type.getRecordComponents();
Class<?>[] parameterTypes = new Class<?>[components.length];
String[] names = new String[components.length];
for (int index = 0; index < components.length; index++) {
parameterTypes[index] = components[index].getType();
names[index] = normalize(components[index].getName());
}
Constructor<?> constructor = type.getDeclaredConstructor(parameterTypes);
constructor.setAccessible(true);
return new RecordMetadata(constructor, parameterTypes, names);
}
Constructor<?> constructor = type.getDeclaredConstructor();
constructor.setAccessible(true);
List<Field> fields = new ArrayList<>();
for (Class<?> current = type; current != null && current != Object.class;
current = current.getSuperclass()) {
for (Field field : current.getDeclaredFields()) {
if (!java.lang.reflect.Modifier.isStatic(field.getModifiers())) {
field.setAccessible(true);
fields.add(field);
}
}
}
return new BeanMetadata(constructor, List.copyOf(fields));
} catch (ReflectiveOperationException exception) {
throw new IllegalArgumentException(
"Response type must be a record or have a no-argument constructor: " + type.getName(), exception);
}
}
private Map<String, Integer> columns(ResultSetMetaData metadata) throws SQLException {
Map<String, Integer> result = new LinkedHashMap<>();
for (int column = 1; column <= metadata.getColumnCount(); column++) {
result.putIfAbsent(normalize(metadata.getColumnLabel(column)), column);
}
return result;
}
private static String normalize(String value) {
return value.replace("_", "").toLowerCase(Locale.ROOT);
}
private static boolean isScalar(Class<?> type) {
return type.isPrimitive() || type.isEnum()
|| Number.class.isAssignableFrom(type)
|| type == String.class || type == Boolean.class || type == Character.class
|| type == java.util.UUID.class
|| type.getPackageName().equals("java.time");
}
private sealed interface TypeMetadata permits RecordMetadata, BeanMetadata {
<T> RowMapper<T> mapper(Class<T> type, Map<String, Integer> columns);
}
private record RecordMetadata(
Constructor<?> constructor, Class<?>[] parameterTypes, String[] names) implements TypeMetadata {
@Override
public <T> RowMapper<T> mapper(Class<T> type, Map<String, Integer> columns) {
int[] indexes = new int[names.length];
for (int index = 0; index < names.length; index++) {
Integer column = columns.get(names[index]);
if (column == null) throw new IllegalArgumentException(
"Result does not contain record component: " + names[index]);
indexes[index] = column;
}
return result -> {
try {
Object[] values = new Object[indexes.length];
for (int index = 0; index < indexes.length; index++) {
values[index] = ValueConverter.convert(
result.getObject(indexes[index]), parameterTypes[index]);
}
return type.cast(constructor.newInstance(values));
} catch (ReflectiveOperationException exception) {
throw new DatabaseExecutionException(0, "Unable to map record " + type.getName(), exception);
}
};
}
}
private record BeanMetadata(Constructor<?> constructor, List<Field> fields) implements TypeMetadata {
@Override
public <T> RowMapper<T> mapper(Class<T> type, Map<String, Integer> columns) {
List<FieldBinding> bindings = fields.stream()
.map(field -> new FieldBinding(field, columns.get(normalize(field.getName()))))
.filter(binding -> binding.column() != null)
.toList();
return result -> {
try {
T instance = type.cast(constructor.newInstance());
for (FieldBinding binding : bindings) {
binding.field().set(instance, ValueConverter.convert(
result.getObject(binding.column()), binding.field().getType()));
}
return instance;
} catch (ReflectiveOperationException exception) {
throw new DatabaseExecutionException(0, "Unable to map bean " + type.getName(), exception);
}
};
}
}
private record FieldBinding(Field field, Integer column) {}
}

View File

@@ -0,0 +1,42 @@
package com.cygnus.db;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.util.UUID;
final class ParameterBinder {
void bind(PreparedStatement statement, Object[] parameters) throws SQLException {
Object[] values = parameters == null ? new Object[0] : parameters;
for (int index = 0; index < values.length; index++) {
bind(statement, index + 1, values[index]);
}
}
private void bind(PreparedStatement statement, int index, Object value) throws SQLException {
if (value instanceof SqlParameter parameter) {
if (parameter.value() == null) statement.setNull(index, parameter.sqlType());
else statement.setObject(index, parameter.value(), parameter.sqlType());
} else if (value == null) {
statement.setObject(index, null);
} else if (value instanceof Instant instant) {
statement.setTimestamp(index, Timestamp.from(instant));
} else if (value instanceof LocalDate date) {
statement.setObject(index, date);
} else if (value instanceof LocalDateTime dateTime) {
statement.setObject(index, dateTime);
} else if (value instanceof OffsetDateTime dateTime) {
statement.setObject(index, dateTime);
} else if (value instanceof UUID uuid) {
statement.setObject(index, uuid);
} else if (value instanceof Enum<?> enumValue) {
statement.setString(index, enumValue.name());
} else {
statement.setObject(index, value);
}
}
}

View File

@@ -0,0 +1,29 @@
package com.cygnus.db;
import java.util.Objects;
import java.util.regex.Pattern;
public record QueryDefinition(int queryId, QueryType type, String sql) {
private static final String DELIMITER = "!C0L!";
private static final Pattern LEGACY_PARAMETER = Pattern.compile("\\bval\\d+\\b");
public QueryDefinition {
if (queryId <= 0) throw new QueryDefinitionException("Query ID must be positive");
Objects.requireNonNull(type, "type");
if (sql == null || sql.isBlank()) throw new QueryDefinitionException("Query SQL is empty: " + queryId);
if (LEGACY_PARAMETER.matcher(sql).find()) {
throw new QueryDefinitionException(
"Query " + queryId + " has not been migrated to parameterized SQL");
}
}
public static QueryDefinition parse(int queryId, String storedValue) {
if (storedValue == null) throw new QueryDefinitionException("Query was not found: " + queryId);
int delimiter = storedValue.indexOf(DELIMITER);
if (delimiter <= 0) throw new QueryDefinitionException("Invalid query definition: " + queryId);
return new QueryDefinition(
queryId,
QueryType.parse(storedValue.substring(0, delimiter)),
storedValue.substring(delimiter + DELIMITER.length()).trim());
}
}

View File

@@ -0,0 +1,6 @@
package com.cygnus.db;
public final class QueryDefinitionException extends RuntimeException {
public QueryDefinitionException(String message) { super(message); }
public QueryDefinitionException(String message, Throwable cause) { super(message, cause); }
}

View File

@@ -0,0 +1,6 @@
package com.cygnus.db;
@FunctionalInterface
public interface QueryDefinitionProvider {
QueryDefinition get(int queryId);
}

View File

@@ -0,0 +1,15 @@
package com.cygnus.db;
import java.util.Locale;
public enum QueryType {
SELECT, INSERT, UPDATE, DELETE, PROCEDURE;
public static QueryType parse(String value) {
try {
return valueOf(value.trim().toUpperCase(Locale.ROOT));
} catch (RuntimeException exception) {
throw new QueryDefinitionException("Unsupported query type: " + value, exception);
}
}
}

View File

@@ -0,0 +1,6 @@
package com.cygnus.db;
@FunctionalInterface
public interface RowConsumer<T> {
void accept(T row) throws Exception;
}

View File

@@ -0,0 +1,9 @@
package com.cygnus.db;
import java.sql.ResultSet;
import java.sql.SQLException;
@FunctionalInterface
public interface RowMapper<T> {
T map(ResultSet resultSet) throws SQLException;
}

View File

@@ -0,0 +1,9 @@
package com.cygnus.db;
import java.util.Map;
public interface RowView {
Object get(String column);
<T> T get(String column, Class<T> type);
Map<String, Object> asMap();
}

View File

@@ -0,0 +1,6 @@
package com.cygnus.db;
public record SqlParameter(int sqlType, Object value) {
public static SqlParameter of(int sqlType, Object value) { return new SqlParameter(sqlType, value); }
public static SqlParameter nullValue(int sqlType) { return new SqlParameter(sqlType, null); }
}

View File

@@ -0,0 +1,6 @@
package com.cygnus.db;
@FunctionalInterface
public interface TransactionCallback<T> {
T execute(TransactionOperations operations) throws Exception;
}

View File

@@ -0,0 +1,12 @@
package com.cygnus.db;
import java.util.List;
import java.util.Optional;
public interface TransactionOperations {
<T> List<T> query(int queryId, Object[] parameters, Class<T> responseType);
<T> Optional<T> queryOne(int queryId, Object[] parameters, Class<T> responseType);
int insert(int queryId, Object[] parameters);
int update(int queryId, Object[] parameters);
int delete(int queryId, Object[] parameters);
}

View File

@@ -0,0 +1,79 @@
package com.cygnus.db;
import java.math.BigDecimal;
import java.sql.Date;
import java.sql.Timestamp;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.util.UUID;
final class ValueConverter {
private ValueConverter() {}
@SuppressWarnings({"unchecked", "rawtypes"})
static <T> T convert(Object value, Class<T> target) {
if (value == null) return target.isPrimitive() ? (T) primitiveDefault(target) : null;
Class<?> boxed = box(target);
if (boxed.isInstance(value)) return (T) value;
if (boxed == String.class) return (T) value.toString();
if (Number.class.isAssignableFrom(boxed) && value instanceof Number number) {
return (T) number(number, boxed);
}
if (boxed == Boolean.class) {
if (value instanceof Number number) return (T) Boolean.valueOf(number.intValue() != 0);
return (T) Boolean.valueOf(value.toString());
}
if (boxed == Character.class) {
String text = value.toString();
if (text.isEmpty()) throw new IllegalArgumentException("Cannot convert an empty value to char");
return (T) Character.valueOf(text.charAt(0));
}
if (boxed == UUID.class) return (T) UUID.fromString(value.toString());
if (boxed == LocalDate.class && value instanceof Date date) return (T) date.toLocalDate();
if (boxed == LocalDateTime.class && value instanceof Timestamp timestamp) return (T) timestamp.toLocalDateTime();
if (boxed == Instant.class && value instanceof Timestamp timestamp) return (T) timestamp.toInstant();
if (boxed == OffsetDateTime.class && value instanceof OffsetDateTime dateTime) return (T) dateTime;
if (boxed.isEnum()) return (T) Enum.valueOf((Class<Enum>) boxed, value.toString());
throw new DatabaseExecutionException(0,
"Cannot convert " + value.getClass().getName() + " to " + target.getName(), null);
}
private static Object number(Number value, Class<?> target) {
if (target == Integer.class) return value.intValue();
if (target == Long.class) return value.longValue();
if (target == Double.class) return value.doubleValue();
if (target == Float.class) return value.floatValue();
if (target == Short.class) return value.shortValue();
if (target == Byte.class) return value.byteValue();
if (target == BigDecimal.class) return value instanceof BigDecimal decimal
? decimal : new BigDecimal(value.toString());
return value;
}
private static Class<?> box(Class<?> type) {
if (!type.isPrimitive()) return type;
if (type == int.class) return Integer.class;
if (type == long.class) return Long.class;
if (type == double.class) return Double.class;
if (type == float.class) return Float.class;
if (type == short.class) return Short.class;
if (type == byte.class) return Byte.class;
if (type == boolean.class) return Boolean.class;
if (type == char.class) return Character.class;
return type;
}
private static Object primitiveDefault(Class<?> type) {
if (type == boolean.class) return false;
if (type == char.class) return '\0';
if (type == byte.class) return (byte) 0;
if (type == short.class) return (short) 0;
if (type == int.class) return 0;
if (type == long.class) return 0L;
if (type == float.class) return 0F;
if (type == double.class) return 0D;
return null;
}
}

View File

@@ -0,0 +1,86 @@
package com.cygnus.db;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.sql.Connection;
import java.sql.Statement;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import org.h2.jdbcx.JdbcDataSource;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
class JdbcCygnusDbExecutorTest {
private JdbcCygnusDbExecutor executor;
@BeforeEach
void setUp() throws Exception {
JdbcDataSource dataSource = new JdbcDataSource();
dataSource.setURL("jdbc:h2:mem:cygnus;DB_CLOSE_DELAY=-1");
try (Connection connection = dataSource.getConnection(); Statement statement = connection.createStatement()) {
statement.execute("DROP TABLE IF EXISTS person");
statement.execute("CREATE TABLE person (id bigint generated by default as identity primary key, name varchar(100), active boolean)");
statement.execute("INSERT INTO person(name, active) VALUES ('Asha', true), ('Ravi', false)");
}
Map<Integer, QueryDefinition> queries = Map.of(
1, new QueryDefinition(1, QueryType.SELECT,
"select id, name from person where active = ? order by id"),
2, new QueryDefinition(2, QueryType.INSERT,
"insert into person(name, active) values (?, ?)"),
3, new QueryDefinition(3, QueryType.UPDATE,
"update person set active = ? where name = ?"),
4, new QueryDefinition(4, QueryType.DELETE,
"delete from person where name = ?"),
5, new QueryDefinition(5, QueryType.PROCEDURE, "call abs(?)"));
executor = new JdbcCygnusDbExecutor(dataSource, queries::get, new ExecutorOptions(10, 5));
}
@Test
void mapsSelectDirectlyToRecordsAndDynamicRows() {
var people = executor.query(1, new Object[]{true}, Person.class);
assertEquals(java.util.List.of(new Person(1L, "Asha")), people);
RowView row = executor.query(1, new Object[]{true}).getFirst();
assertEquals("Asha", row.get("NAME", String.class));
assertEquals(1L, row.get("id", Long.class));
}
@Test
void executesInsertUpdateDeleteAndGeneratedKeys() {
Long id = executor.insert(2, new Object[]{"Mira", true}, Long.class);
assertTrue(id > 0);
assertEquals(1, executor.update(3, new Object[]{false, "Mira"}));
assertEquals(1, executor.delete(4, new Object[]{"Mira"}));
}
@Test
void executesProcedureAndStreamsWithoutMaterializingRows() {
assertEquals(7, executor.procedure(5, new Object[]{-7}, Integer.class));
AtomicInteger count = new AtomicInteger();
executor.stream(1, new Object[]{true},
result -> new Person(result.getLong("id"), result.getString("name")),
row -> count.incrementAndGet());
assertEquals(1, count.get());
}
@Test
void rollsBackFailedTransactions() {
assertThrows(IllegalStateException.class, () -> executor.transaction(tx -> {
tx.insert(2, new Object[]{"Rollback", true});
throw new IllegalStateException("stop");
}));
assertFalse(executor.query(1, new Object[]{true}, Person.class).stream()
.anyMatch(person -> person.name().equals("Rollback")));
}
@Test
void rejectsLegacyStringReplacementQueries() {
assertThrows(QueryDefinitionException.class, () ->
QueryDefinition.parse(513, "select!C0L!select * from person where id=val0"));
}
record Person(long id, String name) {}
}

Some files were not shown because too many files have changed in this diff Show More