| @@ -0,0 +1,37 @@ | |||
| HELP.md | |||
| .gradle | |||
| build/ | |||
| !gradle/wrapper/gradle-wrapper.jar | |||
| !**/src/main/**/build/ | |||
| !**/src/test/**/build/ | |||
| ### STS ### | |||
| .apt_generated | |||
| .classpath | |||
| .factorypath | |||
| .project | |||
| .settings | |||
| .springBeans | |||
| .sts4-cache | |||
| bin/ | |||
| !**/src/main/**/bin/ | |||
| !**/src/test/**/bin/ | |||
| ### IntelliJ IDEA ### | |||
| .idea | |||
| *.iws | |||
| *.iml | |||
| *.ipr | |||
| out/ | |||
| !**/src/main/**/out/ | |||
| !**/src/test/**/out/ | |||
| ### NetBeans ### | |||
| /nbproject/private/ | |||
| /nbbuild/ | |||
| /dist/ | |||
| /nbdist/ | |||
| /.nb-gradle/ | |||
| ### VS Code ### | |||
| .vscode/ | |||
| @@ -0,0 +1,55 @@ | |||
| # 2Fi LIONER Backend Setup | |||
| ## 1. Create MySQL database | |||
| - Run the following command in MySQL | |||
| ``` | |||
| CREATE SCHEMA `lionerdb` DEFAULT CHARACTER SET utf8 COLLATE utf8_bin; | |||
| ``` | |||
| ## 2. Edit the config to match with the environment | |||
| - application-db-local.yml | |||
| - Update the MySQL database location & login info: | |||
| ``` | |||
| spring: | |||
| datasource: | |||
| jdbc-url: jdbc:mysql://127.0.0.1:3308/lionerdb?useUnicode=true&characterEncoding=UTF8&serverTimezone=GMT%2B8 | |||
| username: root | |||
| password: secret | |||
| ``` | |||
| ## 3. Configurations for VScode | |||
| - Build the **launch.json** & **settings.json** files, put them in **.vscode** folder and paste the following code: | |||
| - **launch.json** | |||
| ``` | |||
| { | |||
| // Use IntelliSense to learn about possible attributes. | |||
| // Hover to view descriptions of existing attributes. | |||
| // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 | |||
| "version": "0.2.0", | |||
| "configurations": [ | |||
| { | |||
| "type": "java", | |||
| "name": "Launch Local", | |||
| "request": "launch", | |||
| "mainClass": "com.ffii.lioner.LionerApplication", | |||
| "console": "internalConsole", | |||
| "projectName": "LIONER", | |||
| "vmArgs": "-Xms2g -Xmx4g", | |||
| "args": "--spring.profiles.active=db-local,ldap-local,local" | |||
| } | |||
| ] | |||
| } | |||
| ``` | |||
| - **settings.json** | |||
| - Modify the java sdk directory if necessary | |||
| ``` | |||
| { | |||
| "java.configuration.updateBuildConfiguration": "interactive", | |||
| "java.jdt.ls.java.home": "C:\\java\\jdk-17.0.8", | |||
| "java.jdt.ls.vmargs": "-XX:+UseParallelGC -XX:GCTimeRatio=4 -XX:AdaptiveSizePolicyWeight=90 -Dsun.zip.disableMemoryMapping=true -Xmx4G -Xms100m -Xlog:disable" | |||
| } | |||
| ``` | |||
| ## 4. Run the application | |||
| - Run the application in VScode | |||
| @@ -0,0 +1,69 @@ | |||
| plugins { | |||
| id 'java' | |||
| id 'org.springframework.boot' version '3.1.9' | |||
| id 'io.spring.dependency-management' version '1.1.0' | |||
| } | |||
| group = 'com.ffii' | |||
| version = '0.0.1-SNAPSHOT' | |||
| java { | |||
| sourceCompatibility = '17' | |||
| } | |||
| repositories { | |||
| mavenCentral() | |||
| } | |||
| dependencies { | |||
| implementation 'org.springframework.boot:spring-boot-starter-data-jpa' | |||
| implementation 'org.springframework.boot:spring-boot-starter-data-ldap' | |||
| implementation 'org.springframework.boot:spring-boot-starter-mail' | |||
| implementation 'org.springframework.boot:spring-boot-starter-security' | |||
| implementation 'org.springframework.boot:spring-boot-starter-web' | |||
| implementation 'org.springframework.boot:spring-boot-starter-validation' | |||
| implementation 'org.springframework.boot:spring-boot-starter-log4j2' | |||
| implementation 'org.springframework.security:spring-security-ldap' | |||
| implementation 'org.liquibase:liquibase-core' | |||
| implementation group: 'org.apache.commons', name: 'commons-lang3', version: '3.12.0' | |||
| implementation group: 'org.apache.poi', name: 'poi', version: '5.2.3' | |||
| implementation group: 'org.apache.poi', name: 'poi-ooxml', version: '5.2.3' | |||
| implementation group: 'org.apache.pdfbox', name: 'pdfbox', version: '3.0.0' | |||
| implementation group: 'jakarta.persistence', name: 'jakarta.persistence-api', version: '3.1.0' | |||
| implementation group: 'jakarta.annotation', name: 'jakarta.annotation-api', version: '2.1.1' | |||
| implementation group: 'jakarta.validation', name: 'jakarta.validation-api', version: '3.0.2' | |||
| implementation group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-yaml', version: '2.15.2' | |||
| implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.15.2' | |||
| implementation group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.5' | |||
| implementation group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.5' | |||
| implementation group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.5' | |||
| implementation group: 'org.bytedeco', name: 'ffmpeg-platform', version: '5.1.2-1.5.8' | |||
| implementation group: 'org.bytedeco', name: 'javacv', version: '1.5.8' | |||
| implementation group: 'org.freemarker', name: 'freemarker', version: '2.3.32' | |||
| compileOnly group: 'jakarta.servlet', name: 'jakarta.servlet-api', version: '6.0.0' | |||
| implementation group: 'org.apache.poi', name: 'poi', version: '5.2.2' | |||
| implementation group: 'org.apache.poi', name: 'poi-ooxml', version: '5.2.2' | |||
| implementation group: 'org.docx4j', name: 'docx4j-core', version: '11.4.11' | |||
| implementation group: 'org.docx4j', name: 'docx4j-JAXB-ReferenceImpl', version: '11.4.11' | |||
| implementation group: 'com.google.code.gson', name: 'gson', version: '2.10.1' | |||
| runtimeOnly 'com.mysql:mysql-connector-j' | |||
| runtimeOnly 'com.unboundid:unboundid-ldapsdk:6.0.9' | |||
| testImplementation 'org.springframework.boot:spring-boot-starter-test' | |||
| testImplementation 'org.springframework.security:spring-security-test' | |||
| } | |||
| configurations { | |||
| all { | |||
| exclude group: 'org.springframework.boot', module: 'spring-boot-starter-logging' | |||
| } | |||
| } | |||
| @@ -0,0 +1,6 @@ | |||
| distributionBase=GRADLE_USER_HOME | |||
| distributionPath=wrapper/dists | |||
| distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip | |||
| networkTimeout=10000 | |||
| zipStoreBase=GRADLE_USER_HOME | |||
| zipStorePath=wrapper/dists | |||
| @@ -0,0 +1,240 @@ | |||
| #!/bin/sh | |||
| # | |||
| # Copyright © 2015-2021 the original authors. | |||
| # | |||
| # Licensed under the Apache License, Version 2.0 (the "License"); | |||
| # you may not use this file except in compliance with the License. | |||
| # You may obtain a copy of the License at | |||
| # | |||
| # https://www.apache.org/licenses/LICENSE-2.0 | |||
| # | |||
| # Unless required by applicable law or agreed to in writing, software | |||
| # distributed under the License is distributed on an "AS IS" BASIS, | |||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
| # See the License for the specific language governing permissions and | |||
| # limitations under the License. | |||
| # | |||
| ############################################################################## | |||
| # | |||
| # Gradle start up script for POSIX generated by Gradle. | |||
| # | |||
| # Important for running: | |||
| # | |||
| # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is | |||
| # noncompliant, but you have some other compliant shell such as ksh or | |||
| # bash, then to run this script, type that shell name before the whole | |||
| # command line, like: | |||
| # | |||
| # ksh Gradle | |||
| # | |||
| # Busybox and similar reduced shells will NOT work, because this script | |||
| # requires all of these POSIX shell features: | |||
| # * functions; | |||
| # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», | |||
| # «${var#prefix}», «${var%suffix}», and «$( cmd )»; | |||
| # * compound commands having a testable exit status, especially «case»; | |||
| # * various built-in commands including «command», «set», and «ulimit». | |||
| # | |||
| # Important for patching: | |||
| # | |||
| # (2) This script targets any POSIX shell, so it avoids extensions provided | |||
| # by Bash, Ksh, etc; in particular arrays are avoided. | |||
| # | |||
| # The "traditional" practice of packing multiple parameters into a | |||
| # space-separated string is a well documented source of bugs and security | |||
| # problems, so this is (mostly) avoided, by progressively accumulating | |||
| # options in "$@", and eventually passing that to Java. | |||
| # | |||
| # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, | |||
| # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; | |||
| # see the in-line comments for details. | |||
| # | |||
| # There are tweaks for specific operating systems such as AIX, CygWin, | |||
| # Darwin, MinGW, and NonStop. | |||
| # | |||
| # (3) This script is generated from the Groovy template | |||
| # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt | |||
| # within the Gradle project. | |||
| # | |||
| # You can find Gradle at https://github.com/gradle/gradle/. | |||
| # | |||
| ############################################################################## | |||
| # Attempt to set APP_HOME | |||
| # Resolve links: $0 may be a link | |||
| app_path=$0 | |||
| # Need this for daisy-chained symlinks. | |||
| while | |||
| APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path | |||
| [ -h "$app_path" ] | |||
| do | |||
| ls=$( ls -ld "$app_path" ) | |||
| link=${ls#*' -> '} | |||
| case $link in #( | |||
| /*) app_path=$link ;; #( | |||
| *) app_path=$APP_HOME$link ;; | |||
| esac | |||
| done | |||
| APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit | |||
| APP_NAME="Gradle" | |||
| APP_BASE_NAME=${0##*/} | |||
| # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. | |||
| DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' | |||
| # Use the maximum available, or set MAX_FD != -1 to use that value. | |||
| MAX_FD=maximum | |||
| warn () { | |||
| echo "$*" | |||
| } >&2 | |||
| die () { | |||
| echo | |||
| echo "$*" | |||
| echo | |||
| exit 1 | |||
| } >&2 | |||
| # OS specific support (must be 'true' or 'false'). | |||
| cygwin=false | |||
| msys=false | |||
| darwin=false | |||
| nonstop=false | |||
| case "$( uname )" in #( | |||
| CYGWIN* ) cygwin=true ;; #( | |||
| Darwin* ) darwin=true ;; #( | |||
| MSYS* | MINGW* ) msys=true ;; #( | |||
| NONSTOP* ) nonstop=true ;; | |||
| esac | |||
| CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar | |||
| # Determine the Java command to use to start the JVM. | |||
| if [ -n "$JAVA_HOME" ] ; then | |||
| if [ -x "$JAVA_HOME/jre/sh/java" ] ; then | |||
| # IBM's JDK on AIX uses strange locations for the executables | |||
| JAVACMD=$JAVA_HOME/jre/sh/java | |||
| else | |||
| JAVACMD=$JAVA_HOME/bin/java | |||
| fi | |||
| if [ ! -x "$JAVACMD" ] ; then | |||
| die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME | |||
| Please set the JAVA_HOME variable in your environment to match the | |||
| location of your Java installation." | |||
| fi | |||
| else | |||
| JAVACMD=java | |||
| which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. | |||
| Please set the JAVA_HOME variable in your environment to match the | |||
| location of your Java installation." | |||
| fi | |||
| # Increase the maximum file descriptors if we can. | |||
| if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then | |||
| case $MAX_FD in #( | |||
| max*) | |||
| MAX_FD=$( ulimit -H -n ) || | |||
| warn "Could not query maximum file descriptor limit" | |||
| esac | |||
| case $MAX_FD in #( | |||
| '' | soft) :;; #( | |||
| *) | |||
| ulimit -n "$MAX_FD" || | |||
| warn "Could not set maximum file descriptor limit to $MAX_FD" | |||
| esac | |||
| fi | |||
| # Collect all arguments for the java command, stacking in reverse order: | |||
| # * args from the command line | |||
| # * the main class name | |||
| # * -classpath | |||
| # * -D...appname settings | |||
| # * --module-path (only if needed) | |||
| # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. | |||
| # For Cygwin or MSYS, switch paths to Windows format before running java | |||
| if "$cygwin" || "$msys" ; then | |||
| APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) | |||
| CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) | |||
| JAVACMD=$( cygpath --unix "$JAVACMD" ) | |||
| # Now convert the arguments - kludge to limit ourselves to /bin/sh | |||
| for arg do | |||
| if | |||
| case $arg in #( | |||
| -*) false ;; # don't mess with options #( | |||
| /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath | |||
| [ -e "$t" ] ;; #( | |||
| *) false ;; | |||
| esac | |||
| then | |||
| arg=$( cygpath --path --ignore --mixed "$arg" ) | |||
| fi | |||
| # Roll the args list around exactly as many times as the number of | |||
| # args, so each arg winds up back in the position where it started, but | |||
| # possibly modified. | |||
| # | |||
| # NB: a `for` loop captures its iteration list before it begins, so | |||
| # changing the positional parameters here affects neither the number of | |||
| # iterations, nor the values presented in `arg`. | |||
| shift # remove old arg | |||
| set -- "$@" "$arg" # push replacement arg | |||
| done | |||
| fi | |||
| # Collect all arguments for the java command; | |||
| # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of | |||
| # shell script including quotes and variable substitutions, so put them in | |||
| # double quotes to make sure that they get re-expanded; and | |||
| # * put everything else in single quotes, so that it's not re-expanded. | |||
| set -- \ | |||
| "-Dorg.gradle.appname=$APP_BASE_NAME" \ | |||
| -classpath "$CLASSPATH" \ | |||
| org.gradle.wrapper.GradleWrapperMain \ | |||
| "$@" | |||
| # Stop when "xargs" is not available. | |||
| if ! command -v xargs >/dev/null 2>&1 | |||
| then | |||
| die "xargs is not available" | |||
| fi | |||
| # Use "xargs" to parse quoted args. | |||
| # | |||
| # With -n1 it outputs one arg per line, with the quotes and backslashes removed. | |||
| # | |||
| # In Bash we could simply go: | |||
| # | |||
| # readarray ARGS < <( xargs -n1 <<<"$var" ) && | |||
| # set -- "${ARGS[@]}" "$@" | |||
| # | |||
| # but POSIX shell has neither arrays nor command substitution, so instead we | |||
| # post-process each arg (as a line of input to sed) to backslash-escape any | |||
| # character that might be a shell metacharacter, then use eval to reverse | |||
| # that process (while maintaining the separation between arguments), and wrap | |||
| # the whole thing up as a single "set" statement. | |||
| # | |||
| # This will of course break if any of these variables contains a newline or | |||
| # an unmatched quote. | |||
| # | |||
| eval "set -- $( | |||
| printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | | |||
| xargs -n1 | | |||
| sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | | |||
| tr '\n' ' ' | |||
| )" '"$@"' | |||
| exec "$JAVACMD" "$@" | |||
| @@ -0,0 +1,91 @@ | |||
| @rem | |||
| @rem Copyright 2015 the original author or authors. | |||
| @rem | |||
| @rem Licensed under the Apache License, Version 2.0 (the "License"); | |||
| @rem you may not use this file except in compliance with the License. | |||
| @rem You may obtain a copy of the License at | |||
| @rem | |||
| @rem https://www.apache.org/licenses/LICENSE-2.0 | |||
| @rem | |||
| @rem Unless required by applicable law or agreed to in writing, software | |||
| @rem distributed under the License is distributed on an "AS IS" BASIS, | |||
| @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
| @rem See the License for the specific language governing permissions and | |||
| @rem limitations under the License. | |||
| @rem | |||
| @if "%DEBUG%"=="" @echo off | |||
| @rem ########################################################################## | |||
| @rem | |||
| @rem Gradle startup script for Windows | |||
| @rem | |||
| @rem ########################################################################## | |||
| @rem Set local scope for the variables with windows NT shell | |||
| if "%OS%"=="Windows_NT" setlocal | |||
| set DIRNAME=%~dp0 | |||
| if "%DIRNAME%"=="" set DIRNAME=. | |||
| set APP_BASE_NAME=%~n0 | |||
| set APP_HOME=%DIRNAME% | |||
| @rem Resolve any "." and ".." in APP_HOME to make it shorter. | |||
| for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi | |||
| @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. | |||
| set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" | |||
| @rem Find java.exe | |||
| if defined JAVA_HOME goto findJavaFromJavaHome | |||
| set JAVA_EXE=java.exe | |||
| %JAVA_EXE% -version >NUL 2>&1 | |||
| if %ERRORLEVEL% equ 0 goto execute | |||
| echo. | |||
| echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. | |||
| echo. | |||
| echo Please set the JAVA_HOME variable in your environment to match the | |||
| echo location of your Java installation. | |||
| goto fail | |||
| :findJavaFromJavaHome | |||
| set JAVA_HOME=%JAVA_HOME:"=% | |||
| set JAVA_EXE=%JAVA_HOME%/bin/java.exe | |||
| if exist "%JAVA_EXE%" goto execute | |||
| echo. | |||
| echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% | |||
| echo. | |||
| echo Please set the JAVA_HOME variable in your environment to match the | |||
| echo location of your Java installation. | |||
| goto fail | |||
| :execute | |||
| @rem Setup the command line | |||
| set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar | |||
| @rem Execute Gradle | |||
| "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* | |||
| :end | |||
| @rem End local scope for the variables with windows NT shell | |||
| if %ERRORLEVEL% equ 0 goto mainEnd | |||
| :fail | |||
| rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of | |||
| rem the _cmd.exe /c_ return code! | |||
| set EXIT_CODE=%ERRORLEVEL% | |||
| if %EXIT_CODE% equ 0 set EXIT_CODE=1 | |||
| if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% | |||
| exit /b %EXIT_CODE% | |||
| :mainEnd | |||
| if "%OS%"=="Windows_NT" endlocal | |||
| :omega | |||
| @@ -0,0 +1 @@ | |||
| rootProject.name = 'LIONER' | |||
| @@ -0,0 +1,124 @@ | |||
| package com.ffii.core.entity; | |||
| import java.io.Serializable; | |||
| import java.time.LocalDateTime; | |||
| import java.util.Optional; | |||
| import org.springframework.security.core.context.SecurityContextHolder; | |||
| import com.ffii.lioner.modules.user.entity.User; | |||
| import jakarta.persistence.Column; | |||
| import jakarta.persistence.MappedSuperclass; | |||
| import jakarta.persistence.PrePersist; | |||
| import jakarta.persistence.PreUpdate; | |||
| import jakarta.persistence.Version; | |||
| import jakarta.validation.constraints.NotNull; | |||
| /** @author Terence */ | |||
| @MappedSuperclass | |||
| public abstract class BaseEntity<ID extends Serializable> extends IdEntity<ID> { | |||
| @NotNull | |||
| @Version | |||
| @Column | |||
| private Integer version; | |||
| @NotNull | |||
| @Column(updatable = false) | |||
| private LocalDateTime created; | |||
| @Column(updatable = false) | |||
| private Long createdBy; | |||
| @NotNull | |||
| @Column | |||
| private LocalDateTime modified; | |||
| @Column | |||
| private Long modifiedBy; | |||
| @NotNull | |||
| @Column | |||
| private Boolean deleted; | |||
| @PrePersist | |||
| public void autoSetCreated() { | |||
| this.setCreated(LocalDateTime.now()); | |||
| this.setModified(LocalDateTime.now()); | |||
| this.setDeleted(Boolean.FALSE); | |||
| Optional.ofNullable(SecurityContextHolder.getContext().getAuthentication()) | |||
| .ifPresentOrElse( | |||
| authentication -> { | |||
| Long userId = ((User)authentication.getPrincipal()).getId(); | |||
| this.setCreatedBy(userId); | |||
| this.setModifiedBy(userId); | |||
| }, | |||
| () -> { | |||
| this.setCreatedBy(null); | |||
| this.setModifiedBy(null); | |||
| }); | |||
| } | |||
| @PreUpdate | |||
| public void autoSetModified() { | |||
| this.setModified(LocalDateTime.now()); | |||
| Optional.ofNullable(SecurityContextHolder.getContext().getAuthentication()).ifPresentOrElse( | |||
| authentication -> this.setModifiedBy(((User)authentication.getPrincipal()).getId()), | |||
| () -> this.setModifiedBy(null)); | |||
| } | |||
| public Integer getVersion() { | |||
| return this.version; | |||
| } | |||
| public void setVersion(Integer version) { | |||
| this.version = version; | |||
| } | |||
| public LocalDateTime getCreated() { | |||
| return this.created; | |||
| } | |||
| public void setCreated(LocalDateTime created) { | |||
| this.created = created; | |||
| } | |||
| public Long getCreatedBy() { | |||
| return this.createdBy; | |||
| } | |||
| public void setCreatedBy(Long createdBy) { | |||
| this.createdBy = createdBy; | |||
| } | |||
| public LocalDateTime getModified() { | |||
| return this.modified; | |||
| } | |||
| public void setModified(LocalDateTime modified) { | |||
| this.modified = modified; | |||
| } | |||
| public Long getModifiedBy() { | |||
| return this.modifiedBy; | |||
| } | |||
| public void setModifiedBy(Long modifiedBy) { | |||
| this.modifiedBy = modifiedBy; | |||
| } | |||
| public Boolean isDeleted() { | |||
| return this.deleted; | |||
| } | |||
| public Boolean getDeleted() { | |||
| return this.deleted; | |||
| } | |||
| public void setDeleted(Boolean deleted) { | |||
| this.deleted = deleted; | |||
| } | |||
| } | |||
| @@ -0,0 +1,49 @@ | |||
| package com.ffii.core.entity; | |||
| import java.io.Serializable; | |||
| import jakarta.persistence.GeneratedValue; | |||
| import jakarta.persistence.GenerationType; | |||
| import jakarta.persistence.Id; | |||
| import jakarta.persistence.MappedSuperclass; | |||
| import jakarta.persistence.PostLoad; | |||
| import jakarta.persistence.PrePersist; | |||
| import jakarta.persistence.Transient; | |||
| import org.springframework.data.domain.Persistable; | |||
| import com.fasterxml.jackson.annotation.JsonIgnore; | |||
| /** @author Terence */ | |||
| @MappedSuperclass | |||
| public abstract class IdEntity<ID extends Serializable> implements Persistable<ID> { | |||
| @Id | |||
| @GeneratedValue(strategy = GenerationType.IDENTITY) | |||
| private ID id; | |||
| @Transient | |||
| private boolean isNew = true; | |||
| @JsonIgnore | |||
| @Override | |||
| public boolean isNew() { | |||
| return isNew; | |||
| } | |||
| @PrePersist | |||
| @PostLoad | |||
| void markNotNew() { | |||
| this.isNew = false; | |||
| } | |||
| // getter and setter | |||
| public ID getId() { | |||
| return id; | |||
| } | |||
| public void setId(ID id) { | |||
| this.id = id; | |||
| } | |||
| } | |||
| @@ -0,0 +1,15 @@ | |||
| package com.ffii.core.exception; | |||
| import org.springframework.http.HttpStatus; | |||
| import org.springframework.web.server.ResponseStatusException; | |||
| public class BadRequestException extends ResponseStatusException { | |||
| public BadRequestException() { | |||
| super(HttpStatus.BAD_REQUEST); | |||
| } | |||
| public BadRequestException(String reason) { | |||
| super(HttpStatus.BAD_REQUEST, reason); | |||
| } | |||
| } | |||
| @@ -0,0 +1,16 @@ | |||
| package com.ffii.core.exception; | |||
| import org.springframework.http.HttpStatus; | |||
| import org.springframework.web.server.ResponseStatusException; | |||
| /* e.g. sub record not under record */ | |||
| public class ConflictException extends ResponseStatusException { | |||
| public ConflictException() { | |||
| super(HttpStatus.CONFLICT); | |||
| } | |||
| public ConflictException(String reason) { | |||
| super(HttpStatus.CONFLICT, reason); | |||
| } | |||
| } | |||
| @@ -0,0 +1,19 @@ | |||
| package com.ffii.core.exception; | |||
| import org.springframework.http.HttpStatus; | |||
| import org.springframework.web.server.ResponseStatusException; | |||
| public class InternalServerErrorException extends ResponseStatusException { | |||
| public InternalServerErrorException() { | |||
| super(HttpStatus.INTERNAL_SERVER_ERROR); | |||
| } | |||
| public InternalServerErrorException(String reason) { | |||
| super(HttpStatus.INTERNAL_SERVER_ERROR, reason); | |||
| } | |||
| public InternalServerErrorException(String reason, Throwable e) { | |||
| super(HttpStatus.INTERNAL_SERVER_ERROR, reason, e); | |||
| } | |||
| } | |||
| @@ -0,0 +1,13 @@ | |||
| package com.ffii.core.exception; | |||
| import org.springframework.http.HttpStatus; | |||
| import org.springframework.web.server.ResponseStatusException; | |||
| /* main record not found (e.g. item record) */ | |||
| public class NotFoundException extends ResponseStatusException{ | |||
| public NotFoundException() { | |||
| super(HttpStatus.NOT_FOUND); | |||
| } | |||
| } | |||
| @@ -0,0 +1,37 @@ | |||
| package com.ffii.core.exception; | |||
| import java.util.Map; | |||
| import jakarta.validation.constraints.NotNull; | |||
| import org.springframework.http.HttpStatus; | |||
| import org.springframework.web.server.ResponseStatusException; | |||
| import com.fasterxml.jackson.core.JsonProcessingException; | |||
| import com.fasterxml.jackson.databind.ObjectMapper; | |||
| /* sub record not found (e.g. item_line record) */ | |||
| public class UnprocessableEntityException extends ResponseStatusException { | |||
| public UnprocessableEntityException() { | |||
| super(HttpStatus.UNPROCESSABLE_ENTITY); | |||
| } | |||
| public UnprocessableEntityException(@NotNull Map<String, Object> map) { | |||
| super(HttpStatus.UNPROCESSABLE_ENTITY, map2Str(map)); | |||
| } | |||
| public UnprocessableEntityException(String reason) { | |||
| super(HttpStatus.UNPROCESSABLE_ENTITY, reason); | |||
| } | |||
| private static String map2Str(@NotNull Map<String, Object> map) { | |||
| try { | |||
| return new ObjectMapper().writeValueAsString(map); | |||
| } catch (JsonProcessingException e) { | |||
| e.printStackTrace(); | |||
| return ""; | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,32 @@ | |||
| package com.ffii.core.response; | |||
| public class AuthRes { | |||
| private boolean success; | |||
| private String exception; | |||
| public AuthRes(boolean success, String exception) { | |||
| this.success = success; | |||
| this.exception = exception; | |||
| } | |||
| public boolean isSuccess() { | |||
| return this.success; | |||
| } | |||
| public boolean getSuccess() { | |||
| return this.success; | |||
| } | |||
| public void setSuccess(boolean success) { | |||
| this.success = success; | |||
| } | |||
| public String getException() { | |||
| return this.exception; | |||
| } | |||
| public void setException(String exception) { | |||
| this.exception = exception; | |||
| } | |||
| } | |||
| @@ -0,0 +1,21 @@ | |||
| package com.ffii.core.response; | |||
| public class DataRes<T> { | |||
| private T data; | |||
| public DataRes() { | |||
| } | |||
| public DataRes(T data) { | |||
| this.data = data; | |||
| } | |||
| public T getData() { | |||
| return data; | |||
| } | |||
| public void setData(T data) { | |||
| this.data = data; | |||
| } | |||
| } | |||
| @@ -0,0 +1,36 @@ | |||
| package com.ffii.core.response; | |||
| import java.time.LocalDateTime; | |||
| public class ErrorRes { | |||
| private LocalDateTime timestamp; | |||
| private String traceId; | |||
| public ErrorRes() { | |||
| this.timestamp = LocalDateTime.now(); | |||
| } | |||
| public ErrorRes(String traceId) { | |||
| this.timestamp = LocalDateTime.now(); | |||
| this.traceId = traceId; | |||
| } | |||
| public LocalDateTime getTimestamp() { | |||
| return timestamp; | |||
| } | |||
| public void setTimestamp(LocalDateTime timestamp) { | |||
| this.timestamp = timestamp; | |||
| } | |||
| public String getTraceId() { | |||
| return traceId; | |||
| } | |||
| public void setTraceId(String traceId) { | |||
| this.traceId = traceId; | |||
| } | |||
| } | |||
| @@ -0,0 +1,39 @@ | |||
| package com.ffii.core.response; | |||
| import java.time.LocalDateTime; | |||
| import com.fasterxml.jackson.annotation.JsonInclude; | |||
| public class FailureRes { | |||
| private LocalDateTime timestamp; | |||
| @JsonInclude(JsonInclude.Include.NON_NULL) | |||
| private String error; | |||
| public FailureRes() { | |||
| this.timestamp = LocalDateTime.now(); | |||
| } | |||
| public FailureRes(String error) { | |||
| this.timestamp = LocalDateTime.now(); | |||
| this.error = error; | |||
| } | |||
| public LocalDateTime getTimestamp() { | |||
| return timestamp; | |||
| } | |||
| public void setTimestamp(LocalDateTime timestamp) { | |||
| this.timestamp = timestamp; | |||
| } | |||
| public String getError() { | |||
| return error; | |||
| } | |||
| public void setError(String error) { | |||
| this.error = error; | |||
| } | |||
| } | |||
| @@ -0,0 +1,21 @@ | |||
| package com.ffii.core.response; | |||
| public class IdRes { | |||
| private long id; | |||
| public IdRes() { | |||
| } | |||
| public IdRes(long id) { | |||
| this.id = id; | |||
| } | |||
| public long getId() { | |||
| return id; | |||
| } | |||
| public void setId(long id) { | |||
| this.id = id; | |||
| } | |||
| } | |||
| @@ -0,0 +1,41 @@ | |||
| package com.ffii.core.response; | |||
| import java.util.List; | |||
| import com.fasterxml.jackson.annotation.JsonInclude; | |||
| public class RecordsRes<T> { | |||
| private List<T> records; | |||
| @JsonInclude(JsonInclude.Include.NON_NULL) | |||
| private Integer total; | |||
| public RecordsRes() { | |||
| } | |||
| public RecordsRes(List<T> records) { | |||
| this.records = records; | |||
| } | |||
| public RecordsRes(List<T> records, int total) { | |||
| this.records = records; | |||
| this.total = total; | |||
| } | |||
| public List<T> getRecords() { | |||
| return records; | |||
| } | |||
| public void setRecords(List<T> records) { | |||
| this.records = records; | |||
| } | |||
| public Integer getTotal() { | |||
| return total; | |||
| } | |||
| public void setTotal(Integer total) { | |||
| this.total = total; | |||
| } | |||
| } | |||
| @@ -0,0 +1,42 @@ | |||
| package com.ffii.core.support; | |||
| import java.io.Serializable; | |||
| import java.util.Optional; | |||
| import org.springframework.transaction.annotation.Transactional; | |||
| import org.springframework.util.Assert; | |||
| import com.ffii.core.entity.BaseEntity; | |||
| import com.ffii.core.exception.ConflictException; | |||
| /** @author Alex */ | |||
| public abstract class AbstractBaseEntityService<T extends BaseEntity<ID>, ID extends Serializable, R extends AbstractRepository<T, ID>> | |||
| extends AbstractIdEntityService<T, ID, R> { | |||
| public AbstractBaseEntityService(JdbcDao jdbcDao, R repository) { | |||
| super(jdbcDao, repository); | |||
| } | |||
| /** find and check versionId */ | |||
| public Optional<T> find(ID id, int version) { | |||
| Assert.notNull(id, "id must not be null"); | |||
| return repository.findById(id) | |||
| .map(entity -> { | |||
| if (entity.getVersion() != version) throw new ConflictException("OPTIMISTIC_LOCK"); | |||
| return entity; | |||
| }); | |||
| } | |||
| @Transactional(rollbackFor = Exception.class) | |||
| public void markDelete(ID id) { | |||
| Assert.notNull(id, "id must not be null"); | |||
| find(id).ifPresent(t -> markDelete(t)); | |||
| } | |||
| @Transactional(rollbackFor = Exception.class) | |||
| public void markDelete(T entity) { | |||
| Assert.notNull(entity, "entity must not be null"); | |||
| entity.setDeleted(Boolean.TRUE); | |||
| save(entity); | |||
| } | |||
| } | |||
| @@ -0,0 +1,65 @@ | |||
| package com.ffii.core.support; | |||
| import java.io.Serializable; | |||
| import java.util.List; | |||
| import java.util.Optional; | |||
| import org.springframework.transaction.annotation.Transactional; | |||
| import org.springframework.util.Assert; | |||
| import com.ffii.core.entity.IdEntity; | |||
| /** @author Alex */ | |||
| public abstract class AbstractIdEntityService<T extends IdEntity<ID>, ID extends Serializable, R extends AbstractRepository<T, ID>> | |||
| extends AbstractService { | |||
| protected R repository; | |||
| public AbstractIdEntityService(JdbcDao jdbcDao, R repository) { | |||
| super(jdbcDao); | |||
| this.repository = repository; | |||
| } | |||
| @Transactional(rollbackFor = Exception.class) | |||
| public T save(T entity) { | |||
| Assert.notNull(entity, "entity must not be null"); | |||
| return this.repository.save(entity); | |||
| } | |||
| @Transactional(rollbackFor = Exception.class) | |||
| public T saveAndFlush(T entity) { | |||
| Assert.notNull(entity, "entity must not be null"); | |||
| return this.repository.saveAndFlush(entity); | |||
| } | |||
| public List<T> listAll() { | |||
| return this.repository.findAll(); | |||
| } | |||
| public Optional<T> find(ID id) { | |||
| Assert.notNull(id, "id must not be null"); | |||
| return this.repository.findById(id); | |||
| } | |||
| public boolean existsById(ID id) { | |||
| Assert.notNull(id, "id must not be null"); | |||
| return this.repository.existsById(id); | |||
| } | |||
| public List<T> findAllByIds(List<ID> ids) { | |||
| Assert.notNull(ids, "ids must not be null"); | |||
| return this.repository.findAllById(ids); | |||
| } | |||
| @Transactional(rollbackFor = Exception.class) | |||
| public void delete(ID id) { | |||
| Assert.notNull(id, "id must not be null"); | |||
| this.repository.deleteById(id); | |||
| } | |||
| @Transactional(rollbackFor = Exception.class) | |||
| public void delete(T entity) { | |||
| Assert.notNull(entity, "entity must not be null"); | |||
| this.repository.delete(entity); | |||
| } | |||
| } | |||
| @@ -0,0 +1,16 @@ | |||
| package com.ffii.core.support; | |||
| import java.io.Serializable; | |||
| import org.springframework.data.jpa.repository.JpaRepository; | |||
| import org.springframework.data.repository.NoRepositoryBean; | |||
| import com.ffii.core.entity.IdEntity; | |||
| /** | |||
| * @author Alex | |||
| * @see https://docs.spring.io/spring-data/jpa/docs/2.7.0/reference/html/#jpa.query-methods.query-creation | |||
| */ | |||
| @NoRepositoryBean | |||
| public interface AbstractRepository<T extends IdEntity<ID>, ID extends Serializable> extends JpaRepository<T, ID> { | |||
| } | |||
| @@ -0,0 +1,15 @@ | |||
| package com.ffii.core.support; | |||
| import org.apache.commons.logging.Log; | |||
| import org.apache.commons.logging.LogFactory; | |||
| /** @author Terence */ | |||
| public abstract class AbstractService { | |||
| protected final Log logger = LogFactory.getLog(getClass()); | |||
| protected JdbcDao jdbcDao; | |||
| public AbstractService(JdbcDao jdbcDao) { | |||
| this.jdbcDao = jdbcDao; | |||
| } | |||
| } | |||
| @@ -0,0 +1,44 @@ | |||
| package com.ffii.core.support; | |||
| import java.util.UUID; | |||
| import org.apache.commons.logging.Log; | |||
| import org.apache.commons.logging.LogFactory; | |||
| import org.springframework.http.HttpStatus; | |||
| import org.springframework.http.ResponseEntity; | |||
| import org.springframework.security.access.AccessDeniedException; | |||
| import org.springframework.web.bind.annotation.ExceptionHandler; | |||
| import org.springframework.web.bind.annotation.ResponseStatus; | |||
| import org.springframework.web.bind.annotation.RestControllerAdvice; | |||
| import org.springframework.web.server.ResponseStatusException; | |||
| import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; | |||
| import com.ffii.core.exception.ConflictException; | |||
| import com.ffii.core.exception.InternalServerErrorException; | |||
| import com.ffii.core.response.ErrorRes; | |||
| import com.ffii.core.response.FailureRes; | |||
| @RestControllerAdvice | |||
| public class ErrorHandler extends ResponseEntityExceptionHandler { | |||
| private final Log logger = LogFactory.getLog(getClass()); | |||
| @ExceptionHandler({ ConflictException.class, ResponseStatusException.class }) | |||
| public ResponseEntity<FailureRes> error409422(final Exception ex) { | |||
| ResponseStatusException e = (ResponseStatusException) ex; | |||
| return new ResponseEntity<>(new FailureRes(e.getReason()), e.getStatusCode()); | |||
| } | |||
| @ExceptionHandler(AccessDeniedException.class) | |||
| public ResponseEntity<?> error403(final Exception ex) { | |||
| return ResponseEntity.status(HttpStatus.FORBIDDEN).build(); | |||
| } | |||
| @ExceptionHandler({ InternalServerErrorException.class, Exception.class }) | |||
| @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) | |||
| public ResponseEntity<ErrorRes> error500(final Exception ex) { | |||
| UUID traceId = UUID.randomUUID(); | |||
| logger.error("traceId: " + traceId, ex); | |||
| return new ResponseEntity<>(new ErrorRes(traceId.toString()), HttpStatus.INTERNAL_SERVER_ERROR); | |||
| } | |||
| } | |||
| @@ -0,0 +1,463 @@ | |||
| package com.ffii.core.support; | |||
| import java.math.BigDecimal; | |||
| import java.sql.Blob; | |||
| import java.time.LocalDate; | |||
| import java.time.LocalDateTime; | |||
| import java.util.List; | |||
| import java.util.Map; | |||
| import java.util.Optional; | |||
| import javax.sql.DataSource; | |||
| import org.apache.commons.lang3.StringUtils; | |||
| import org.springframework.dao.EmptyResultDataAccessException; | |||
| import org.springframework.dao.IncorrectResultSizeDataAccessException; | |||
| import org.springframework.dao.InvalidDataAccessApiUsageException; | |||
| import org.springframework.jdbc.BadSqlGrammarException; | |||
| import org.springframework.jdbc.IncorrectResultSetColumnCountException; | |||
| import org.springframework.jdbc.core.BeanPropertyRowMapper; | |||
| import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource; | |||
| import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; | |||
| import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; | |||
| import org.springframework.jdbc.core.namedparam.SqlParameterSourceUtils; | |||
| import org.springframework.jdbc.support.GeneratedKeyHolder; | |||
| import org.springframework.jdbc.support.KeyHolder; | |||
| /** @author Terence */ | |||
| public class JdbcDao { | |||
| private NamedParameterJdbcTemplate template; | |||
| public JdbcDao(DataSource dataSource) { | |||
| this.template = new NamedParameterJdbcTemplate(dataSource); | |||
| } | |||
| /** | |||
| * @throws BadSqlGrammarException sql error | |||
| * @throws IncorrectResultSetColumnCountException Incorrect column count | |||
| * @throws IncorrectResultSizeDataAccessException: Incorrect result size | |||
| */ | |||
| public String queryForString(String sql) { | |||
| return this.queryForString(sql, (Map<String, ?>) null); | |||
| } | |||
| /** | |||
| * @throws BadSqlGrammarException sql error | |||
| * @throws InvalidDataAccessApiUsageException params missing when needed | |||
| * @throws IncorrectResultSetColumnCountException Incorrect column count | |||
| * @throws IncorrectResultSizeDataAccessException: Incorrect result size | |||
| */ | |||
| public String queryForString(String sql, Map<String, ?> paramMap) { | |||
| try { | |||
| return this.template.queryForObject(sql, paramMap, String.class); | |||
| } catch (EmptyResultDataAccessException e) { | |||
| return StringUtils.EMPTY; | |||
| } | |||
| } | |||
| /** | |||
| * @throws BadSqlGrammarException sql error | |||
| * @throws InvalidDataAccessApiUsageException params missing when needed | |||
| * @throws IncorrectResultSetColumnCountException Incorrect column count | |||
| * @throws IncorrectResultSizeDataAccessException: Incorrect result size | |||
| */ | |||
| public String queryForString(String sql, Object paramObj) { | |||
| try { | |||
| return this.template.queryForObject(sql, new BeanPropertySqlParameterSource(paramObj), String.class); | |||
| } catch (EmptyResultDataAccessException e) { | |||
| return StringUtils.EMPTY; | |||
| } | |||
| } | |||
| /** | |||
| * @return {@code true} if non-zero, {@code false} if zero | |||
| * @throws BadSqlGrammarException sql error | |||
| * @throws IncorrectResultSetColumnCountException Incorrect column count | |||
| * @throws IncorrectResultSizeDataAccessException: Incorrect result size | |||
| */ | |||
| public boolean queryForBoolean(String sql) { | |||
| return this.queryForBoolean(sql, (Map<String, ?>) null); | |||
| } | |||
| /** | |||
| * @return {@code true} if non-zero, {@code false} if zero | |||
| * @throws BadSqlGrammarException sql error | |||
| * @throws InvalidDataAccessApiUsageException params missing when needed | |||
| * @throws IncorrectResultSetColumnCountException Incorrect column count | |||
| * @throws IncorrectResultSizeDataAccessException: Incorrect result size | |||
| */ | |||
| public boolean queryForBoolean(String sql, Map<String, ?> paramMap) { | |||
| try { | |||
| var rs = this.template.queryForObject(sql, paramMap, Boolean.class); | |||
| return rs == null ? false : rs; | |||
| } catch (EmptyResultDataAccessException e) { | |||
| return false; | |||
| } | |||
| } | |||
| /** | |||
| * @return {@code true} if non-zero, {@code false} if zero | |||
| * @throws BadSqlGrammarException sql error | |||
| * @throws InvalidDataAccessApiUsageException params missing when needed | |||
| * @throws IncorrectResultSetColumnCountException Incorrect column count | |||
| * @throws IncorrectResultSizeDataAccessException: Incorrect result size | |||
| */ | |||
| public boolean queryForBoolean(String sql, Object paramObj) { | |||
| try { | |||
| var rs = this.template.queryForObject(sql, new BeanPropertySqlParameterSource(paramObj), Boolean.class); | |||
| return rs == null ? false : rs; | |||
| } catch (EmptyResultDataAccessException e) { | |||
| return false; | |||
| } | |||
| } | |||
| /** | |||
| * @throws BadSqlGrammarException sql error | |||
| * @throws IncorrectResultSetColumnCountException Incorrect column count | |||
| * @throws IncorrectResultSizeDataAccessException: Incorrect result size | |||
| */ | |||
| public int queryForInt(String sql) { | |||
| return this.queryForInt(sql, (Map<String, ?>) null); | |||
| } | |||
| /** | |||
| * @throws BadSqlGrammarException sql error | |||
| * @throws InvalidDataAccessApiUsageException params missing when needed | |||
| * @throws IncorrectResultSetColumnCountException Incorrect column count | |||
| * @throws IncorrectResultSizeDataAccessException: Incorrect result size | |||
| */ | |||
| public int queryForInt(String sql, Map<String, ?> paramMap) { | |||
| try { | |||
| var rs = this.template.queryForObject(sql, paramMap, Integer.class); | |||
| return rs == null ? 0 : rs; | |||
| } catch (EmptyResultDataAccessException e) { | |||
| return 0; | |||
| } | |||
| } | |||
| /** | |||
| * @throws BadSqlGrammarException sql error | |||
| * @throws InvalidDataAccessApiUsageException params missing when needed | |||
| * @throws IncorrectResultSetColumnCountException Incorrect column count | |||
| * @throws IncorrectResultSizeDataAccessException: Incorrect result size | |||
| */ | |||
| public int queryForInt(String sql, Object paramObj) { | |||
| try { | |||
| var rs = this.template.queryForObject(sql, | |||
| new BeanPropertySqlParameterSource(paramObj), Integer.class); | |||
| return rs == null ? 0 : rs; | |||
| } catch (EmptyResultDataAccessException e) { | |||
| return 0; | |||
| } | |||
| } | |||
| /** | |||
| * @throws BadSqlGrammarException sql error | |||
| * @throws IncorrectResultSetColumnCountException Incorrect column count | |||
| * @throws IncorrectResultSizeDataAccessException: Incorrect result size | |||
| */ | |||
| public BigDecimal queryForDecimal(String sql) { | |||
| return this.queryForDecimal(sql, (Map<String, ?>) null); | |||
| } | |||
| /** | |||
| * @throws BadSqlGrammarException sql error | |||
| * @throws InvalidDataAccessApiUsageException params missing when needed | |||
| * @throws IncorrectResultSetColumnCountException Incorrect column count | |||
| * @throws IncorrectResultSizeDataAccessException: Incorrect result size | |||
| */ | |||
| public BigDecimal queryForDecimal(String sql, Map<String, ?> paramMap) { | |||
| try { | |||
| return this.template.queryForObject(sql, paramMap, BigDecimal.class); | |||
| } catch (EmptyResultDataAccessException e) { | |||
| return BigDecimal.ZERO; | |||
| } | |||
| } | |||
| /** | |||
| * @throws BadSqlGrammarException sql error | |||
| * @throws InvalidDataAccessApiUsageException params missing when needed | |||
| * @throws IncorrectResultSetColumnCountException Incorrect column count | |||
| * @throws IncorrectResultSizeDataAccessException: Incorrect result size | |||
| */ | |||
| public BigDecimal queryForDecimal(String sql, Object paramObj) { | |||
| try { | |||
| return this.template.queryForObject(sql, | |||
| new BeanPropertySqlParameterSource(paramObj), BigDecimal.class); | |||
| } catch (EmptyResultDataAccessException e) { | |||
| return BigDecimal.ZERO; | |||
| } | |||
| } | |||
| /** | |||
| * @throws BadSqlGrammarException sql error | |||
| * @throws IncorrectResultSizeDataAccessException: Incorrect result size | |||
| */ | |||
| public <T> Optional<T> queryForEntity(String sql, Class<T> entity) { | |||
| return this.queryForEntity(sql, (Map<String, ?>) null, entity); | |||
| } | |||
| /** | |||
| * @throws BadSqlGrammarException sql error | |||
| * @throws InvalidDataAccessApiUsageException params missing when needed | |||
| * @throws IncorrectResultSizeDataAccessException: Incorrect result size | |||
| */ | |||
| public <T> Optional<T> queryForEntity(String sql, Map<String, ?> paramMap, Class<T> entity) { | |||
| try { | |||
| return Optional.of(this.template.queryForObject(sql, paramMap, | |||
| new BeanPropertyRowMapper<T>(entity))); | |||
| } catch (EmptyResultDataAccessException e) { | |||
| return Optional.empty(); | |||
| } | |||
| } | |||
| /** | |||
| * @throws BadSqlGrammarException sql error | |||
| * @throws InvalidDataAccessApiUsageException params missing when needed | |||
| * @throws IncorrectResultSizeDataAccessException: Incorrect result size | |||
| */ | |||
| public <T> Optional<T> queryForEntity(String sql, Object paramObj, Class<T> entity) { | |||
| try { | |||
| return Optional.of(this.template.queryForObject(sql, | |||
| new BeanPropertySqlParameterSource(paramObj), new BeanPropertyRowMapper<T>(entity))); | |||
| } catch (EmptyResultDataAccessException e) { | |||
| return Optional.empty(); | |||
| } | |||
| } | |||
| /** | |||
| * @throws BadSqlGrammarException sql error | |||
| */ | |||
| public <T> List<T> queryForList(String sql, Class<T> entity) { | |||
| return this.queryForList(sql, (Map<String, ?>) null, entity); | |||
| } | |||
| /** | |||
| * @throws BadSqlGrammarException sql error | |||
| * @throws InvalidDataAccessApiUsageException params missing when needed | |||
| */ | |||
| public <T> List<T> queryForList(String sql, Map<String, ?> paramMap, Class<T> entity) { | |||
| return this.template.query(sql, paramMap, new BeanPropertyRowMapper<T>(entity)); | |||
| } | |||
| /** | |||
| * @throws BadSqlGrammarException sql error | |||
| * @throws InvalidDataAccessApiUsageException params missing when needed | |||
| */ | |||
| public <T> List<T> queryForList(String sql, Object paramObj, Class<T> entity) { | |||
| return this.template.query(sql, new BeanPropertySqlParameterSource(paramObj), | |||
| new BeanPropertyRowMapper<T>(entity)); | |||
| } | |||
| /** | |||
| * @throws BadSqlGrammarException sql error | |||
| * @throws IncorrectResultSetColumnCountException Incorrect column count | |||
| */ | |||
| public List<Integer> queryForInts(String sql) { | |||
| return this.queryForInts(sql, (Map<String, ?>) null); | |||
| } | |||
| /** | |||
| * @throws BadSqlGrammarException sql error | |||
| * @throws InvalidDataAccessApiUsageException params missing when needed | |||
| * @throws IncorrectResultSetColumnCountException Incorrect column count | |||
| */ | |||
| public List<Integer> queryForInts(String sql, Map<String, ?> paramMap) { | |||
| return this.template.queryForList(sql, paramMap, Integer.class); | |||
| } | |||
| /** | |||
| * @throws BadSqlGrammarException sql error | |||
| * @throws InvalidDataAccessApiUsageException params missing when needed | |||
| * @throws IncorrectResultSetColumnCountException Incorrect column count | |||
| */ | |||
| public List<Integer> queryForInts(String sql, Object paramObj) { | |||
| return this.template.queryForList(sql, new BeanPropertySqlParameterSource(paramObj), Integer.class); | |||
| } | |||
| /** | |||
| * @throws BadSqlGrammarException sql error | |||
| * @throws IncorrectResultSetColumnCountException Incorrect column count | |||
| */ | |||
| public List<LocalDate> queryForDates(String sql) { | |||
| return this.queryForDates(sql, (Map<String, ?>) null); | |||
| } | |||
| /** | |||
| * @throws BadSqlGrammarException sql error | |||
| * @throws InvalidDataAccessApiUsageException params missing when needed | |||
| * @throws IncorrectResultSetColumnCountException Incorrect column count | |||
| */ | |||
| public List<LocalDate> queryForDates(String sql, Map<String, ?> paramMap) { | |||
| return this.template.queryForList(sql, paramMap, LocalDate.class); | |||
| } | |||
| /** | |||
| * @throws BadSqlGrammarException sql error | |||
| * @throws InvalidDataAccessApiUsageException params missing when needed | |||
| * @throws IncorrectResultSetColumnCountException Incorrect column count | |||
| */ | |||
| public List<LocalDate> queryForDates(String sql, Object paramObj) { | |||
| return this.template.queryForList(sql, new BeanPropertySqlParameterSource(paramObj), LocalDate.class); | |||
| } | |||
| /** | |||
| * @throws BadSqlGrammarException sql error | |||
| * @throws IncorrectResultSetColumnCountException Incorrect column count | |||
| */ | |||
| public List<LocalDateTime> queryForDatetimes(String sql) { | |||
| return this.queryForDatetimes(sql, (Map<String, ?>) null); | |||
| } | |||
| /** | |||
| * @throws BadSqlGrammarException sql error | |||
| * @throws InvalidDataAccessApiUsageException params missing when needed | |||
| * @throws IncorrectResultSetColumnCountException Incorrect column count | |||
| */ | |||
| public List<LocalDateTime> queryForDatetimes(String sql, Map<String, ?> paramMap) { | |||
| return this.template.queryForList(sql, paramMap, LocalDateTime.class); | |||
| } | |||
| /** | |||
| * @throws BadSqlGrammarException sql error | |||
| * @throws InvalidDataAccessApiUsageException params missing when needed | |||
| * @throws IncorrectResultSetColumnCountException Incorrect column count | |||
| */ | |||
| public List<LocalDateTime> queryForDatetimes(String sql, Object paramObj) { | |||
| return this.template.queryForList(sql, new BeanPropertySqlParameterSource(paramObj), LocalDateTime.class); | |||
| } | |||
| /** | |||
| * @throws BadSqlGrammarException sql error | |||
| * @throws IncorrectResultSetColumnCountException Incorrect column count | |||
| */ | |||
| public List<String> queryForStrings(String sql) { | |||
| return this.queryForStrings(sql, (Map<String, ?>) null); | |||
| } | |||
| /** | |||
| * @throws BadSqlGrammarException sql error | |||
| * @throws InvalidDataAccessApiUsageException params missing when needed | |||
| * @throws IncorrectResultSetColumnCountException Incorrect column count | |||
| */ | |||
| public List<String> queryForStrings(String sql, Map<String, ?> paramMap) { | |||
| return this.template.queryForList(sql, paramMap, String.class); | |||
| } | |||
| /** | |||
| * @throws BadSqlGrammarException sql error | |||
| * @throws InvalidDataAccessApiUsageException params missing when needed | |||
| * @throws IncorrectResultSetColumnCountException Incorrect column count | |||
| */ | |||
| public List<String> queryForStrings(String sql, Object paramObj) { | |||
| return this.template.queryForList(sql, new BeanPropertySqlParameterSource(paramObj), String.class); | |||
| } | |||
| /** | |||
| * @throws BadSqlGrammarException sql error | |||
| */ | |||
| public List<Map<String, Object>> queryForList(String sql) { | |||
| return this.queryForList(sql, (Map<String, ?>) null); | |||
| } | |||
| /** | |||
| * @throws BadSqlGrammarException sql error | |||
| * @throws InvalidDataAccessApiUsageException params missing when needed | |||
| */ | |||
| public List<Map<String, Object>> queryForList(String sql, Map<String, ?> paramMap) { | |||
| return this.template.queryForList(sql, paramMap); | |||
| } | |||
| /** | |||
| * @throws BadSqlGrammarException sql error | |||
| * @throws InvalidDataAccessApiUsageException params missing when needed | |||
| */ | |||
| public List<Map<String, Object>> queryForList(String sql, Object paramObj) { | |||
| return this.template.queryForList(sql, new BeanPropertySqlParameterSource(paramObj)); | |||
| } | |||
| /** | |||
| * @throws BadSqlGrammarException sql error | |||
| */ | |||
| public Optional<Map<String, Object>> queryForMap(String sql) { | |||
| return this.queryForMap(sql, (Map<String, ?>) null); | |||
| } | |||
| /** | |||
| * @throws BadSqlGrammarException sql error | |||
| * @throws InvalidDataAccessApiUsageException params missing when needed | |||
| */ | |||
| public Optional<Map<String, Object>> queryForMap(String sql, Map<String, ?> paramMap) { | |||
| try { | |||
| return Optional.of(this.template.queryForMap(sql, paramMap)); | |||
| } catch (EmptyResultDataAccessException e) { | |||
| return Optional.empty(); | |||
| } | |||
| } | |||
| /** | |||
| * @throws BadSqlGrammarException sql error | |||
| * @throws InvalidDataAccessApiUsageException params missing when needed | |||
| */ | |||
| public Optional<Map<String, Object>> queryForMap(String sql, Object paramObj) { | |||
| try { | |||
| return Optional.of(this.template.queryForMap(sql, new BeanPropertySqlParameterSource(paramObj))); | |||
| } catch (EmptyResultDataAccessException e) { | |||
| return Optional.empty(); | |||
| } | |||
| } | |||
| /** | |||
| * @throws BadSqlGrammarException sql error | |||
| */ | |||
| public int executeUpdate(String sql) { | |||
| return this.executeUpdate(sql, (Map<String, ?>) null); | |||
| } | |||
| /** | |||
| * @throws BadSqlGrammarException sql error | |||
| * @throws InvalidDataAccessApiUsageException params missing when needed | |||
| */ | |||
| public int executeUpdate(String sql, Map<String, ?> paramMap) { | |||
| return this.template.update(sql, paramMap); | |||
| } | |||
| /** | |||
| * @throws BadSqlGrammarException sql error | |||
| * @throws InvalidDataAccessApiUsageException params missing when needed | |||
| */ | |||
| public long executeUpdateAndReturnId(String sql, Map<String, ?> paramMap) { | |||
| KeyHolder keyHolder = new GeneratedKeyHolder(); | |||
| this.template.update(sql, new MapSqlParameterSource(paramMap), keyHolder); | |||
| Number generatedId = keyHolder.getKey(); | |||
| if (generatedId != null) { | |||
| return generatedId.longValue(); | |||
| } | |||
| return 0; | |||
| } | |||
| /** | |||
| * @throws BadSqlGrammarException sql error | |||
| * @throws InvalidDataAccessApiUsageException params missing when needed | |||
| */ | |||
| public int executeUpdate(String sql, Object paramObj) { | |||
| return this.template.update(sql, new BeanPropertySqlParameterSource(paramObj)); | |||
| } | |||
| /** | |||
| * @throws BadSqlGrammarException sql error | |||
| * @throws InvalidDataAccessApiUsageException params missing when needed | |||
| */ | |||
| public int[] batchUpdate(String sql, List<?> paramsMapOrObject) { | |||
| return this.template.batchUpdate(sql, SqlParameterSourceUtils.createBatch(paramsMapOrObject)); | |||
| } | |||
| public Blob queryForBlob(String sql, Map<String, ?> paramMap) { | |||
| try { | |||
| return this.template.queryForObject(sql, paramMap, Blob.class); | |||
| } catch (EmptyResultDataAccessException e) { | |||
| return null; | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,85 @@ | |||
| package com.ffii.core.utils; | |||
| import java.io.UnsupportedEncodingException; | |||
| import java.security.MessageDigest; | |||
| import java.security.NoSuchAlgorithmException; | |||
| import java.util.Arrays; | |||
| import java.util.Base64; | |||
| import javax.crypto.Cipher; | |||
| import javax.crypto.spec.SecretKeySpec; | |||
| import org.apache.commons.logging.Log; | |||
| import org.apache.commons.logging.LogFactory; | |||
| public class AES { | |||
| protected final Log logger = LogFactory.getLog(getClass()); | |||
| private static SecretKeySpec secretKey; | |||
| private static byte[] key; | |||
| public static void setKey(String myKey) { | |||
| MessageDigest sha = null; | |||
| try { | |||
| key = myKey.getBytes("UTF-8"); | |||
| sha = MessageDigest.getInstance("SHA-1"); | |||
| key = sha.digest(key); | |||
| key = Arrays.copyOf(key, 16); | |||
| secretKey = new SecretKeySpec(key, "AES"); | |||
| } catch (NoSuchAlgorithmException e) { | |||
| e.printStackTrace(); | |||
| } catch (UnsupportedEncodingException e) { | |||
| e.printStackTrace(); | |||
| } | |||
| } | |||
| public static String encrypt(String strToEncrypt, String secret) { | |||
| try { | |||
| setKey(secret); | |||
| Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); | |||
| cipher.init(Cipher.ENCRYPT_MODE, secretKey); | |||
| return Base64.getEncoder().encodeToString(cipher.doFinal(strToEncrypt.getBytes("UTF-8"))); | |||
| } catch (Exception e) { | |||
| System.out.println("Error while encrypting: " + e.toString()); | |||
| } | |||
| return null; | |||
| } | |||
| public static String urlEncrypt(String strToEncrypt, String secret) { | |||
| try { | |||
| setKey(secret); | |||
| Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); | |||
| cipher.init(Cipher.ENCRYPT_MODE, secretKey); | |||
| return Base64.getUrlEncoder().encodeToString(cipher.doFinal(strToEncrypt.getBytes("UTF-8"))); | |||
| } catch (Exception e) { | |||
| System.out.println("Error while encrypting: " + e.toString()); | |||
| } | |||
| return null; | |||
| } | |||
| public static String decrypt(String strToDecrypt, String secret) { | |||
| try { | |||
| setKey(secret); | |||
| Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5PADDING"); | |||
| cipher.init(Cipher.DECRYPT_MODE, secretKey); | |||
| return new String(cipher.doFinal(Base64.getDecoder().decode(strToDecrypt)), "UTF-8"); | |||
| } catch (Exception e) { | |||
| System.out.println("Error while decrypting: " + e.toString()); | |||
| } | |||
| return null; | |||
| } | |||
| public static String urlDecrypt(String strToDecrypt, String secret) { | |||
| try { | |||
| setKey(secret); | |||
| System.out.println("strToDecrypt: " + strToDecrypt); | |||
| Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5PADDING"); | |||
| cipher.init(Cipher.DECRYPT_MODE, secretKey); | |||
| return new String(cipher.doFinal(Base64.getUrlDecoder().decode(strToDecrypt)), "UTF-8"); | |||
| } catch (Exception e) { | |||
| System.out.println("Error while decrypting: " + e.toString()); | |||
| } | |||
| return null; | |||
| } | |||
| } | |||
| @@ -0,0 +1,33 @@ | |||
| package com.ffii.core.utils; | |||
| import java.lang.reflect.Field; | |||
| import java.lang.reflect.Method; | |||
| import org.springframework.beans.BeansException; | |||
| import com.ffii.core.entity.BaseEntity; | |||
| public class BeanUtils extends org.springframework.beans.BeanUtils { | |||
| @SuppressWarnings("unchecked") | |||
| public static void copyProperties(Object source, Object target) throws BeansException { | |||
| boolean validId = false; | |||
| Field idField = null; | |||
| Method getIdMethod = null; | |||
| try { | |||
| idField = source.getClass().getDeclaredField("id"); | |||
| getIdMethod = source.getClass().getMethod("getId"); | |||
| validId = idField != null && getIdMethod != null && (Long) getIdMethod.invoke(source) > 0L; | |||
| } catch (Exception e) { | |||
| } | |||
| if ((source instanceof BaseEntity && (((BaseEntity<Long>) source).getId() == null || ((BaseEntity<Long>) source).getId() <= 0L)) || !validId) { | |||
| org.springframework.beans.BeanUtils.copyProperties(source, target, "id"); | |||
| } else { | |||
| org.springframework.beans.BeanUtils.copyProperties(source, target); | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,254 @@ | |||
| package com.ffii.core.utils; | |||
| import java.time.LocalDate; | |||
| import java.time.LocalDateTime; | |||
| import java.time.format.DateTimeFormatter; | |||
| import java.time.format.DateTimeParseException; | |||
| import java.util.ArrayList; | |||
| import java.util.Arrays; | |||
| import java.util.HashMap; | |||
| import java.util.List; | |||
| import java.util.Map; | |||
| import java.util.stream.Collectors; | |||
| import org.apache.commons.lang3.StringUtils; | |||
| import org.apache.commons.logging.Log; | |||
| import org.apache.commons.logging.LogFactory; | |||
| import org.springframework.web.bind.ServletRequestBindingException; | |||
| import org.springframework.web.bind.ServletRequestUtils; | |||
| import jakarta.servlet.http.HttpServletRequest; | |||
| /** @author Alex */ | |||
| public class CriteriaArgsBuilder { | |||
| private HttpServletRequest request; | |||
| private Map<String, Object> args; | |||
| private final Log logger = LogFactory.getLog(getClass()); | |||
| private CriteriaArgsBuilder(HttpServletRequest request, Map<String, Object> args) { | |||
| this.args = args; | |||
| this.request = request; | |||
| } | |||
| public static CriteriaArgsBuilder withRequest(HttpServletRequest request) { | |||
| return new CriteriaArgsBuilder(request, new HashMap<String, Object>()); | |||
| } | |||
| public static CriteriaArgsBuilder withRequestNMap(HttpServletRequest request, Map<String, Object> args) { | |||
| return new CriteriaArgsBuilder(request, args); | |||
| } | |||
| public CriteriaArgsBuilder addStringExact(String paramName) throws ServletRequestBindingException { | |||
| String value = StringUtils.trimToNull(ServletRequestUtils.getStringParameter(this.request, paramName)); | |||
| if (value != null) | |||
| args.put(paramName, value); | |||
| return this; | |||
| } | |||
| public CriteriaArgsBuilder addStringLike(String paramName) throws ServletRequestBindingException { | |||
| String value = StringUtils.trimToNull(ServletRequestUtils.getStringParameter(this.request, paramName)); | |||
| if (value != null) | |||
| args.put(paramName, "%" + value + "%"); | |||
| return this; | |||
| } | |||
| public CriteriaArgsBuilder addString(String paramName) throws ServletRequestBindingException { | |||
| return this.addStringExact(paramName); | |||
| } | |||
| public CriteriaArgsBuilder addStringStartsWith(String paramName) throws ServletRequestBindingException { | |||
| String value = StringUtils.trimToNull(ServletRequestUtils.getStringParameter(this.request, paramName)); | |||
| if (value != null) | |||
| args.put(paramName, value + "%"); | |||
| return this; | |||
| } | |||
| public CriteriaArgsBuilder addStringEndsWith(String paramName) throws ServletRequestBindingException { | |||
| String value = StringUtils.trimToNull(ServletRequestUtils.getStringParameter(this.request, paramName)); | |||
| if (value != null) | |||
| args.put(paramName, "%" + value); | |||
| return this; | |||
| } | |||
| public CriteriaArgsBuilder addStringList(String paramName) throws ServletRequestBindingException { | |||
| String[] params = ServletRequestUtils.getStringParameters(this.request, paramName); | |||
| if (params.length > 0) { | |||
| List<String> value = new ArrayList<String>(params.length); | |||
| for (String param : params) | |||
| if (StringUtils.isNotBlank(param)) | |||
| value.add(param); | |||
| if (value.size() > 0) | |||
| args.put(paramName, value); | |||
| } | |||
| return this; | |||
| } | |||
| public CriteriaArgsBuilder addStringCsv(String paramName) throws ServletRequestBindingException { | |||
| String text = ServletRequestUtils.getStringParameter(this.request, paramName); | |||
| if (text != null && StringUtils.isNotEmpty(text)) | |||
| args.put(paramName, Arrays.asList(text.split(","))); | |||
| return this; | |||
| } | |||
| public CriteriaArgsBuilder addInteger(String paramName) throws ServletRequestBindingException { | |||
| Integer value = StringUtils.isNotBlank(this.request.getParameter(paramName)) | |||
| ? ServletRequestUtils.getRequiredIntParameter(request, paramName) | |||
| : null; | |||
| if (value != null) | |||
| args.put(paramName, value); | |||
| return this; | |||
| } | |||
| public CriteriaArgsBuilder addNonZeroInteger(String paramName) throws ServletRequestBindingException { | |||
| Integer value = StringUtils.isNotBlank(this.request.getParameter(paramName)) | |||
| ? ServletRequestUtils.getRequiredIntParameter(request, paramName) | |||
| : null; | |||
| if (value != null && value.intValue() != 0) | |||
| args.put(paramName, value); | |||
| return this; | |||
| } | |||
| public CriteriaArgsBuilder addIntegerList(String paramName) throws ServletRequestBindingException { | |||
| int[] params = ServletRequestUtils.getIntParameters(request, paramName); | |||
| if (params.length > 0) { | |||
| List<Integer> values = new ArrayList<Integer>(); | |||
| for (int param : params) | |||
| values.add(param); | |||
| args.put(paramName, values); | |||
| } | |||
| return this; | |||
| } | |||
| public CriteriaArgsBuilder addIntegerListString(String paramName) throws ServletRequestBindingException { | |||
| int[] params = ServletRequestUtils.getIntParameters(request, paramName); | |||
| if (params.length > 0) { | |||
| args.put(paramName, Arrays.toString(params)); | |||
| } | |||
| return this; | |||
| } | |||
| public CriteriaArgsBuilder addNonZeroIntegerList(String paramName) throws ServletRequestBindingException { | |||
| int[] params = ServletRequestUtils.getIntParameters(request, paramName); | |||
| if (params.length > 0) { | |||
| List<Integer> values = new ArrayList<Integer>(); | |||
| for (int param : params) | |||
| if (param != 0) | |||
| values.add(param); | |||
| args.put(paramName, values); | |||
| } | |||
| return this; | |||
| } | |||
| public CriteriaArgsBuilder addLong(String paramName) throws ServletRequestBindingException { | |||
| Long value = StringUtils.isNotBlank(this.request.getParameter(paramName)) | |||
| ? ServletRequestUtils.getRequiredLongParameter(request, paramName) | |||
| : null; | |||
| if (value != null) | |||
| args.put(paramName, value); | |||
| return this; | |||
| } | |||
| public CriteriaArgsBuilder addNonZeroLong(String paramName) throws ServletRequestBindingException { | |||
| Long value = StringUtils.isNotBlank(this.request.getParameter(paramName)) | |||
| ? ServletRequestUtils.getRequiredLongParameter(request, paramName) | |||
| : null; | |||
| if (value != null && value.longValue() != 0L) | |||
| args.put(paramName, value); | |||
| return this; | |||
| } | |||
| public CriteriaArgsBuilder addDatetime(String paramName) throws ServletRequestBindingException { | |||
| String value = ServletRequestUtils.getStringParameter(request, paramName); | |||
| if (StringUtils.isNotBlank(value)) { | |||
| try { | |||
| args.put(paramName, LocalDateTime.parse(value)); | |||
| } catch (DateTimeParseException e) { | |||
| throw new ServletRequestBindingException(paramName); | |||
| } | |||
| } | |||
| return this; | |||
| } | |||
| public CriteriaArgsBuilder addDatetime(String paramName, DateTimeFormatter formatter) | |||
| throws ServletRequestBindingException { | |||
| String value = ServletRequestUtils.getStringParameter(request, paramName); | |||
| if (StringUtils.isNotBlank(value)) { | |||
| try { | |||
| args.put(paramName, LocalDateTime.parse(value, formatter)); | |||
| } catch (DateTimeParseException e) { | |||
| throw new ServletRequestBindingException(paramName); | |||
| } | |||
| } | |||
| return this; | |||
| } | |||
| public CriteriaArgsBuilder addDate(String paramName) throws ServletRequestBindingException { | |||
| String value = ServletRequestUtils.getStringParameter(request, paramName); | |||
| if (StringUtils.isNotBlank(value)) { | |||
| try { | |||
| args.put(paramName, LocalDate.parse(value)); | |||
| } catch (DateTimeParseException e) { | |||
| throw new ServletRequestBindingException(paramName); | |||
| } | |||
| } | |||
| return this; | |||
| } | |||
| public CriteriaArgsBuilder addDate(String paramName, DateTimeFormatter formatter) | |||
| throws ServletRequestBindingException { | |||
| String value = ServletRequestUtils.getStringParameter(request, paramName); | |||
| if (StringUtils.isNotBlank(value)) { | |||
| try { | |||
| args.put(paramName, LocalDate.parse(value, formatter)); | |||
| } catch (DateTimeParseException e) { | |||
| throw new ServletRequestBindingException(paramName); | |||
| } | |||
| } | |||
| return this; | |||
| } | |||
| public CriteriaArgsBuilder addDateTo(String paramName) throws ServletRequestBindingException { | |||
| String value = ServletRequestUtils.getStringParameter(request, paramName); | |||
| if (StringUtils.isNotBlank(value)) { | |||
| try { | |||
| args.put(paramName, LocalDate.parse(value).plusDays(1)); | |||
| } catch (DateTimeParseException e) { | |||
| throw new ServletRequestBindingException(paramName); | |||
| } | |||
| } | |||
| return this; | |||
| } | |||
| public CriteriaArgsBuilder addDateTo(String paramName, DateTimeFormatter formatter) | |||
| throws ServletRequestBindingException { | |||
| String value = ServletRequestUtils.getStringParameter(request, paramName); | |||
| if (StringUtils.isNotBlank(value)) { | |||
| try { | |||
| args.put(paramName, LocalDate.parse(value, formatter).plusDays(1)); | |||
| } catch (DateTimeParseException e) { | |||
| throw new ServletRequestBindingException(paramName); | |||
| } | |||
| } | |||
| return this; | |||
| } | |||
| public CriteriaArgsBuilder addBoolean(String paramName) throws ServletRequestBindingException { | |||
| if (request.getParameter(paramName) == null || request.getParameter(paramName).isEmpty()) { | |||
| return this; | |||
| } | |||
| Boolean value = ServletRequestUtils.getBooleanParameter(request, paramName); | |||
| args.put(paramName, value); | |||
| return this; | |||
| } | |||
| public CriteriaArgsBuilder put(String key, Object value) { | |||
| args.put(key, value); | |||
| return this; | |||
| } | |||
| public Map<String, Object> build() { | |||
| return this.args; | |||
| } | |||
| } | |||
| @@ -0,0 +1,778 @@ | |||
| package com.ffii.core.utils; | |||
| import java.io.ByteArrayInputStream; | |||
| import java.io.ByteArrayOutputStream; | |||
| import java.io.IOException; | |||
| import java.io.InputStream; | |||
| import java.io.OutputStream; | |||
| import java.math.BigDecimal; | |||
| import java.math.RoundingMode; | |||
| import java.time.LocalDate; | |||
| import java.time.LocalDateTime; | |||
| import java.time.LocalTime; | |||
| import java.time.ZoneId; | |||
| import java.time.format.DateTimeFormatter; | |||
| import java.time.format.DateTimeParseException; | |||
| import java.util.Calendar; | |||
| import java.util.Date; | |||
| import java.util.HashMap; | |||
| import java.util.Map; | |||
| import org.apache.commons.lang3.StringUtils; | |||
| import org.apache.commons.lang3.math.NumberUtils; | |||
| import org.apache.poi.openxml4j.exceptions.InvalidFormatException; | |||
| import org.apache.poi.openxml4j.opc.OPCPackage; | |||
| import org.apache.poi.poifs.crypt.EncryptionInfo; | |||
| import org.apache.poi.poifs.crypt.EncryptionMode; | |||
| import org.apache.poi.poifs.crypt.Encryptor; | |||
| import org.apache.poi.poifs.filesystem.POIFSFileSystem; | |||
| import org.apache.poi.ss.usermodel.Cell; | |||
| import org.apache.poi.ss.usermodel.CellType; | |||
| import org.apache.poi.ss.usermodel.DataFormatter; | |||
| import org.apache.poi.ss.usermodel.DateUtil; | |||
| import org.apache.poi.ss.usermodel.RichTextString; | |||
| import org.apache.poi.ss.usermodel.Row; | |||
| import org.apache.poi.ss.usermodel.Sheet; | |||
| import org.apache.poi.ss.usermodel.Workbook; | |||
| import org.apache.poi.ss.util.CellRangeAddress; | |||
| import org.apache.poi.xssf.usermodel.XSSFWorkbook; | |||
| import org.springframework.core.io.ClassPathResource; | |||
| import org.springframework.core.io.Resource; | |||
| import org.springframework.core.io.ResourceLoader; | |||
| import jakarta.servlet.http.HttpServletResponse; | |||
| public abstract class ExcelUtils { | |||
| /** | |||
| * static A to Z char array | |||
| */ | |||
| private static final char[] A2Z = { | |||
| 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', | |||
| 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', | |||
| 'U', 'V', 'W', 'X', 'Y', 'Z' }; | |||
| private static final DataFormatter DATA_FORMATTER = new DataFormatter(); | |||
| /** max rows limit of .xls **/ | |||
| public static final int MAX_ROWS = 65536; | |||
| /** max columns limit of .xls **/ | |||
| public static final int MAX_COLS = 256; | |||
| /** | |||
| * Column reference to index (0-based) map, support up to 256 columns (compatible with .xls format) | |||
| */ | |||
| public static final Map<String, Integer> COL_IDX = new HashMap<String, Integer>(MAX_COLS, 1.0f); | |||
| static { | |||
| for (int columnIndex = 0; columnIndex < MAX_COLS; columnIndex++) { | |||
| int tempColumnCount = columnIndex; | |||
| StringBuilder sb = new StringBuilder(2); | |||
| do { | |||
| sb.insert(0, A2Z[tempColumnCount % 26]); | |||
| tempColumnCount = (tempColumnCount / 26) - 1; | |||
| } while (tempColumnCount >= 0); | |||
| COL_IDX.put(sb.toString(), Integer.valueOf(columnIndex)); | |||
| } | |||
| } | |||
| /** | |||
| * Load XSSF workbook (xlsx file) from template source. | |||
| * | |||
| * @param url | |||
| * the relative path to the template source, e.g. "WEB-INF/excel/exampleReportTemplate.xlsx" | |||
| * | |||
| * @return the workbook, or null if the template file cannot be loaded | |||
| */ | |||
| public static final Workbook loadXSSFWorkbookFromTemplateSource(ResourceLoader resourceLoader, String url) { | |||
| Resource resource = resourceLoader.getResource(url); | |||
| try { | |||
| return new XSSFWorkbook(resource.getInputStream()); | |||
| } catch (IOException e) { | |||
| return null; | |||
| } | |||
| } | |||
| /** | |||
| * Write the workbook to byte array. | |||
| * | |||
| * @param workbook | |||
| * The Excel workbook (cannot be null) | |||
| * | |||
| * @return the byte[], or null if IO exception occurred | |||
| */ | |||
| public static final byte[] toByteArray(Workbook workbook) { | |||
| ByteArrayOutputStream baos = new ByteArrayOutputStream(); | |||
| try { | |||
| workbook.write(baos); | |||
| } catch (IOException e) { | |||
| return null; | |||
| } | |||
| return baos.toByteArray(); | |||
| } | |||
| /** | |||
| * Check if the cell exists in the given sheet, row and column. | |||
| * | |||
| * @param sheet | |||
| * the Sheet (cannot be null) | |||
| * @param rowIndex | |||
| * 0-based row index | |||
| * @param colIndex | |||
| * 0-based column index | |||
| * | |||
| * @return {@code true} if cell exists, else {@code false} | |||
| */ | |||
| public static final boolean isCellExists(Sheet sheet, int rowIndex, int colIndex) { | |||
| Row row = sheet.getRow(rowIndex); | |||
| if (row != null) { | |||
| Cell cell = row.getCell(colIndex); | |||
| return cell != null; | |||
| } | |||
| return false; | |||
| } | |||
| /** | |||
| * Convenient method to obtain the cell in the given sheet, row and column. | |||
| * <p> | |||
| * Creates the row and the cell if not already exist. | |||
| * | |||
| * @param sheet | |||
| * the Sheet (cannot be null) | |||
| * @param rowIndex | |||
| * 0-based row index | |||
| * @param colIndex | |||
| * 0-based column index | |||
| * | |||
| * @return the Cell (never null) | |||
| */ | |||
| public static final Cell getCell(Sheet sheet, int rowIndex, int colIndex) { | |||
| Row row = sheet.getRow(rowIndex); | |||
| if (row == null) { | |||
| row = sheet.createRow(rowIndex); | |||
| } | |||
| Cell cell = row.getCell(colIndex); | |||
| if (cell == null) { | |||
| cell = row.createCell(colIndex); | |||
| } | |||
| return cell; | |||
| } | |||
| /** | |||
| * Get column index by column reference (support up to 256 columns) | |||
| * | |||
| * @param columnRef | |||
| * column reference such as "A", "B", "AA", "AB"... | |||
| * | |||
| * @return the column index | |||
| * | |||
| * @throws NullPointerException | |||
| * if column reference is invalid or the index exceeds 256 | |||
| */ | |||
| public static final int getColumnIndex(String columnRef) { | |||
| return COL_IDX.get(columnRef); | |||
| } | |||
| /** | |||
| * Get column reference by column index | |||
| * | |||
| * @param columnIndex | |||
| * 0-based column index | |||
| * | |||
| * @return the column reference such as "A", "B", "AA", "AB"... | |||
| */ | |||
| public static final String getColumnRef(int columnIndex) { | |||
| StringBuilder sb = new StringBuilder(); | |||
| int tempColumnCount = columnIndex; | |||
| do { | |||
| sb.insert(0, A2Z[tempColumnCount % 26]); | |||
| tempColumnCount = (tempColumnCount / 26) - 1; | |||
| } while (tempColumnCount >= 0); | |||
| return sb.toString(); | |||
| } | |||
| /** | |||
| * Get the Excel Cell Ref String by columnIndex and rowIndex | |||
| * | |||
| * @param columnIndex | |||
| * 0-based column index | |||
| * @param rowIndex | |||
| * 0-based row index | |||
| */ | |||
| public static final String getCellRefString(int columnIndex, int rowIndex) { | |||
| StringBuilder sb = new StringBuilder(); | |||
| int tempColumnCount = columnIndex; | |||
| do { | |||
| sb.insert(0, A2Z[tempColumnCount % 26]); | |||
| tempColumnCount = (tempColumnCount / 26) - 1; | |||
| } while (tempColumnCount >= 0); | |||
| sb.append(rowIndex + 1); | |||
| return sb.toString(); | |||
| } | |||
| /** | |||
| * Get Cell value as <code>String</code> | |||
| */ | |||
| public static String getStringValue(Cell cell) { | |||
| if (cell != null && cell.getCellType() == CellType.FORMULA) { | |||
| try { | |||
| return cell.getStringCellValue(); | |||
| } catch (Exception e) { | |||
| return ""; | |||
| } | |||
| } | |||
| return DATA_FORMATTER.formatCellValue(cell); | |||
| } | |||
| /** | |||
| * Get Cell value as <code>BigDecimal</code>, with a fallback value | |||
| * <p> | |||
| * Only support {@link CellType#NUMERIC} and {@link CellType#STRING} | |||
| * | |||
| * @return the <code>BigDecimal</code> value, or the default value if cell is <code>null</code> or cell type is {@link CellType#BLANK} | |||
| */ | |||
| public static BigDecimal getDecimalValue(Cell cell, BigDecimal defaultValue) { | |||
| if (cell == null || cell.getCellType() == CellType.BLANK) return defaultValue; | |||
| if (cell.getCellType() == CellType.STRING) { | |||
| return new BigDecimal(cell.getStringCellValue()); | |||
| } else { | |||
| return BigDecimal.valueOf(cell.getNumericCellValue()); | |||
| } | |||
| } | |||
| /** | |||
| * Get Cell value as <code>BigDecimal</code> | |||
| * <p> | |||
| * Only support {@link CellType#NUMERIC} and {@link CellType#STRING} | |||
| * | |||
| * @return the <code>BigDecimal</code> value, or <code>BigDecimal.ZERO</code> if cell is <code>null</code> or cell type is {@link CellType#BLANK} | |||
| */ | |||
| public static BigDecimal getDecimalValue(Cell cell) { | |||
| return getDecimalValue(cell, BigDecimal.ZERO); | |||
| } | |||
| /** | |||
| * Get Cell value as <code>double</code> | |||
| * <p> | |||
| * Only support {@link CellType#NUMERIC} and {@link CellType#STRING} | |||
| */ | |||
| public static double getDoubleValue(Cell cell) { | |||
| if (cell == null) return 0.0; | |||
| if (cell.getCellType() == CellType.STRING) { | |||
| return NumberUtils.toDouble(cell.getStringCellValue()); | |||
| } else { | |||
| return cell.getNumericCellValue(); | |||
| } | |||
| } | |||
| /** | |||
| * Get Cell value as <code>int</code> (rounded half-up to the nearest integer) | |||
| * <p> | |||
| * Only support {@link CellType#NUMERIC} and {@link CellType#STRING} | |||
| */ | |||
| public static int getIntValue(Cell cell) { | |||
| return BigDecimal.valueOf(getDoubleValue(cell)).setScale(0, RoundingMode.HALF_UP).intValue(); | |||
| } | |||
| /** | |||
| * Get Cell Integer value (truncated) | |||
| */ | |||
| public static Integer getIntValue(Cell cell, Integer defaultValue) { | |||
| if (cell == null) return defaultValue; | |||
| if (cell.getCellType() == CellType.STRING) { | |||
| return NumberUtils.toInt(cell.getStringCellValue(), defaultValue); | |||
| } else { | |||
| return (int) cell.getNumericCellValue(); | |||
| } | |||
| } | |||
| public static LocalDate getDateValue(Cell cell, DateTimeFormatter formatter) { | |||
| if (cell == null) return null; | |||
| if (cell.getCellType() == CellType.STRING) { | |||
| try { | |||
| return LocalDate.parse(cell.getStringCellValue(), formatter); | |||
| } catch (DateTimeParseException e) { | |||
| return null; | |||
| } | |||
| } | |||
| if (DateUtil.isCellDateFormatted(cell)) { | |||
| try { | |||
| return DateUtil.getJavaDate(cell.getNumericCellValue()).toInstant() | |||
| .atZone(ZoneId.systemDefault()) | |||
| .toLocalDate(); | |||
| } catch (NumberFormatException e) { | |||
| return null; | |||
| } | |||
| } else { | |||
| return null; | |||
| } | |||
| } | |||
| public static LocalDateTime getDatetimeValue(Cell cell, DateTimeFormatter formatter) { | |||
| if (cell == null) return null; | |||
| if (cell.getCellType() == CellType.STRING) { | |||
| try { | |||
| return LocalDateTime.parse(cell.getStringCellValue(), formatter); | |||
| } catch (DateTimeParseException e) { | |||
| return null; | |||
| } | |||
| } | |||
| if (DateUtil.isCellDateFormatted(cell)) { | |||
| try { | |||
| return DateUtil.getJavaDate(cell.getNumericCellValue()).toInstant() | |||
| .atZone(ZoneId.systemDefault()) | |||
| .toLocalDateTime(); | |||
| } catch (NumberFormatException e) { | |||
| return null; | |||
| } | |||
| } else { | |||
| return null; | |||
| } | |||
| } | |||
| /** | |||
| * Convenient method to set Cell value | |||
| * | |||
| * @param cell | |||
| * the Cell (cannot be null) | |||
| * @param value | |||
| * the value to set | |||
| */ | |||
| public static void setCellValue(Cell cell, Object value) { | |||
| if (value instanceof String) | |||
| cell.setCellValue((String) value); | |||
| else if (value instanceof RichTextString) | |||
| cell.setCellValue((RichTextString) value); | |||
| else if (value instanceof Number) | |||
| cell.setCellValue(((Number) value).doubleValue()); | |||
| else if (value instanceof Boolean) | |||
| cell.setCellValue(((Boolean) value).booleanValue()); | |||
| else if (value instanceof Calendar) | |||
| cell.setCellValue((Calendar) value); | |||
| else if (value instanceof Date) | |||
| cell.setCellValue((Date) value); | |||
| else if (value instanceof LocalDate) | |||
| cell.setCellValue((LocalDate) value); | |||
| else if (value instanceof LocalTime) | |||
| cell.setCellValue(((LocalTime) value).toString()); | |||
| else if (value instanceof LocalDateTime) | |||
| cell.setCellValue((LocalDateTime) value); | |||
| else if (value == null) | |||
| cell.setCellValue(""); | |||
| else | |||
| throw new IllegalArgumentException(value.getClass().toString() + " is not supported"); | |||
| } | |||
| /** | |||
| * Convenient method to set Cell value by Sheet, row index, and column index | |||
| * | |||
| * @param sheet | |||
| * the Sheet (cannot be null) | |||
| * @param rowIndex | |||
| * 0-based row index | |||
| * @param colIndex | |||
| * 0-based column index | |||
| * @param value | |||
| * the value to set | |||
| */ | |||
| public static void setCellValue(Sheet sheet, int rowIndex, int colIndex, Object value) { | |||
| setCellValue(getCell(sheet, rowIndex, colIndex), value); | |||
| } | |||
| /** | |||
| * Increase Row Height (if necessary, but never decrease it) by counting the no. of lines in a String value | |||
| * | |||
| * @param sheet | |||
| * The Excel worksheet | |||
| * @param row | |||
| * The row index (0-based) | |||
| * @param value | |||
| * The (multi-line) String value to count for the no. of lines | |||
| * @param heightInPoints | |||
| * The height (in points) for 1 line of text | |||
| */ | |||
| public static void increaseRowHeight(Sheet sheet, int row, String value, int heightInPoints) { | |||
| int lines = StringUtils.countMatches(value, "\n") + 1; // count no. of lines | |||
| float newHeight = heightInPoints * lines; | |||
| Row r = sheet.getRow(row); | |||
| if (r == null) r = sheet.createRow(row); | |||
| // increase the row height if necessary, but never decrease it | |||
| if (r.getHeightInPoints() < newHeight) { | |||
| r.setHeightInPoints(newHeight); | |||
| } | |||
| } | |||
| /** | |||
| * Add merged region (i.e. merge cells) | |||
| * | |||
| * @param sheet | |||
| * The Excel worksheet | |||
| * @param firstRowIdx | |||
| * The first row index (0-based) | |||
| * @param lastRowIdx | |||
| * The last row index (0-based) | |||
| * @param firstColIdx | |||
| * The first column index (0-based) | |||
| * @param lastColIdx | |||
| * The last column index (0-based) | |||
| */ | |||
| public static void addMergedRegion(Sheet sheet, int firstRowIdx, int lastRowIdx, int firstColIdx, int lastColIdx) { | |||
| CellRangeAddress cellRangeAddress = new CellRangeAddress(firstRowIdx, lastRowIdx, firstColIdx, lastColIdx); | |||
| sheet.addMergedRegion(cellRangeAddress); | |||
| } | |||
| /** | |||
| * Copy and Insert Row | |||
| * | |||
| * @param workbook | |||
| * The Excel workbook | |||
| * @param sourceSheet | |||
| * The source Excel worksheet | |||
| * @param destinationSheet | |||
| * The destination Excel worksheet | |||
| * @param sourceRowNum | |||
| * The source row index (0-based) to copy from | |||
| * @param destinationRowNum | |||
| * The destination row index (0-based) to insert into (from the copied row) | |||
| */ | |||
| public static void copyAndInsertRow(Workbook workbook, Sheet sourceSheet, Sheet destinationSheet, int sourceRowNum, int destinationRowNum) { | |||
| // get the source / destination row | |||
| Row sourceRow = sourceSheet.getRow(sourceRowNum); | |||
| Row destRow = destinationSheet.getRow(destinationRowNum); | |||
| // if the row exist in destination, push down all rows by 1 | |||
| if (destRow != null) { | |||
| destinationSheet.shiftRows(destinationRowNum, destinationSheet.getLastRowNum(), 1, true, false); | |||
| } | |||
| // create a new row | |||
| destRow = destinationSheet.createRow(destinationRowNum); | |||
| // loop through source columns to add to new row | |||
| for (int i = 0; i < sourceRow.getLastCellNum(); i++) { | |||
| // grab a copy of the old cell | |||
| Cell oldCell = sourceRow.getCell(i); | |||
| // if the old cell is null jump to next cell | |||
| if (oldCell == null) continue; | |||
| // create a new cell in destination row | |||
| Cell newCell = destRow.createCell(i); | |||
| // apply cell style to new cell from old cell | |||
| newCell.setCellStyle(oldCell.getCellStyle()); | |||
| // if there is a cell comment, copy | |||
| if (oldCell.getCellComment() != null) { | |||
| newCell.setCellComment(oldCell.getCellComment()); | |||
| } | |||
| // if there is a cell hyperlink, copy | |||
| if (oldCell.getHyperlink() != null) { | |||
| newCell.setHyperlink(oldCell.getHyperlink()); | |||
| } | |||
| // copy the cell data value | |||
| switch (oldCell.getCellType()) { | |||
| case NUMERIC: | |||
| newCell.setCellValue(oldCell.getNumericCellValue()); | |||
| break; | |||
| case STRING: | |||
| newCell.setCellValue(oldCell.getRichStringCellValue()); | |||
| break; | |||
| case FORMULA: | |||
| newCell.setCellFormula(oldCell.getCellFormula()); | |||
| break; | |||
| case BLANK: | |||
| newCell.setCellValue(oldCell.getStringCellValue()); | |||
| break; | |||
| case BOOLEAN: | |||
| newCell.setCellValue(oldCell.getBooleanCellValue()); | |||
| break; | |||
| case ERROR: | |||
| newCell.setCellErrorValue(oldCell.getErrorCellValue()); | |||
| break; | |||
| default: | |||
| break; | |||
| } | |||
| } | |||
| // if there are any merged regions in the source row, copy to new row | |||
| for (int i = 0; i < sourceSheet.getNumMergedRegions(); i++) { | |||
| CellRangeAddress cellRangeAddress = sourceSheet.getMergedRegion(i); | |||
| if (cellRangeAddress.getFirstRow() == sourceRow.getRowNum()) { | |||
| addMergedRegion( | |||
| destinationSheet, | |||
| destRow.getRowNum(), | |||
| (destRow.getRowNum() + (cellRangeAddress.getLastRow() - cellRangeAddress.getFirstRow())), | |||
| cellRangeAddress.getFirstColumn(), | |||
| cellRangeAddress.getLastColumn()); | |||
| } | |||
| } | |||
| // copy row height | |||
| destRow.setHeight(sourceRow.getHeight()); | |||
| } | |||
| /** | |||
| * Copy and Insert Row | |||
| * | |||
| * @param workbook | |||
| * The Excel workbook | |||
| * @param sheet | |||
| * The Excel worksheet | |||
| * @param sourceRowNum | |||
| * The source row index (0-based) to copy from | |||
| * @param destinationRowNum | |||
| * The destination row index (0-based) to insert into (from the copied row) | |||
| */ | |||
| public static void copyAndInsertRow(Workbook workbook, Sheet sheet, int sourceRowNum, int destinationRowNum) { | |||
| copyAndInsertRow(workbook, sheet, sheet, sourceRowNum, destinationRowNum); | |||
| } | |||
| public static void copyAndInsertRow(Workbook workbook, Sheet sourceSheet, int sourceRowNum, int destinationRowNum, int times) { | |||
| // get the source / destination row | |||
| Row sourceRow = sourceSheet.getRow(sourceRowNum); | |||
| Row[] destRows = new Row[times]; | |||
| for (int j = 0; j < times; j++) { | |||
| Row destRow = sourceSheet.getRow(destinationRowNum + j); | |||
| // if the row exist in destination, push down all rows by 1 | |||
| if (destRow != null) { | |||
| sourceSheet.shiftRows(destinationRowNum + j, sourceSheet.getLastRowNum(), 1, true, false); | |||
| } | |||
| // create a new row | |||
| destRows[j] = sourceSheet.createRow(destinationRowNum + j); | |||
| // copy row height | |||
| destRows[j].setHeight(sourceRow.getHeight()); | |||
| } | |||
| // loop through source columns to add to new row | |||
| for (int i = 0; i < sourceRow.getLastCellNum(); i++) { | |||
| // grab a copy of the old cell | |||
| Cell oldCell = sourceRow.getCell(i); | |||
| // if the old cell is null jump to next cell | |||
| if (oldCell == null) continue; | |||
| for (int k = 0; k < times; k++) { | |||
| // create a new cell in destination row | |||
| Cell newCell = destRows[k].createCell(i); | |||
| // apply cell style to new cell from old cell | |||
| newCell.setCellStyle(oldCell.getCellStyle()); | |||
| } | |||
| } | |||
| } | |||
| /** | |||
| * Copy Column | |||
| * | |||
| * @param workbook | |||
| * The Excel workbook | |||
| * @param sourceSheet | |||
| * The source Excel worksheet | |||
| * @param destinationSheet | |||
| * The destination Excel worksheet | |||
| * @param rowStart | |||
| * The source row start index (0-based) to copy from | |||
| * @param rowEnd | |||
| * The source row end index (0-based) to copy from | |||
| * @param sourceColumnNum | |||
| * The source column index (0-based) to copy from | |||
| * @param destinationColumnNum | |||
| * The destination column index (0-based) to copy into (from the copied row) | |||
| */ | |||
| public static void copyColumn(Workbook workbook, Sheet sourceSheet, Sheet destinationSheet, int rowStart, int rowEnd, int sourceColumnNum, | |||
| int destinationColumnNum) { | |||
| for (int i = rowStart; i <= rowEnd; i++) { | |||
| Row sourceRow = sourceSheet.getRow(i); | |||
| if (sourceRow == null) continue; | |||
| Row destinationRow = destinationSheet.getRow(i); | |||
| if (destinationRow == null) destinationRow = destinationSheet.createRow(i); | |||
| Cell oldCell = sourceRow.getCell(sourceColumnNum); | |||
| if (oldCell == null) continue; | |||
| Cell newCell = destinationRow.createCell(destinationColumnNum); | |||
| newCell.setCellStyle(oldCell.getCellStyle()); | |||
| if (oldCell.getCellComment() != null) { | |||
| newCell.setCellComment(oldCell.getCellComment()); | |||
| } | |||
| if (oldCell.getHyperlink() != null) { | |||
| newCell.setHyperlink(oldCell.getHyperlink()); | |||
| } | |||
| switch (oldCell.getCellType()) { | |||
| case NUMERIC: | |||
| newCell.setCellValue(oldCell.getNumericCellValue()); | |||
| break; | |||
| case STRING: | |||
| newCell.setCellValue(oldCell.getRichStringCellValue()); | |||
| break; | |||
| case FORMULA: | |||
| newCell.setCellFormula(oldCell.getCellFormula()); | |||
| break; | |||
| case BLANK: | |||
| newCell.setCellValue(oldCell.getStringCellValue()); | |||
| break; | |||
| case BOOLEAN: | |||
| newCell.setCellValue(oldCell.getBooleanCellValue()); | |||
| break; | |||
| case ERROR: | |||
| newCell.setCellErrorValue(oldCell.getErrorCellValue()); | |||
| break; | |||
| default: | |||
| break; | |||
| } | |||
| for (int ii = 0; ii < sourceSheet.getNumMergedRegions(); ii++) { | |||
| CellRangeAddress cellRangeAddress = sourceSheet.getMergedRegion(ii); | |||
| if (cellRangeAddress.getFirstRow() == sourceRow.getRowNum()) { | |||
| addMergedRegion( | |||
| destinationSheet, | |||
| cellRangeAddress.getFirstRow(), | |||
| cellRangeAddress.getLastRow(), | |||
| destinationColumnNum, | |||
| (destinationColumnNum + (cellRangeAddress.getLastColumn() - cellRangeAddress.getFirstColumn()))); | |||
| } | |||
| } | |||
| } | |||
| destinationSheet.setColumnWidth(destinationColumnNum, sourceSheet.getColumnWidth(sourceColumnNum)); | |||
| } | |||
| /** | |||
| * Copy Column | |||
| * | |||
| * @param workbook | |||
| * The Excel workbook | |||
| * @param sheet | |||
| * The Excel worksheet | |||
| * @param rowStart | |||
| * The source row start index (0-based) to copy from | |||
| * @param rowEnd | |||
| * The source row end index (0-based) to copy from | |||
| * @param sourceColumnNum | |||
| * The source column index (0-based) to copy from | |||
| * @param destinationColumnNum | |||
| * The destination column index (0-based) to copy into (from the copied row) | |||
| */ | |||
| public static void copyColumn(Workbook workbook, Sheet sheet, int rowStart, int rowEnd, int sourceColumnNum, int destinationColumnNum) { | |||
| copyColumn(workbook, sheet, sheet, rowStart, rowEnd, sourceColumnNum, destinationColumnNum); | |||
| } | |||
| public static void shiftColumns(Row row, int startingIndex, int shiftCount) { | |||
| for (int i = row.getPhysicalNumberOfCells() - 1; i >= startingIndex; i--) { | |||
| Cell oldCell = row.getCell(i); | |||
| Cell newCell = row.createCell(i + shiftCount); | |||
| // apply cell style to new cell from old cell | |||
| newCell.setCellStyle(oldCell.getCellStyle()); | |||
| // if there is a cell comment, copy | |||
| if (oldCell.getCellComment() != null) { | |||
| newCell.setCellComment(oldCell.getCellComment()); | |||
| } | |||
| // if there is a cell hyperlink, copy | |||
| if (oldCell.getHyperlink() != null) { | |||
| newCell.setHyperlink(oldCell.getHyperlink()); | |||
| } | |||
| // copy the cell data value | |||
| switch (oldCell.getCellType()) { | |||
| case NUMERIC: | |||
| newCell.setCellValue(oldCell.getNumericCellValue()); | |||
| break; | |||
| case STRING: | |||
| newCell.setCellValue(oldCell.getRichStringCellValue()); | |||
| break; | |||
| case FORMULA: | |||
| newCell.setCellFormula(oldCell.getCellFormula()); | |||
| break; | |||
| case BLANK: | |||
| newCell.setCellValue(oldCell.getStringCellValue()); | |||
| break; | |||
| case BOOLEAN: | |||
| newCell.setCellValue(oldCell.getBooleanCellValue()); | |||
| break; | |||
| case ERROR: | |||
| newCell.setCellErrorValue(oldCell.getErrorCellValue()); | |||
| break; | |||
| default: | |||
| break; | |||
| } | |||
| } | |||
| } | |||
| /** handle some invalid char included ( /\*[]:? ) */ | |||
| public static void setSheetName(Workbook workbook, Sheet sheet, String name) { | |||
| if (workbook != null && sheet != null && StringUtils.isNotBlank(name)) | |||
| workbook.setSheetName(workbook.getSheetIndex(sheet), name.replaceAll("[/\\\\*\\[\\]:\\?]", "_")); | |||
| } | |||
| /** delete row */ | |||
| public static void deleteRow(Sheet sheet, int rowIndex) { | |||
| if (sheet != null) { | |||
| sheet.removeRow(sheet.getRow(rowIndex)); | |||
| if (rowIndex < sheet.getLastRowNum()) | |||
| sheet.shiftRows(rowIndex, sheet.getLastRowNum(), -1); | |||
| } | |||
| } | |||
| public static byte[] encrypt(Workbook workbook, String password) { | |||
| return encrypt(toByteArray(workbook), password); | |||
| } | |||
| public static byte[] encrypt(byte[] bytes, String password) { | |||
| try { | |||
| POIFSFileSystem fs = new POIFSFileSystem(); | |||
| EncryptionInfo info = new EncryptionInfo(EncryptionMode.agile); | |||
| // EncryptionInfo info = new EncryptionInfo(EncryptionMode.agile, CipherAlgorithm.aes192, HashAlgorithm.sha384, -1, -1, null); | |||
| Encryptor enc = info.getEncryptor(); | |||
| enc.confirmPassword(password); | |||
| // Read in an existing OOXML file and write to encrypted output stream | |||
| // don't forget to close the output stream otherwise the padding bytes aren't added | |||
| OPCPackage opc = OPCPackage.open(new ByteArrayInputStream(bytes)); | |||
| OutputStream os = enc.getDataStream(fs); | |||
| opc.save(os); | |||
| ByteArrayOutputStream bos = new ByteArrayOutputStream(); | |||
| fs.writeFilesystem(bos); | |||
| return bos.toByteArray(); | |||
| } catch (Exception e) { | |||
| throw new RuntimeException(e); | |||
| } | |||
| } | |||
| public static Workbook loadTemplate(String templateClasspath) throws InvalidFormatException, IOException { | |||
| return loadTemplateFile(templateClasspath); | |||
| } | |||
| public static Workbook loadTemplateFile(String templateClasspath) throws InvalidFormatException, IOException { | |||
| ClassPathResource r = new ClassPathResource(templateClasspath + "_" + ".xlsx"); | |||
| if (!r.exists()) r = new ClassPathResource(templateClasspath + ".xlsx"); | |||
| try (InputStream in = r.getInputStream()) { | |||
| return new XSSFWorkbook(in); | |||
| } | |||
| } | |||
| public static void send(HttpServletResponse response, Workbook workbook, String filename) throws IOException { | |||
| response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); | |||
| response.setHeader("Content-Disposition", String.format("attachment; filename=\"%s\"", | |||
| response.encodeURL(filename + ".xlsx"))); | |||
| try (OutputStream out = response.getOutputStream()) { | |||
| workbook.write(out); | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,125 @@ | |||
| /******************************************************************************* | |||
| * Copyright 2013 2Fi Business Solutions Ltd. | |||
| * | |||
| * This code is part of the Core project. | |||
| * | |||
| * This code is copyrighted. Under no circumstances should any party, people, | |||
| * or organization should redistribute any portions of this code in any form, | |||
| * either verbatim or through electronic media, to any third parties, unless | |||
| * under explicit written permission by 2Fi Business Solutions Ltd. | |||
| ******************************************************************************/ | |||
| package com.ffii.core.utils; | |||
| import java.util.HashMap; | |||
| import java.util.Map; | |||
| /** | |||
| * File Utils | |||
| * | |||
| * @author Patrick | |||
| */ | |||
| public abstract class FileUtils { | |||
| private static final Map<String, String> MIMETYPES = new HashMap<>(); | |||
| static { | |||
| MIMETYPES.put("pdf", "application/pdf"); | |||
| MIMETYPES.put("doc", "application/msword"); | |||
| MIMETYPES.put("dot", "application/msword"); | |||
| MIMETYPES.put("docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"); | |||
| MIMETYPES.put("xls", "application/vnd.ms-excel"); | |||
| MIMETYPES.put("xlm", "application/vnd.ms-excel"); | |||
| MIMETYPES.put("xla", "application/vnd.ms-excel"); | |||
| MIMETYPES.put("xlc", "application/vnd.ms-excel"); | |||
| MIMETYPES.put("xlt", "application/vnd.ms-excel"); | |||
| MIMETYPES.put("xlw", "application/vnd.ms-excel"); | |||
| MIMETYPES.put("xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); | |||
| MIMETYPES.put("ppt", "application/vnd.ms-powerpoint"); | |||
| MIMETYPES.put("pps", "application/vnd.ms-powerpoint"); | |||
| MIMETYPES.put("pot", "application/vnd.ms-powerpoint"); | |||
| MIMETYPES.put("pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"); | |||
| MIMETYPES.put("bat", "application/x-msdownload"); | |||
| MIMETYPES.put("com", "application/x-msdownload"); | |||
| MIMETYPES.put("dll", "application/x-msdownload"); | |||
| MIMETYPES.put("exe", "application/x-msdownload"); | |||
| MIMETYPES.put("msi", "application/x-msdownload"); | |||
| MIMETYPES.put("swf", "application/x-shockwave-flash"); | |||
| MIMETYPES.put("7z", "application/x-7z-compressed"); | |||
| MIMETYPES.put("rar", "application/x-rar-compressed"); | |||
| MIMETYPES.put("zip", "application/zip"); | |||
| MIMETYPES.put("js", "application/javascript"); | |||
| MIMETYPES.put("json", "application/json"); | |||
| MIMETYPES.put("mpga", "audio/mpeg"); | |||
| MIMETYPES.put("mp2", "audio/mpeg"); | |||
| MIMETYPES.put("mp2a", "audio/mpeg"); | |||
| MIMETYPES.put("mp3", "audio/mpeg"); | |||
| MIMETYPES.put("m2a", "audio/mpeg"); | |||
| MIMETYPES.put("m3a", "audio/mpeg"); | |||
| MIMETYPES.put("bmp", "image/bmp"); | |||
| MIMETYPES.put("gif", "image/gif"); | |||
| MIMETYPES.put("jpeg", "image/jpeg"); | |||
| MIMETYPES.put("jpg", "image/jpeg"); | |||
| MIMETYPES.put("jpe", "image/jpeg"); | |||
| MIMETYPES.put("png", "image/png"); | |||
| MIMETYPES.put("tiff", "image/tiff"); | |||
| MIMETYPES.put("tif", "image/tiff"); | |||
| MIMETYPES.put("avif", "image/avif"); | |||
| MIMETYPES.put("css", "text/css"); | |||
| MIMETYPES.put("csv", "text/csv"); | |||
| MIMETYPES.put("html", "text/html"); | |||
| MIMETYPES.put("htm", "text/html"); | |||
| MIMETYPES.put("txt", "text/plain"); | |||
| MIMETYPES.put("text", "text/plain"); | |||
| MIMETYPES.put("conf", "text/plain"); | |||
| MIMETYPES.put("log", "text/plain"); | |||
| MIMETYPES.put("mp4", "video/mp4"); | |||
| MIMETYPES.put("mp4v", "video/mp4"); | |||
| MIMETYPES.put("mpg4", "video/mp4"); | |||
| MIMETYPES.put("mkv", "video/x-matroska"); | |||
| MIMETYPES.put("mpeg", "video/mpeg"); | |||
| MIMETYPES.put("mpg", "video/mpeg"); | |||
| MIMETYPES.put("mpe", "video/mpeg"); | |||
| MIMETYPES.put("m1v", "video/mpeg"); | |||
| MIMETYPES.put("m2v", "video/mpeg"); | |||
| MIMETYPES.put("qt", "video/quicktime"); | |||
| MIMETYPES.put("mov", "video/quicktime"); | |||
| MIMETYPES.put("wmv", "video/x-ms-wmv"); | |||
| MIMETYPES.put("wmx", "video/x-ms-wmx"); | |||
| MIMETYPES.put("wvx", "video/x-ms-wvx"); | |||
| MIMETYPES.put("avi", "video/x-msvideo"); | |||
| // MIMETYPES.put("xxxxx", "xxxxx"); | |||
| } | |||
| /** | |||
| * Guess the mimetype from the file name extension | |||
| * | |||
| * @return The mimetype guessed from the file name extension, or {@code null} if the mimetype cannot be determined | |||
| */ | |||
| public static String guessMimetype(String filename) { | |||
| String extension = StringUtils.substringAfterLast(filename, "."); | |||
| extension = extension.toLowerCase(); | |||
| String mimetype = MIMETYPES.get(extension); | |||
| return mimetype != null ? mimetype : "application/octet-stream"; | |||
| } | |||
| } | |||
| @@ -0,0 +1,47 @@ | |||
| package com.ffii.core.utils; | |||
| import java.io.IOException; | |||
| import com.fasterxml.jackson.core.JsonParseException; | |||
| import com.fasterxml.jackson.core.JsonProcessingException; | |||
| import com.fasterxml.jackson.databind.JsonMappingException; | |||
| import com.fasterxml.jackson.databind.ObjectMapper; | |||
| import com.fasterxml.jackson.databind.SerializationFeature; | |||
| import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; | |||
| /** | |||
| * JSON Utils | |||
| * | |||
| * @author Patrick | |||
| */ | |||
| public abstract class JsonUtils { | |||
| // Default mapper instance | |||
| private static final ObjectMapper mapper = new ObjectMapper(); | |||
| /** | |||
| * Method that can be used to serialize any Java value as a JSON String. | |||
| */ | |||
| public static String toJsonString(Object obj) { | |||
| try { | |||
| mapper.registerModule(new JavaTimeModule()); | |||
| mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); | |||
| return mapper.writeValueAsString(obj); | |||
| } catch (JsonProcessingException e) { | |||
| return null; | |||
| } | |||
| } | |||
| /** | |||
| * Read from JSON String. | |||
| * | |||
| * @param content | |||
| * JSON String content | |||
| * @param valueType | |||
| * the return type | |||
| */ | |||
| public static <T> T fromJsonString(String content, Class<T> valueType) throws JsonParseException, JsonMappingException, IOException { | |||
| return mapper.readValue(content, valueType); | |||
| } | |||
| } | |||
| @@ -0,0 +1,114 @@ | |||
| package com.ffii.core.utils; | |||
| import java.io.Serializable; | |||
| import java.security.Key; | |||
| import java.time.Instant; | |||
| import java.util.Date; | |||
| import java.util.HashMap; | |||
| import java.util.Map; | |||
| import java.util.function.Function; | |||
| import org.slf4j.Logger; | |||
| import org.slf4j.LoggerFactory; | |||
| import org.springframework.beans.factory.config.ConfigurableBeanFactory; | |||
| import org.springframework.context.annotation.Scope; | |||
| import org.springframework.security.core.userdetails.UserDetails; | |||
| import org.springframework.stereotype.Component; | |||
| import com.ffii.lioner.model.RefreshToken; | |||
| import io.jsonwebtoken.Claims; | |||
| import io.jsonwebtoken.Jwts; | |||
| import io.jsonwebtoken.SignatureAlgorithm; | |||
| import io.jsonwebtoken.security.Keys; | |||
| @Component | |||
| @Scope(value = ConfigurableBeanFactory. SCOPE_SINGLETON) | |||
| public class JwtTokenUtil implements Serializable { | |||
| Logger logger = LoggerFactory.getLogger(JwtTokenUtil.class); | |||
| private static final long serialVersionUID = -2550185165626007488L; | |||
| // * 60000 = 1 Min | |||
| public static final long JWT_REFRESH_TOKEN_EXPIRED_TIME = 60000 * 30; | |||
| public static final String AES_SECRET = "ffii"; | |||
| public static final String TOKEN_SEPARATOR = "@@"; | |||
| // @Value("${jwt.secret}") | |||
| // private String secret; | |||
| private static final Key secretKey = Keys.secretKeyFor(SignatureAlgorithm.HS512); | |||
| // retrieve username from jwt token | |||
| public String getUsernameFromToken(String token) { | |||
| return getClaimFromToken(token, Claims::getSubject); | |||
| } | |||
| // retrieve expiration date from jwt token | |||
| public Date getExpirationDateFromToken(String token) { | |||
| return getClaimFromToken(token, Claims::getExpiration); | |||
| } | |||
| public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) { | |||
| final Claims claims = getAllClaimsFromToken(token); | |||
| return claimsResolver.apply(claims); | |||
| } | |||
| // for retrieveing any information from token we will need the secret key | |||
| private Claims getAllClaimsFromToken(String token) { | |||
| return Jwts.parserBuilder().setSigningKey(secretKey).build().parseClaimsJws(token).getBody(); | |||
| } | |||
| // check if the token has expired | |||
| private Boolean isTokenExpired(String token) { | |||
| final Date expiration = getExpirationDateFromToken(token); | |||
| return expiration.before(new Date()); | |||
| } | |||
| // generate token for user | |||
| public String generateToken(UserDetails userDetails, long accessTokenExpiry) { | |||
| Map<String, Object> claims = new HashMap<>(); | |||
| return doGenerateToken(claims, userDetails.getUsername(), accessTokenExpiry); | |||
| } | |||
| // while creating the token - | |||
| // 1. Define claims of the token, like Issuer, Expiration, Subject, and the ID | |||
| // 2. Sign the JWT using the HS512 algorithm and secret key. | |||
| // 3. According to JWS Compact | |||
| // Serialization(https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-41#section-3.1) | |||
| // compaction of the JWT to a URL-safe string | |||
| private String doGenerateToken(Map<String, Object> claims, String subject, long accessTokenExpiry) { | |||
| logger.info((new Date(System.currentTimeMillis() + accessTokenExpiry)).toString()); | |||
| return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis())) | |||
| .setExpiration(new Date(System.currentTimeMillis() + accessTokenExpiry)) | |||
| .signWith(secretKey).compact(); | |||
| } | |||
| // validate token | |||
| public Boolean validateToken(String token, UserDetails userDetails) { | |||
| final String username = getUsernameFromToken(token); | |||
| return (username.equals(userDetails.getUsername()) && !isTokenExpired(token)); | |||
| } | |||
| public RefreshToken createRefreshToken(String username) { | |||
| RefreshToken refreshToken = new RefreshToken(); | |||
| refreshToken.setUserName(username); | |||
| refreshToken.setExpiryDate(Instant.now().plusMillis(JWT_REFRESH_TOKEN_EXPIRED_TIME)); | |||
| long instantNum = Instant.now().plusMillis(JWT_REFRESH_TOKEN_EXPIRED_TIME).toEpochMilli(); | |||
| refreshToken.setToken(AES.encrypt(username + TOKEN_SEPARATOR + instantNum, AES_SECRET)); | |||
| return refreshToken; | |||
| } | |||
| public boolean verifyExpiration(RefreshToken token) throws Exception { | |||
| if (token.getExpiryDate().compareTo(Instant.now()) < 0) { | |||
| return false; | |||
| } | |||
| return true; | |||
| } | |||
| public String getUsernameFromRefreshToken(String refreshToken) { | |||
| return AES.decrypt(refreshToken, AES_SECRET); | |||
| } | |||
| } | |||
| @@ -0,0 +1,42 @@ | |||
| package com.ffii.core.utils; | |||
| import java.util.Locale; | |||
| import org.apache.commons.lang3.StringUtils; | |||
| import org.springframework.context.i18n.LocaleContextHolder; | |||
| /** this utils follow "-" standard ("zh-TW", no "zh_TW") */ | |||
| public abstract class LocaleUtils { | |||
| public static Locale getLocale() { | |||
| return LocaleContextHolder.getLocale(); | |||
| } | |||
| public static String getLocaleStr() { | |||
| return toLocaleStr(LocaleContextHolder.getLocale()); | |||
| } | |||
| public static String toLocaleStr(Locale locale) { | |||
| String language = locale.getLanguage(); | |||
| String country = locale.getCountry(); | |||
| if (StringUtils.isNotBlank(country)) { | |||
| return language + "-" + country; | |||
| } else { | |||
| return language; | |||
| } | |||
| } | |||
| /** | |||
| * @param localeStr | |||
| * e.g. zh-TW | |||
| */ | |||
| public static Locale from(String localeStr) { | |||
| String[] localeArr = localeStr.split("-"); | |||
| if (localeArr.length == 1) { | |||
| return new Locale(localeArr[0]); | |||
| } else { | |||
| return new Locale(localeArr[0], localeArr[1]); | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,35 @@ | |||
| package com.ffii.core.utils; | |||
| import java.util.HashMap; | |||
| import java.util.Map; | |||
| /** | |||
| * MapUtils | |||
| * | |||
| * @author Patrick | |||
| */ | |||
| public class MapUtils { | |||
| /** | |||
| * Convert key-value pairs to HashMap | |||
| * | |||
| * @param keyValuePairs | |||
| * Keys and values must be in pairs | |||
| * | |||
| * @return Map | |||
| */ | |||
| @SuppressWarnings("unchecked") | |||
| public static <K, V> Map<K, V> toHashMap(Object... keyValuePairs) { | |||
| if (keyValuePairs.length % 2 != 0) | |||
| throw new IllegalArgumentException("Keys and values must be in pairs"); | |||
| Map<K, V> map = new HashMap<K, V>(keyValuePairs.length / 2); | |||
| for (int i = 0; i < keyValuePairs.length; i += 2) { | |||
| map.put((K) keyValuePairs[i], (V) keyValuePairs[i + 1]); | |||
| } | |||
| return map; | |||
| } | |||
| } | |||
| @@ -0,0 +1,45 @@ | |||
| package com.ffii.core.utils; | |||
| public class NumberUtils extends org.apache.commons.lang3.math.NumberUtils{ | |||
| private static final String[] units = { "", "one", "two", "three", "four", | |||
| "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve", | |||
| "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", | |||
| "eighteen", "nineteen" }; | |||
| private static final String[] tens = { | |||
| "", "", "twenty", "thirty", "forty", "fifty", "sixty", "seventy", | |||
| "eighty", "ninety" }; | |||
| public static String convertToWord(int number) { | |||
| if (number == 0) { | |||
| return "zero"; | |||
| } | |||
| if (number < 0) { | |||
| return "minus " + convertToWord(-number); | |||
| } | |||
| if (number < 20) { | |||
| return units[number]; | |||
| } | |||
| if (number < 100) { | |||
| return tens[number / 10] + ((number % 10 != 0) ? " " : "") + units[number % 10]; | |||
| } | |||
| if (number < 1000) { | |||
| return units[number / 100] + " hundred" + ((number % 100 != 0) ? " " : "") + convertToWord(number % 100); | |||
| } | |||
| if (number < 1000000) { | |||
| return convertToWord(number / 1000) + " thousand" + ((number % 1000 != 0) ? " " : "") + convertToWord(number % 1000); | |||
| } | |||
| if (number < 1000000000) { | |||
| return convertToWord(number / 1000000) + " million" + ((number % 1000000 != 0) ? " " : "") + convertToWord(number % 1000000); | |||
| } | |||
| return convertToWord(number / 1000000000) + " billion" + ((number % 1000000000 != 0) ? " " : "") + convertToWord(number % 1000000000); | |||
| } | |||
| } | |||
| @@ -0,0 +1,42 @@ | |||
| package com.ffii.core.utils; | |||
| /** @author Alex */ | |||
| public abstract class Params { | |||
| public static final String ERROR = "error"; | |||
| public static final String SUCCESS = "success"; | |||
| public static final String DATA = "data"; | |||
| public static final String RECORDS = "records"; | |||
| public static final String TOTAL = "total"; | |||
| public static final String ID = "id"; | |||
| public static final String CODE = "code"; | |||
| public static final String NAME = "name"; | |||
| public static final String TYPE = "type"; | |||
| public static final String MSG = "msg"; | |||
| public static final String MSG_CODE = "msgCode"; | |||
| public static final String MESSAGES = "messages"; | |||
| public static final String FROM = "from"; | |||
| public static final String TO = "to"; | |||
| // sql | |||
| public static final String QUERY = "query"; | |||
| // pagin | |||
| public static final String PAGE = "page"; | |||
| public static final String START = "start"; | |||
| public static final String LIMIT = "limit"; | |||
| // filter | |||
| public static final String FILTER = "filter"; | |||
| public static final String OPERATOR = "operator"; | |||
| public static final String LIKE = "like"; | |||
| public static final String PROPERTY = "property"; | |||
| // sort | |||
| public static final String SORT = "sort"; | |||
| public static final String DIRECTION = "direction"; | |||
| public static final String VALUE = "value"; | |||
| } | |||
| @@ -0,0 +1,111 @@ | |||
| /******************************************************************************* | |||
| * Copyright 2Fi Business Solutions Ltd. | |||
| * | |||
| * This code is copyrighted. Under no circumstances should any party, people, | |||
| * or organization should redistribute any portions of this code in any form, | |||
| * either verbatim or through electronic media, to any third parties, unless | |||
| * under explicit written permission by 2Fi Business Solutions Ltd. | |||
| ******************************************************************************/ | |||
| package com.ffii.core.utils; | |||
| import java.util.regex.Pattern; | |||
| public abstract class PasswordUtils { | |||
| private static final Pattern PATTERN_DIGITS = Pattern.compile("[0-9]"); | |||
| private static final Pattern PATTERN_A2Z_LOWER = Pattern.compile("[a-z]"); | |||
| private static final Pattern PATTERN_A2Z_UPPER = Pattern.compile("[A-Z]"); | |||
| private static final String A2Z_LOWER = "abcdefghijklmnopqrstuvwxyz"; | |||
| private static final String A2Z_UPPER = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; | |||
| private static final String DIGITS = "0123456789"; | |||
| private static final String SPECIAL_CHARS = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"; | |||
| private static Pattern PATTERN_SPECIAL_CHARS = Pattern.compile("[!\"#$%&'()*+,-./:;<=>?@\\[\\\\\\]^_`{|}~]"); | |||
| /* | |||
| * Ref: https://www.owasp.org/index.php/Password_special_characters | |||
| * without space character | |||
| */ | |||
| public static final boolean checkPwd(String pwd, IPasswordRule rule, String username) { | |||
| if (pwd == null) return false; | |||
| if (pwd.length() < rule.getMin()) return false; | |||
| if (rule.needSpecialCharacter() && !containsSpecialCharacters(pwd)) | |||
| return false; | |||
| if (rule.needNumberAndAlphabetic() && !containsAlphabeticAndNumericCharacters(pwd)) | |||
| return false; | |||
| if (rule.needNotContainUsername() && containsReferenceSubstring(pwd, username)) | |||
| return false; | |||
| if (rule.needNotContainThreeConsecutiveCharacters() && containsThreeConsecutiveCharacters(pwd)) | |||
| return false; | |||
| return true; | |||
| } | |||
| private static boolean containsSpecialCharacters(String input) { | |||
| return PATTERN_SPECIAL_CHARS.matcher(input).find(); | |||
| } | |||
| private static boolean containsAlphabeticAndNumericCharacters(String input) { | |||
| boolean containsAlphabetic = PATTERN_A2Z_LOWER.matcher(input).find() || PATTERN_A2Z_UPPER.matcher(input).find(); | |||
| boolean containsNumeric = PATTERN_DIGITS.matcher(input).find(); | |||
| return containsAlphabetic && containsNumeric; | |||
| } | |||
| private static boolean containsReferenceSubstring(String input, String username) { | |||
| return input.contains(username); | |||
| } | |||
| private static boolean containsThreeConsecutiveCharacters(String input) { | |||
| for (int i = 0; i < input.length() - 2; i++) { | |||
| char currentChar = input.charAt(i); | |||
| char nextChar = input.charAt(i + 1); | |||
| char nextNextChar = input.charAt(i + 2); | |||
| if (currentChar == nextChar && nextChar == nextNextChar) { | |||
| return true; | |||
| } | |||
| } | |||
| return false; | |||
| } | |||
| /*public static String genPwd(IPasswordRule rule, String username) { | |||
| int length = rule.getMin(); | |||
| StringBuilder password = new StringBuilder(length); | |||
| Random random = new Random(System.nanoTime()); | |||
| List<String> charCategories = new ArrayList<>(4); | |||
| if (rule.needSpecialCharacter()) charCategories.add(SPECIAL_CHARS); | |||
| if (rule.needNumberAndAlphabetic()){ | |||
| charCategories.add(A2Z_UPPER); | |||
| charCategories.add(A2Z_LOWER); | |||
| charCategories.add(DIGITS); | |||
| } | |||
| if (rule.needSpecialChar()) charCategories.add(SPECIAL_CHARS); | |||
| for (int i = 0; i < length; i++) { | |||
| String charCategory = charCategories.get(i % charCategories.size()); | |||
| char randomChar = charCategory.charAt(random.nextInt(charCategory.length())); | |||
| if (password.length() > 0) | |||
| password.insert(random.nextInt(password.length()), randomChar); | |||
| else | |||
| password.append(randomChar); | |||
| } | |||
| return password.toString(); | |||
| }*/ | |||
| public static interface IPasswordRule { | |||
| public Integer getMin(); | |||
| public boolean needNumberAndAlphabetic(); | |||
| public boolean needSpecialCharacter(); | |||
| public boolean needNotContainUsername(); | |||
| public boolean needNotContainThreeConsecutiveCharacters(); | |||
| } | |||
| } | |||
| @@ -0,0 +1,24 @@ | |||
| package com.ffii.core.utils; | |||
| public class RomanConverter { | |||
| private static final int[] VALUES = { 1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1 }; | |||
| private static final String[] CAP_SYMBOLS = { "M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I" }; | |||
| private static final String[] SYMBOLS = { "m", "cm", "d", "cd", "c", "xc", "l", "xl", "x", "ix", "v", "iv", "i" }; | |||
| public static String convertToRoman(int number, boolean isCapitalLetter) { | |||
| if (number <= 0 || number > 3999) { | |||
| throw new IllegalArgumentException("Number out of range. Please provide a value between 1 and 3999."); | |||
| } | |||
| StringBuilder roman = new StringBuilder(); | |||
| for (int i = 0; i < VALUES.length; i++) { | |||
| while (number >= VALUES[i]) { | |||
| roman.append(isCapitalLetter ? CAP_SYMBOLS[i] : SYMBOLS[i]); | |||
| number -= VALUES[i]; | |||
| } | |||
| } | |||
| return roman.toString(); | |||
| } | |||
| } | |||
| @@ -0,0 +1,83 @@ | |||
| /******************************************************************************* | |||
| * Copyright 2017 2Fi Business Solutions Ltd. | |||
| * | |||
| * This code is part of the Core project. | |||
| * | |||
| * This code is copyrighted. Under no circumstances should any party, people, | |||
| * or organization should redistribute any portions of this code in any form, | |||
| * either verbatim or through electronic media, to any third parties, unless | |||
| * under explicit written permission by 2Fi Business Solutions Ltd. | |||
| ******************************************************************************/ | |||
| package com.ffii.core.utils; | |||
| /** | |||
| * String Utils based on Apache Commons StringUtils. | |||
| * | |||
| * @author Patrick | |||
| */ | |||
| public abstract class StringUtils extends org.apache.commons.lang3.StringUtils { | |||
| /** | |||
| * The String {@code "0"}. | |||
| */ | |||
| public static final String ZERO = "0"; | |||
| /** | |||
| * The String {@code "1"}. | |||
| */ | |||
| public static final String ONE = "1"; | |||
| /** | |||
| * The String {@code "%"}. | |||
| */ | |||
| public static final String PERCENT = "%"; | |||
| /** | |||
| * The String {@code ","}. | |||
| */ | |||
| public static final String COMMA = ","; | |||
| /** | |||
| * The String {@code "\r\n"} for line break on Windows | |||
| */ | |||
| public static final String LINE_BREAK_WINDOWS = "\r\n"; | |||
| /** | |||
| * The String {@code "\n"} for line break on Unix/Linux | |||
| */ | |||
| public static final String LINE_BREAK_LINUX = "\n"; | |||
| public static final String[] A2Z_LOWWER = { "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", | |||
| "o", "p", "q", "r", "s", "t", "u", "v", | |||
| "w", "x", "y", "z" }; | |||
| public static final String[] A2Z_UPPER = { "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", | |||
| "O", "P", "Q", "R", "S", "T", "U", "V", | |||
| "W", "X", "Y", "Z" }; | |||
| public static final String concat(String segregator, final String... chars) { | |||
| if (segregator == null) | |||
| segregator = ""; | |||
| String rs = ""; | |||
| for (String c : chars) { | |||
| if (c == null) | |||
| continue; | |||
| else { | |||
| if (StringUtils.isBlank(rs)) { | |||
| rs = c; | |||
| } else { | |||
| rs += segregator + c; | |||
| } | |||
| } | |||
| } | |||
| return rs; | |||
| } | |||
| public static final String removeLineBreak(String str) { | |||
| if (str == null) | |||
| return str; | |||
| return str.replace("\r\n", " ") | |||
| .replace("\n", " ") | |||
| .replace("\r", " ") | |||
| .trim(); | |||
| } | |||
| } | |||
| @@ -0,0 +1,13 @@ | |||
| package com.ffii.lioner; | |||
| import org.springframework.boot.SpringApplication; | |||
| import org.springframework.boot.autoconfigure.SpringBootApplication; | |||
| @SpringBootApplication | |||
| public class LionerApplication { | |||
| public static void main(String[] args) { | |||
| SpringApplication.run(LionerApplication.class, args); | |||
| } | |||
| } | |||
| @@ -0,0 +1,36 @@ | |||
| package com.ffii.lioner.config; | |||
| import javax.sql.DataSource; | |||
| import org.springframework.boot.context.properties.ConfigurationProperties; | |||
| import org.springframework.boot.jdbc.DataSourceBuilder; | |||
| import org.springframework.context.annotation.Bean; | |||
| import org.springframework.context.annotation.ComponentScan; | |||
| import org.springframework.context.annotation.Configuration; | |||
| import org.springframework.scheduling.annotation.EnableAsync; | |||
| import org.springframework.scheduling.annotation.EnableScheduling; | |||
| import com.ffii.core.support.JdbcDao; | |||
| /** @author Terence */ | |||
| @Configuration | |||
| // @EnableJpaRepositories("com.ffii.lioner.*") | |||
| // @ComponentScan(basePackages = { "com.ffii.core.*" }) | |||
| @ComponentScan(basePackages = { "com.ffii.core.*","com.ffii.lioner.*"}) | |||
| // @EntityScan("com.ffii.lioner.*") | |||
| @EnableScheduling | |||
| @EnableAsync | |||
| public class AppConfig { | |||
| @Bean | |||
| @ConfigurationProperties(prefix = "spring.datasource") | |||
| public DataSource dataSource() { | |||
| return DataSourceBuilder.create().build(); | |||
| } | |||
| @Bean | |||
| public JdbcDao jdbcDao(DataSource dataSource) { | |||
| return new JdbcDao(dataSource); | |||
| } | |||
| } | |||
| @@ -0,0 +1,15 @@ | |||
| package com.ffii.lioner.config; | |||
| import org.springframework.context.annotation.Bean; | |||
| import org.springframework.context.annotation.Configuration; | |||
| import org.springframework.web.client.RestTemplate; | |||
| @Configuration | |||
| public class RestTemplateConfig { | |||
| @Bean | |||
| public RestTemplate restTemplate() { | |||
| return new RestTemplate(); | |||
| } | |||
| } | |||
| @@ -0,0 +1,33 @@ | |||
| package com.ffii.lioner.config; | |||
| import org.springframework.beans.factory.annotation.Value; | |||
| import org.springframework.context.annotation.Bean; | |||
| import org.springframework.context.annotation.Configuration; | |||
| import org.springframework.web.servlet.config.annotation.CorsRegistry; | |||
| import org.springframework.web.servlet.config.annotation.EnableWebMvc; | |||
| import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; | |||
| import org.springframework.web.servlet.view.InternalResourceViewResolver; | |||
| @Configuration | |||
| @EnableWebMvc | |||
| public class WebConfig implements WebMvcConfigurer { | |||
| @Value("${host.url}") | |||
| private String url; | |||
| @Override | |||
| public void addCorsMappings(CorsRegistry registry) { | |||
| registry.addMapping("/**") | |||
| .allowedHeaders("*") | |||
| .allowedOrigins(url) | |||
| .exposedHeaders("filename") | |||
| .allowedMethods("GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"); | |||
| } | |||
| @Bean | |||
| public InternalResourceViewResolver defaultViewResolver() { | |||
| return new InternalResourceViewResolver(); | |||
| } | |||
| } | |||
| @@ -0,0 +1,87 @@ | |||
| package com.ffii.lioner.config.security; | |||
| import org.springframework.beans.factory.annotation.Autowired; | |||
| import org.springframework.beans.factory.annotation.Qualifier; | |||
| import org.springframework.context.annotation.Bean; | |||
| import org.springframework.context.annotation.Configuration; | |||
| import org.springframework.context.annotation.Lazy; | |||
| import org.springframework.core.annotation.Order; | |||
| import org.springframework.http.HttpStatus; | |||
| import org.springframework.ldap.core.LdapClient; | |||
| import org.springframework.ldap.core.LdapTemplate; | |||
| import org.springframework.ldap.core.support.BaseLdapPathContextSource; | |||
| import org.springframework.security.authentication.AuthenticationManager; | |||
| import org.springframework.security.config.Customizer; | |||
| import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; | |||
| import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; | |||
| import org.springframework.security.config.annotation.web.builders.HttpSecurity; | |||
| import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; | |||
| import org.springframework.security.config.http.SessionCreationPolicy; | |||
| import org.springframework.security.config.ldap.LdapBindAuthenticationManagerFactory; | |||
| import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; | |||
| import org.springframework.security.crypto.password.PasswordEncoder; | |||
| import org.springframework.security.web.SecurityFilterChain; | |||
| import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; | |||
| import com.ffii.lioner.config.security.jwt.JwtRequestFilter; | |||
| @Configuration | |||
| @EnableWebSecurity | |||
| @EnableMethodSecurity | |||
| public class SecurityConfig { | |||
| public static final String INDEX_URL = "/"; | |||
| public static final String LOGIN_URL = "/login"; | |||
| public static final String REFRESH_TOKEN_URL = "/refresh-token"; | |||
| public static final String[] URL_WHITELIST = { | |||
| INDEX_URL, | |||
| LOGIN_URL, | |||
| REFRESH_TOKEN_URL | |||
| }; | |||
| @Lazy | |||
| @Autowired | |||
| private JwtRequestFilter jwtRequestFilter; | |||
| @Bean | |||
| @Qualifier("AuthenticationManager") | |||
| public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) | |||
| throws Exception { | |||
| return authenticationConfiguration.getAuthenticationManager(); | |||
| } | |||
| @Bean | |||
| @Qualifier("LdapAuthenticationManager") | |||
| public AuthenticationManager ldapAuthenticationManager(BaseLdapPathContextSource contextSource) { | |||
| LdapBindAuthenticationManagerFactory factory = new LdapBindAuthenticationManagerFactory(contextSource); | |||
| factory.setUserSearchFilter("cn={0}"); | |||
| return factory.createAuthenticationManager(); | |||
| } | |||
| @Bean | |||
| public LdapTemplate ldapTemplate(BaseLdapPathContextSource contextSource) { | |||
| return new LdapTemplate(contextSource); | |||
| } | |||
| @Bean | |||
| public PasswordEncoder passwordEncoder() { | |||
| return new BCryptPasswordEncoder(); | |||
| } | |||
| @Bean | |||
| @Order(1) | |||
| public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { | |||
| return http | |||
| .cors(Customizer.withDefaults()).csrf(csrf -> csrf.disable()) | |||
| .requestCache(requestCache -> requestCache.disable()) | |||
| .authorizeHttpRequests( | |||
| authRequest -> authRequest.requestMatchers(URL_WHITELIST).permitAll().anyRequest().authenticated()) | |||
| .httpBasic(httpBasic -> httpBasic.authenticationEntryPoint( | |||
| (request, response, authException) -> response.sendError(HttpStatus.UNAUTHORIZED.value()))) | |||
| .sessionManagement( | |||
| sessionManagement -> sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) | |||
| .addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class) | |||
| .build(); | |||
| } | |||
| } | |||
| @@ -0,0 +1,78 @@ | |||
| package com.ffii.lioner.config.security.jwt; | |||
| import java.io.IOException; | |||
| import org.springframework.beans.factory.annotation.Autowired; | |||
| import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; | |||
| import org.springframework.security.core.context.SecurityContextHolder; | |||
| import org.springframework.security.core.userdetails.UserDetails; | |||
| import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; | |||
| import org.springframework.stereotype.Component; | |||
| import org.springframework.web.filter.OncePerRequestFilter; | |||
| import com.ffii.lioner.config.security.jwt.service.JwtUserDetailsService; | |||
| import com.ffii.core.utils.JwtTokenUtil; | |||
| import io.jsonwebtoken.ExpiredJwtException; | |||
| import io.jsonwebtoken.security.SignatureException; | |||
| import jakarta.servlet.FilterChain; | |||
| import jakarta.servlet.ServletException; | |||
| import jakarta.servlet.http.HttpServletRequest; | |||
| import jakarta.servlet.http.HttpServletResponse; | |||
| @Component | |||
| public class JwtRequestFilter extends OncePerRequestFilter { | |||
| @Autowired | |||
| private JwtUserDetailsService jwtUserDetailsService; | |||
| @Autowired | |||
| private JwtTokenUtil jwtTokenUtil; | |||
| @Override | |||
| protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) | |||
| throws ServletException, IOException { | |||
| final String requestTokenHeader = request.getHeader("Authorization"); | |||
| String username = null; | |||
| String jwtToken = null; | |||
| // JWT Token is in the form "Bearer token". Remove Bearer word and get | |||
| // only the Token | |||
| if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) { | |||
| jwtToken = requestTokenHeader.substring(7).replaceAll("\"", ""); | |||
| try { | |||
| username = jwtTokenUtil.getUsernameFromToken(jwtToken); | |||
| } catch (IllegalArgumentException e) { | |||
| logger.error("Unable to get JWT Token"); | |||
| } catch (ExpiredJwtException e) { | |||
| logger.error("JWT Token has expired"); | |||
| } catch (SignatureException e) { | |||
| logger.error("JWT signature does not match locally computed signature. JWT validity cannot be asserted and should not be trust."); | |||
| } | |||
| } else { | |||
| logger.warn("JWT Token does not begin with Bearer String"); | |||
| } | |||
| // Once we get the token validate it. | |||
| if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { | |||
| UserDetails userDetails = jwtUserDetailsService.loadUserByUsername(username); | |||
| // if token is valid configure Spring Security to manually set | |||
| // authentication | |||
| if (jwtTokenUtil.validateToken(jwtToken, userDetails)) { | |||
| UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken( | |||
| userDetails, null, userDetails.getAuthorities()); | |||
| usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); | |||
| // After setting the Authentication in the context, we specify | |||
| // that the current user is authenticated. So it passes the | |||
| // Spring Security Configurations successfully. | |||
| SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken); | |||
| } | |||
| } | |||
| chain.doFilter(request, response); | |||
| } | |||
| } | |||
| @@ -0,0 +1,31 @@ | |||
| package com.ffii.lioner.config.security.jwt.service; | |||
| import org.springframework.beans.factory.annotation.Autowired; | |||
| import org.springframework.security.core.userdetails.UserDetailsService; | |||
| import org.springframework.security.core.userdetails.UsernameNotFoundException; | |||
| import org.springframework.stereotype.Service; | |||
| import com.ffii.lioner.modules.user.entity.User; | |||
| import com.ffii.lioner.modules.user.entity.UserRepository; | |||
| import com.ffii.lioner.modules.user.service.UserAuthorityService; | |||
| import com.ffii.lioner.modules.user.service.UserService; | |||
| @Service | |||
| public class JwtUserDetailsService implements UserDetailsService { | |||
| @Autowired | |||
| UserRepository userRepository; | |||
| @Autowired | |||
| UserAuthorityService userAuthService; | |||
| @Autowired | |||
| UserService userService; | |||
| @Override | |||
| public User loadUserByUsername(String username) throws UsernameNotFoundException { | |||
| return userService.loadUserOptByUsername(username).orElseThrow(() -> new UsernameNotFoundException(username)); | |||
| } | |||
| } | |||
| @@ -0,0 +1,226 @@ | |||
| package com.ffii.lioner.config.security.jwt.web; | |||
| import java.time.Instant; | |||
| import java.time.LocalDateTime; | |||
| import java.time.temporal.ChronoUnit; | |||
| import java.util.ArrayList; | |||
| import java.util.List; | |||
| import java.util.Map; | |||
| import java.util.stream.Collectors; | |||
| import org.apache.commons.lang3.exception.ExceptionUtils; | |||
| import org.apache.commons.logging.Log; | |||
| import org.apache.commons.logging.LogFactory; | |||
| import org.springframework.beans.factory.annotation.Autowired; | |||
| import org.springframework.beans.factory.annotation.Qualifier; | |||
| import org.springframework.http.HttpStatus; | |||
| import org.springframework.http.ResponseEntity; | |||
| import org.springframework.security.authentication.AuthenticationManager; | |||
| import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; | |||
| import org.springframework.security.core.userdetails.UserDetails; | |||
| import org.springframework.security.core.userdetails.UsernameNotFoundException; | |||
| import org.springframework.web.bind.annotation.CrossOrigin; | |||
| import org.springframework.web.bind.annotation.PostMapping; | |||
| import org.springframework.web.bind.annotation.RequestBody; | |||
| import org.springframework.web.bind.annotation.RestController; | |||
| import org.springframework.web.server.ResponseStatusException; | |||
| import com.ffii.lioner.config.security.jwt.service.JwtUserDetailsService; | |||
| import com.ffii.lioner.config.security.service.LoginLogService; | |||
| import com.ffii.lioner.model.ExceptionResponse; | |||
| import com.ffii.lioner.model.JwtRequest; | |||
| import com.ffii.lioner.model.JwtResponse; | |||
| import com.ffii.lioner.model.RefreshToken; | |||
| import com.ffii.lioner.model.TokenRefreshRequest; | |||
| import com.ffii.lioner.model.TokenRefreshResponse; | |||
| import com.ffii.lioner.modules.common.SettingNames; | |||
| import com.ffii.lioner.modules.settings.service.SettingsService; | |||
| import com.ffii.lioner.modules.user.entity.User; | |||
| import com.ffii.lioner.modules.user.entity.UserRepository; | |||
| import com.ffii.lioner.modules.user.service.UserAuthorityService; | |||
| import com.ffii.core.response.AuthRes; | |||
| import com.ffii.core.utils.AES; | |||
| import com.ffii.core.utils.JwtTokenUtil; | |||
| import jakarta.servlet.http.HttpServletRequest; | |||
| import jakarta.validation.Valid; | |||
| @RestController | |||
| public class JwtAuthenticationController { | |||
| @Autowired | |||
| @Qualifier("AuthenticationManager") | |||
| private AuthenticationManager authenticationManager; | |||
| @Autowired | |||
| @Qualifier("LdapAuthenticationManager") | |||
| private AuthenticationManager ldapAuthenticationManager; | |||
| @Autowired | |||
| private JwtTokenUtil jwtTokenUtil; | |||
| @Autowired | |||
| private JwtUserDetailsService userDetailsService; | |||
| @Autowired | |||
| private UserRepository userRepository; | |||
| @Autowired | |||
| private UserAuthorityService userAuthorityService; | |||
| @Autowired | |||
| private LoginLogService loginLogService; | |||
| @Autowired | |||
| private SettingsService settingsService; | |||
| private final Log logger = LogFactory.getLog(getClass()); | |||
| private static final long EXPIRY_IN_MINTUE = 60000; | |||
| private static final long TOKEN_DURATION = 5; | |||
| @PostMapping("/login") | |||
| public ResponseEntity<?> login(@RequestBody JwtRequest authenticationRequest, HttpServletRequest request) | |||
| throws Exception { | |||
| String username = authenticationRequest.getUsername(); | |||
| HttpStatus httpStatus = HttpStatus.UNAUTHORIZED; | |||
| String message = "Invalid username and password."; | |||
| Integer maxAttempt = settingsService.getInt(SettingNames.SYS_LOGIN_ATTEMPT_LIMIT); | |||
| Integer penalityTime = settingsService.getInt(SettingNames.SYS_LOGIN_ATTEMPT_PENALITY_TIME); | |||
| String checkAddrr = request.getHeader("X-Forwarded-For"); | |||
| if (checkAddrr == null || "".equals(checkAddrr)) { | |||
| checkAddrr = request.getRemoteAddr(); | |||
| } | |||
| List<Map<String, Object>> failAttempts = loginLogService.failChecking(username, checkAddrr, maxAttempt); | |||
| if (failAttempts.size() >= maxAttempt) { | |||
| // check if last failAttempts not contain sucess login then trigger penality | |||
| // time | |||
| if (!failAttempts.stream().map(x -> { | |||
| return x.get("success"); | |||
| }).collect(Collectors.toList()).contains(true)) { | |||
| LocalDateTime lastAttemptTime = (LocalDateTime) failAttempts.get(0).get("loginTime"); | |||
| if (LocalDateTime.now().isBefore(lastAttemptTime.plusMinutes(penalityTime))) { | |||
| return ResponseEntity.status(httpStatus) | |||
| .body(new ExceptionResponse( | |||
| "Login failure reach the limit.\n Please try again after " | |||
| // add 1 to round up | |||
| + (ChronoUnit.MINUTES.between(LocalDateTime.now(), lastAttemptTime.plusMinutes(penalityTime)) + 1) | |||
| + " minutes.", | |||
| null)); | |||
| } | |||
| } | |||
| } | |||
| AuthRes authRes = ldapAuthenticate(authenticationRequest.getUsername(), authenticationRequest.getPassword()); | |||
| if (!authRes.isSuccess()) { | |||
| authRes = authenticate(authenticationRequest.getUsername(), authenticationRequest.getPassword()); | |||
| } | |||
| UserDetails userDetails = null; | |||
| if (authRes.isSuccess()) { | |||
| try { | |||
| userDetails = userDetailsService.loadUserByUsername(authenticationRequest.getUsername()); | |||
| } catch (UsernameNotFoundException e) { | |||
| message = username + " not yet register in the system."; | |||
| authRes.setSuccess(false); | |||
| authRes.setException(ExceptionUtils.getStackTrace(e)); | |||
| } catch (Exception e) { | |||
| httpStatus = HttpStatus.INTERNAL_SERVER_ERROR; | |||
| message = "Internal Server Error"; | |||
| authRes.setSuccess(false); | |||
| authRes.setException(ExceptionUtils.getStackTrace(e)); | |||
| } | |||
| } | |||
| String remoteAddrr = request.getHeader("X-Forwarded-For"); | |||
| if (remoteAddrr == null || "".equals(remoteAddrr)) { | |||
| remoteAddrr = request.getRemoteAddr(); | |||
| } | |||
| loginLogService.createLoginLog(username, remoteAddrr, authRes.isSuccess()); | |||
| if (userDetails == null) { | |||
| return ResponseEntity.status(httpStatus) | |||
| .body(new ExceptionResponse(message, authRes.getException())); | |||
| } else { | |||
| User user = userRepository.findByName(userDetails.getUsername()).get(0); | |||
| if (user.isLocked()) { | |||
| return ResponseEntity.status(httpStatus) | |||
| .body(new ExceptionResponse(username + " has been locked.", null)); | |||
| } | |||
| } | |||
| return createAuthTokenResponse(userDetails); | |||
| } | |||
| private AuthRes authenticate(String username, String password) throws Exception { | |||
| boolean success = false; | |||
| String exception = ""; | |||
| try { | |||
| authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password)); | |||
| success = true; | |||
| } catch (Exception e) { | |||
| exception = ExceptionUtils.getStackTrace(e); | |||
| } | |||
| return new AuthRes(success, exception); | |||
| } | |||
| private AuthRes ldapAuthenticate(String username, String password) throws Exception { | |||
| boolean success = false; | |||
| String exception = ""; | |||
| try { | |||
| ldapAuthenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password)); | |||
| success = true; | |||
| } catch (Exception e) { | |||
| logger.info(ExceptionUtils.getStackTrace(e)); | |||
| exception = ExceptionUtils.getStackTrace(e); | |||
| } | |||
| return new AuthRes(success, exception); | |||
| } | |||
| private ResponseEntity<?> createAuthTokenResponse(UserDetails userDetails) { | |||
| long accessTokenExpiry = /* settingsService.getInt(SettingNames.SYS_IDLE_LOGOUT_TIME) */ TOKEN_DURATION | |||
| * EXPIRY_IN_MINTUE; | |||
| final String accessToken = jwtTokenUtil.generateToken(userDetails, accessTokenExpiry); | |||
| final String refreshToken = jwtTokenUtil.createRefreshToken(userDetails.getUsername()).getToken(); | |||
| User user = userRepository.findByUsernameAndDeletedFalse(userDetails.getUsername()).get(); | |||
| // Set<AbilityModel> abilities = new HashSet<>(); | |||
| // userAuthorityService.getUserAuthority(user).forEach(auth -> abilities.add(new | |||
| // AbilityModel(auth.getAuthority()))); | |||
| List<String> abilities = new ArrayList<String>(); | |||
| userAuthorityService.getUserAuthority(user).forEach(auth -> abilities.add(auth.get("authority").toString())); | |||
| return ResponseEntity.ok(new JwtResponse(accessToken, refreshToken, null, user, abilities)); | |||
| } | |||
| @PostMapping("/refresh-token") | |||
| public ResponseEntity<TokenRefreshResponse> refreshtoken(@Valid @RequestBody TokenRefreshRequest request) | |||
| throws Exception { | |||
| long accessTokenExpiry = /* settingsService.getInt(SettingNames.SYS_IDLE_LOGOUT_TIME) */ TOKEN_DURATION | |||
| * EXPIRY_IN_MINTUE; | |||
| String requestRefreshToken = request.getRefreshToken(); | |||
| requestRefreshToken = requestRefreshToken.replaceAll("\"", ""); | |||
| String[] decryptStringList = AES.decrypt(requestRefreshToken, JwtTokenUtil.AES_SECRET) | |||
| .split(JwtTokenUtil.TOKEN_SEPARATOR); | |||
| RefreshToken instance = new RefreshToken(); | |||
| String username = decryptStringList[0]; | |||
| instance.setExpiryDate(Instant.ofEpochMilli(Long.valueOf(decryptStringList[1]))); | |||
| instance.setToken(requestRefreshToken); | |||
| instance.setUserName(decryptStringList[0]); | |||
| if (!jwtTokenUtil.verifyExpiration(instance)) { | |||
| throw new ResponseStatusException(HttpStatus.EXPECTATION_FAILED, | |||
| "Refresh token was expired. Please make a new signin request"); | |||
| } | |||
| final UserDetails userDetails = userDetailsService.loadUserByUsername(username); | |||
| String accessToken = jwtTokenUtil.generateToken(userDetails, accessTokenExpiry); | |||
| String refreshToken = jwtTokenUtil.createRefreshToken(username).getToken(); | |||
| return ResponseEntity.ok(new TokenRefreshResponse(accessToken, refreshToken)); | |||
| } | |||
| } | |||
| @@ -0,0 +1,56 @@ | |||
| package com.ffii.lioner.config.security.service; | |||
| import java.util.Date; | |||
| import java.util.HashMap; | |||
| import java.util.List; | |||
| import java.util.Map; | |||
| import org.springframework.stereotype.Service; | |||
| import org.springframework.transaction.annotation.Isolation; | |||
| import org.springframework.transaction.annotation.Transactional; | |||
| import com.ffii.core.support.AbstractService; | |||
| import com.ffii.core.support.JdbcDao; | |||
| import com.ffii.core.utils.MapUtils; | |||
| @Service | |||
| public class LoginLogService extends AbstractService { | |||
| public LoginLogService(JdbcDao jdbcDao) { | |||
| super(jdbcDao); | |||
| } | |||
| @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = false) | |||
| public boolean createLoginLog(String username, String remoteAddr, boolean success) { | |||
| String sql = "INSERT INTO user_login_log (`username`, `loginTime`, `ipAddr`, `success`) " | |||
| +"VALUES (:username, :loginTime, :ipAddr, :success)"; | |||
| Map<String, Object> args = new HashMap<>(4); | |||
| args.put("username", username); | |||
| args.put("loginTime", new Date()); | |||
| args.put("ipAddr", remoteAddr); | |||
| args.put("success", success); | |||
| return (jdbcDao.executeUpdate(sql, args) == 1); | |||
| } | |||
| @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true) | |||
| public List<Map<String, Object>> listLastLog(String username, int limit) { | |||
| return jdbcDao.queryForList("SELECT success FROM user_login_log where username = :username ORDER BY loginTime DESC LIMIT " + limit, | |||
| MapUtils.toHashMap("username", username)); | |||
| } | |||
| public List<Map<String,Object>> failChecking(String username,String remoteAddrr, Integer maxAttempt){ | |||
| StringBuilder sql = new StringBuilder("SELECT" | |||
| + " * " | |||
| + " FROM user_login_log ull " | |||
| //+ " WHERE ull.username = :username " | |||
| + " WHERE ull.ipAddr = :remoteAddrr " | |||
| //+ " AND ull.loginTime >= NOW() - INTERVAL 5 MINUTE " | |||
| + " ORDER BY ull.loginTime DESC " | |||
| + " LIMIT :maxAttempt " | |||
| ); | |||
| return jdbcDao.queryForList(sql.toString(), Map.of("username", username, "remoteAddrr", remoteAddrr, "maxAttempt", maxAttempt)); | |||
| } | |||
| } | |||
| @@ -0,0 +1,13 @@ | |||
| package com.ffii.lioner.model; | |||
| public class AbilityModel { | |||
| private final String actionSubjectCombo; | |||
| public AbilityModel(String actionSubjectCombo) { | |||
| this.actionSubjectCombo = actionSubjectCombo; | |||
| } | |||
| public String getActionSubjectCombo() { | |||
| return actionSubjectCombo; | |||
| } | |||
| } | |||
| @@ -0,0 +1,28 @@ | |||
| package com.ffii.lioner.model; | |||
| public class ExceptionResponse { | |||
| private String message; | |||
| private String exception; | |||
| public ExceptionResponse(String message, String exception) { | |||
| this.message = message; | |||
| this.exception = exception; | |||
| } | |||
| public String getMessage() { | |||
| return message; | |||
| } | |||
| public void setMessage(String message) { | |||
| this.message = message; | |||
| } | |||
| public String getException() { | |||
| return exception; | |||
| } | |||
| public void setException(String exception) { | |||
| this.exception = exception; | |||
| } | |||
| } | |||
| @@ -0,0 +1,45 @@ | |||
| package com.ffii.lioner.model; | |||
| import java.io.Serializable; | |||
| import jakarta.validation.constraints.NotNull; | |||
| public class JwtRequest implements Serializable { | |||
| private static final long serialVersionUID = 5926468583005150707L; | |||
| @NotNull | |||
| private String username; | |||
| @NotNull | |||
| private String password; | |||
| //need default constructor for JSON Parsing | |||
| public JwtRequest() | |||
| { | |||
| } | |||
| public JwtRequest(String username, String password) { | |||
| this.setUsername(username); | |||
| this.setPassword(password); | |||
| } | |||
| public String getUsername() { | |||
| return this.username; | |||
| } | |||
| public void setUsername(String username) { | |||
| this.username = username; | |||
| } | |||
| public String getPassword() { | |||
| return this.password; | |||
| } | |||
| public void setPassword(String password) { | |||
| this.password = password; | |||
| } | |||
| } | |||
| @@ -0,0 +1,75 @@ | |||
| package com.ffii.lioner.model; | |||
| import java.io.Serializable; | |||
| import java.util.List; | |||
| import java.util.Set; | |||
| import com.ffii.lioner.modules.user.entity.User; | |||
| public class JwtResponse implements Serializable { | |||
| private static final long serialVersionUID = -8091879091924046844L; | |||
| private final Long id; | |||
| private final String name; | |||
| private final String email; | |||
| private final String accessToken; | |||
| private final String refreshToken; | |||
| private final String role; | |||
| //private final Set<AbilityModel> abilities; | |||
| private final Long subDivisionId; | |||
| private final Boolean lotusNotesUser; | |||
| private final List<String> abilities; | |||
| public JwtResponse(String accessToken, String refreshToken, String role, User user, /*Set<AbilityModel>*/List<String> abilities) { | |||
| this.accessToken = accessToken; | |||
| this.refreshToken = refreshToken; | |||
| this.role = role; | |||
| this.id = user.getId(); | |||
| this.name = user.getName(); | |||
| this.email = user.getEmail(); | |||
| this.subDivisionId = user.getSubDivisionId(); | |||
| this.lotusNotesUser = user.getLotusNotesUser(); | |||
| this.abilities = abilities; | |||
| } | |||
| public String getAccessToken() { | |||
| return this.accessToken; | |||
| } | |||
| public String getRole() { | |||
| return role; | |||
| } | |||
| public String getRefreshToken() { | |||
| return refreshToken; | |||
| } | |||
| public Long getId() { | |||
| return id; | |||
| } | |||
| public String getName() { | |||
| return name; | |||
| } | |||
| public String getEmail() { | |||
| return email; | |||
| } | |||
| public List<String>/*Set<AbilityModel>*/ getAbilities() { | |||
| return abilities; | |||
| } | |||
| public Long getSubDivisionId() { | |||
| return this.subDivisionId; | |||
| } | |||
| public Boolean isLotusNotesUser() { | |||
| return this.lotusNotesUser; | |||
| } | |||
| public Boolean getLotusNotesUser() { | |||
| return this.lotusNotesUser; | |||
| } | |||
| } | |||
| @@ -0,0 +1,39 @@ | |||
| package com.ffii.lioner.model; | |||
| import java.time.Instant; | |||
| public class RefreshToken { | |||
| private String userName; | |||
| private String token; | |||
| private Instant expiryDate; | |||
| public String getUserName() { | |||
| return userName; | |||
| } | |||
| public void setUserName(String userName) { | |||
| this.userName = userName; | |||
| } | |||
| public String getToken() { | |||
| return token; | |||
| } | |||
| public void setToken(String token) { | |||
| this.token = token; | |||
| } | |||
| public Instant getExpiryDate() { | |||
| return expiryDate; | |||
| } | |||
| public void setExpiryDate(Instant expiryDate) { | |||
| this.expiryDate = expiryDate; | |||
| } | |||
| } | |||
| @@ -0,0 +1,16 @@ | |||
| package com.ffii.lioner.model; | |||
| import jakarta.validation.constraints.NotBlank; | |||
| public class TokenRefreshRequest { | |||
| @NotBlank | |||
| private String refreshToken; | |||
| public String getRefreshToken() { | |||
| return refreshToken; | |||
| } | |||
| public void setRefreshToken(String refreshToken) { | |||
| this.refreshToken = refreshToken; | |||
| } | |||
| } | |||
| @@ -0,0 +1,37 @@ | |||
| package com.ffii.lioner.model; | |||
| public class TokenRefreshResponse { | |||
| private String accessToken; | |||
| private String refreshToken; | |||
| private String tokenType = "Bearer"; | |||
| public TokenRefreshResponse(String accessToken, String refreshToken) { | |||
| this.accessToken = accessToken; | |||
| this.refreshToken = refreshToken; | |||
| } | |||
| public String getAccessToken() { | |||
| return accessToken; | |||
| } | |||
| public void setAccessToken(String accessToken) { | |||
| this.accessToken = accessToken; | |||
| } | |||
| public String getRefreshToken() { | |||
| return refreshToken; | |||
| } | |||
| public void setRefreshToken(String refreshToken) { | |||
| this.refreshToken = refreshToken; | |||
| } | |||
| public String getTokenType() { | |||
| return tokenType; | |||
| } | |||
| public void setTokenType(String tokenType) { | |||
| this.tokenType = tokenType; | |||
| } | |||
| } | |||
| @@ -0,0 +1,17 @@ | |||
| package com.ffii.lioner.modules.common; | |||
| public class ErrorCodes { | |||
| public static final String FILE_UPLOAD_ERROR = "FILE_UPLOAD_ERROR"; | |||
| public static final String STOCK_IN_WRONG_POST = "STOCK_IN_WRONG_POST"; | |||
| public static final String USER_WRONG_NEW_PWD = "USER_WRONG_NEW_PWD"; | |||
| public static final String SEND_EMAIL_ERROR = "SEND_EMAIL_ERROR"; | |||
| public static final String USERNAME_NOT_AVAILABLE = "USERNAME_NOT_AVAILABLE"; | |||
| public static final String INIT_EXCEL_ERROR = "INIT_EXCEL_ERROR"; | |||
| public static final String CHANGE_MAIN_CUSTOMER_ERROR = "CHANGE_MAIN_CUSTOMER_ERROR"; | |||
| } | |||
| @@ -0,0 +1,23 @@ | |||
| package com.ffii.lioner.modules.common; | |||
| import com.google.gson.*; | |||
| import java.lang.reflect.Type; | |||
| import java.time.LocalDate; | |||
| import java.time.format.DateTimeFormatter; | |||
| public class LocalDateAdapter implements JsonSerializer<LocalDate>, JsonDeserializer<LocalDate> { | |||
| private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd"); | |||
| @Override | |||
| public JsonElement serialize(LocalDate date, Type type, JsonSerializationContext context) { | |||
| String dateString = date.format(DATE_FORMATTER); | |||
| return new JsonPrimitive(dateString); | |||
| } | |||
| @Override | |||
| public LocalDate deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException { | |||
| String dateString = json.getAsString(); | |||
| return LocalDate.parse(dateString, DATE_FORMATTER); | |||
| } | |||
| } | |||
| @@ -0,0 +1,69 @@ | |||
| package com.ffii.lioner.modules.common; | |||
| import org.apache.commons.lang3.StringUtils; | |||
| import com.ffii.lioner.modules.settings.service.SettingsService; | |||
| public class MailSMTP { | |||
| private String host; | |||
| private int port; | |||
| private String username; | |||
| private String password; | |||
| private boolean auth; | |||
| public MailSMTP(SettingsService settingsService) { | |||
| if (settingsService == null) | |||
| throw new IllegalArgumentException("settingsService"); | |||
| this.host = settingsService.getString(SettingNames.MAIL_SMTP_HOST); | |||
| this.port = settingsService.getInt(SettingNames.MAIL_SMTP_PORT); | |||
| this.username = settingsService.getString(SettingNames.MAIL_SMTP_USERNAME); | |||
| this.password = settingsService.getString(SettingNames.MAIL_SMTP_PASSWORD); | |||
| this.auth = settingsService.getBoolean(SettingNames.MAIL_SMTP_AUTH); | |||
| } | |||
| public String getHost() { | |||
| return host; | |||
| } | |||
| public int getPort() { | |||
| return port; | |||
| } | |||
| public String getUsername() { | |||
| return username; | |||
| } | |||
| public String getPassword() { | |||
| return password; | |||
| } | |||
| public boolean isAuth() { | |||
| return this.auth; | |||
| } | |||
| public boolean getAuth() { | |||
| return this.auth; | |||
| } | |||
| public void setAuth(boolean auth) { | |||
| this.auth = auth; | |||
| } | |||
| @Override | |||
| public boolean equals(Object obj) { | |||
| if (obj == null || !(obj instanceof MailSMTP)) | |||
| return false; | |||
| MailSMTP o = (MailSMTP) obj; | |||
| if (StringUtils.equals(this.getHost(), o.getHost()) && | |||
| this.getPort() == o.getPort() && | |||
| StringUtils.equals(this.getUsername(), o.getUsername()) && | |||
| StringUtils.equals(this.getPassword(), o.getPassword())) { | |||
| return true; | |||
| } | |||
| return false; | |||
| } | |||
| } | |||
| @@ -0,0 +1,119 @@ | |||
| package com.ffii.lioner.modules.common; | |||
| import com.fasterxml.jackson.annotation.JsonIgnore; | |||
| import com.ffii.lioner.modules.settings.service.SettingsService; | |||
| import com.ffii.core.utils.PasswordUtils.IPasswordRule; | |||
| public class PasswordRule implements IPasswordRule { | |||
| private Integer min; | |||
| private Boolean numberAndAlphabetic; | |||
| private Boolean specialCharacter; | |||
| private Boolean notContainUsername; | |||
| private Boolean notContainThreeConsecutiveCharacters; | |||
| public PasswordRule(SettingsService settingsService) { | |||
| if (settingsService == null){ | |||
| throw new IllegalArgumentException("settingsService"); | |||
| } | |||
| this.min = settingsService.getInt(SettingNames.SYS_PASSWORD_RULE_MIN); | |||
| this.numberAndAlphabetic = settingsService.getBoolean(SettingNames.SYS_PASSWORD_RULE_NUMBER_AND_ALPHABETIC); | |||
| this.specialCharacter = settingsService.getBoolean(SettingNames.SYS_PASSWORD_RULE_SPECIAL_CHAR); | |||
| this.notContainUsername = settingsService.getBoolean(SettingNames.SYS_PASSWORD_RULE_NOT_CONTAIN_USERNAME); | |||
| this.notContainThreeConsecutiveCharacters = settingsService.getBoolean(SettingNames.SYS_PASSWORD_RULE_NOT_CONTAIN_THREE_CONSECUTIVE_CHAR); | |||
| } | |||
| public Integer getMin() { | |||
| return this.min; | |||
| } | |||
| public void setMin(Integer min) { | |||
| this.min = min; | |||
| } | |||
| public Boolean isNumberAndAlphabetic() { | |||
| return this.numberAndAlphabetic; | |||
| } | |||
| public Boolean getNumberAndAlphabetic() { | |||
| return this.numberAndAlphabetic; | |||
| } | |||
| public void setNumberAndAlphabetic(Boolean numberAndAlphabetic) { | |||
| this.numberAndAlphabetic = numberAndAlphabetic; | |||
| } | |||
| public Boolean isSpecialCharacter() { | |||
| return this.specialCharacter; | |||
| } | |||
| public Boolean getSpecialCharacter() { | |||
| return this.specialCharacter; | |||
| } | |||
| public void setSpecialCharacter(Boolean specialCharacter) { | |||
| this.specialCharacter = specialCharacter; | |||
| } | |||
| public Boolean isNotContainUsername() { | |||
| return this.notContainUsername; | |||
| } | |||
| public Boolean getNotContainUsername() { | |||
| return this.notContainUsername; | |||
| } | |||
| public void setNotContainUsername(Boolean notContainUsername) { | |||
| this.notContainUsername = notContainUsername; | |||
| } | |||
| public Boolean isNotContainThreeConsecutiveCharacters() { | |||
| return this.notContainThreeConsecutiveCharacters; | |||
| } | |||
| public Boolean getNotContainThreeConsecutiveCharacters() { | |||
| return this.notContainThreeConsecutiveCharacters; | |||
| } | |||
| public void setNotContainThreeConsecutiveCharacters(Boolean notContainThreeConsecutiveCharacters) { | |||
| this.notContainThreeConsecutiveCharacters = notContainThreeConsecutiveCharacters; | |||
| } | |||
| @JsonIgnore | |||
| public String getWrongMsg() { | |||
| StringBuilder msg = new StringBuilder("Please Following Password Rule:\n"); | |||
| msg.append("- Minimum " + getMin() + " Characters.\n"); | |||
| if (needNumberAndAlphabetic()) | |||
| msg.append("- Need numbers and alphabetic characters.\n"); | |||
| if (needSpecialCharacter()) | |||
| msg.append("- Need at least one special characters.\n"); | |||
| if (needNotContainUsername()) | |||
| msg.append("- Cannot contain username.\n"); | |||
| if (needNotContainThreeConsecutiveCharacters()) | |||
| msg.append("- Cannot contain three consecutive characters.\n"); | |||
| return msg.toString(); | |||
| } | |||
| @Override | |||
| public boolean needNumberAndAlphabetic() { | |||
| return numberAndAlphabetic; | |||
| } | |||
| @Override | |||
| public boolean needSpecialCharacter() { | |||
| return specialCharacter; | |||
| } | |||
| @Override | |||
| public boolean needNotContainUsername() { | |||
| return notContainUsername; | |||
| } | |||
| @Override | |||
| public boolean needNotContainThreeConsecutiveCharacters() { | |||
| return notContainThreeConsecutiveCharacters; | |||
| } | |||
| } | |||
| @@ -0,0 +1,146 @@ | |||
| package com.ffii.lioner.modules.common; | |||
| import java.util.Optional; | |||
| import org.springframework.dao.DataAccessException; | |||
| import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; | |||
| import org.springframework.security.core.Authentication; | |||
| import org.springframework.security.core.GrantedAuthority; | |||
| import org.springframework.security.core.context.SecurityContext; | |||
| import org.springframework.security.core.context.SecurityContextHolder; | |||
| import org.springframework.security.core.userdetails.UserDetails; | |||
| import org.springframework.security.core.userdetails.UserDetailsService; | |||
| import org.springframework.security.core.userdetails.UsernameNotFoundException; | |||
| import com.ffii.lioner.modules.user.entity.User; | |||
| /** | |||
| * Security Utils - for Spring Security | |||
| * | |||
| * @author Patrick | |||
| */ | |||
| public class SecurityUtils { | |||
| /** | |||
| * Obtains the current {@code SecurityContext}. | |||
| * | |||
| * @return the security context (never {@code null}) | |||
| */ | |||
| public static final SecurityContext getSecurityContext() { | |||
| return SecurityContextHolder.getContext(); | |||
| } | |||
| /** | |||
| * @return the authenticated {@code Principal}) | |||
| * @see Authentication#getPrincipal() | |||
| */ | |||
| public static final Optional<User> getUser() { | |||
| try { | |||
| return Optional.of((User) getSecurityContext().getAuthentication().getPrincipal()); | |||
| } catch (ClassCastException e) { | |||
| // no authenticated principal | |||
| return Optional.empty(); | |||
| } catch (NullPointerException e) { | |||
| // no authentication information is available | |||
| return Optional.empty(); | |||
| } | |||
| } | |||
| /** | |||
| * Updates the Authentication Token with the user (e.g. user changed the password) | |||
| * | |||
| * @see SecurityContext#setAuthentication(Authentication) | |||
| */ | |||
| public static final void updateUserAuthentication(final UserDetails user) { | |||
| getSecurityContext().setAuthentication(new UsernamePasswordAuthenticationToken(user, user.getPassword(), user.getAuthorities())); | |||
| } | |||
| /** | |||
| * Checks if the current user is GRANTED the {@code role} | |||
| * | |||
| * @param role | |||
| * the {@code role} to check for | |||
| * @return {@code true} if the current user is GRANTED the {@code role}, else {@code false} | |||
| */ | |||
| public static final boolean isGranted(String role) { | |||
| Authentication authentication = getSecurityContext().getAuthentication(); | |||
| if (authentication == null) return false; | |||
| for (GrantedAuthority auth : authentication.getAuthorities()) { | |||
| if (role.equals(auth.getAuthority())) return true; | |||
| } | |||
| return false; | |||
| } | |||
| /** | |||
| * Checks if the current user is NOT GRANTED the {@code role} | |||
| * | |||
| * @param role | |||
| * the {@code role} to check for | |||
| * @return {@code true} if the current user is NOT GRANTED the {@code role}, else {@code false} | |||
| */ | |||
| public static final boolean isNotGranted(String role) { | |||
| return !isGranted(role); | |||
| } | |||
| /** | |||
| * Checks if the current user is GRANTED ANY of the {@code role}s | |||
| * | |||
| * @param roles | |||
| * the {@code role}s to check for | |||
| * @return {@code true} if the current user is GRANTED ANY of the {@code role}s, else {@code false} | |||
| */ | |||
| public static final boolean isGrantedAny(String... roles) { | |||
| for (int i = 0; i < roles.length; i++) { | |||
| if (isGranted(roles[i])) return true; | |||
| } | |||
| return false; | |||
| } | |||
| /** | |||
| * Checks if the current user is NOT GRANTED ANY of the {@code role}s | |||
| * | |||
| * @param roles | |||
| * the {@code role}s to check for | |||
| * @return {@code true} if the current user is NOT GRANTED ANY of the {@code role}s, else {@code false} | |||
| */ | |||
| public static final boolean isNotGrantedAny(String... roles) { | |||
| return !isGrantedAny(roles); | |||
| } | |||
| /** | |||
| * Checks if the current user is GRANTED ALL of the {@code role}s | |||
| * | |||
| * @param roles | |||
| * the {@code role}s to check for | |||
| * @return {@code true} if the current user is GRANTED ALL of the {@code role}s, else {@code false} | |||
| */ | |||
| public static final boolean isGrantedAll(String... roles) { | |||
| for (int i = 0; i < roles.length; i++) { | |||
| if (isNotGranted(roles[i])) return false; | |||
| } | |||
| return true; | |||
| } | |||
| /** | |||
| * Login a user non-interactively | |||
| * | |||
| * @param userService | |||
| * any implementation of {@link UserDetailsService} | |||
| * @param username | |||
| * the username | |||
| * | |||
| * @throws UsernameNotFoundException | |||
| * if the user could not be found or the user has no GrantedAuthority | |||
| * @throws DataAccessException | |||
| * if user could not be found for a repository-specific reason | |||
| */ | |||
| public static final void loginUser(UserDetailsService userService, String username) { | |||
| /* load the user, throw exception if user not found */ | |||
| UserDetails userDetails = userService.loadUserByUsername(username); | |||
| /* create authentication token for the specified user */ | |||
| Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails, userDetails.getPassword(), userDetails.getAuthorities()); | |||
| getSecurityContext().setAuthentication(authentication); | |||
| } | |||
| } | |||
| @@ -0,0 +1,53 @@ | |||
| package com.ffii.lioner.modules.common; | |||
| public abstract class SettingNames { | |||
| /* | |||
| * System-wide settings | |||
| */ | |||
| /** System Idle Logout Time*/ | |||
| public static final String SYS_IDLE_LOGOUT_TIME = "SYS.idleLogoutTime"; | |||
| /* | |||
| * Mail settings | |||
| */ | |||
| /** Mail - SMTP host */ | |||
| public static final String MAIL_SMTP_HOST = "MAIL.smtp.host"; | |||
| /** Mail - SMTP port */ | |||
| public static final String MAIL_SMTP_PORT = "MAIL.smtp.port"; | |||
| /** Mail - SMTP username */ | |||
| public static final String MAIL_SMTP_USERNAME = "MAIL.smtp.username"; | |||
| /** Mail - SMTP password */ | |||
| public static final String MAIL_SMTP_PASSWORD = "MAIL.smtp.password"; | |||
| /** Mail - SMTP auth */ | |||
| public static final String MAIL_SMTP_AUTH = "MAIL.smtp.auth"; | |||
| public static final String JS_VERSION = "JS.version"; | |||
| public static final String SYS_PASSWORD_RULE_MIN = "SYS.password.rule.length.min"; | |||
| public static final String SYS_PASSWORD_RULE_DURATION = "SYS.password.rule.duration"; | |||
| public static final String SYS_PASSWORD_RULE_HISTORY = "SYS.password.rule.history"; | |||
| public static final String SYS_LOGIN_ATTEMPT_LIMIT = "SYS.loginAttempt.limit"; | |||
| public static final String SYS_LOGIN_ATTEMPT_PENALITY_TIME = "SYS.loginAttempt.penalityTime"; | |||
| public static final String SYS_REMINDER_BEFORE = "SYS.reminder.before"; | |||
| public static final String SYS_REMINDER_INTERVAL = "SYS.reminder.interval"; | |||
| public static final String SYS_REMINDER_LIMIT = "SYS.reminder.limit"; | |||
| public static final String SYS_REMINDER_LIMIT_MAX = "SYS.reminder.limit.maxValue"; | |||
| public static final String SYS_REMINDER_WITHIN = "SYS.reminder.eventWithin"; | |||
| public static final String SYS_PASSWORD_RULE_NUMBER_AND_ALPHABETIC = "SYS.password.rule.numberAndAlphabetic"; | |||
| public static final String SYS_PASSWORD_RULE_SPECIAL_CHAR = "SYS.password.rule.specialCharacter"; | |||
| public static final String SYS_PASSWORD_RULE_NOT_CONTAIN_USERNAME = "SYS.password.rule.notContainUsername"; | |||
| public static final String SYS_PASSWORD_RULE_NOT_CONTAIN_THREE_CONSECUTIVE_CHAR = "SYS.password.rule.notContainThreeConsecutiveCharacters"; | |||
| public static final String SYS_EMAIL_TEMPLATE_ANNOUNCEMENT = "SYS.email.template.announcement"; | |||
| public static final String SYS_EMAIL_TEMPLATE_REMINDER_NEWEVENT = "SYS.email.template.application.reminder.newEvent"; | |||
| public static final String SYS_EMAIL_TEMPLATE_REMINDER_OLDEVENT = "SYS.email.template.application.reminder.oldEvent"; | |||
| } | |||
| @@ -0,0 +1,122 @@ | |||
| package com.ffii.lioner.modules.common; | |||
| public abstract class TempConst { | |||
| public static final String LOTUS_TEMP_CONST = "<RECORDS>\n" + | |||
| "<RECORD>\n" + | |||
| "<EMSDtitledescch>資訊科技經理/企業傳訊/1</EMSDtitledescch>\n" + | |||
| "<EMSDactdesc1>NULL</EMSDactdesc1>\n" + | |||
| "<EMSDarrayorgobjid>10001001->10002101->10008587->10009567->10009595->10009660</EMSDarrayorgobjid>\n" + | |||
| "<EMSDactpost1>NULL</EMSDactpost1>\n" + | |||
| "<EMSDdivision>CSD</EMSDdivision>\n" + | |||
| "<EMSDotherdeptemail>NULL</EMSDotherdeptemail>\n" + | |||
| "<EMSDupddate>16/6/2023 5:45:13</EMSDupddate>\n" + | |||
| "<EMSDactdesc2>NULL</EMSDactdesc2>\n" + | |||
| "<EMSDapmail>[email protected]</EMSDapmail>\n" + | |||
| "<EMSDdpac>yat_sing_leung</EMSDdpac>\n" + | |||
| "<EMSDarrayorgengna>Electrical and Mechanical Services Dept->Trading Services->Engineering Services Branch 3->Corporate Services Division->Corporate Communications Sub-division->Corporate Communications 2</EMSDarrayorgengna>\n" + | |||
| "<EMSDarrayorgchina>機電工程署->營運服務->工程服務科3->企業服務部->企業傳訊分部->企業傳訊2</EMSDarrayorgchina>\n" + | |||
| "<EMSDccresp>SE/CC</EMSDccresp>\n" + | |||
| "<EMSDotherdeptapmail>NULL</EMSDotherdeptapmail>\n" + | |||
| "<EMSDorgdesc>Customer Partnership 2</EMSDorgdesc>\n" + | |||
| "<EMSDactdesc3>NULL</EMSDactdesc3>\n" + | |||
| "<EMSDformaddr>Mr</EMSDformaddr>\n" + | |||
| "<EMSDactrank3>NULL</EMSDactrank3>\n" + | |||
| "<EMSDtitle>ITM/CC/1</EMSDtitle>\n" + | |||
| "<EMSDofftel>39120600 OR 63717182</EMSDofftel>\n" + | |||
| "<EMSDsgrpemploy>ITM</EMSDsgrpemploy>\n" + | |||
| "<EMSDactrank2>NULL</EMSDactrank2>\n" + | |||
| "<EMSDactpost3>NULL</EMSDactpost3>\n" + | |||
| "<EMSDgrpemploy>NCSC</EMSDgrpemploy>\n" + | |||
| "<EMSDorgunit>10009660</EMSDorgunit>\n" + | |||
| "<EMSDgender>MALE</EMSDgender>\n" + | |||
| "<EMSDnickname>NULL</EMSDnickname>\n" + | |||
| "<EMSDfirstname>Ho Sing</EMSDfirstname>\n" + | |||
| "<EMSDmangid>00011627</EMSDmangid>\n" + | |||
| "<EMSDemsdemail>[email protected]</EMSDemsdemail>\n" + | |||
| "<EMSDcc>89C0</EMSDcc>\n" + | |||
| "<EMSDposition>00018114</EMSDposition>\n" + | |||
| "<objectClass>EMSDPerson</objectClass>\n" + | |||
| "<EMSDactpid3>NULL</EMSDactpid3>\n" + | |||
| "<EMSDactpost2>NULL</EMSDactpost2>\n" + | |||
| "<cn>00019383</cn>\n" + | |||
| "<EMSDstream1>NULL</EMSDstream1>\n" + | |||
| "<EMSDstream2>NULL</EMSDstream2>\n" + | |||
| "<EMSDtitledescen>Information Technology Manager/Corporate Communications/1</EMSDtitledescen>\n" + | |||
| "<EMSDrankcode>E4</EMSDrankcode>\n" + | |||
| "<EMSDrankdesc>Information Technology Manager</EMSDrankdesc>\n" + | |||
| "<EMSDpersonno>00019383</EMSDpersonno>\n" + | |||
| "<EMSDknownas>梁日昇</EMSDknownas>\n" + | |||
| "<EMSDemploystatus>Active</EMSDemploystatus>\n" + | |||
| "<EMSDactldesc2>NULL</EMSDactldesc2>\n" + | |||
| "<EMSDactldesc3>NULL</EMSDactldesc3>\n" + | |||
| "<EMSDlotus>ITMCC1</EMSDlotus>\n" + | |||
| "<EMSDactldesc1>NULL</EMSDactldesc1>\n" + | |||
| "<EMSDgoamail>NULL</EMSDgoamail>\n" + | |||
| "<EMSDactpid1>NULL</EMSDactpid1>\n" + | |||
| "<EMSDactpid2>NULL</EMSDactpid2>\n" + | |||
| "<EMSDactrank1>NULL</EMSDactrank1>\n" + | |||
| "<EMSDlastname>LEUNG</EMSDlastname>\n" + | |||
| "</RECORD>\n" + | |||
| "<RECORD>\n" + | |||
| "<EMSDtitledescch>資訊科技經理/企業傳訊123/1</EMSDtitledescch>\n" + | |||
| "<EMSDactdesc1>NULL</EMSDactdesc1>\n" + | |||
| "<EMSDarrayorgobjid>10001001->10002101->10008587->10009567->10009595->10009660</EMSDarrayorgobjid>\n" + | |||
| "<EMSDactpost1>NULL</EMSDactpost1>\n" + | |||
| "<EMSDdivision>CSD</EMSDdivision>\n" + | |||
| "<EMSDotherdeptemail>NULL</EMSDotherdeptemail>\n" + | |||
| "<EMSDupddate>16/6/2023 5:45:13</EMSDupddate>\n" + | |||
| "<EMSDactdesc2>NULL</EMSDactdesc2>\n" + | |||
| "<EMSDapmail>[email protected]</EMSDapmail>\n" + | |||
| "<EMSDdpac>yat_sing_leung</EMSDdpac>\n" + | |||
| "<EMSDarrayorgengna>Electrical and Mechanical Services Dept->Trading Services->Engineering Services Branch 3->Corporate Services Division->Corporate Communications Sub-division->Corporate Communications 2</EMSDarrayorgengna>\n" + | |||
| "<EMSDarrayorgchina>機電工程署->營運服務->工程服務科3->企業服務部->企業傳訊分部->企業傳訊2</EMSDarrayorgchina>\n" + | |||
| "<EMSDccresp>SE/CC</EMSDccresp>\n" + | |||
| "<EMSDotherdeptapmail>NULL</EMSDotherdeptapmail>\n" + | |||
| "<EMSDorgdesc>Customer Partnership 2</EMSDorgdesc>\n" + | |||
| "<EMSDactdesc3>NULL</EMSDactdesc3>\n" + | |||
| "<EMSDformaddr>Mr</EMSDformaddr>\n" + | |||
| "<EMSDactrank3>NULL</EMSDactrank3>\n" + | |||
| "<EMSDtitle>ITM/CC/1</EMSDtitle>\n" + | |||
| "<EMSDofftel>39120600 OR 63717182</EMSDofftel>\n" + | |||
| "<EMSDsgrpemploy>ITM</EMSDsgrpemploy>\n" + | |||
| "<EMSDactrank2>NULL</EMSDactrank2>\n" + | |||
| "<EMSDactpost3>NULL</EMSDactpost3>\n" + | |||
| "<EMSDgrpemploy>NCSC</EMSDgrpemploy>\n" + | |||
| "<EMSDorgunit>10009660</EMSDorgunit>\n" + | |||
| "<EMSDgender>MALE</EMSDgender>\n" + | |||
| "<EMSDnickname>NULL</EMSDnickname>\n" + | |||
| "<EMSDfirstname>Yat Sing</EMSDfirstname>\n" + | |||
| "<EMSDmangid>00011627</EMSDmangid>\n" + | |||
| "<EMSDemsdemail>[email protected]</EMSDemsdemail>\n" + | |||
| "<EMSDcc>89C0</EMSDcc>\n" + | |||
| "<EMSDposition>00018114</EMSDposition>\n" + | |||
| "<objectClass>EMSDPerson</objectClass>\n" + | |||
| "<EMSDactpid3>NULL</EMSDactpid3>\n" + | |||
| "<EMSDactpost2>NULL</EMSDactpost2>\n" + | |||
| "<cn>00019383</cn>\n" + | |||
| "<EMSDstream1>NULL</EMSDstream1>\n" + | |||
| "<EMSDstream2>NULL</EMSDstream2>\n" + | |||
| "<EMSDtitledescen>Information Technology Manager/Corporate Communications/1</EMSDtitledescen>\n" + | |||
| "<EMSDrankcode>E4</EMSDrankcode>\n" + | |||
| "<EMSDrankdesc>Information Technology Manager</EMSDrankdesc>\n" + | |||
| "<EMSDpersonno>00019383</EMSDpersonno>\n" + | |||
| "<EMSDknownas>梁日昇2.0</EMSDknownas>\n" + | |||
| "<EMSDemploystatus>Active</EMSDemploystatus>\n" + | |||
| "<EMSDactldesc2>NULL</EMSDactldesc2>\n" + | |||
| "<EMSDactldesc3>NULL</EMSDactldesc3>\n" + | |||
| "<EMSDlotus>ITMCC1</EMSDlotus>\n" + | |||
| "<EMSDactldesc1>NULL</EMSDactldesc1>\n" + | |||
| "<EMSDgoamail>NULL</EMSDgoamail>\n" + | |||
| "<EMSDactpid1>NULL</EMSDactpid1>\n" + | |||
| "<EMSDactpid2>NULL</EMSDactpid2>\n" + | |||
| "<EMSDactrank1>NULL</EMSDactrank1>\n" + | |||
| "<EMSDlastname>LEUNG</EMSDlastname>\n" + | |||
| "</RECORD>\n" + | |||
| "</RECORDS>"; | |||
| public static final String LOTUS_TEMP_NONE_CONST = "<RECORDS>\n" + | |||
| "<RECORD>\n" + | |||
| "None" + | |||
| "</RECORD>\n" + | |||
| "</RECORDS>"; | |||
| } | |||
| @@ -0,0 +1,372 @@ | |||
| package com.ffii.lioner.modules.common.mail.pojo; | |||
| import java.util.ArrayList; | |||
| import java.util.HashMap; | |||
| import java.util.List; | |||
| import java.util.Map; | |||
| import jakarta.mail.internet.AddressException; | |||
| import jakarta.mail.internet.InternetAddress; | |||
| public class MailRequest { | |||
| public final static int PRIORITY_HIGHEST = 1; | |||
| public final static int PRIORITY_HIGH = 2; | |||
| public final static int PRIORITY_NORMAL = 3; | |||
| public final static int PRIORITY_LOW = 4; | |||
| public final static int PRIORITY_LOWEST = 5; | |||
| private static final String MAIL_SUFFIX = "@emsd.gov.hk"; | |||
| //private static final String MAIL_SUFFIX = "@2fi-solutions.com.hk"; | |||
| private InternetAddress from; | |||
| private List<InternetAddress> to; | |||
| private String subject; | |||
| private String template; | |||
| private String templateContent; | |||
| private Map<String, ?> args; | |||
| private Integer priority; | |||
| private InternetAddress replyTo; | |||
| private List<InternetAddress> cc; | |||
| private List<InternetAddress> bcc; | |||
| private Map<String, byte[]> attachments; | |||
| public MailRequest() { | |||
| } | |||
| public static Builder builder() { | |||
| return new Builder(); | |||
| } | |||
| public void addAttachment(String attachmentFilename, byte[] byteArray) { | |||
| if (this.attachments == null) this.attachments = new HashMap<>(); | |||
| this.attachments.put(attachmentFilename, byteArray); | |||
| } | |||
| public void addTo(InternetAddress to) { | |||
| if (this.to == null) this.to = new ArrayList<>(); | |||
| this.to.add(to); | |||
| } | |||
| public void addCc(InternetAddress cc) { | |||
| if (this.cc == null) this.cc = new ArrayList<>(); | |||
| this.cc.add(cc); | |||
| } | |||
| public void addBcc(InternetAddress bcc) { | |||
| if (this.bcc == null) this.bcc = new ArrayList<>(); | |||
| this.bcc.add(bcc); | |||
| } | |||
| // getter setter | |||
| public InternetAddress getFrom() { | |||
| return from; | |||
| } | |||
| public void setFrom(InternetAddress from) { | |||
| this.from = from; | |||
| } | |||
| public List<InternetAddress> getTo() { | |||
| return to; | |||
| } | |||
| public void setTo(List<InternetAddress> to) { | |||
| this.to = to; | |||
| } | |||
| public void setTo(String[] to) throws AddressException { | |||
| if (to == null) { | |||
| this.to = null; | |||
| } else { | |||
| for (String a : to) { | |||
| this.addTo(new InternetAddress(a)); | |||
| } | |||
| } | |||
| } | |||
| public void setStringListTo(List<String> to) throws AddressException { | |||
| if (to == null) { | |||
| this.to = null; | |||
| } else { | |||
| for (String a : to) { | |||
| this.addTo(new InternetAddress(a)); | |||
| } | |||
| } | |||
| } | |||
| public String getSubject() { | |||
| return subject; | |||
| } | |||
| public void setSubject(String subject) { | |||
| this.subject = subject; | |||
| } | |||
| public String getTemplate() { | |||
| return template; | |||
| } | |||
| public void setTemplate(String template) { | |||
| this.template = template; | |||
| } | |||
| public String getTemplateContent() { | |||
| return this.templateContent; | |||
| } | |||
| public void setTemplateContent(String templateContent) { | |||
| this.templateContent = templateContent; | |||
| } | |||
| public Map<String, ?> getArgs() { | |||
| return args; | |||
| } | |||
| public void setArgs(Map<String, ?> args) { | |||
| this.args = args; | |||
| } | |||
| public InternetAddress getReplyTo() { | |||
| return replyTo; | |||
| } | |||
| public void setReplyTo(InternetAddress replyTo) { | |||
| this.replyTo = replyTo; | |||
| } | |||
| public List<InternetAddress> getCc() { | |||
| return cc; | |||
| } | |||
| public void setCc(List<InternetAddress> cc) { | |||
| this.cc = cc; | |||
| } | |||
| public void setCc(String[] cc) throws AddressException { | |||
| if (cc == null) { | |||
| this.cc = null; | |||
| } else { | |||
| for (String a : cc) { | |||
| this.addCc(new InternetAddress(a)); | |||
| } | |||
| } | |||
| } | |||
| public void setStringListCc(List<String> cc) throws AddressException { | |||
| if (cc == null) { | |||
| this.cc = null; | |||
| } else { | |||
| for (String a : cc) { | |||
| this.addCc(new InternetAddress(a)); | |||
| } | |||
| } | |||
| } | |||
| public List<InternetAddress> getBcc() { | |||
| return bcc; | |||
| } | |||
| public void setBcc(List<InternetAddress> bcc) { | |||
| this.bcc = bcc; | |||
| } | |||
| public void setBcc(String[] bcc) throws AddressException { | |||
| if (bcc == null) { | |||
| this.bcc = null; | |||
| } else { | |||
| for (String a : bcc) { | |||
| this.addBcc(new InternetAddress(a)); | |||
| } | |||
| } | |||
| } | |||
| public void setStringListBcc(List<String> bcc) throws AddressException { | |||
| if (bcc == null) { | |||
| this.bcc = null; | |||
| } else { | |||
| for (String a : bcc) { | |||
| this.addBcc(new InternetAddress(a)); | |||
| } | |||
| } | |||
| } | |||
| public Map<String, byte[]> getAttachments() { | |||
| return attachments; | |||
| } | |||
| public void setAttachments(Map<String, byte[]> attachments) { | |||
| this.attachments = attachments; | |||
| } | |||
| public Integer getPriority() { | |||
| return priority; | |||
| } | |||
| public void setPriority(Integer priority) { | |||
| this.priority = priority; | |||
| } | |||
| // classes | |||
| public static class Builder { | |||
| private MailRequest mailRequest; | |||
| private Builder() { | |||
| this.mailRequest = new MailRequest(); | |||
| } | |||
| public MailRequest build() { | |||
| return this.mailRequest; | |||
| } | |||
| public Builder addAttachment(String attachmentFilename, byte[] byteArray) { | |||
| this.mailRequest.addAttachment(attachmentFilename, byteArray); | |||
| return this; | |||
| } | |||
| public Builder addTo(InternetAddress to) { | |||
| if (!to.getAddress().contains("@")) { | |||
| to.setAddress(to.getAddress() + MAIL_SUFFIX); | |||
| } | |||
| this.mailRequest.addTo(to); | |||
| return this; | |||
| } | |||
| public Builder addCc(InternetAddress cc) { | |||
| this.mailRequest.addCc(cc); | |||
| return this; | |||
| } | |||
| public Builder addBcc(InternetAddress bcc) { | |||
| this.mailRequest.addBcc(bcc); | |||
| return this; | |||
| } | |||
| public Builder from(InternetAddress from) { | |||
| this.mailRequest.setFrom(from); | |||
| return this; | |||
| } | |||
| public Builder to(List<InternetAddress> to) { | |||
| this.mailRequest.setTo(to); | |||
| return this; | |||
| } | |||
| public Builder to(String[] to) throws AddressException { | |||
| if (to == null) { | |||
| this.mailRequest.setTo((List<InternetAddress>) null); | |||
| } else { | |||
| for (String a : to) { | |||
| this.addTo(new InternetAddress(a)); | |||
| } | |||
| } | |||
| return this; | |||
| } | |||
| public Builder stringListTo(List<String> to) throws AddressException { | |||
| if (to == null) { | |||
| this.mailRequest.setTo((List<InternetAddress>) null); | |||
| } else { | |||
| for (String a : to) { | |||
| this.addTo(new InternetAddress(a)); | |||
| } | |||
| } | |||
| return this; | |||
| } | |||
| public Builder subject(String subject) { | |||
| this.mailRequest.setSubject(subject); | |||
| return this; | |||
| } | |||
| public Builder template(String template) { | |||
| this.mailRequest.setTemplate(template); | |||
| return this; | |||
| } | |||
| public Builder templateContent(String templateContent) { | |||
| this.mailRequest.setTemplateContent(templateContent); | |||
| return this; | |||
| } | |||
| public Builder args(Map<String, ?> args) { | |||
| this.mailRequest.setArgs(args); | |||
| return this; | |||
| } | |||
| public Builder replyTo(InternetAddress replyTo) { | |||
| this.mailRequest.setReplyTo(replyTo); | |||
| return this; | |||
| } | |||
| public Builder cc(List<InternetAddress> cc) { | |||
| this.mailRequest.setCc(cc); | |||
| return this; | |||
| } | |||
| public Builder cc(String[] cc) throws AddressException { | |||
| if (cc == null) { | |||
| this.mailRequest.setCc((List<InternetAddress>) null); | |||
| } else { | |||
| for (String a : cc) { | |||
| this.addCc(new InternetAddress(a)); | |||
| } | |||
| } | |||
| return this; | |||
| } | |||
| public Builder stringListCc(List<String> cc) throws AddressException { | |||
| if (cc == null) { | |||
| this.mailRequest.setCc((List<InternetAddress>) null); | |||
| } else { | |||
| for (String a : cc) { | |||
| this.addCc(new InternetAddress(a)); | |||
| } | |||
| } | |||
| return this; | |||
| } | |||
| public Builder bcc(List<InternetAddress> bcc) { | |||
| this.mailRequest.setBcc(bcc); | |||
| return this; | |||
| } | |||
| public Builder bcc(String[] bcc) throws AddressException { | |||
| if (bcc == null) { | |||
| this.mailRequest.setBcc((List<InternetAddress>) null); | |||
| } else { | |||
| for (String a : bcc) { | |||
| this.addCc(new InternetAddress(a)); | |||
| } | |||
| } | |||
| return this; | |||
| } | |||
| public Builder stringListBcc(List<String> bcc) throws AddressException { | |||
| if (bcc == null) { | |||
| this.mailRequest.setBcc((List<InternetAddress>) null); | |||
| } else { | |||
| for (String a : bcc) { | |||
| this.addCc(new InternetAddress(a)); | |||
| } | |||
| } | |||
| return this; | |||
| } | |||
| public Builder attachments(Map<String, byte[]> attachments) { | |||
| this.mailRequest.setAttachments(attachments); | |||
| return this; | |||
| } | |||
| public Builder priority(Integer priority) { | |||
| this.mailRequest.setPriority(priority); | |||
| return this; | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,57 @@ | |||
| package com.ffii.lioner.modules.common.mail.service; | |||
| import java.util.Properties; | |||
| import org.springframework.beans.factory.annotation.Value; | |||
| import org.springframework.mail.javamail.JavaMailSender; | |||
| import org.springframework.mail.javamail.JavaMailSenderImpl; | |||
| import org.springframework.stereotype.Service; | |||
| import com.ffii.lioner.modules.common.MailSMTP; | |||
| import com.ffii.lioner.modules.settings.service.SettingsService; | |||
| /** caching mail sender if config no changed */ | |||
| @Service | |||
| public class MailSenderService { | |||
| private SettingsService settingsService; | |||
| private JavaMailSender sender; | |||
| private MailSMTP mailConfigCachs; | |||
| @Value("${emsd.webservice.required}") | |||
| private Boolean ldapWebServiceRequired; | |||
| public MailSenderService(SettingsService settingsService) { | |||
| this.settingsService = settingsService; | |||
| } | |||
| public JavaMailSender get() { | |||
| MailSMTP config = new MailSMTP(settingsService); | |||
| if (this.sender == null || mailConfigCachs == null || !config.equals(this.mailConfigCachs)) { | |||
| this.mailConfigCachs = config; | |||
| JavaMailSenderImpl sender = new JavaMailSenderImpl(); | |||
| Properties props = new Properties(); | |||
| if(!ldapWebServiceRequired){ | |||
| props.put("mail.smtp.timeout", "20000"); | |||
| props.put("mail.smtp.connectiontimeout", "10000"); | |||
| } | |||
| props.put("mail.smtp.auth", config.getAuth()); | |||
| props.put("mail.smtp.starttls.enable", "true"); | |||
| sender.setHost(config.getHost()); | |||
| sender.setPort(config.getPort()); | |||
| if(!ldapWebServiceRequired){ | |||
| sender.setUsername(config.getUsername()); | |||
| sender.setPassword(config.getPassword()); | |||
| } | |||
| sender.setJavaMailProperties(props); | |||
| this.sender = sender; | |||
| } | |||
| return this.sender; | |||
| } | |||
| } | |||
| @@ -0,0 +1,228 @@ | |||
| package com.ffii.lioner.modules.common.mail.service; | |||
| import java.io.IOException; | |||
| import java.text.ParseException; | |||
| import java.time.LocalDate; | |||
| import java.time.LocalDateTime; | |||
| import java.util.ArrayList; | |||
| import java.util.Arrays; | |||
| import java.util.List; | |||
| import java.util.Locale; | |||
| import java.util.Map; | |||
| import org.apache.commons.logging.Log; | |||
| import org.apache.commons.logging.LogFactory; | |||
| import org.springframework.core.io.ByteArrayResource; | |||
| import org.springframework.mail.javamail.JavaMailSender; | |||
| import org.springframework.mail.javamail.MimeMessageHelper; | |||
| import org.springframework.scheduling.annotation.Async; | |||
| import org.springframework.stereotype.Service; | |||
| import org.springframework.ui.freemarker.FreeMarkerTemplateUtils; | |||
| import com.ffii.lioner.modules.common.ErrorCodes; | |||
| import com.ffii.lioner.modules.common.LocalDateAdapter; | |||
| import com.ffii.lioner.modules.common.SettingNames; | |||
| import com.ffii.lioner.modules.common.mail.pojo.MailRequest; | |||
| import com.ffii.lioner.modules.settings.service.SettingsService; | |||
| import com.ffii.core.exception.InternalServerErrorException; | |||
| import com.ffii.core.support.JdbcDao; | |||
| import com.ffii.core.utils.LocaleUtils; | |||
| import com.google.gson.Gson; | |||
| import com.google.gson.GsonBuilder; | |||
| import freemarker.cache.StringTemplateLoader; | |||
| import freemarker.template.Configuration; | |||
| import freemarker.template.MalformedTemplateNameException; | |||
| import freemarker.template.Template; | |||
| import freemarker.template.TemplateException; | |||
| import freemarker.template.TemplateNotFoundException; | |||
| import jakarta.mail.MessagingException; | |||
| import jakarta.mail.internet.InternetAddress; | |||
| import jakarta.mail.internet.MimeMessage; | |||
| @Service | |||
| public class MailService { | |||
| protected final Log logger = LogFactory.getLog(getClass()); | |||
| private JdbcDao jdbcDao; | |||
| private MailSenderService mailSenderService; | |||
| private Configuration freemarkerConfig; | |||
| private SettingsService settingsService; | |||
| public MailService(JdbcDao jdbcDao, MailSenderService mailSenderService, Configuration freemarkerConfig, | |||
| SettingsService settingsService) { | |||
| this.jdbcDao = jdbcDao; | |||
| this.mailSenderService = mailSenderService; | |||
| this.freemarkerConfig = freemarkerConfig; | |||
| this.settingsService = settingsService; | |||
| } | |||
| private void doSend(List<MailRequest> mailRequests, Locale locale) | |||
| throws MessagingException, TemplateNotFoundException, MalformedTemplateNameException, ParseException, IOException, | |||
| TemplateException { | |||
| JavaMailSender sender = mailSenderService.get(); | |||
| for (MailRequest mailRequest : mailRequests) { | |||
| MimeMessage mimeMessage = sender.createMimeMessage(); | |||
| MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true, "UTF-8"); | |||
| helper.setSubject(mailRequest.getSubject()); | |||
| Template template = null; | |||
| if (mailRequest.getTemplate() != null) { | |||
| try { | |||
| template = freemarkerConfig | |||
| .getTemplate(mailRequest.getTemplate() + "_" + LocaleUtils.toLocaleStr(locale) + ".ftl"); | |||
| } catch (TemplateNotFoundException e) { | |||
| template = freemarkerConfig.getTemplate(mailRequest.getTemplate() + ".ftl"); | |||
| } | |||
| } else { | |||
| // Create a FreeMarker configuration | |||
| Configuration configuration = new Configuration(Configuration.VERSION_2_3_32); | |||
| // Create a template loader and add the template content | |||
| StringTemplateLoader templateLoader = new StringTemplateLoader(); | |||
| templateLoader.putTemplate("myTemplate", mailRequest.getTemplateContent()); | |||
| configuration.setTemplateLoader(templateLoader); | |||
| try { | |||
| // Get the template by name | |||
| template = configuration.getTemplate("myTemplate"); | |||
| } catch (IOException e) { | |||
| // Handle any IO exceptions | |||
| } | |||
| } | |||
| helper.setText( | |||
| FreeMarkerTemplateUtils.processTemplateIntoString(template, mailRequest.getArgs()), | |||
| true); | |||
| if (mailRequest.getFrom() != null) { | |||
| helper.setFrom(mailRequest.getFrom()); | |||
| } else { | |||
| helper.setFrom(settingsService.getString(SettingNames.MAIL_SMTP_USERNAME)); | |||
| } | |||
| if (mailRequest.getPriority() != null) | |||
| helper.setPriority(mailRequest.getPriority()); | |||
| if (mailRequest.getReplyTo() != null) | |||
| helper.setReplyTo(mailRequest.getReplyTo()); | |||
| if (mailRequest.getTo() != null) | |||
| helper.setTo(mailRequest.getTo().toArray(new InternetAddress[mailRequest.getTo().size()])); | |||
| if (mailRequest.getCc() != null) | |||
| helper.setCc(mailRequest.getCc().toArray(new InternetAddress[mailRequest.getCc().size()])); | |||
| if (mailRequest.getBcc() != null) | |||
| helper.setBcc(mailRequest.getBcc().toArray(new InternetAddress[mailRequest.getBcc().size()])); | |||
| if (mailRequest.getAttachments() != null) { | |||
| for (Map.Entry<String, byte[]> entry : mailRequest.getAttachments().entrySet()) { | |||
| helper.addAttachment(entry.getKey(), new ByteArrayResource(entry.getValue())); | |||
| } | |||
| } | |||
| sender.send(mimeMessage); | |||
| } | |||
| } | |||
| public void send(List<MailRequest> mailRequests, Locale locale) throws ParseException { | |||
| try { | |||
| doSend(mailRequests, locale); | |||
| } catch (MessagingException | IOException | TemplateException e) { | |||
| throw new InternalServerErrorException(ErrorCodes.SEND_EMAIL_ERROR, e); | |||
| } | |||
| } | |||
| public void sendARS(List<MailRequest> mailRequests, Locale locale, Long eventId, Long subDivisionId, Long userId, | |||
| String reminderType) throws ParseException { | |||
| // Create an instance of the GsonBuilder | |||
| GsonBuilder gsonBuilder = new GsonBuilder(); | |||
| gsonBuilder.registerTypeAdapter(LocalDate.class, new LocalDateAdapter()); | |||
| Gson gson = gsonBuilder.create(); | |||
| String jsonString = gson.toJson(mailRequests.get(0)); | |||
| try { | |||
| doSend(mailRequests, locale); | |||
| } catch (MessagingException | IOException | TemplateException e) { | |||
| Map<String, Object> value = Map.of( | |||
| "eventId", eventId, | |||
| "subDivisionId", subDivisionId, | |||
| "reminderType", reminderType, | |||
| "userId", userId, | |||
| "sendDate", LocalDateTime.now(), | |||
| "success", false, | |||
| "response", e.getMessage(), | |||
| "content", jsonString, | |||
| "resendSuccess", false); | |||
| jdbcDao.executeUpdate( | |||
| "INSERT IGNORE INTO todo_reminder_email_log (eventId, subDivisionId, reminderType, userId, sendDate, success, response, content, resendSuccess)" | |||
| + " VALUE (:eventId, :subDivisionId, :reminderType, :userId, :sendDate, :success, :response, :content, :resendSuccess)", | |||
| value); | |||
| } | |||
| Map<String, Object> value = Map.of( | |||
| "eventId", eventId, | |||
| "subDivisionId", subDivisionId, | |||
| "reminderType", reminderType, | |||
| "userId", userId, | |||
| "sendDate", LocalDateTime.now(), | |||
| "success", true, | |||
| "content", jsonString, | |||
| "resendSuccess", false); | |||
| jdbcDao.executeUpdate( | |||
| "INSERT IGNORE INTO todo_reminder_email_log (eventId, subDivisionId, reminderType, userId, sendDate, success, content, resendSuccess)" | |||
| + " VALUE (:eventId, :subDivisionId, :reminderType, :userId, :sendDate, :success ,:content, :resendSuccess)", | |||
| value); | |||
| } | |||
| public void resendARS(MailRequest mailRequestObj, Long emailLogId) throws ParseException { | |||
| List<MailRequest> mailRequests = new ArrayList<MailRequest>(); | |||
| mailRequests.add(mailRequestObj); | |||
| Locale locale = Locale.ENGLISH; | |||
| // Convert the object to JSON string | |||
| try { | |||
| doSend(mailRequests, locale); | |||
| } catch (MessagingException | IOException | TemplateException e) { | |||
| //error still occur | |||
| } | |||
| jdbcDao.executeUpdate( | |||
| " UPDATE todo_reminder_email_log " | |||
| + " SET resendSuccess=1 " | |||
| + " WHERE id = :emailLogId", | |||
| Map.of("emailLogId", emailLogId)); | |||
| } | |||
| @Async | |||
| public void asyncSend(List<MailRequest> mailRequests, Locale locale) throws ParseException { | |||
| try { | |||
| doSend(mailRequests, locale); | |||
| } catch (MessagingException | IOException | TemplateException e) { | |||
| logger.error("send email error", e); | |||
| } | |||
| } | |||
| public void send(List<MailRequest> mailRequests) throws ParseException { | |||
| send(mailRequests, LocaleUtils.getLocale()); | |||
| } | |||
| @Async | |||
| public void asyncSend(List<MailRequest> mailRequests) throws ParseException { | |||
| asyncSend(mailRequests, LocaleUtils.getLocale()); | |||
| } | |||
| public void send(MailRequest mailRequest) throws ParseException { | |||
| send(Arrays.asList(mailRequest)); | |||
| } | |||
| @Async | |||
| public void asyncSend(MailRequest mailRequest) throws ParseException { | |||
| asyncSend(Arrays.asList(mailRequest)); | |||
| } | |||
| public void send(MailRequest mailRequest, Locale locale) throws ParseException { | |||
| send(Arrays.asList(mailRequest), locale); | |||
| } | |||
| @Async | |||
| public void asyncSend(MailRequest mailRequest, Locale locale) throws ParseException { | |||
| asyncSend(Arrays.asList(mailRequest), locale); | |||
| } | |||
| } | |||
| @@ -0,0 +1,108 @@ | |||
| package com.ffii.lioner.modules.common.service; | |||
| import java.util.Arrays; | |||
| import java.util.Date; | |||
| import java.util.HashMap; | |||
| import java.util.List; | |||
| import java.util.Map; | |||
| import org.springframework.stereotype.Service; | |||
| import org.springframework.transaction.annotation.Isolation; | |||
| import org.springframework.transaction.annotation.Transactional; | |||
| import com.ffii.core.support.AbstractService; | |||
| import com.ffii.core.support.JdbcDao; | |||
| import com.ffii.core.utils.MapUtils; | |||
| import com.ffii.core.utils.Params; | |||
| import jakarta.annotation.Nullable; | |||
| @Service | |||
| public class AuditLogService extends AbstractService { | |||
| public AuditLogService(JdbcDao jdbcDao) { | |||
| super(jdbcDao); | |||
| } | |||
| private static final String SQL_INSERT_AUDIT_LOG = "INSERT INTO audit_log (`tableName`, `recordId`, `recordName`, `modifiedBy`, `modified`, `oldData`, `newData`) " | |||
| + "VALUES (:tableName, :recordId, :recordName, :modifiedBy, :modified, :oldData, :newData)"; | |||
| @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = false) | |||
| public int save(String tableName, Long recordId, String recordName, Long modifiedBy, Date modified, @Nullable String oldData, String newData) { | |||
| return jdbcDao.executeUpdate(SQL_INSERT_AUDIT_LOG,MapUtils.toHashMap("tableName", tableName, "recordId", recordId, | |||
| "recordName",recordName, "modifiedBy", modifiedBy, "modified", modified, | |||
| "oldData", oldData, "newData", newData)); | |||
| } | |||
| @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true) | |||
| public List<Map<String, Object>> search(String tableName, Integer recordId) { | |||
| String sql = "SELECT * FROM audit_log WHERE tableName = :tableName AND recordId = :recordId ORDER BY modified"; | |||
| return jdbcDao.queryForList(sql, Map.of("tableName", tableName, "recordId", recordId)); | |||
| } | |||
| @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true) | |||
| public List<Map<String, Object>> search(String tableName, String recordId) { | |||
| String sql = "SELECT * FROM audit_log WHERE tableName = :tableName AND recordId = :recordId ORDER BY modified"; | |||
| return jdbcDao.queryForList(sql, Map.of("tableName", tableName, "recordId", recordId)); | |||
| } | |||
| @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true) | |||
| public List<Map<String, Object>> arsSearch(Map<String,Object> args) { | |||
| StringBuilder sql = new StringBuilder("SELECT" | |||
| + " *, " | |||
| + " u.username " | |||
| + " FROM audit_log al" | |||
| + " LEFT JOIN user u on u.id = al.modifiedBy " | |||
| + " WHERE al.tableName is not NULL " | |||
| ); | |||
| if (args != null) { | |||
| if (args.containsKey("tableName")) sql.append(" AND al.tableName = :tableName "); | |||
| if (args.containsKey("recordName")) sql.append(" AND al.recordName LIKE :recordName"); | |||
| if (args.containsKey("fromDate"))sql.append(" AND DATE(al.modified) >= :fromDate"); | |||
| if (args.containsKey("toDate"))sql.append(" AND DATE(al.modified) < :toDate"); | |||
| if (args.containsKey("modifiedBy")) sql.append(" AND al.modifiedBy = :modifiedBy"); | |||
| } | |||
| sql.append(" ORDER BY al.modified desc "); | |||
| return jdbcDao.queryForList(sql.toString(), args); | |||
| } | |||
| @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true) | |||
| public List<Map<String, Object>> getTables() { | |||
| String sql = "SELECT DISTINCT tableName FROM audit_log"; | |||
| return jdbcDao.queryForList(sql, ""); | |||
| } | |||
| public Map<String, Object> compareMaps(Map<String, Object> map1, Map<String, Object> map2) { | |||
| Map<String, Object> diffMap = new HashMap<>(); | |||
| // Iterate over the entries of map2 | |||
| for (Map.Entry<String, Object> entry : map2.entrySet()) { | |||
| String key = entry.getKey(); | |||
| Object value2 = entry.getValue(); | |||
| Object value1 = map1.get(key); | |||
| // Compare the values | |||
| if (value1 != null) { | |||
| if (value1.getClass().isArray() && value2.getClass().isArray()) { | |||
| if (!Arrays.deepEquals((Object[]) value1, (Object[]) value2)) { | |||
| diffMap.put(key, value2); | |||
| } | |||
| } else if (!value1.equals(value2)) { | |||
| diffMap.put(key, value2); | |||
| } | |||
| } | |||
| else if(value2 != null){ | |||
| diffMap.put(key, value2); | |||
| } | |||
| } | |||
| return diffMap; | |||
| } | |||
| } | |||
| @@ -0,0 +1,546 @@ | |||
| package com.ffii.lioner.modules.common.service; | |||
| import java.io.ByteArrayOutputStream; | |||
| import java.io.IOException; | |||
| import java.io.InputStream; | |||
| import java.time.LocalDate; | |||
| import java.time.LocalDateTime; | |||
| import java.time.format.DateTimeFormatter; | |||
| import java.util.List; | |||
| import java.util.Map; | |||
| import org.apache.commons.logging.Log; | |||
| import org.apache.commons.logging.LogFactory; | |||
| import org.apache.poi.ss.usermodel.Cell; | |||
| import org.apache.poi.ss.usermodel.CellStyle; | |||
| import org.apache.poi.ss.usermodel.Row; | |||
| import org.apache.poi.ss.usermodel.Sheet; | |||
| import org.apache.poi.ss.usermodel.Workbook; | |||
| import org.apache.poi.xssf.usermodel.XSSFWorkbook; | |||
| import org.springframework.core.io.ClassPathResource; | |||
| import org.springframework.stereotype.Service; | |||
| import com.ffii.lioner.modules.lioner.reportDao.ImportErrorRecord; | |||
| @Service | |||
| public class ExcelReportService { | |||
| private static final String DASHBOARD_SUMMARY_FILE_PATH = "templates/report/awardSummaryByDivision.xlsx"; | |||
| private static final String IMPORT_TEMPLATE_PATH = "templates/report/Appreciation_Records_Import_Template_v1.0.xlsx"; | |||
| private static final String AWARD_IMPORT_TEMPLATE_PATH = "templates/report/Award_Record_Import_Template_v1.0.xlsx"; | |||
| private final Log logger = LogFactory.getLog(getClass()); | |||
| public byte[] generateDashboardExcelReport(List<Map<String,Object>> queryResults, List<Map<String,Object>> detailResult, Integer year) throws IOException { | |||
| // Generate the Excel report with query results | |||
| Workbook workbook = createDashboardExcelReport(queryResults, detailResult, DASHBOARD_SUMMARY_FILE_PATH,year); | |||
| // Write the workbook to a ByteArrayOutputStream | |||
| ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); | |||
| workbook.write(outputStream); | |||
| workbook.close(); | |||
| return outputStream.toByteArray(); | |||
| } | |||
| private Workbook createDashboardExcelReport(List<Map<String,Object>> queryResults, List<Map<String,Object>> detailResult, String templatePath, Integer year) throws IOException { | |||
| ClassPathResource resource = new ClassPathResource(templatePath); | |||
| InputStream templateInputStream = resource.getInputStream(); | |||
| Workbook workbook = new XSSFWorkbook(templateInputStream); | |||
| // Page 1 | |||
| Sheet sheet = workbook.getSheetAt(0); | |||
| int rowIndex = 1; // Assuming the location is in the first row | |||
| int columnIndex = 0; | |||
| Row tempRow = sheet.getRow(rowIndex); | |||
| LocalDate currentDate = LocalDate.now(); | |||
| DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd MMM yyyy"); | |||
| DateTimeFormatter displayFormatter = DateTimeFormatter.ofPattern("dd-MM-yyyy"); | |||
| String formattedDate = currentDate.format(formatter); | |||
| sheet.getHeader().setCenter(year.toString() + " Award Summary by Division" ); | |||
| sheet.getHeader().setRight("Printed Date: " + formattedDate ); | |||
| Cell tempCell = tempRow.getCell(columnIndex); | |||
| CellStyle cellStyle = tempCell.getCellStyle(); | |||
| int rowNum = 1; | |||
| for (Map<String,Object> result : queryResults) { | |||
| Row row = sheet.createRow(rowNum++); | |||
| Cell nameCell = row.createCell(0); | |||
| nameCell.setCellValue(result.get("name").toString()); | |||
| nameCell.setCellStyle(cellStyle); | |||
| Cell countCell = row.createCell(1); | |||
| countCell.setCellValue( Integer.parseInt(result.get("count").toString())); | |||
| countCell.setCellStyle(cellStyle); | |||
| // Add more cells/columns as needed | |||
| } | |||
| // Auto-size columns | |||
| for (int i = 0; i < queryResults.size(); i++) { | |||
| sheet.autoSizeColumn(i); | |||
| } | |||
| // Page 2 | |||
| sheet = workbook.getSheetAt(1); | |||
| rowIndex = 1; // Assuming the location is in the first row | |||
| columnIndex = 0; | |||
| tempRow = sheet.getRow(rowIndex); | |||
| tempCell = tempRow.getCell(columnIndex); | |||
| cellStyle = tempCell.getCellStyle(); | |||
| currentDate = LocalDate.now(); | |||
| formatter = DateTimeFormatter.ofPattern("dd MMM yyyy"); | |||
| formattedDate = currentDate.format(formatter); | |||
| sheet.getHeader().setCenter(year.toString() + " Award Summary by Division" ); | |||
| sheet.getHeader().setRight("Printed Date: " + formattedDate ); | |||
| rowNum = 1; | |||
| for (Map<String,Object> result : detailResult) { | |||
| Row row = sheet.createRow(rowNum++); | |||
| Cell nameCell = row.createCell(0); | |||
| nameCell.setCellValue(result.get("name").toString()); | |||
| nameCell.setCellStyle(cellStyle); | |||
| Cell countCell = row.createCell(1); | |||
| countCell.setCellValue( result.get("subDivision").toString()); | |||
| countCell.setCellStyle(cellStyle); | |||
| CellStyle temp = countCell.getCellStyle(); | |||
| temp.setWrapText(true); | |||
| countCell.setCellStyle(temp); | |||
| Cell eventNameCell = row.createCell(2); | |||
| eventNameCell.setCellValue( result.get("eventName").toString()); | |||
| eventNameCell.setCellStyle(cellStyle); | |||
| Cell applicationNameCell = row.createCell(3); | |||
| applicationNameCell.setCellValue( result.get("applicationName").toString()); | |||
| applicationNameCell.setCellStyle(cellStyle); | |||
| Cell awardNameCell = row.createCell(4); | |||
| awardNameCell.setCellValue( result.get("awardName").toString()); | |||
| awardNameCell.setCellStyle(cellStyle); | |||
| Cell receiveDateCell = row.createCell(5); | |||
| receiveDateCell.setCellValue( result.get("receiveDate") == null ? "" : ((LocalDateTime) result.get("receiveDate")).format(displayFormatter)); | |||
| receiveDateCell.setCellStyle(cellStyle); | |||
| Cell categoryCell = row.createCell(6); | |||
| categoryCell.setCellValue( result.get("category").toString()); | |||
| categoryCell.setCellStyle(cellStyle); | |||
| Cell responsibleOfficerCell = row.createCell(7); | |||
| responsibleOfficerCell.setCellValue(result.get("responsibleOfficer").toString()); | |||
| responsibleOfficerCell.setCellStyle(cellStyle); | |||
| } | |||
| // Auto-size columns | |||
| for (int i = 0; i < queryResults.size(); i++) { | |||
| if(i!= 1){ | |||
| sheet.autoSizeColumn(i); | |||
| } | |||
| } | |||
| return workbook; | |||
| } | |||
| public byte[] generateEventExcelReport(List<Map<String,Object>> queryResults) throws IOException { | |||
| // Generate the Excel report with query results | |||
| Workbook workbook = createEventExcelReport(queryResults); | |||
| // Write the workbook to a ByteArrayOutputStream | |||
| ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); | |||
| workbook.write(outputStream); | |||
| workbook.close(); | |||
| return outputStream.toByteArray(); | |||
| } | |||
| private Workbook createEventExcelReport(List<Map<String,Object>> queryResults) throws IOException { | |||
| Workbook workbook = new XSSFWorkbook(); | |||
| DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); | |||
| // Page 1 | |||
| Sheet sheet = workbook.createSheet("Event Data"); | |||
| Row headerRow = sheet.createRow(0); | |||
| headerRow.createCell(0).setCellValue("id"); | |||
| headerRow.createCell(1).setCellValue("Created By"); | |||
| headerRow.createCell(2).setCellValue("Created"); | |||
| headerRow.createCell(3).setCellValue("Name"); | |||
| headerRow.createCell(4).setCellValue("Chinese Name"); | |||
| headerRow.createCell(5).setCellValue("Description"); | |||
| headerRow.createCell(6).setCellValue("Region"); | |||
| headerRow.createCell(7).setCellValue("Division"); | |||
| headerRow.createCell(8).setCellValue("Organization"); | |||
| headerRow.createCell(9).setCellValue("Event Type"); | |||
| headerRow.createCell(10).setCellValue("Frequency"); | |||
| headerRow.createCell(11).setCellValue("Series"); | |||
| headerRow.createCell(12).setCellValue("Application Date"); | |||
| headerRow.createCell(13).setCellValue("Application Deadline"); | |||
| headerRow.createCell(14).setCellValue("Next Application Date"); | |||
| headerRow.createCell(15).setCellValue("Announcement Date"); | |||
| headerRow.createCell(16).setCellValue("Award Date"); | |||
| headerRow.createCell(17).setCellValue("Reminder Flag"); | |||
| headerRow.createCell(18).setCellValue("Reminder Threshold"); | |||
| headerRow.createCell(19).setCellValue("Reminder Interval"); | |||
| headerRow.createCell(20).setCellValue("Reminder Limit"); | |||
| int rowNum = 1; | |||
| for (Map<String,Object> result : queryResults) { | |||
| Row row = sheet.createRow(rowNum++); | |||
| row.createCell(0).setCellValue(Integer.parseInt(result.get("id").toString())); | |||
| row.createCell(1).setCellValue(result.get("username") == null ? "" : result.get("username").toString()); | |||
| row.createCell(2).setCellValue(((LocalDateTime)result.get("created")).format(formatter)); | |||
| row.createCell(3).setCellValue(result.get("name").toString()); | |||
| row.createCell(4).setCellValue(result.get("nameCht") == null ? "" : result.get("nameCht").toString()); | |||
| row.createCell(5).setCellValue(result.get("description") == null ? "" : result.get("description").toString()); | |||
| row.createCell(6).setCellValue(result.get("region") == null ? "" : result.get("region").toString()); | |||
| row.createCell(7).setCellValue(result.get("divisionList") == null ? "" : result.get("divisionList").toString()); | |||
| row.createCell(8).setCellValue(result.get("organization") == null ? "" : result.get("organization").toString()); | |||
| row.createCell(9).setCellValue(result.get("eventType") == null ? "" : result.get("eventType").toString()); | |||
| row.createCell(10).setCellValue(result.get("frequency") == null ? "" : result.get("frequency").toString()); | |||
| row.createCell(11).setCellValue(result.get("series") == null ? "" : result.get("series").toString()); | |||
| row.createCell(12).setCellValue(((LocalDateTime)result.get("startDate")).format(formatter)); | |||
| row.createCell(13).setCellValue(((LocalDateTime)result.get("applicationDeadline")).format(formatter)); | |||
| row.createCell(14).setCellValue(result.get("nextApplicationDate") == null ? "" :((LocalDateTime)result.get("nextApplicationDate")).format(formatter)); | |||
| row.createCell(15).setCellValue(result.get("announcementDate") == null ? "" : ((LocalDateTime)result.get("announcementDate")).format(formatter)); | |||
| row.createCell(16).setCellValue(result.get("awardDate") == null ? "" : ((LocalDateTime )result.get("awardDate")).format(formatter)); | |||
| row.createCell(17).setCellValue(result.get("reminderFlag").toString()); | |||
| row.createCell(18).setCellValue(result.get("reminderThreshold") == null ? 0 : Integer.parseInt(result.get("reminderThreshold").toString())); | |||
| row.createCell(19).setCellValue(result.get("reminderInterval") == null ? 0 : Integer.parseInt(result.get("reminderInterval").toString())); | |||
| row.createCell(20).setCellValue(result.get("reminderLimit") == null ? 0 : Integer.parseInt(result.get("reminderLimit").toString())); | |||
| // Add more cells/columns as needed | |||
| } | |||
| // Auto-size columns | |||
| for (int i = 0; i < headerRow.getLastCellNum(); i++) { | |||
| sheet.autoSizeColumn(i); | |||
| } | |||
| return workbook; | |||
| } | |||
| public byte[] generateApplicationExcelReport(List<Map<String,Object>> queryResults) throws IOException { | |||
| // Generate the Excel report with query results | |||
| Workbook workbook = createApplicationExcelReport(queryResults); | |||
| // Write the workbook to a ByteArrayOutputStream | |||
| ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); | |||
| workbook.write(outputStream); | |||
| workbook.close(); | |||
| return outputStream.toByteArray(); | |||
| } | |||
| private Workbook createApplicationExcelReport(List<Map<String,Object>> queryResults) throws IOException { | |||
| Workbook workbook = new XSSFWorkbook(); | |||
| DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); | |||
| // Page 1 | |||
| Sheet sheet = workbook.createSheet("Application Data"); | |||
| Row headerRow = sheet.createRow(0); | |||
| headerRow.createCell(0).setCellValue("id"); | |||
| headerRow.createCell(1).setCellValue("Created By"); | |||
| headerRow.createCell(2).setCellValue("Created"); | |||
| headerRow.createCell(3).setCellValue("Event Name"); | |||
| headerRow.createCell(4).setCellValue("Application Name"); | |||
| headerRow.createCell(5).setCellValue("Description"); | |||
| headerRow.createCell(6).setCellValue("Division"); | |||
| headerRow.createCell(7).setCellValue("Responsible Officer"); | |||
| headerRow.createCell(8).setCellValue("Status"); | |||
| int rowNum = 1; | |||
| for (Map<String,Object> result : queryResults) { | |||
| Row row = sheet.createRow(rowNum++); | |||
| row.createCell(0).setCellValue(Integer.parseInt(result.get("id").toString())); | |||
| row.createCell(1).setCellValue(result.get("username") == null ? "" : result.get("username").toString()); | |||
| row.createCell(2).setCellValue(((LocalDateTime)result.get("created")).format(formatter)); | |||
| row.createCell(3).setCellValue(result.get("eventName").toString()); | |||
| row.createCell(4).setCellValue(result.get("name").toString()); | |||
| row.createCell(5).setCellValue(result.get("description") == null ? "" : result.get("description").toString()); | |||
| row.createCell(6).setCellValue(result.get("divisionList") == null ? "" : result.get("divisionList").toString()); | |||
| row.createCell(7).setCellValue(result.get("responsibleOfficer").toString()); | |||
| row.createCell(8).setCellValue(result.get("status").toString()); | |||
| // Add more cells/columns as needed | |||
| } | |||
| // Auto-size columns | |||
| for (int i = 0; i < headerRow.getLastCellNum(); i++) { | |||
| sheet.autoSizeColumn(i); | |||
| } | |||
| return workbook; | |||
| } | |||
| public byte[] generateAwardExcelReport(List<Map<String,Object>> queryResults) throws IOException { | |||
| // Generate the Excel report with query results | |||
| Workbook workbook = createAwardExcelReport(queryResults); | |||
| // Write the workbook to a ByteArrayOutputStream | |||
| ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); | |||
| workbook.write(outputStream); | |||
| workbook.close(); | |||
| return outputStream.toByteArray(); | |||
| } | |||
| private Workbook createAwardExcelReport(List<Map<String,Object>> queryResults) throws IOException { | |||
| Workbook workbook = new XSSFWorkbook(); | |||
| DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); | |||
| // Page 1 | |||
| Sheet sheet = workbook.createSheet("Award Data"); | |||
| Row headerRow = sheet.createRow(0); | |||
| headerRow.createCell(0).setCellValue("id"); | |||
| headerRow.createCell(1).setCellValue("Created By"); | |||
| headerRow.createCell(2).setCellValue("Created"); | |||
| headerRow.createCell(3).setCellValue("Event Name"); | |||
| headerRow.createCell(4).setCellValue("Application Name"); | |||
| headerRow.createCell(5).setCellValue("Award Name"); | |||
| headerRow.createCell(6).setCellValue("Award Chinese Name"); | |||
| headerRow.createCell(7).setCellValue("Remarks"); | |||
| headerRow.createCell(8).setCellValue("Division"); | |||
| headerRow.createCell(9).setCellValue("Category"); | |||
| headerRow.createCell(10).setCellValue("Receive Date"); | |||
| headerRow.createCell(11).setCellValue("Storage Location"); | |||
| headerRow.createCell(12).setCellValue("Physical Award"); | |||
| headerRow.createCell(13).setCellValue("Responsible Officer"); | |||
| int rowNum = 1; | |||
| for (Map<String,Object> result : queryResults) { | |||
| Row row = sheet.createRow(rowNum++); | |||
| row.createCell(0).setCellValue(Integer.parseInt(result.get("id").toString())); | |||
| row.createCell(1).setCellValue(result.get("username") == null ? "" : result.get("username").toString()); | |||
| row.createCell(2).setCellValue(((LocalDateTime)result.get("created")).format(formatter)); | |||
| row.createCell(3).setCellValue(result.get("eventName").toString()); | |||
| row.createCell(4).setCellValue(result.get("applicationName").toString()); | |||
| row.createCell(5).setCellValue(result.get("name").toString()); | |||
| row.createCell(6).setCellValue(result.get("nameCht") == null ? "" : result.get("nameCht").toString()); | |||
| row.createCell(7).setCellValue(result.get("remarks") == null ? "" : result.get("remarks").toString()); | |||
| row.createCell(8).setCellValue(result.get("divisionList") == null ? "" : result.get("divisionList").toString()); | |||
| row.createCell(9).setCellValue(result.get("categoryName").toString()); | |||
| row.createCell(10).setCellValue(result.get("receiveDate") == null ? "" : ((LocalDateTime)result.get("receiveDate")).format(formatter)); | |||
| row.createCell(11).setCellValue(result.get("storageLocation") == null ? "" : result.get("storageLocation").toString()); | |||
| row.createCell(12).setCellValue(result.get("physicalAward") == null ? "" : result.get("physicalAward").toString()); | |||
| row.createCell(13).setCellValue(result.get("responsibleOfficer") == null ? "" : result.get("responsibleOfficer").toString()); | |||
| // Add more cells/columns as needed | |||
| } | |||
| // Auto-size columns | |||
| for (int i = 0; i < headerRow.getLastCellNum(); i++) { | |||
| sheet.autoSizeColumn(i); | |||
| } | |||
| return workbook; | |||
| } | |||
| public byte[] exportAppreciationTemplate() throws IOException { | |||
| ClassPathResource resource = new ClassPathResource(IMPORT_TEMPLATE_PATH); | |||
| InputStream templateInputStream = resource.getInputStream(); | |||
| Workbook workbook = new XSSFWorkbook(templateInputStream); | |||
| // Write the workbook to a ByteArrayOutputStream | |||
| ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); | |||
| workbook.write(outputStream); | |||
| workbook.close(); | |||
| return outputStream.toByteArray(); | |||
| } | |||
| public byte[] exportAwardImportTemplate(Map<String,List<Map<String,Object>>> masterData) throws IOException { | |||
| ClassPathResource resource = new ClassPathResource(AWARD_IMPORT_TEMPLATE_PATH); | |||
| InputStream templateInputStream = resource.getInputStream(); | |||
| Workbook workbook = new XSSFWorkbook(templateInputStream); | |||
| // Iterate over the map entries | |||
| for (Map.Entry<String, List<Map<String,Object>>> entry : masterData.entrySet()) { | |||
| String key = entry.getKey(); | |||
| List<Map<String,Object>> values = entry.getValue(); | |||
| // Find the sheet by the key | |||
| Sheet sheet = workbook.getSheet(key); | |||
| if (sheet != null) { | |||
| // Write the list values to the sheet | |||
| int rowNum = 0; | |||
| for (Map<String,Object>value : values) { | |||
| Row row = sheet.createRow(rowNum++); | |||
| Cell cell = row.createCell(0); | |||
| cell.setCellValue(value.get("name").toString()); | |||
| } | |||
| } else { | |||
| // Handle case where sheet with the key doesn't exist | |||
| System.out.println("Sheet with key " + key + " not found."); | |||
| } | |||
| } | |||
| // Write the workbook to a ByteArrayOutputStream | |||
| ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); | |||
| workbook.write(outputStream); | |||
| workbook.close(); | |||
| return outputStream.toByteArray(); | |||
| } | |||
| public byte[] generateAppreciationExcelReport(List<Map<String,Object>> queryResults) throws IOException { | |||
| // Generate the Excel report with query results | |||
| Workbook workbook = createAppreciationExcelReport(queryResults); | |||
| // Write the workbook to a ByteArrayOutputStream | |||
| ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); | |||
| workbook.write(outputStream); | |||
| workbook.close(); | |||
| return outputStream.toByteArray(); | |||
| } | |||
| private Workbook createAppreciationExcelReport(List<Map<String,Object>> queryResults) throws IOException { | |||
| Workbook workbook = new XSSFWorkbook(); | |||
| DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); | |||
| // Page 1 | |||
| Sheet sheet = workbook.createSheet("Award Data"); | |||
| Row headerRow = sheet.createRow(0); | |||
| //headerRow.createCell(0).setCellValue("id"); | |||
| headerRow.createCell(0).setCellValue("Created By"); | |||
| headerRow.createCell(1).setCellValue("Created"); | |||
| headerRow.createCell(2).setCellValue("SBU / Division"); | |||
| headerRow.createCell(3).setCellValue("Receipt Date"); | |||
| headerRow.createCell(4).setCellValue("Brief Description"); | |||
| headerRow.createCell(5).setCellValue("Venue Involved"); | |||
| headerRow.createCell(6).setCellValue("Client Department / Organization"); | |||
| headerRow.createCell(7).setCellValue("Client Full Name"); | |||
| headerRow.createCell(8).setCellValue("Client Post"); | |||
| headerRow.createCell(9).setCellValue("Client Reply Date"); | |||
| headerRow.createCell(10).setCellValue("Staff Reply Date"); | |||
| headerRow.createCell(11).setCellValue("No. of Staff"); | |||
| headerRow.createCell(12).setCellValue("LN Receipt Date"); | |||
| headerRow.createCell(13).setCellValue("Category"); | |||
| headerRow.createCell(14).setCellValue("Remarks"); | |||
| int rowNum = 1; | |||
| for (Map<String,Object> result : queryResults) { | |||
| Row row = sheet.createRow(rowNum++); | |||
| //row.createCell(0).setCellValue(Integer.parseInt(result.get("id").toString())); | |||
| row.createCell(0).setCellValue(result.get("username") == null ? "" : result.get("username").toString()); | |||
| row.createCell(1).setCellValue(((LocalDateTime)result.get("created")).format(formatter)); | |||
| row.createCell(2).setCellValue(result.get("divisionString").toString()); | |||
| row.createCell(3).setCellValue(result.get("receiptDate") == null ? "" : ((LocalDateTime)result.get("receiptDate")).format(formatter)); | |||
| row.createCell(4).setCellValue(result.get("description").toString()); | |||
| row.createCell(5).setCellValue(result.get("venue") == null ? "" : result.get("venue").toString()); | |||
| row.createCell(6).setCellValue(result.get("clientDepartment") == null ? | |||
| result.get("clientOrganization").toString().length() == 0 ? "" : result.get("clientOrganization").toString() | |||
| : result.get("clientDepartment").toString()); | |||
| row.createCell(7).setCellValue(result.get("clientFullname") == null ? "" : result.get("clientFullname").toString()); | |||
| row.createCell(8).setCellValue(result.get("clientPost") == null ? "" : result.get("clientPost").toString()); | |||
| row.createCell(9).setCellValue(result.get("clientReplyDate") == null ? "" : ((LocalDateTime)result.get("clientReplyDate")).format(formatter)); | |||
| row.createCell(10).setCellValue(result.get("staffReplyDate") == null ? "" : ((LocalDateTime)result.get("staffReplyDate")).format(formatter)); | |||
| row.createCell(11).setCellValue(result.get("noOfStaff") == null ? "" : result.get("noOfStaff").toString()); | |||
| row.createCell(12).setCellValue(((LocalDateTime)result.get("lnReceiptDate")).format(formatter)); | |||
| row.createCell(13).setCellValue(result.get("category").toString()); | |||
| row.createCell(14).setCellValue(result.get("remarks") == null ? "" : result.get("remarks").toString()); | |||
| // Add more cells/columns as needed | |||
| } | |||
| // Auto-size columns | |||
| for (int i = 0; i < headerRow.getLastCellNum(); i++) { | |||
| sheet.autoSizeColumn(i); | |||
| } | |||
| return workbook; | |||
| } | |||
| public byte[] generateAppreciationImportErrorReport(List<ImportErrorRecord> record) throws IOException { | |||
| // Generate the Excel report with query results | |||
| Workbook workbook = createAppreciationImportErrorExcelReport(record); | |||
| // Write the workbook to a ByteArrayOutputStream | |||
| ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); | |||
| workbook.write(outputStream); | |||
| workbook.close(); | |||
| return outputStream.toByteArray(); | |||
| } | |||
| private Workbook createAppreciationImportErrorExcelReport(List<ImportErrorRecord> record) throws IOException { | |||
| Workbook workbook = new XSSFWorkbook(); | |||
| DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); | |||
| // Page 1 | |||
| Sheet sheet = workbook.createSheet("Error Record"); | |||
| Row headerRow = sheet.createRow(0); | |||
| headerRow.createCell(0).setCellValue("Row"); | |||
| headerRow.createCell(1).setCellValue("Column"); | |||
| headerRow.createCell(2).setCellValue("Error Message"); | |||
| int rowNum = 1; | |||
| for (ImportErrorRecord result : record) { | |||
| Row row = sheet.createRow(rowNum++); | |||
| row.createCell(0).setCellValue(result.getRowId().toString()); | |||
| row.createCell(1).setCellValue(result.getColumn()); | |||
| row.createCell(2).setCellValue(result.getRemarks()); | |||
| } | |||
| // Auto-size columns | |||
| for (int i = 0; i < headerRow.getLastCellNum(); i++) { | |||
| sheet.autoSizeColumn(i); | |||
| } | |||
| return workbook; | |||
| } | |||
| public byte[] generateAwardImportErrorReport(List<ImportErrorRecord> record) throws IOException { | |||
| // Generate the Excel report with query results | |||
| Workbook workbook = createAwardImportErrorExcelReport(record); | |||
| // Write the workbook to a ByteArrayOutputStream | |||
| ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); | |||
| workbook.write(outputStream); | |||
| workbook.close(); | |||
| return outputStream.toByteArray(); | |||
| } | |||
| private Workbook createAwardImportErrorExcelReport(List<ImportErrorRecord> record) throws IOException { | |||
| Workbook workbook = new XSSFWorkbook(); | |||
| DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); | |||
| // Page 1 | |||
| Sheet sheet = workbook.createSheet("Error Record"); | |||
| Row headerRow = sheet.createRow(0); | |||
| headerRow.createCell(0).setCellValue("Row"); | |||
| headerRow.createCell(1).setCellValue("Page Name"); | |||
| headerRow.createCell(2).setCellValue("Column"); | |||
| headerRow.createCell(3).setCellValue("Error Message"); | |||
| int rowNum = 1; | |||
| for (ImportErrorRecord result : record) { | |||
| Row row = sheet.createRow(rowNum++); | |||
| row.createCell(0).setCellValue(result.getRowId().toString()); | |||
| row.createCell(1).setCellValue(result.getPageName()); | |||
| row.createCell(2).setCellValue(result.getColumn()); | |||
| row.createCell(3).setCellValue(result.getRemarks()); | |||
| } | |||
| // Auto-size columns | |||
| for (int i = 0; i < headerRow.getLastCellNum(); i++) { | |||
| sheet.autoSizeColumn(i); | |||
| } | |||
| return workbook; | |||
| } | |||
| } | |||
| @@ -0,0 +1,308 @@ | |||
| package com.ffii.lioner.modules.common.service; | |||
| import java.io.IOException; | |||
| import java.io.InputStream; | |||
| import java.io.StringWriter; | |||
| import java.time.LocalDateTime; | |||
| import java.time.format.DateTimeFormatter; | |||
| import java.util.ArrayList; | |||
| import java.util.Arrays; | |||
| import java.util.HashMap; | |||
| import java.util.LinkedHashMap; | |||
| import java.util.List; | |||
| import java.util.Map; | |||
| import org.springframework.core.io.ClassPathResource; | |||
| import org.springframework.stereotype.Service; | |||
| import com.ffii.lioner.modules.lioner.reportDao.AppreciationCaseReportDao; | |||
| import com.ffii.lioner.modules.lioner.reportDao.AppreciationChartDataDao; | |||
| import com.ffii.core.utils.NumberUtils; | |||
| import com.ffii.core.utils.RomanConverter; | |||
| import freemarker.template.Configuration; | |||
| import freemarker.template.Template; | |||
| import io.micrometer.common.util.StringUtils; | |||
| @Service | |||
| public class WordReportService { | |||
| private static final String XML_TEST_TEMPLATE_PATH = "templates/report/Report 1 - Appreciations Case (Sample) - test.ftl"; | |||
| private static final String APPRECIATION_SUMMARY_TEMPLATE_PATH = "templates/report/Report 2 - Annex III – Summary of Appreciation Cases.ftl"; | |||
| private static final String[] chartColors = new String[] { "7FC817", "FF4F90", "FFD453", "FFF2CB", "41D3F3", | |||
| "795AEE", "FFF547", "F9CB27", "EA871E", "FC001D", "4A2E95", "C8CBCD" }; | |||
| public byte[] generateTestXMLFile() throws IOException { | |||
| ClassPathResource resource = new ClassPathResource(XML_TEST_TEMPLATE_PATH); | |||
| InputStream templateInputStream = resource.getInputStream(); | |||
| try { | |||
| // Load the FTL template | |||
| Configuration cfg = new Configuration(Configuration.VERSION_2_3_32); | |||
| cfg.setClassForTemplateLoading(WordReportService.class, "/"); | |||
| Template template = cfg.getTemplate(XML_TEST_TEMPLATE_PATH); | |||
| // Create a data model | |||
| Map<String, Object> dataModel = new HashMap<>(); | |||
| dataModel.put("reportDate", "June 2023"); | |||
| dataModel.put("monthTotal", String.format("%s (%d)", NumberUtils.convertToWord(14), 14)); | |||
| List<Map<String, Object>> catBreakDowns = new ArrayList<>(); | |||
| catBreakDowns.add(Map.of("catTotal", String.format("%s (%d)", NumberUtils.convertToWord(6), 6), "catName", | |||
| "project")); | |||
| catBreakDowns.add(Map.of("catTotal", String.format("%s (%d)", NumberUtils.convertToWord(4), 4), "catName", | |||
| "maintenance")); | |||
| catBreakDowns.add( | |||
| Map.of("catTotal", String.format("%s (%d)", NumberUtils.convertToWord(4), 4), "catName", "AA&I")); | |||
| dataModel.put("catBreakDowns", catBreakDowns); | |||
| dataModel.put("compareStr1", "more than"); | |||
| dataModel.put("compareStr2", "by five"); | |||
| dataModel.put("finYear", "2023/24"); | |||
| dataModel.put("yearTotal", 50); | |||
| LinkedHashMap<String, Object> sbuYearTotal = new LinkedHashMap<>(); | |||
| sbuYearTotal.put("BTSD", 1); | |||
| sbuYearTotal.put("CSD", 2); | |||
| sbuYearTotal.put("GESD", 3); | |||
| sbuYearTotal.put("HSD", 4); | |||
| sbuYearTotal.put("MunSD", 5); | |||
| sbuYearTotal.put("SVSD", 6); | |||
| sbuYearTotal.put("DTD", 7); | |||
| dataModel.put("sbuYearTotal", sbuYearTotal); | |||
| int idx = 0; | |||
| List<Map<String, Object>> series = new ArrayList<>(); | |||
| List<String> sbuList = Arrays.asList("BTSD", "CSD", "GESD", "HSD", "MunSD", "SVSD", "DTD"); | |||
| for (String sbu : sbuList) { | |||
| Map<String, Object> serie = new HashMap<>(); | |||
| serie.put("serieName", sbu); | |||
| serie.put("color", chartColors[idx % (chartColors.length - 1)]); | |||
| serie.put("data", | |||
| Arrays.asList((int) (Math.random() * 1), (int) (Math.random() * 2), (int) (Math.random() * 3), | |||
| (int) (Math.random() * 4), (int) (Math.random() * 5), (int) (Math.random() * 6), | |||
| (int) (Math.random() * 7), (int) (Math.random() * 8), (int) (Math.random() * 9), | |||
| (int) (Math.random() * 10), (int) (Math.random() * 11), (int) (Math.random() * 12))); | |||
| series.add(serie); | |||
| idx++; | |||
| } | |||
| dataModel.put("series", series); | |||
| dataModel.put("lnReceiptDateRange", "17 Jul 2023 – 31 Jul 2023"); | |||
| List<Map<String, Object>> aprRecords = new ArrayList<>(); | |||
| aprRecords.add(Map.of("sbu", "BTSD", "receiptDate", "17 Jul 2023", "description", | |||
| "An appreciation received on 28 July 2023 via 1823 from a citizen expressing appreciation to EMSD colleagues for their prompt action to rectify damaged traffic signal equipment located at Chatham Road North, Hung Hom.", | |||
| "clientReplyDate", "03 Jul 2023", "staffReplyDate", "04 Jul 2023", "numOfStaff", 4, "clientDeptOrg", | |||
| "Transport Department")); | |||
| aprRecords.add(Map.of("sbu", "BTSD", "receiptDate", "22 Jul 2023", "description", | |||
| "Mr. Patrick K P CHENG, Chief Engineer/Traffic Survey & Support of TD, expressed the appreciation to BTSD’s colleagues for all the efforts and continuous support in commissioning of Journey Time Indicators (JTIs) to facilitate the opening of Tseung Kwan O – Lam Tin Tunnel (TKO-LTT) within very tight schedule.", | |||
| "clientReplyDate", "03 Jul 2023", "staffReplyDate", "04 Jul 2023", "numOfStaff", 7, "clientDeptOrg", | |||
| "Transport Department")); | |||
| dataModel.put("aprRecords", aprRecords); | |||
| /* | |||
| * dataModel.put("reportTitle", "New Title"); | |||
| * | |||
| * List<String> headers = Arrays.asList("Header 0", "Header 1", "Header 2", | |||
| * "Header 3"); | |||
| * dataModel.put("headers", headers); | |||
| * | |||
| * List<String> rows1 = Arrays.asList("R1-1", "R1-2", "R1-3", "R1-4"); | |||
| * dataModel.put("rows1", rows1); | |||
| * | |||
| * List<String> rows2 = Arrays.asList("R2-1", "R2-2", "R2-3", "R2-4"); | |||
| * dataModel.put("rows2", rows2); | |||
| * | |||
| * Map<String, Object> chartData = new HashMap<>(); | |||
| * | |||
| * List<String> categories = Arrays.asList("Category 1", "Category 2", | |||
| * "Category 3", "Category 4", | |||
| * "Category 5"); | |||
| * chartData.put("categories", categories); | |||
| * | |||
| * LinkedHashMap<String, Object> series = new LinkedHashMap<>(); | |||
| * | |||
| * List<Double> series1 = Arrays.asList(4.33, 2.55, 3.55, 4.55, 14.0); | |||
| * Map<String, Object> serie1 = new HashMap<>(); | |||
| * serie1.put("col", "B"); | |||
| * serie1.put("data", series1); | |||
| * serie1.put("size", 5); | |||
| * series.put("Series 1", serie1); | |||
| * | |||
| * List<Double> series2 = Arrays.asList(44.33, 22.55, 33.55, 44.55, 13.0); | |||
| * Map<String, Object> serie2 = new HashMap<>(); | |||
| * serie2.put("col", "C"); | |||
| * serie2.put("data", series2); | |||
| * serie2.put("size", 5); | |||
| * series.put("Series 2", serie2); | |||
| * | |||
| * List<Double> series3 = Arrays.asList(4.333, 2.555, 3.555, 4.555, 12.0); | |||
| * Map<String, Object> serie3 = new HashMap<>(); | |||
| * serie3.put("col", "D"); | |||
| * serie3.put("data", series3); | |||
| * serie3.put("size", 5); | |||
| * series.put("Series 3", serie3); | |||
| * | |||
| * chartData.put("series", series); | |||
| * | |||
| * dataModel.put("chartData", chartData); | |||
| */ | |||
| // Render the template with the data model | |||
| StringWriter writer = new StringWriter(); | |||
| template.process(dataModel, writer); | |||
| String output = writer.toString(); | |||
| byte[] byteArray = output.getBytes(); | |||
| return byteArray; | |||
| } catch (Exception e) { | |||
| e.printStackTrace(); | |||
| return null; // or handle the exception according to your requirements | |||
| } finally { | |||
| templateInputStream.close(); | |||
| } | |||
| } | |||
| public byte[] generateAppreciationCasesXMLFile(AppreciationCaseReportDao reportDao) throws IOException { | |||
| try { | |||
| // Load the FTL template | |||
| Configuration cfg = new Configuration(Configuration.VERSION_2_3_32); | |||
| cfg.setClassForTemplateLoading(WordReportService.class, "/"); | |||
| Template template = cfg.getTemplate(XML_TEST_TEMPLATE_PATH); | |||
| // Create a data model | |||
| Map<String, Object> dataModel = new HashMap<>(); | |||
| dataModel.put("reportDate", reportDao.getFormattedDateFrom()); | |||
| dataModel.put("monthTotal", String.format("%s (%d)", NumberUtils.convertToWord(reportDao.getCount().intValue()), reportDao.getCount().intValue())); | |||
| dataModel.put("catBreakDowns", reportDao.getCatBreakDowns()); | |||
| dataModel.put("compareStr1", reportDao.getCompareStr1()); | |||
| dataModel.put("compareStr2", reportDao.getCompareStr2()); | |||
| dataModel.put("finYear", reportDao.getFinYear()); | |||
| dataModel.put("yearTotal", reportDao.getYearlyCount()); | |||
| LinkedHashMap<String, Object> sbuYearTotal = new LinkedHashMap<>(); | |||
| for (Map<String,Object> record : reportDao.getYearlyRecordCount()) { | |||
| sbuYearTotal.put( | |||
| (String) record.get("name"), | |||
| (Long) record.get("count") | |||
| ); | |||
| } | |||
| dataModel.put("sbuYearTotal", sbuYearTotal); | |||
| int idx = 0; | |||
| List<Map<String, Object>> series = new ArrayList<>(); | |||
| for (AppreciationChartDataDao record : reportDao.getChartList()) { | |||
| Map<String, Object> serie = new HashMap<>(); | |||
| serie.put("serieName", record.getSbuName()); | |||
| serie.put("color", chartColors[idx % (chartColors.length - 1)]); | |||
| serie.put("data",record.getRecordByMonth()); | |||
| series.add(serie); | |||
| idx++; | |||
| } | |||
| dataModel.put("series", series); | |||
| dataModel.put("lnReceiptDateRange", reportDao.getLnReceiptDateRange()); | |||
| List<Map<String, Object>> aprRecords = new ArrayList<>(); | |||
| final DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("dd MMM YYYY"); | |||
| for (Map<String,Object> record : reportDao.getAppreciationRecordDetail()) { | |||
| aprRecords.add(Map.of( | |||
| "sbu", record.get("sbuNames") == null ? "" : record.get("sbuNames"), | |||
| "receiptDate", record.get("receiptDate") == null ? "" : ((LocalDateTime)record.get("receiptDate")).format(dateFormat), | |||
| "description", record.get("description") == null ? "" : record.get("description"), | |||
| "clientReplyDate", record.get("clientReplyDate") == null ? "" : ((LocalDateTime)record.get("clientReplyDate")).format(dateFormat), | |||
| "staffReplyDate", record.get("staffReplyDate") == null ? "" : ((LocalDateTime)record.get("staffReplyDate")).format(dateFormat), | |||
| "numOfStaff", record.get("noOfStaff") == null ? "" : record.get("noOfStaff").toString(), | |||
| "clientDeptOrg", !StringUtils.isBlank((String)record.get("clientDepartment")) ? record.get("clientDepartment") : record.get("clientOrganization") | |||
| )); | |||
| } | |||
| dataModel.put("aprRecords", aprRecords); | |||
| // Render the template with the data model | |||
| StringWriter writer = new StringWriter(); | |||
| template.process(dataModel, writer); | |||
| String output = writer.toString(); | |||
| byte[] byteArray = output.getBytes(); | |||
| return byteArray; | |||
| } catch (Exception e) { | |||
| e.printStackTrace(); | |||
| return null; // or handle the exception according to your requirements | |||
| } | |||
| } | |||
| public byte[] generateAppreciationSummaryXMLFile(String reportHeading, List<Map<String,Object>> reportRecord) throws IOException { | |||
| ClassPathResource resource = new ClassPathResource(APPRECIATION_SUMMARY_TEMPLATE_PATH); | |||
| InputStream templateInputStream = resource.getInputStream(); | |||
| try { | |||
| // Load the FTL template | |||
| Configuration cfg = new Configuration(Configuration.VERSION_2_3_32); | |||
| cfg.setClassForTemplateLoading(WordReportService.class, "/"); | |||
| Template template = cfg.getTemplate(APPRECIATION_SUMMARY_TEMPLATE_PATH); | |||
| // Create a data model | |||
| Map<String, Object> dataModel = new HashMap<>(); | |||
| dataModel.put("reportDate", reportHeading); | |||
| List<Map<String, Object>> aprRecords = new ArrayList<>(); | |||
| Integer count = 1; | |||
| for (Map<String,Object> record : reportRecord) { | |||
| aprRecords.add(Map.of( | |||
| "index", "(" + RomanConverter.convertToRoman(count, false) + ")", | |||
| "description", record.get("description"), | |||
| "category", record.get("category") | |||
| )); | |||
| count++; | |||
| } | |||
| dataModel.put("aprRecords", aprRecords); | |||
| // Render the template with the data model | |||
| StringWriter writer = new StringWriter(); | |||
| template.process(dataModel, writer); | |||
| String output = writer.toString(); | |||
| byte[] byteArray = output.getBytes(); | |||
| return byteArray; | |||
| } catch (Exception e) { | |||
| e.printStackTrace(); | |||
| return null; // or handle the exception according to your requirements | |||
| } finally { | |||
| templateInputStream.close(); | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,123 @@ | |||
| package com.ffii.lioner.modules.lioner.client.entity; | |||
| import java.time.LocalDate; | |||
| import java.time.LocalDateTime; | |||
| import com.ffii.core.entity.BaseEntity; | |||
| import jakarta.persistence.Column; | |||
| import jakarta.persistence.Entity; | |||
| import jakarta.persistence.Inheritance; | |||
| import jakarta.persistence.InheritanceType; | |||
| import jakarta.persistence.Table; | |||
| import jakarta.validation.constraints.NotBlank; | |||
| import jakarta.validation.constraints.NotNull; | |||
| /** @author Jason Lam */ | |||
| @Entity | |||
| @Table(name = "client") | |||
| @Inheritance(strategy=InheritanceType.TABLE_PER_CLASS) | |||
| public class Client extends BaseEntity<Long>{ | |||
| @NotBlank | |||
| @Column | |||
| private String fullname; | |||
| @Column | |||
| private String lastname; | |||
| @Column | |||
| private String firstname; | |||
| @Column | |||
| private String title; | |||
| @Column | |||
| private String email; | |||
| @Column | |||
| private String phone1; | |||
| @Column | |||
| private String phone2; | |||
| @Column | |||
| private String remarks; | |||
| @Column | |||
| private LocalDateTime joinDate; | |||
| public String getFullname() { | |||
| return fullname; | |||
| } | |||
| public void setFullname(String fullname) { | |||
| this.fullname = fullname; | |||
| } | |||
| public String getLastname() { | |||
| return lastname; | |||
| } | |||
| public void setLastname(String lastname) { | |||
| this.lastname = lastname; | |||
| } | |||
| public String getFirstname() { | |||
| return firstname; | |||
| } | |||
| public void setFirstname(String firstname) { | |||
| this.firstname = firstname; | |||
| } | |||
| public String getTitle() { | |||
| return title; | |||
| } | |||
| public void setTitle(String title) { | |||
| this.title = title; | |||
| } | |||
| public String getEmail() { | |||
| return email; | |||
| } | |||
| public void setEmail(String email) { | |||
| this.email = email; | |||
| } | |||
| public String getPhone1() { | |||
| return phone1; | |||
| } | |||
| public void setPhone1(String phone1) { | |||
| this.phone1 = phone1; | |||
| } | |||
| public String getPhone2() { | |||
| return phone2; | |||
| } | |||
| public void setPhone2(String phone2) { | |||
| this.phone2 = phone2; | |||
| } | |||
| public String getRemarks() { | |||
| return remarks; | |||
| } | |||
| public void setRemarks(String remarks) { | |||
| this.remarks = remarks; | |||
| } | |||
| public LocalDateTime getJoinDate() { | |||
| return joinDate; | |||
| } | |||
| public void setJoinDate(LocalDateTime joinDate) { | |||
| this.joinDate = joinDate; | |||
| } | |||
| } | |||
| @@ -0,0 +1,6 @@ | |||
| package com.ffii.lioner.modules.lioner.client.entity; | |||
| import com.ffii.core.support.AbstractRepository; | |||
| public interface ClientRepository extends AbstractRepository<Client, Long> { | |||
| } | |||
| @@ -0,0 +1,108 @@ | |||
| package com.ffii.lioner.modules.lioner.client.req; | |||
| import java.util.List; | |||
| import jakarta.validation.constraints.Size; | |||
| import java.time.LocalDate; | |||
| public class UpdateClientReq { | |||
| private Long id; | |||
| @Size(max = 500) | |||
| String fullname; | |||
| @Size(max = 255) | |||
| String lastname; | |||
| @Size(max = 255) | |||
| String firstname; | |||
| @Size(max = 50) | |||
| String title; | |||
| @Size(max = 500) | |||
| String email; | |||
| @Size(max = 50) | |||
| String phone1; | |||
| @Size(max = 50) | |||
| String phone2; | |||
| String remarks; | |||
| public Long getId() { | |||
| return id; | |||
| } | |||
| public void setId(Long id) { | |||
| this.id = id; | |||
| } | |||
| public String getFullname() { | |||
| return fullname; | |||
| } | |||
| public void setFullname(String fullname) { | |||
| this.fullname = fullname; | |||
| } | |||
| public String getLastname() { | |||
| return lastname; | |||
| } | |||
| public void setLastname(String lastname) { | |||
| this.lastname = lastname; | |||
| } | |||
| public String getFirstname() { | |||
| return firstname; | |||
| } | |||
| public void setFirstname(String firstname) { | |||
| this.firstname = firstname; | |||
| } | |||
| public String getTitle() { | |||
| return title; | |||
| } | |||
| public void setTitle(String title) { | |||
| this.title = title; | |||
| } | |||
| public String getEmail() { | |||
| return email; | |||
| } | |||
| public void setEmail(String email) { | |||
| this.email = email; | |||
| } | |||
| public String getPhone1() { | |||
| return phone1; | |||
| } | |||
| public void setPhone1(String phone1) { | |||
| this.phone1 = phone1; | |||
| } | |||
| public String getPhone2() { | |||
| return phone2; | |||
| } | |||
| public void setPhone2(String phone2) { | |||
| this.phone2 = phone2; | |||
| } | |||
| public String getRemarks() { | |||
| return remarks; | |||
| } | |||
| public void setRemarks(String remarks) { | |||
| this.remarks = remarks; | |||
| } | |||
| } | |||
| @@ -0,0 +1,252 @@ | |||
| package com.ffii.lioner.modules.lioner.client.web; | |||
| import java.io.IOException; | |||
| import java.time.LocalDate; | |||
| import java.util.List; | |||
| import java.util.Map; | |||
| import java.util.concurrent.TimeUnit; | |||
| import org.apache.commons.logging.Log; | |||
| import org.apache.commons.logging.LogFactory; | |||
| import org.springframework.core.io.ByteArrayResource; | |||
| import org.springframework.core.io.Resource; | |||
| import org.springframework.http.CacheControl; | |||
| import org.springframework.http.HttpStatus; | |||
| import org.springframework.http.ResponseEntity; | |||
| import org.springframework.transaction.annotation.Isolation; | |||
| import org.springframework.transaction.annotation.Transactional; | |||
| import org.springframework.web.bind.ServletRequestBindingException; | |||
| import org.springframework.web.bind.annotation.DeleteMapping; | |||
| import org.springframework.web.bind.annotation.GetMapping; | |||
| import org.springframework.web.bind.annotation.PathVariable; | |||
| import org.springframework.web.bind.annotation.PostMapping; | |||
| import org.springframework.web.bind.annotation.RequestBody; | |||
| import org.springframework.web.bind.annotation.RequestMapping; | |||
| import org.springframework.web.bind.annotation.RequestParam; | |||
| import org.springframework.web.bind.annotation.ResponseStatus; | |||
| import org.springframework.web.bind.annotation.RestController; | |||
| import com.ffii.lioner.modules.lioner.client.req.UpdateClientReq; | |||
| import com.ffii.lioner.modules.lioner.client.service.ClientService; | |||
| import aj.org.objectweb.asm.Type; | |||
| import com.ffii.lioner.modules.common.service.ExcelReportService; | |||
| import com.ffii.core.exception.NotFoundException; | |||
| import com.ffii.core.response.DataRes; | |||
| import com.ffii.core.response.RecordsRes; | |||
| import com.ffii.core.utils.CriteriaArgsBuilder; | |||
| import com.ffii.core.utils.Params; | |||
| import jakarta.servlet.http.HttpServletRequest; | |||
| import jakarta.validation.Valid; | |||
| @RestController | |||
| @RequestMapping("/client") | |||
| public class ClientController{ | |||
| private final Log logger = LogFactory.getLog(getClass()); | |||
| private ClientService clientService; | |||
| private ExcelReportService excelReportService; | |||
| public ClientController(ClientService clientService, ExcelReportService excelReportService) { | |||
| this.clientService = clientService; | |||
| this.excelReportService = excelReportService; | |||
| } | |||
| @GetMapping("/{id}") | |||
| public Map<String, Object> get(@PathVariable Long id) { | |||
| return Map.of( | |||
| Params.DATA, clientService.find(id).orElseThrow(NotFoundException::new) | |||
| // "eventDivision", clientService.getEventDivision(Map.of("id",id)) | |||
| ); | |||
| } | |||
| @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = false) | |||
| @PostMapping("/save") | |||
| public Map<String, Object> saveOrUpdate(@RequestBody @Valid UpdateClientReq req) { | |||
| return Map.of( | |||
| Params.DATA,clientService.saveOrUpdate(req) | |||
| ); | |||
| } | |||
| @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = false) | |||
| @DeleteMapping("/{id}") | |||
| @ResponseStatus(HttpStatus.NO_CONTENT) | |||
| public void delete(@PathVariable Long id) { | |||
| clientService.markDelete(clientService.find(id).get()); | |||
| // clientService.markDeleteWithAuditLog(clientService.find(id).get()); | |||
| } | |||
| @GetMapping | |||
| public RecordsRes<Map<String, Object>> list(HttpServletRequest request) throws ServletRequestBindingException { | |||
| return new RecordsRes<>(clientService.list( | |||
| CriteriaArgsBuilder.withRequest(request) | |||
| .addStringLike("fullname") | |||
| .addStringLike("lastname") | |||
| .addStringLike("firstname") | |||
| // .addStringLike("title") | |||
| .addStringLike("email") | |||
| .addStringLike("phone1") | |||
| .addStringLike("phone2") | |||
| .addDate("joinDateFrom") | |||
| .addDate("joinDateTo") | |||
| .addStringLike("remarks") | |||
| .build())); | |||
| } | |||
| // @GetMapping("/export") | |||
| // public ResponseEntity<Resource> getEventExport(HttpServletRequest request) throws ServletRequestBindingException, IOException { | |||
| // List<Map<String,Object>> record = clientService.listReport(CriteriaArgsBuilder.withRequest(request) | |||
| // .addStringLike("eventName") | |||
| // .addStringLike("description") | |||
| // .addStringLike("organization") | |||
| // .addDate("fromDate") | |||
| // .addDateTo("toDate") | |||
| // .addStringLike("region") | |||
| // .addStringLike("type") | |||
| // .addIntegerList("divisionIdList") | |||
| // .addIntegerList("subDivisionIdList") | |||
| // .addStringLike("keyword") | |||
| // .build()); | |||
| // byte[] reportResult = excelReportService.generateEventExcelReport(record); | |||
| // return ResponseEntity.ok() | |||
| // .header("filename", "event_export_" + LocalDate.now()) | |||
| // .body(new ByteArrayResource(reportResult)); | |||
| // } | |||
| // @GetMapping("/application") | |||
| // public RecordsRes<Map<String, Object>> listByEvent(HttpServletRequest request) | |||
| // throws ServletRequestBindingException { | |||
| // return new RecordsRes<>(clientService.listByEvent( | |||
| // CriteriaArgsBuilder.withRequest(request) | |||
| // .addInteger("eventId") | |||
| // .addInteger("userSubDivisionId") | |||
| // .build() | |||
| // )); | |||
| // } | |||
| // @GetMapping("/combo") | |||
| // public RecordsRes<Map<String, Object>> getEventCombo(HttpServletRequest request) throws ServletRequestBindingException { | |||
| // System.out.println(request); | |||
| // return new RecordsRes<>(clientService.listCombo( | |||
| // CriteriaArgsBuilder.withRequest(request) | |||
| // .addInteger("userSubDivisionId") | |||
| // .build())); | |||
| // } | |||
| // @GetMapping("/checkDuplicate") | |||
| // public Map<String, Boolean> checkDuplicate(@RequestParam String name, @RequestParam Long id) { | |||
| // boolean isNameTaken = clientService.isNameTaken(name,id); | |||
| // return Map.of( | |||
| // "isTaken", isNameTaken | |||
| // ); | |||
| // } | |||
| // @GetMapping("/checkOvertime/{id}") | |||
| // public Map<String, Object> checkOvertime(@PathVariable Long id) { | |||
| // return clientService.isReminderOvertime(id); | |||
| // } | |||
| // @GetMapping("/dashboard/combo") | |||
| // public RecordsRes<Map<String, Object>> getDashboardCombo() { | |||
| // return new RecordsRes<>(clientService.getDashboardCombo()); | |||
| // } | |||
| // @GetMapping("/dashboard/topRecord") | |||
| // public RecordsRes<Map<String, Object>> getTop6(HttpServletRequest request) throws ServletRequestBindingException { | |||
| // return new RecordsRes<>(clientService.getTop6( | |||
| // CriteriaArgsBuilder.withRequest(request) | |||
| // .addInteger("year") | |||
| // .addInteger("userSubDivisionId") | |||
| // .build() | |||
| // )); | |||
| // } | |||
| // @GetMapping("/dashboard/yearAward") | |||
| // public DataRes<Map<String, Object>> getYearAwardCount(HttpServletRequest request) throws ServletRequestBindingException { | |||
| // return new DataRes<>(clientService.getYearAwardCount( | |||
| // CriteriaArgsBuilder.withRequest(request) | |||
| // .addInteger("year") | |||
| // .addInteger("userSubDivisionId") | |||
| // .build() | |||
| // )); | |||
| // } | |||
| // @GetMapping("/dashboard/yearEvent") | |||
| // public DataRes<Map<String, Object>> getYearEventCount(HttpServletRequest request) throws ServletRequestBindingException { | |||
| // return new DataRes<>(clientService.getYearEventCount( | |||
| // CriteriaArgsBuilder.withRequest(request) | |||
| // .addInteger("year") | |||
| // .addInteger("userSubDivisionId") | |||
| // .build() | |||
| // )); | |||
| // } | |||
| // @GetMapping("/dashboard/yearDivisionSummary") | |||
| // public RecordsRes<Map<String, Object>> getDivisionAwardSummary(@RequestParam Integer year, @RequestParam String branch, @RequestParam Boolean viewDivisionOnly, @RequestParam Integer userSubDivisionId) { | |||
| // if(!viewDivisionOnly){ | |||
| // List<Map<String,Object>> tempRecords = clientService.getDivisionAwardSummary(Map.of("year",year, "branch", branch)); | |||
| // if(tempRecords.size() == 0){ | |||
| // tempRecords = clientService.getDummyDivisionAwardSummary(Map.of("year",year,"branch", branch)); | |||
| // } | |||
| // return new RecordsRes<>(tempRecords); | |||
| // } | |||
| // else{ | |||
| // List<Map<String,Object>> tempRecords = clientService.getDummyDivisionAwardSummary(Map.of("year",year,"userSubDivisionId",userSubDivisionId)); | |||
| // return new RecordsRes<>(tempRecords); | |||
| // } | |||
| // } | |||
| // @GetMapping("/dashboard/yearTagSummary") | |||
| // public RecordsRes<Map<String, Object>> getTagAwardSummary(HttpServletRequest request) throws ServletRequestBindingException { | |||
| // return new RecordsRes<>(clientService.getTagAwardSummary( | |||
| // CriteriaArgsBuilder.withRequest(request) | |||
| // .addInteger("year") | |||
| // .addInteger("userSubDivisionId") | |||
| // .build() | |||
| // )); | |||
| // } | |||
| // @GetMapping("/dashboard/yearCategorySummary") | |||
| // public RecordsRes<Map<String, Object>> getCategoryAwardSummary(HttpServletRequest request) throws ServletRequestBindingException { | |||
| // return new RecordsRes<>(clientService.getCategoryAwardSummary( | |||
| // CriteriaArgsBuilder.withRequest(request) | |||
| // .addInteger("year") | |||
| // .addInteger("userSubDivisionId") | |||
| // .build() | |||
| // )); | |||
| // } | |||
| // @GetMapping("/dashboard/awardSummaryReport") | |||
| // public ResponseEntity<Resource> getAwardSummaryReport(@RequestParam Integer year, | |||
| // @RequestParam Integer userSubDivisionId, | |||
| // @RequestParam String branch, | |||
| // @RequestParam Boolean viewDivisionOnly | |||
| // ) throws IOException { | |||
| // if(!viewDivisionOnly){ | |||
| // List<Map<String,Object>> result = clientService.getAwardSummaryReportPage1(Map.of("year",year, "branch", branch)); | |||
| // List<Map<String,Object>> detailResult = clientService.getAwardSummaryReportPage2(Map.of("year",year, "branch", branch)); | |||
| // byte[] reportResult = excelReportService.generateDashboardExcelReport(result, detailResult, year); | |||
| // return ResponseEntity.ok() | |||
| // .header("filename", "award_summary_report_" + LocalDate.now()) | |||
| // .body(new ByteArrayResource(reportResult)); | |||
| // } | |||
| // else{ | |||
| // List<Map<String,Object>> result = clientService.getAwardSummaryReportPage1(Map.of("year",year, "userSubDivisionId", userSubDivisionId)); | |||
| // List<Map<String,Object>> detailResult = clientService.getAwardSummaryReportPage2(Map.of("year",year, "userSubDivisionId", userSubDivisionId)); | |||
| // byte[] reportResult = excelReportService.generateDashboardExcelReport(result, detailResult, year); | |||
| // return ResponseEntity.ok() | |||
| // .header("filename", "award_summary_report_" + LocalDate.now()) | |||
| // .body(new ByteArrayResource(reportResult)); | |||
| // } | |||
| // } | |||
| } | |||
| @@ -0,0 +1,93 @@ | |||
| package com.ffii.lioner.modules.lioner.entity; | |||
| import java.time.LocalDateTime; | |||
| import com.ffii.core.entity.BaseEntity; | |||
| import jakarta.persistence.Column; | |||
| import jakarta.persistence.Entity; | |||
| import jakarta.persistence.Inheritance; | |||
| import jakarta.persistence.InheritanceType; | |||
| import jakarta.persistence.Table; | |||
| import jakarta.validation.constraints.NotBlank; | |||
| import jakarta.validation.constraints.NotNull; | |||
| /** @author Jason Lam */ | |||
| @Entity | |||
| @Table(name = "application") | |||
| @Inheritance(strategy=InheritanceType.TABLE_PER_CLASS) | |||
| public class Application extends BaseEntity<Long>{ | |||
| @NotNull | |||
| @Column | |||
| private Long eventId; | |||
| @NotBlank | |||
| @Column | |||
| private String name; | |||
| @Column | |||
| private String description; | |||
| @NotBlank | |||
| @Column | |||
| private String responsibleOfficer; | |||
| @NotBlank | |||
| @Column | |||
| private String status; | |||
| @Column | |||
| private LocalDateTime importDate; | |||
| public LocalDateTime getImportDate() { | |||
| return this.importDate; | |||
| } | |||
| public void setImportDate(LocalDateTime importDate) { | |||
| this.importDate = importDate; | |||
| } | |||
| public Long getEventId() { | |||
| return this.eventId; | |||
| } | |||
| public void setEventId(Long eventId) { | |||
| this.eventId = eventId; | |||
| } | |||
| public String getName() { | |||
| return this.name; | |||
| } | |||
| public void setName(String name) { | |||
| this.name = name; | |||
| } | |||
| public String getDescription() { | |||
| return this.description; | |||
| } | |||
| public void setDescription(String description) { | |||
| this.description = description; | |||
| } | |||
| public String getResponsibleOfficer() { | |||
| return this.responsibleOfficer; | |||
| } | |||
| public void setResponsibleOfficer(String responsibleOfficer) { | |||
| this.responsibleOfficer = responsibleOfficer; | |||
| } | |||
| public String getStatus() { | |||
| return this.status; | |||
| } | |||
| public void setStatus(String status) { | |||
| this.status = status; | |||
| } | |||
| } | |||
| @@ -0,0 +1,6 @@ | |||
| package com.ffii.lioner.modules.lioner.entity; | |||
| import com.ffii.core.support.AbstractRepository; | |||
| public interface ApplicationRepository extends AbstractRepository<Application, Long> { | |||
| } | |||
| @@ -0,0 +1,192 @@ | |||
| package com.ffii.lioner.modules.lioner.entity; | |||
| import java.time.LocalDate; | |||
| import java.time.LocalDateTime; | |||
| import com.ffii.core.entity.BaseEntity; | |||
| import jakarta.persistence.Column; | |||
| import jakarta.persistence.Entity; | |||
| import jakarta.persistence.Table; | |||
| import jakarta.validation.constraints.NotBlank; | |||
| import jakarta.validation.constraints.NotNull; | |||
| /** @author Jason Lam */ | |||
| @Entity | |||
| @Table(name = "apr_appreciation") | |||
| public class Appreciation extends BaseEntity<Long>{ | |||
| @NotBlank | |||
| @NotNull | |||
| @Column(columnDefinition = "JSON") | |||
| private String sbuIds; | |||
| @Column | |||
| @NotNull | |||
| private LocalDate receiptDate; | |||
| @Column | |||
| @NotNull | |||
| private String description; | |||
| @Column | |||
| private String clientDepartment; | |||
| @Column | |||
| private String clientOrganization; | |||
| @Column | |||
| private String clientFullname; | |||
| @Column | |||
| private String clientPost; | |||
| @Column | |||
| private String venue; | |||
| @Column | |||
| private LocalDate clientReplyDate; | |||
| @Column | |||
| private LocalDate staffReplyDate; | |||
| @Column | |||
| private Long noOfStaff; | |||
| @Column | |||
| @NotNull | |||
| private LocalDate lnReceiptDate; | |||
| @Column | |||
| @NotNull | |||
| private Long aprCategoryId; | |||
| @Column | |||
| private String remarks; | |||
| @Column | |||
| private LocalDateTime importDate; | |||
| public String getSbuIds() { | |||
| return this.sbuIds; | |||
| } | |||
| public void setSbuIds(String sbuIds) { | |||
| this.sbuIds = sbuIds; | |||
| } | |||
| public LocalDate getReceiptDate() { | |||
| return this.receiptDate; | |||
| } | |||
| public void setReceiptDate(LocalDate receiptDate) { | |||
| this.receiptDate = receiptDate; | |||
| } | |||
| public String getDescription() { | |||
| return this.description; | |||
| } | |||
| public void setDescription(String description) { | |||
| this.description = description; | |||
| } | |||
| public String getClientDepartment() { | |||
| return this.clientDepartment; | |||
| } | |||
| public void setClientDepartment(String clientDepartment) { | |||
| this.clientDepartment = clientDepartment; | |||
| } | |||
| public String getClientOrganization() { | |||
| return this.clientOrganization; | |||
| } | |||
| public void setClientOrganization(String clientOrganization) { | |||
| this.clientOrganization = clientOrganization; | |||
| } | |||
| public String getClientFullname() { | |||
| return this.clientFullname; | |||
| } | |||
| public void setClientFullname(String clientFullname) { | |||
| this.clientFullname = clientFullname; | |||
| } | |||
| public String getClientPost() { | |||
| return this.clientPost; | |||
| } | |||
| public void setClientPost(String clientPost) { | |||
| this.clientPost = clientPost; | |||
| } | |||
| public String getVenue() { | |||
| return this.venue; | |||
| } | |||
| public void setVenue(String venue) { | |||
| this.venue = venue; | |||
| } | |||
| public LocalDate getClientReplyDate() { | |||
| return this.clientReplyDate; | |||
| } | |||
| public void setClientReplyDate(LocalDate clientReplyDate) { | |||
| this.clientReplyDate = clientReplyDate; | |||
| } | |||
| public LocalDate getStaffReplyDate() { | |||
| return this.staffReplyDate; | |||
| } | |||
| public void setStaffReplyDate(LocalDate staffReplyDate) { | |||
| this.staffReplyDate = staffReplyDate; | |||
| } | |||
| public Long getNoOfStaff() { | |||
| return this.noOfStaff; | |||
| } | |||
| public void setNoOfStaff(Long noOfStaff) { | |||
| this.noOfStaff = noOfStaff; | |||
| } | |||
| public LocalDate getLnReceiptDate() { | |||
| return this.lnReceiptDate; | |||
| } | |||
| public void setLnReceiptDate(LocalDate lnReceiptDate) { | |||
| this.lnReceiptDate = lnReceiptDate; | |||
| } | |||
| public Long getAprCategoryId() { | |||
| return this.aprCategoryId; | |||
| } | |||
| public void setAprCategoryId(Long aprCategoryId) { | |||
| this.aprCategoryId = aprCategoryId; | |||
| } | |||
| public String getRemarks() { | |||
| return this.remarks; | |||
| } | |||
| public void setRemarks(String remarks) { | |||
| this.remarks = remarks; | |||
| } | |||
| public LocalDateTime getImportDate() { | |||
| return this.importDate; | |||
| } | |||
| public void setImportDate(LocalDateTime importDate) { | |||
| this.importDate = importDate; | |||
| } | |||
| } | |||
| @@ -0,0 +1,8 @@ | |||
| package com.ffii.lioner.modules.lioner.entity; | |||
| import com.ffii.core.support.AbstractRepository; | |||
| public interface AppreciationRepository extends AbstractRepository<Appreciation, Long> { | |||
| } | |||
| @@ -0,0 +1,149 @@ | |||
| package com.ffii.lioner.modules.lioner.entity; | |||
| import java.time.LocalDate; | |||
| import java.time.LocalDateTime; | |||
| import com.ffii.core.entity.BaseEntity; | |||
| import jakarta.persistence.Column; | |||
| import jakarta.persistence.Entity; | |||
| import jakarta.persistence.Inheritance; | |||
| import jakarta.persistence.InheritanceType; | |||
| import jakarta.persistence.Table; | |||
| import jakarta.validation.constraints.NotBlank; | |||
| import jakarta.validation.constraints.NotNull; | |||
| /** @author Jason Lam */ | |||
| @Entity | |||
| @Table(name = "award") | |||
| @Inheritance(strategy=InheritanceType.TABLE_PER_CLASS) | |||
| public class Award extends BaseEntity<Long>{ | |||
| @NotNull | |||
| @Column | |||
| private Long applicationId; | |||
| @NotBlank | |||
| @Column | |||
| private String name; | |||
| @Column | |||
| private String nameCht; | |||
| @Column | |||
| private String remarks; | |||
| @Column | |||
| private Long categoryId; | |||
| @Column | |||
| private LocalDate receiveDate; | |||
| @Column | |||
| private String storageLocation; | |||
| @Column | |||
| private String physicalAward; | |||
| @NotBlank | |||
| @Column | |||
| private String responsibleOfficer; | |||
| @Column(columnDefinition = "JSON") | |||
| private String promotionChannel; | |||
| @Column | |||
| private LocalDateTime importDate; | |||
| public LocalDateTime getImportDate() { | |||
| return this.importDate; | |||
| } | |||
| public void setImportDate(LocalDateTime importDate) { | |||
| this.importDate = importDate; | |||
| } | |||
| public Long getApplicationId() { | |||
| return this.applicationId; | |||
| } | |||
| public void setApplicationId(Long applicationId) { | |||
| this.applicationId = applicationId; | |||
| } | |||
| public String getName() { | |||
| return this.name; | |||
| } | |||
| public void setName(String name) { | |||
| this.name = name; | |||
| } | |||
| public String getNameCht() { | |||
| return this.nameCht; | |||
| } | |||
| public void setNameCht(String nameCht) { | |||
| this.nameCht = nameCht; | |||
| } | |||
| public String getRemarks() { | |||
| return this.remarks; | |||
| } | |||
| public void setRemarks(String remarks) { | |||
| this.remarks = remarks; | |||
| } | |||
| public Long getCategoryId() { | |||
| return this.categoryId; | |||
| } | |||
| public void setCategoryId(Long categoryId) { | |||
| this.categoryId = categoryId; | |||
| } | |||
| public LocalDate getReceiveDate() { | |||
| return this.receiveDate; | |||
| } | |||
| public void setReceiveDate(LocalDate receiveDate) { | |||
| this.receiveDate = receiveDate; | |||
| } | |||
| public String getStorageLocation() { | |||
| return this.storageLocation; | |||
| } | |||
| public void setStorageLocation(String storageLocation) { | |||
| this.storageLocation = storageLocation; | |||
| } | |||
| public String getPhysicalAward() { | |||
| return this.physicalAward; | |||
| } | |||
| public void setPhysicalAward(String physicalAward) { | |||
| this.physicalAward = physicalAward; | |||
| } | |||
| public String getResponsibleOfficer() { | |||
| return this.responsibleOfficer; | |||
| } | |||
| public void setResponsibleOfficer(String responsibleOfficer) { | |||
| this.responsibleOfficer = responsibleOfficer; | |||
| } | |||
| public String getPromotionChannel() { | |||
| return this.promotionChannel; | |||
| } | |||
| public void setPromotionChannel(String promotionChannel) { | |||
| this.promotionChannel = promotionChannel; | |||
| } | |||
| } | |||
| @@ -0,0 +1,8 @@ | |||
| package com.ffii.lioner.modules.lioner.entity; | |||
| import com.ffii.core.support.AbstractRepository; | |||
| public interface AwardRepository extends AbstractRepository<Award, Long> { | |||
| } | |||
| @@ -0,0 +1,253 @@ | |||
| package com.ffii.lioner.modules.lioner.entity; | |||
| import java.time.LocalDate; | |||
| import java.time.LocalDateTime; | |||
| import com.ffii.core.entity.BaseEntity; | |||
| import jakarta.persistence.Column; | |||
| import jakarta.persistence.Entity; | |||
| import jakarta.persistence.Inheritance; | |||
| import jakarta.persistence.InheritanceType; | |||
| import jakarta.persistence.Table; | |||
| import jakarta.validation.constraints.NotBlank; | |||
| import jakarta.validation.constraints.NotNull; | |||
| /** @author Jason Lam */ | |||
| @Entity | |||
| @Table(name = "event") | |||
| @Inheritance(strategy=InheritanceType.TABLE_PER_CLASS) | |||
| public class Event extends BaseEntity<Long>{ | |||
| @NotBlank | |||
| @Column | |||
| private String name; | |||
| @Column | |||
| private String nameCht; | |||
| @Column | |||
| private String description; | |||
| @NotBlank | |||
| @Column | |||
| private String region; | |||
| @NotBlank | |||
| @Column | |||
| private String organization; | |||
| @NotBlank | |||
| @Column | |||
| private String eventType; | |||
| @Column | |||
| private String frequency; | |||
| @Column | |||
| private String series; | |||
| @Column | |||
| @NotNull | |||
| private LocalDate startDate; | |||
| @Column | |||
| private LocalDate eventFrom; | |||
| @Column | |||
| private LocalDate eventTo; | |||
| @Column | |||
| @NotNull | |||
| private LocalDate applicationDeadline; | |||
| @Column | |||
| private LocalDate nextApplicationDate; | |||
| @Column | |||
| private LocalDate announcementDate; | |||
| @Column | |||
| private LocalDate awardDate; | |||
| @Column | |||
| private Boolean reminderFlag; | |||
| @Column | |||
| private Long reminderThreshold; | |||
| @Column | |||
| private Long reminderInterval; | |||
| @Column | |||
| private Long reminderLimit; | |||
| @Column | |||
| private LocalDateTime importDate; | |||
| public LocalDateTime getImportDate() { | |||
| return this.importDate; | |||
| } | |||
| public void setImportDate(LocalDateTime importDate) { | |||
| this.importDate = importDate; | |||
| } | |||
| public String getName() { | |||
| return this.name; | |||
| } | |||
| public void setName(String name) { | |||
| this.name = name; | |||
| } | |||
| public String getNameCht() { | |||
| return this.nameCht; | |||
| } | |||
| public void setNameCht(String nameCht) { | |||
| this.nameCht = nameCht; | |||
| } | |||
| public String getDescription() { | |||
| return this.description; | |||
| } | |||
| public void setDescription(String description) { | |||
| this.description = description; | |||
| } | |||
| public String getRegion() { | |||
| return this.region; | |||
| } | |||
| public void setRegion(String region) { | |||
| this.region = region; | |||
| } | |||
| public String getOrganization() { | |||
| return this.organization; | |||
| } | |||
| public void setOrganization(String organization) { | |||
| this.organization = organization; | |||
| } | |||
| public String getEventType() { | |||
| return this.eventType; | |||
| } | |||
| public void setEventType(String eventType) { | |||
| this.eventType = eventType; | |||
| } | |||
| public String getFrequency() { | |||
| return this.frequency; | |||
| } | |||
| public void setFrequency(String frequency) { | |||
| this.frequency = frequency; | |||
| } | |||
| public String getSeries() { | |||
| return this.series; | |||
| } | |||
| public void setSeries(String series) { | |||
| this.series = series; | |||
| } | |||
| public LocalDate getStartDate() { | |||
| return this.startDate; | |||
| } | |||
| public void setStartDate(LocalDate startDate) { | |||
| this.startDate = startDate; | |||
| } | |||
| public LocalDate getEventFrom() { | |||
| return this.eventFrom; | |||
| } | |||
| public void setEventFrom(LocalDate eventFrom) { | |||
| this.eventFrom = eventFrom; | |||
| } | |||
| public LocalDate getEventTo() { | |||
| return this.eventTo; | |||
| } | |||
| public void setEventTo(LocalDate eventTo) { | |||
| this.eventTo = eventTo; | |||
| } | |||
| public LocalDate getApplicationDeadline() { | |||
| return this.applicationDeadline; | |||
| } | |||
| public void setApplicationDeadline(LocalDate applicationDeadline) { | |||
| this.applicationDeadline = applicationDeadline; | |||
| } | |||
| public LocalDate getNextApplicationDate() { | |||
| return this.nextApplicationDate; | |||
| } | |||
| public void setNextApplicationDate(LocalDate nextApplicationDate) { | |||
| this.nextApplicationDate = nextApplicationDate; | |||
| } | |||
| public LocalDate getAnnouncementDate() { | |||
| return this.announcementDate; | |||
| } | |||
| public void setAnnouncementDate(LocalDate announcementDate) { | |||
| this.announcementDate = announcementDate; | |||
| } | |||
| public LocalDate getAwardDate() { | |||
| return this.awardDate; | |||
| } | |||
| public void setAwardDate(LocalDate awardDate) { | |||
| this.awardDate = awardDate; | |||
| } | |||
| public Boolean isReminderFlag() { | |||
| return this.reminderFlag; | |||
| } | |||
| public Boolean getReminderFlag() { | |||
| return this.reminderFlag; | |||
| } | |||
| public void setReminderFlag(Boolean reminderFlag) { | |||
| this.reminderFlag = reminderFlag; | |||
| } | |||
| public Long getReminderThreshold() { | |||
| return this.reminderThreshold; | |||
| } | |||
| public void setReminderThreshold(Long reminderThreshold) { | |||
| this.reminderThreshold = reminderThreshold; | |||
| } | |||
| public Long getReminderInterval() { | |||
| return this.reminderInterval; | |||
| } | |||
| public void setReminderInterval(Long reminderInterval) { | |||
| this.reminderInterval = reminderInterval; | |||
| } | |||
| public Long getReminderLimit() { | |||
| return this.reminderLimit; | |||
| } | |||
| public void setReminderLimit(Long reminderLimit) { | |||
| this.reminderLimit = reminderLimit; | |||
| } | |||
| } | |||
| @@ -0,0 +1,6 @@ | |||
| package com.ffii.lioner.modules.lioner.entity; | |||
| import com.ffii.core.support.AbstractRepository; | |||
| public interface EventRepository extends AbstractRepository<Event, Long> { | |||
| } | |||
| @@ -0,0 +1,85 @@ | |||
| package com.ffii.lioner.modules.lioner.entity; | |||
| import com.ffii.core.entity.BaseEntity; | |||
| import jakarta.persistence.Column; | |||
| import jakarta.persistence.Entity; | |||
| import jakarta.persistence.Table; | |||
| import jakarta.validation.constraints.NotBlank; | |||
| /** @author Jason Lam */ | |||
| @Entity | |||
| @Table(name = "file") | |||
| public class File extends BaseEntity<Long>{ | |||
| @NotBlank | |||
| @Column | |||
| private String filename; | |||
| @NotBlank | |||
| @Column | |||
| private String skey; | |||
| @Column | |||
| private String extension; | |||
| @Column | |||
| private String mimetype; | |||
| @Column | |||
| private Long filesize; | |||
| @Column | |||
| private String remarks; | |||
| public String getFilename() { | |||
| return this.filename; | |||
| } | |||
| public void setFilename(String filename) { | |||
| this.filename = filename; | |||
| } | |||
| public String getSkey() { | |||
| return this.skey; | |||
| } | |||
| public void setSkey(String skey) { | |||
| this.skey = skey; | |||
| } | |||
| public String getExtension() { | |||
| return this.extension; | |||
| } | |||
| public void setExtension(String extension) { | |||
| this.extension = extension; | |||
| } | |||
| public String getMimetype() { | |||
| return this.mimetype; | |||
| } | |||
| public void setMimetype(String mimetype) { | |||
| this.mimetype = mimetype; | |||
| } | |||
| public Long getFilesize() { | |||
| return this.filesize; | |||
| } | |||
| public void setFilesize(Long filesize) { | |||
| this.filesize = filesize; | |||
| } | |||
| public String getRemarks() { | |||
| return this.remarks; | |||
| } | |||
| public void setRemarks(String remarks) { | |||
| this.remarks = remarks; | |||
| } | |||
| } | |||
| @@ -0,0 +1,47 @@ | |||
| package com.ffii.lioner.modules.lioner.entity; | |||
| import com.ffii.core.entity.BaseEntity; | |||
| import jakarta.persistence.Column; | |||
| import jakarta.persistence.Entity; | |||
| import jakarta.persistence.Table; | |||
| /** @author Jason Lam */ | |||
| @Entity | |||
| @Table(name = "file_blob") | |||
| public class FileBlob extends BaseEntity<Long>{ | |||
| @Column | |||
| private Long id; | |||
| @Column | |||
| private Long fileId; | |||
| @Column | |||
| private byte[] bytes; | |||
| public Long getFileId() { | |||
| return this.fileId; | |||
| } | |||
| public void setFileId(Long fileId) { | |||
| this.fileId = fileId; | |||
| } | |||
| public byte[] getBytes() { | |||
| return this.bytes; | |||
| } | |||
| public void setBytes(byte[] bytes) { | |||
| this.bytes = bytes; | |||
| } | |||
| public Long getId() { | |||
| return this.id; | |||
| } | |||
| public void setId(Long id) { | |||
| this.id = id; | |||
| } | |||
| } | |||
| @@ -0,0 +1,11 @@ | |||
| package com.ffii.lioner.modules.lioner.entity; | |||
| import java.util.Optional; | |||
| import org.springframework.data.repository.query.Param; | |||
| import com.ffii.core.support.AbstractRepository; | |||
| public interface FileBlobRepository extends AbstractRepository<FileBlob, Long> { | |||
| Optional<FileBlob> findByFileId(@Param("fileId") Long fileId); | |||
| } | |||
| @@ -0,0 +1,98 @@ | |||
| package com.ffii.lioner.modules.lioner.entity; | |||
| import java.time.LocalDateTime; | |||
| import com.ffii.core.entity.BaseEntity; | |||
| import jakarta.persistence.Column; | |||
| import jakarta.persistence.Entity; | |||
| import jakarta.persistence.Table; | |||
| import jakarta.validation.constraints.NotBlank; | |||
| import jakarta.validation.constraints.NotNull; | |||
| /** @author Jason Lam */ | |||
| @Entity | |||
| @Table(name = "file_ref") | |||
| public class FileRef extends BaseEntity<Long>{ | |||
| @Column | |||
| private Long fileId; | |||
| @Column | |||
| private String refCode; | |||
| @NotNull | |||
| @Column | |||
| private Long refId; | |||
| @NotBlank | |||
| @Column | |||
| private String refType; | |||
| @Column | |||
| private String remarks; | |||
| @Column | |||
| private Long thumbnailFileId; | |||
| @Column | |||
| private LocalDateTime importDate; | |||
| public Long getFileId() { | |||
| return this.fileId; | |||
| } | |||
| public void setFileId(Long fileId) { | |||
| this.fileId = fileId; | |||
| } | |||
| public String getRefCode() { | |||
| return this.refCode; | |||
| } | |||
| public void setRefCode(String refCode) { | |||
| this.refCode = refCode; | |||
| } | |||
| public Long getRefId() { | |||
| return this.refId; | |||
| } | |||
| public void setRefId(Long refId) { | |||
| this.refId = refId; | |||
| } | |||
| public String getRefType() { | |||
| return this.refType; | |||
| } | |||
| public void setRefType(String refType) { | |||
| this.refType = refType; | |||
| } | |||
| public String getRemarks() { | |||
| return this.remarks; | |||
| } | |||
| public void setRemarks(String remarks) { | |||
| this.remarks = remarks; | |||
| } | |||
| public Long getThumbnailFileId() { | |||
| return this.thumbnailFileId; | |||
| } | |||
| public void setThumbnailFileId(Long thumbnailFileId) { | |||
| this.thumbnailFileId = thumbnailFileId; | |||
| } | |||
| public LocalDateTime getImportDate() { | |||
| return importDate; | |||
| } | |||
| public void setImportDate(LocalDateTime importDate) { | |||
| this.importDate = importDate; | |||
| } | |||
| } | |||
| @@ -0,0 +1,6 @@ | |||
| package com.ffii.lioner.modules.lioner.entity; | |||
| import com.ffii.core.support.AbstractRepository; | |||
| public interface FileRefRepository extends AbstractRepository<FileRef, Long> { | |||
| } | |||
| @@ -0,0 +1,5 @@ | |||
| package com.ffii.lioner.modules.lioner.entity; | |||
| import com.ffii.core.support.AbstractRepository; | |||
| public interface FileRepository extends AbstractRepository<File, Long> { | |||
| } | |||
| @@ -0,0 +1,41 @@ | |||
| package com.ffii.lioner.modules.lioner.entity; | |||
| import java.util.List; | |||
| import jakarta.persistence.Entity; | |||
| public class ImpApplication extends Application { | |||
| //For import use | |||
| private Long tempId; | |||
| private List<Long> tempSubDivIdList; | |||
| private List<Long> tempTagIdList; | |||
| public Long getTempId() { | |||
| return this.tempId; | |||
| } | |||
| public void setTempId(Long tempId) { | |||
| this.tempId = tempId; | |||
| } | |||
| public List<Long> getTempSubDivIdList() { | |||
| return this.tempSubDivIdList; | |||
| } | |||
| public void setTempSubDivIdList(List<Long> tempSubDivIdList) { | |||
| this.tempSubDivIdList = tempSubDivIdList; | |||
| } | |||
| public List<Long> getTempTagIdList() { | |||
| return this.tempTagIdList; | |||
| } | |||
| public void setTempTagIdList(List<Long> tempTagIdList) { | |||
| this.tempTagIdList = tempTagIdList; | |||
| } | |||
| } | |||
| @@ -0,0 +1,49 @@ | |||
| package com.ffii.lioner.modules.lioner.entity; | |||
| import java.util.List; | |||
| import jakarta.persistence.Entity; | |||
| public class ImpAward extends Award { | |||
| //For import use | |||
| private Long tempId; | |||
| private List<Long> tempSubDivIdList; | |||
| private List<Long> tempChannelIdList; | |||
| private String tempExternalLink; | |||
| public Long getTempId() { | |||
| return this.tempId; | |||
| } | |||
| public void setTempId(Long tempId) { | |||
| this.tempId = tempId; | |||
| } | |||
| public List<Long> getTempSubDivIdList() { | |||
| return this.tempSubDivIdList; | |||
| } | |||
| public void setTempSubDivIdList(List<Long> tempSubDivIdList) { | |||
| this.tempSubDivIdList = tempSubDivIdList; | |||
| } | |||
| public List<Long> getTempChannelIdList() { | |||
| return this.tempChannelIdList; | |||
| } | |||
| public void setTempChannelIdList(List<Long> tempChannelIdList) { | |||
| this.tempChannelIdList = tempChannelIdList; | |||
| } | |||
| public String getTempExternalLink() { | |||
| return this.tempExternalLink; | |||
| } | |||
| public void setTempExternalLink(String tempExternalLink) { | |||
| this.tempExternalLink = tempExternalLink; | |||
| } | |||
| } | |||
| @@ -0,0 +1,26 @@ | |||
| package com.ffii.lioner.modules.lioner.entity; | |||
| import java.util.List; | |||
| public class ImpEvent extends Event { | |||
| private Long tempId; | |||
| private List<Long> tempIdList; | |||
| public Long getTempId() { | |||
| return tempId; | |||
| } | |||
| public void setTempId(Long tempId) { | |||
| this.tempId = tempId; | |||
| } | |||
| public List<Long> getTempIdList() { | |||
| return tempIdList; | |||
| } | |||
| public void setTempIdList(List<Long> tempIdList) { | |||
| this.tempIdList = tempIdList; | |||
| } | |||
| } | |||
| @@ -0,0 +1,67 @@ | |||
| package com.ffii.lioner.modules.lioner.entity; | |||
| import com.ffii.core.entity.BaseEntity; | |||
| import jakarta.persistence.Column; | |||
| import jakarta.persistence.Entity; | |||
| import jakarta.persistence.Table; | |||
| import jakarta.validation.constraints.NotBlank; | |||
| import jakarta.validation.constraints.NotNull; | |||
| import java.util.Objects; | |||
| /** @author Jason Lam */ | |||
| @Entity | |||
| @Table(name = "search_criteria_template") | |||
| public class SearchCriteriaTemplate extends BaseEntity<Long>{ | |||
| @NotNull | |||
| @Column | |||
| private Long userId; | |||
| @NotBlank | |||
| @Column | |||
| private String name; | |||
| @NotBlank | |||
| @Column | |||
| private String module; | |||
| @NotBlank | |||
| @Column(columnDefinition = "JSON") | |||
| private String criteria; | |||
| public Long getUserId() { | |||
| return this.userId; | |||
| } | |||
| public void setUserId(Long userId) { | |||
| this.userId = userId; | |||
| } | |||
| public String getName() { | |||
| return this.name; | |||
| } | |||
| public void setName(String name) { | |||
| this.name = name; | |||
| } | |||
| public String getModule() { | |||
| return this.module; | |||
| } | |||
| public void setModule(String module) { | |||
| this.module = module; | |||
| } | |||
| public String getCriteria() { | |||
| return this.criteria; | |||
| } | |||
| public void setCriteria(String criteria) { | |||
| this.criteria = criteria; | |||
| } | |||
| } | |||
| @@ -0,0 +1,6 @@ | |||
| package com.ffii.lioner.modules.lioner.entity; | |||
| import com.ffii.core.support.AbstractRepository; | |||
| public interface SearchCriteriaTemplateRepository extends AbstractRepository<SearchCriteriaTemplate, Long> { | |||
| } | |||
| @@ -0,0 +1,111 @@ | |||
| package com.ffii.lioner.modules.lioner.entity; | |||
| import java.time.LocalDateTime; | |||
| import com.ffii.core.entity.BaseEntity; | |||
| import jakarta.persistence.Column; | |||
| import jakarta.persistence.Entity; | |||
| import jakarta.persistence.Table; | |||
| import jakarta.validation.constraints.NotBlank; | |||
| import jakarta.validation.constraints.NotNull; | |||
| /** @author Jason Lam */ | |||
| @Entity | |||
| @Table(name = "todo_reminder") | |||
| public class TodoReminder extends BaseEntity<Long>{ | |||
| @Column | |||
| private Long id; | |||
| @NotNull | |||
| @Column | |||
| private Long eventId; | |||
| @NotNull | |||
| @Column | |||
| private Long subDivisionId; | |||
| @NotBlank | |||
| @Column | |||
| private String title; | |||
| @NotBlank | |||
| @Column | |||
| private String message; | |||
| @Column | |||
| private Long suppressedBy; | |||
| @Column | |||
| private LocalDateTime suppressedDate; | |||
| @Column | |||
| private String reminderType; | |||
| public Long getId() { | |||
| return this.id; | |||
| } | |||
| public void setId(Long id) { | |||
| this.id = id; | |||
| } | |||
| public Long getEventId() { | |||
| return this.eventId; | |||
| } | |||
| public void setEventId(Long eventId) { | |||
| this.eventId = eventId; | |||
| } | |||
| public Long getSubDivisionId() { | |||
| return this.subDivisionId; | |||
| } | |||
| public void setSubDivisionId(Long subDivisionId) { | |||
| this.subDivisionId = subDivisionId; | |||
| } | |||
| public String getTitle() { | |||
| return this.title; | |||
| } | |||
| public void setTitle(String title) { | |||
| this.title = title; | |||
| } | |||
| public String getMessage() { | |||
| return this.message; | |||
| } | |||
| public void setMessage(String message) { | |||
| this.message = message; | |||
| } | |||
| public Long getSuppressedBy() { | |||
| return this.suppressedBy; | |||
| } | |||
| public void setSuppressedBy(Long suppressedBy) { | |||
| this.suppressedBy = suppressedBy; | |||
| } | |||
| public LocalDateTime getSuppressedDate() { | |||
| return this.suppressedDate; | |||
| } | |||
| public void setSuppressedDate(LocalDateTime suppressedDate) { | |||
| this.suppressedDate = suppressedDate; | |||
| } | |||
| public String getReminderType() { | |||
| return this.reminderType; | |||
| } | |||
| public void setReminderType(String reminderType) { | |||
| this.reminderType = reminderType; | |||
| } | |||
| } | |||
| @@ -0,0 +1,141 @@ | |||
| package com.ffii.lioner.modules.lioner.entity; | |||
| import java.time.LocalDateTime; | |||
| import com.ffii.core.entity.BaseEntity; | |||
| import jakarta.persistence.Column; | |||
| import jakarta.persistence.Entity; | |||
| import jakarta.persistence.Table; | |||
| import jakarta.validation.constraints.NotNull; | |||
| /** @author Jason Lam */ | |||
| @Entity | |||
| @Table(name = "todo_reminder_email_log") | |||
| public class TodoReminderEmailLog extends BaseEntity<Long>{ | |||
| @NotNull | |||
| @Column | |||
| private Long id; | |||
| @NotNull | |||
| @Column | |||
| private Long eventId; | |||
| @NotNull | |||
| @Column | |||
| private Long subDivisionId; | |||
| @NotNull | |||
| @Column | |||
| private Long userId; | |||
| @NotNull | |||
| @Column | |||
| private LocalDateTime sendDate; | |||
| @NotNull | |||
| @Column | |||
| private Boolean success; | |||
| @Column | |||
| private String response; | |||
| @NotNull | |||
| @Column | |||
| private String reminderType; | |||
| @NotNull | |||
| @Column | |||
| private String content; | |||
| @NotNull | |||
| @Column | |||
| private String resendSuccess; | |||
| public Long getId() { | |||
| return this.id; | |||
| } | |||
| public void setId(Long id) { | |||
| this.id = id; | |||
| } | |||
| public Long getEventId() { | |||
| return this.eventId; | |||
| } | |||
| public void setEventId(Long eventId) { | |||
| this.eventId = eventId; | |||
| } | |||
| public Long getSubDivisionId() { | |||
| return this.subDivisionId; | |||
| } | |||
| public void setSubDivisionId(Long subDivisionId) { | |||
| this.subDivisionId = subDivisionId; | |||
| } | |||
| public Long getUserId() { | |||
| return this.userId; | |||
| } | |||
| public void setUserId(Long userId) { | |||
| this.userId = userId; | |||
| } | |||
| public LocalDateTime getSendDate() { | |||
| return this.sendDate; | |||
| } | |||
| public void setSendDate(LocalDateTime sendDate) { | |||
| this.sendDate = sendDate; | |||
| } | |||
| public Boolean isSuccess() { | |||
| return this.success; | |||
| } | |||
| public Boolean getSuccess() { | |||
| return this.success; | |||
| } | |||
| public void setSuccess(Boolean success) { | |||
| this.success = success; | |||
| } | |||
| public String getResponse() { | |||
| return this.response; | |||
| } | |||
| public void setResponse(String response) { | |||
| this.response = response; | |||
| } | |||
| public String getReminderType() { | |||
| return this.reminderType; | |||
| } | |||
| public void setReminderType(String reminderType) { | |||
| this.reminderType = reminderType; | |||
| } | |||
| public String getContent() { | |||
| return this.content; | |||
| } | |||
| public void setContent(String content) { | |||
| this.content = content; | |||
| } | |||
| public String getResendSuccess() { | |||
| return this.resendSuccess; | |||
| } | |||
| public void setResendSuccess(String resendSuccess) { | |||
| this.resendSuccess = resendSuccess; | |||
| } | |||
| } | |||
| @@ -0,0 +1,6 @@ | |||
| package com.ffii.lioner.modules.lioner.entity; | |||
| import com.ffii.core.support.AbstractRepository; | |||
| public interface TodoReminderEmailLogRepository extends AbstractRepository<TodoReminderEmailLog, Long> { | |||
| } | |||
| @@ -0,0 +1,66 @@ | |||
| package com.ffii.lioner.modules.lioner.entity; | |||
| import java.time.LocalDateTime; | |||
| import com.ffii.core.entity.BaseEntity; | |||
| import jakarta.persistence.Column; | |||
| import jakarta.persistence.Entity; | |||
| import jakarta.persistence.Table; | |||
| import jakarta.validation.constraints.NotNull; | |||
| /** @author Jason Lam */ | |||
| @Entity | |||
| @Table(name = "todo_reminder_noted") | |||
| public class TodoReminderNoted extends BaseEntity<Long>{ | |||
| @NotNull | |||
| @Column | |||
| private Long id; | |||
| @NotNull | |||
| @Column | |||
| private Long todoReminderId; | |||
| @NotNull | |||
| @Column | |||
| private Long userId; | |||
| @NotNull | |||
| @Column | |||
| private LocalDateTime notedDate; | |||
| public Long getId() { | |||
| return this.id; | |||
| } | |||
| public void setId(Long id) { | |||
| this.id = id; | |||
| } | |||
| public Long getTodoReminderId() { | |||
| return this.todoReminderId; | |||
| } | |||
| public void setTodoReminderId(Long todoReminderId) { | |||
| this.todoReminderId = todoReminderId; | |||
| } | |||
| public Long getUserId() { | |||
| return this.userId; | |||
| } | |||
| public void setUserId(Long userId) { | |||
| this.userId = userId; | |||
| } | |||
| public LocalDateTime getNotedDate() { | |||
| return this.notedDate; | |||
| } | |||
| public void setNotedDate(LocalDateTime notedDate) { | |||
| this.notedDate = notedDate; | |||
| } | |||
| } | |||
| @@ -0,0 +1,6 @@ | |||
| package com.ffii.lioner.modules.lioner.entity; | |||
| import com.ffii.core.support.AbstractRepository; | |||
| public interface TodoReminderNotedRepository extends AbstractRepository<TodoReminderNoted, Long> { | |||
| } | |||
| @@ -0,0 +1,6 @@ | |||
| package com.ffii.lioner.modules.lioner.entity; | |||
| import com.ffii.core.support.AbstractRepository; | |||
| public interface TodoReminderRepository extends AbstractRepository<TodoReminder, Long> { | |||
| } | |||