Sfoglia il codice sorgente

init commit

master
kelvinsuen 2 mesi fa
commit
1c35b731fb
100 ha cambiato i file con 9439 aggiunte e 0 eliminazioni
  1. +37
    -0
      .gitignore
  2. +55
    -0
      README.md
  3. +69
    -0
      build.gradle
  4. BIN
      gradle/wrapper/gradle-wrapper.jar
  5. +6
    -0
      gradle/wrapper/gradle-wrapper.properties
  6. +240
    -0
      gradlew
  7. +91
    -0
      gradlew.bat
  8. +1
    -0
      settings.gradle
  9. +124
    -0
      src/main/java/com/ffii/core/entity/BaseEntity.java
  10. +49
    -0
      src/main/java/com/ffii/core/entity/IdEntity.java
  11. +15
    -0
      src/main/java/com/ffii/core/exception/BadRequestException.java
  12. +16
    -0
      src/main/java/com/ffii/core/exception/ConflictException.java
  13. +19
    -0
      src/main/java/com/ffii/core/exception/InternalServerErrorException.java
  14. +13
    -0
      src/main/java/com/ffii/core/exception/NotFoundException.java
  15. +37
    -0
      src/main/java/com/ffii/core/exception/UnprocessableEntityException.java
  16. +32
    -0
      src/main/java/com/ffii/core/response/AuthRes.java
  17. +21
    -0
      src/main/java/com/ffii/core/response/DataRes.java
  18. +36
    -0
      src/main/java/com/ffii/core/response/ErrorRes.java
  19. +39
    -0
      src/main/java/com/ffii/core/response/FailureRes.java
  20. +21
    -0
      src/main/java/com/ffii/core/response/IdRes.java
  21. +41
    -0
      src/main/java/com/ffii/core/response/RecordsRes.java
  22. +42
    -0
      src/main/java/com/ffii/core/support/AbstractBaseEntityService.java
  23. +65
    -0
      src/main/java/com/ffii/core/support/AbstractIdEntityService.java
  24. +16
    -0
      src/main/java/com/ffii/core/support/AbstractRepository.java
  25. +15
    -0
      src/main/java/com/ffii/core/support/AbstractService.java
  26. +44
    -0
      src/main/java/com/ffii/core/support/ErrorHandler.java
  27. +463
    -0
      src/main/java/com/ffii/core/support/JdbcDao.java
  28. +85
    -0
      src/main/java/com/ffii/core/utils/AES.java
  29. +33
    -0
      src/main/java/com/ffii/core/utils/BeanUtils.java
  30. +254
    -0
      src/main/java/com/ffii/core/utils/CriteriaArgsBuilder.java
  31. +778
    -0
      src/main/java/com/ffii/core/utils/ExcelUtils.java
  32. +125
    -0
      src/main/java/com/ffii/core/utils/FileUtils.java
  33. +47
    -0
      src/main/java/com/ffii/core/utils/JsonUtils.java
  34. +114
    -0
      src/main/java/com/ffii/core/utils/JwtTokenUtil.java
  35. +42
    -0
      src/main/java/com/ffii/core/utils/LocaleUtils.java
  36. +35
    -0
      src/main/java/com/ffii/core/utils/MapUtils.java
  37. +45
    -0
      src/main/java/com/ffii/core/utils/NumberUtils.java
  38. +42
    -0
      src/main/java/com/ffii/core/utils/Params.java
  39. +111
    -0
      src/main/java/com/ffii/core/utils/PasswordUtils.java
  40. +24
    -0
      src/main/java/com/ffii/core/utils/RomanConverter.java
  41. +83
    -0
      src/main/java/com/ffii/core/utils/StringUtils.java
  42. +13
    -0
      src/main/java/com/ffii/lioner/LionerApplication.java
  43. +36
    -0
      src/main/java/com/ffii/lioner/config/AppConfig.java
  44. +15
    -0
      src/main/java/com/ffii/lioner/config/RestTemplateConfig.java
  45. +33
    -0
      src/main/java/com/ffii/lioner/config/WebConfig.java
  46. +87
    -0
      src/main/java/com/ffii/lioner/config/security/SecurityConfig.java
  47. +78
    -0
      src/main/java/com/ffii/lioner/config/security/jwt/JwtRequestFilter.java
  48. +31
    -0
      src/main/java/com/ffii/lioner/config/security/jwt/service/JwtUserDetailsService.java
  49. +226
    -0
      src/main/java/com/ffii/lioner/config/security/jwt/web/JwtAuthenticationController.java
  50. +56
    -0
      src/main/java/com/ffii/lioner/config/security/service/LoginLogService.java
  51. +13
    -0
      src/main/java/com/ffii/lioner/model/AbilityModel.java
  52. +28
    -0
      src/main/java/com/ffii/lioner/model/ExceptionResponse.java
  53. +45
    -0
      src/main/java/com/ffii/lioner/model/JwtRequest.java
  54. +75
    -0
      src/main/java/com/ffii/lioner/model/JwtResponse.java
  55. +39
    -0
      src/main/java/com/ffii/lioner/model/RefreshToken.java
  56. +16
    -0
      src/main/java/com/ffii/lioner/model/TokenRefreshRequest.java
  57. +37
    -0
      src/main/java/com/ffii/lioner/model/TokenRefreshResponse.java
  58. +17
    -0
      src/main/java/com/ffii/lioner/modules/common/ErrorCodes.java
  59. +23
    -0
      src/main/java/com/ffii/lioner/modules/common/LocalDateAdapter.java
  60. +69
    -0
      src/main/java/com/ffii/lioner/modules/common/MailSMTP.java
  61. +119
    -0
      src/main/java/com/ffii/lioner/modules/common/PasswordRule.java
  62. +146
    -0
      src/main/java/com/ffii/lioner/modules/common/SecurityUtils.java
  63. +53
    -0
      src/main/java/com/ffii/lioner/modules/common/SettingNames.java
  64. +122
    -0
      src/main/java/com/ffii/lioner/modules/common/TempConst.java
  65. +372
    -0
      src/main/java/com/ffii/lioner/modules/common/mail/pojo/MailRequest.java
  66. +57
    -0
      src/main/java/com/ffii/lioner/modules/common/mail/service/MailSenderService.java
  67. +228
    -0
      src/main/java/com/ffii/lioner/modules/common/mail/service/MailService.java
  68. +108
    -0
      src/main/java/com/ffii/lioner/modules/common/service/AuditLogService.java
  69. +546
    -0
      src/main/java/com/ffii/lioner/modules/common/service/ExcelReportService.java
  70. +308
    -0
      src/main/java/com/ffii/lioner/modules/common/service/WordReportService.java
  71. +123
    -0
      src/main/java/com/ffii/lioner/modules/lioner/client/entity/Client.java
  72. +6
    -0
      src/main/java/com/ffii/lioner/modules/lioner/client/entity/ClientRepository.java
  73. +108
    -0
      src/main/java/com/ffii/lioner/modules/lioner/client/req/UpdateClientReq.java
  74. +1037
    -0
      src/main/java/com/ffii/lioner/modules/lioner/client/service/ClientService.java
  75. +252
    -0
      src/main/java/com/ffii/lioner/modules/lioner/client/web/ClientController.java
  76. +93
    -0
      src/main/java/com/ffii/lioner/modules/lioner/entity/Application.java
  77. +6
    -0
      src/main/java/com/ffii/lioner/modules/lioner/entity/ApplicationRepository.java
  78. +192
    -0
      src/main/java/com/ffii/lioner/modules/lioner/entity/Appreciation.java
  79. +8
    -0
      src/main/java/com/ffii/lioner/modules/lioner/entity/AppreciationRepository.java
  80. +149
    -0
      src/main/java/com/ffii/lioner/modules/lioner/entity/Award.java
  81. +8
    -0
      src/main/java/com/ffii/lioner/modules/lioner/entity/AwardRepository.java
  82. +253
    -0
      src/main/java/com/ffii/lioner/modules/lioner/entity/Event.java
  83. +6
    -0
      src/main/java/com/ffii/lioner/modules/lioner/entity/EventRepository.java
  84. +85
    -0
      src/main/java/com/ffii/lioner/modules/lioner/entity/File.java
  85. +47
    -0
      src/main/java/com/ffii/lioner/modules/lioner/entity/FileBlob.java
  86. +11
    -0
      src/main/java/com/ffii/lioner/modules/lioner/entity/FileBlobRepository.java
  87. +98
    -0
      src/main/java/com/ffii/lioner/modules/lioner/entity/FileRef.java
  88. +6
    -0
      src/main/java/com/ffii/lioner/modules/lioner/entity/FileRefRepository.java
  89. +5
    -0
      src/main/java/com/ffii/lioner/modules/lioner/entity/FileRepository.java
  90. +41
    -0
      src/main/java/com/ffii/lioner/modules/lioner/entity/ImpApplication.java
  91. +49
    -0
      src/main/java/com/ffii/lioner/modules/lioner/entity/ImpAward.java
  92. +26
    -0
      src/main/java/com/ffii/lioner/modules/lioner/entity/ImpEvent.java
  93. +67
    -0
      src/main/java/com/ffii/lioner/modules/lioner/entity/SearchCriteriaTemplate.java
  94. +6
    -0
      src/main/java/com/ffii/lioner/modules/lioner/entity/SearchCriteriaTemplateRepository.java
  95. +111
    -0
      src/main/java/com/ffii/lioner/modules/lioner/entity/TodoReminder.java
  96. +141
    -0
      src/main/java/com/ffii/lioner/modules/lioner/entity/TodoReminderEmailLog.java
  97. +6
    -0
      src/main/java/com/ffii/lioner/modules/lioner/entity/TodoReminderEmailLogRepository.java
  98. +66
    -0
      src/main/java/com/ffii/lioner/modules/lioner/entity/TodoReminderNoted.java
  99. +6
    -0
      src/main/java/com/ffii/lioner/modules/lioner/entity/TodoReminderNotedRepository.java
  100. +6
    -0
      src/main/java/com/ffii/lioner/modules/lioner/entity/TodoReminderRepository.java

+ 37
- 0
.gitignore Vedi File

@@ -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/

+ 55
- 0
README.md Vedi File

@@ -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

+ 69
- 0
build.gradle Vedi File

@@ -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'
}
}

BIN
gradle/wrapper/gradle-wrapper.jar Vedi File


+ 6
- 0
gradle/wrapper/gradle-wrapper.properties Vedi File

@@ -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

+ 240
- 0
gradlew Vedi File

@@ -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" "$@"

+ 91
- 0
gradlew.bat Vedi File

@@ -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

+ 1
- 0
settings.gradle Vedi File

@@ -0,0 +1 @@
rootProject.name = 'LIONER'

+ 124
- 0
src/main/java/com/ffii/core/entity/BaseEntity.java Vedi File

@@ -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;
}

}

+ 49
- 0
src/main/java/com/ffii/core/entity/IdEntity.java Vedi File

@@ -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;
}
}

+ 15
- 0
src/main/java/com/ffii/core/exception/BadRequestException.java Vedi File

@@ -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);
}
}

+ 16
- 0
src/main/java/com/ffii/core/exception/ConflictException.java Vedi File

@@ -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);
}
}

+ 19
- 0
src/main/java/com/ffii/core/exception/InternalServerErrorException.java Vedi File

@@ -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);
}
}

+ 13
- 0
src/main/java/com/ffii/core/exception/NotFoundException.java Vedi File

@@ -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);
}
}

+ 37
- 0
src/main/java/com/ffii/core/exception/UnprocessableEntityException.java Vedi File

@@ -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 "";
}
}

}

+ 32
- 0
src/main/java/com/ffii/core/response/AuthRes.java Vedi File

@@ -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;
}

}

+ 21
- 0
src/main/java/com/ffii/core/response/DataRes.java Vedi File

@@ -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;
}

}

+ 36
- 0
src/main/java/com/ffii/core/response/ErrorRes.java Vedi File

@@ -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;
}

}

+ 39
- 0
src/main/java/com/ffii/core/response/FailureRes.java Vedi File

@@ -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;
}

}

+ 21
- 0
src/main/java/com/ffii/core/response/IdRes.java Vedi File

@@ -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;
}

}

+ 41
- 0
src/main/java/com/ffii/core/response/RecordsRes.java Vedi File

@@ -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;
}

}

+ 42
- 0
src/main/java/com/ffii/core/support/AbstractBaseEntityService.java Vedi File

@@ -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);
}
}

+ 65
- 0
src/main/java/com/ffii/core/support/AbstractIdEntityService.java Vedi File

@@ -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);
}
}

+ 16
- 0
src/main/java/com/ffii/core/support/AbstractRepository.java Vedi File

@@ -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> {
}

+ 15
- 0
src/main/java/com/ffii/core/support/AbstractService.java Vedi File

@@ -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;
}
}

+ 44
- 0
src/main/java/com/ffii/core/support/ErrorHandler.java Vedi File

@@ -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);
}

}

+ 463
- 0
src/main/java/com/ffii/core/support/JdbcDao.java Vedi File

@@ -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;
}
}
}

+ 85
- 0
src/main/java/com/ffii/core/utils/AES.java Vedi File

@@ -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;
}
}

+ 33
- 0
src/main/java/com/ffii/core/utils/BeanUtils.java Vedi File

@@ -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);
}
}
}

+ 254
- 0
src/main/java/com/ffii/core/utils/CriteriaArgsBuilder.java Vedi File

@@ -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;
}
}

+ 778
- 0
src/main/java/com/ffii/core/utils/ExcelUtils.java Vedi File

@@ -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);
}
}
}

+ 125
- 0
src/main/java/com/ffii/core/utils/FileUtils.java Vedi File

@@ -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";
}

}

+ 47
- 0
src/main/java/com/ffii/core/utils/JsonUtils.java Vedi File

@@ -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);
}

}

+ 114
- 0
src/main/java/com/ffii/core/utils/JwtTokenUtil.java Vedi File

@@ -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);
}
}

+ 42
- 0
src/main/java/com/ffii/core/utils/LocaleUtils.java Vedi File

@@ -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]);
}
}
}

+ 35
- 0
src/main/java/com/ffii/core/utils/MapUtils.java Vedi File

@@ -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;
}

}

+ 45
- 0
src/main/java/com/ffii/core/utils/NumberUtils.java Vedi File

@@ -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);
}
}

+ 42
- 0
src/main/java/com/ffii/core/utils/Params.java Vedi File

@@ -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";

}

+ 111
- 0
src/main/java/com/ffii/core/utils/PasswordUtils.java Vedi File

@@ -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();
}
}

+ 24
- 0
src/main/java/com/ffii/core/utils/RomanConverter.java Vedi File

@@ -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();
}
}

+ 83
- 0
src/main/java/com/ffii/core/utils/StringUtils.java Vedi File

@@ -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();
}
}

+ 13
- 0
src/main/java/com/ffii/lioner/LionerApplication.java Vedi File

@@ -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);
}

}

+ 36
- 0
src/main/java/com/ffii/lioner/config/AppConfig.java Vedi File

@@ -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);
}
}

+ 15
- 0
src/main/java/com/ffii/lioner/config/RestTemplateConfig.java Vedi File

@@ -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();
}

}

+ 33
- 0
src/main/java/com/ffii/lioner/config/WebConfig.java Vedi File

@@ -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();
}

}

+ 87
- 0
src/main/java/com/ffii/lioner/config/security/SecurityConfig.java Vedi File

@@ -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();
}
}

+ 78
- 0
src/main/java/com/ffii/lioner/config/security/jwt/JwtRequestFilter.java Vedi File

@@ -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);
}

}

+ 31
- 0
src/main/java/com/ffii/lioner/config/security/jwt/service/JwtUserDetailsService.java Vedi File

@@ -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));
}

}

+ 226
- 0
src/main/java/com/ffii/lioner/config/security/jwt/web/JwtAuthenticationController.java Vedi File

@@ -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));
}

}

+ 56
- 0
src/main/java/com/ffii/lioner/config/security/service/LoginLogService.java Vedi File

@@ -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));
}

}

+ 13
- 0
src/main/java/com/ffii/lioner/model/AbilityModel.java Vedi File

@@ -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;
}
}

+ 28
- 0
src/main/java/com/ffii/lioner/model/ExceptionResponse.java Vedi File

@@ -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;
}

}

+ 45
- 0
src/main/java/com/ffii/lioner/model/JwtRequest.java Vedi File

@@ -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;
}

}

+ 75
- 0
src/main/java/com/ffii/lioner/model/JwtResponse.java Vedi File

@@ -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;
}

}

+ 39
- 0
src/main/java/com/ffii/lioner/model/RefreshToken.java Vedi File

@@ -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;
}

}

+ 16
- 0
src/main/java/com/ffii/lioner/model/TokenRefreshRequest.java Vedi File

@@ -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;
}

}

+ 37
- 0
src/main/java/com/ffii/lioner/model/TokenRefreshResponse.java Vedi File

@@ -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;
}

}

+ 17
- 0
src/main/java/com/ffii/lioner/modules/common/ErrorCodes.java Vedi File

@@ -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";
}

+ 23
- 0
src/main/java/com/ffii/lioner/modules/common/LocalDateAdapter.java Vedi File

@@ -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);
}
}

+ 69
- 0
src/main/java/com/ffii/lioner/modules/common/MailSMTP.java Vedi File

@@ -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;
}

}

+ 119
- 0
src/main/java/com/ffii/lioner/modules/common/PasswordRule.java Vedi File

@@ -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;
}

}

+ 146
- 0
src/main/java/com/ffii/lioner/modules/common/SecurityUtils.java Vedi File

@@ -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);
}

}

+ 53
- 0
src/main/java/com/ffii/lioner/modules/common/SettingNames.java Vedi File

@@ -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";
}

+ 122
- 0
src/main/java/com/ffii/lioner/modules/common/TempConst.java Vedi File

@@ -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>";
}

+ 372
- 0
src/main/java/com/ffii/lioner/modules/common/mail/pojo/MailRequest.java Vedi File

@@ -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;
}
}
}

+ 57
- 0
src/main/java/com/ffii/lioner/modules/common/mail/service/MailSenderService.java Vedi File

@@ -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;
}
}

+ 228
- 0
src/main/java/com/ffii/lioner/modules/common/mail/service/MailService.java Vedi File

@@ -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);
}

}


+ 108
- 0
src/main/java/com/ffii/lioner/modules/common/service/AuditLogService.java Vedi File

@@ -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;
}
}

+ 546
- 0
src/main/java/com/ffii/lioner/modules/common/service/ExcelReportService.java Vedi File

@@ -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;
}

}

+ 308
- 0
src/main/java/com/ffii/lioner/modules/common/service/WordReportService.java Vedi File

@@ -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();
}
}


}

+ 123
- 0
src/main/java/com/ffii/lioner/modules/lioner/client/entity/Client.java Vedi File

@@ -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;
}

}


+ 6
- 0
src/main/java/com/ffii/lioner/modules/lioner/client/entity/ClientRepository.java Vedi File

@@ -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> {
}

+ 108
- 0
src/main/java/com/ffii/lioner/modules/lioner/client/req/UpdateClientReq.java Vedi File

@@ -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;
}
}

+ 1037
- 0
src/main/java/com/ffii/lioner/modules/lioner/client/service/ClientService.java
File diff soppresso perché troppo grande
Vedi File


+ 252
- 0
src/main/java/com/ffii/lioner/modules/lioner/client/web/ClientController.java Vedi File

@@ -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));
// }

// }

}

+ 93
- 0
src/main/java/com/ffii/lioner/modules/lioner/entity/Application.java Vedi File

@@ -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;
}

}



+ 6
- 0
src/main/java/com/ffii/lioner/modules/lioner/entity/ApplicationRepository.java Vedi File

@@ -0,0 +1,6 @@
package com.ffii.lioner.modules.lioner.entity;

import com.ffii.core.support.AbstractRepository;

public interface ApplicationRepository extends AbstractRepository<Application, Long> {
}

+ 192
- 0
src/main/java/com/ffii/lioner/modules/lioner/entity/Appreciation.java Vedi File

@@ -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;
}

}



+ 8
- 0
src/main/java/com/ffii/lioner/modules/lioner/entity/AppreciationRepository.java Vedi File

@@ -0,0 +1,8 @@
package com.ffii.lioner.modules.lioner.entity;

import com.ffii.core.support.AbstractRepository;

public interface AppreciationRepository extends AbstractRepository<Appreciation, Long> {
}



+ 149
- 0
src/main/java/com/ffii/lioner/modules/lioner/entity/Award.java Vedi File

@@ -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;
}

}




+ 8
- 0
src/main/java/com/ffii/lioner/modules/lioner/entity/AwardRepository.java Vedi File

@@ -0,0 +1,8 @@
package com.ffii.lioner.modules.lioner.entity;

import com.ffii.core.support.AbstractRepository;

public interface AwardRepository extends AbstractRepository<Award, Long> {
}



+ 253
- 0
src/main/java/com/ffii/lioner/modules/lioner/entity/Event.java Vedi File

@@ -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;
}

}


+ 6
- 0
src/main/java/com/ffii/lioner/modules/lioner/entity/EventRepository.java Vedi File

@@ -0,0 +1,6 @@
package com.ffii.lioner.modules.lioner.entity;

import com.ffii.core.support.AbstractRepository;

public interface EventRepository extends AbstractRepository<Event, Long> {
}

+ 85
- 0
src/main/java/com/ffii/lioner/modules/lioner/entity/File.java Vedi File

@@ -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;
}


}


+ 47
- 0
src/main/java/com/ffii/lioner/modules/lioner/entity/FileBlob.java Vedi File

@@ -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;
}

}


+ 11
- 0
src/main/java/com/ffii/lioner/modules/lioner/entity/FileBlobRepository.java Vedi File

@@ -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);
}

+ 98
- 0
src/main/java/com/ffii/lioner/modules/lioner/entity/FileRef.java Vedi File

@@ -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;
}

}


+ 6
- 0
src/main/java/com/ffii/lioner/modules/lioner/entity/FileRefRepository.java Vedi File

@@ -0,0 +1,6 @@
package com.ffii.lioner.modules.lioner.entity;

import com.ffii.core.support.AbstractRepository;

public interface FileRefRepository extends AbstractRepository<FileRef, Long> {
}

+ 5
- 0
src/main/java/com/ffii/lioner/modules/lioner/entity/FileRepository.java Vedi File

@@ -0,0 +1,5 @@
package com.ffii.lioner.modules.lioner.entity;
import com.ffii.core.support.AbstractRepository;

public interface FileRepository extends AbstractRepository<File, Long> {
}

+ 41
- 0
src/main/java/com/ffii/lioner/modules/lioner/entity/ImpApplication.java Vedi File

@@ -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;
}


}

+ 49
- 0
src/main/java/com/ffii/lioner/modules/lioner/entity/ImpAward.java Vedi File

@@ -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;
}
}

+ 26
- 0
src/main/java/com/ffii/lioner/modules/lioner/entity/ImpEvent.java Vedi File

@@ -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;
}
}

+ 67
- 0
src/main/java/com/ffii/lioner/modules/lioner/entity/SearchCriteriaTemplate.java Vedi File

@@ -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;
}

}



+ 6
- 0
src/main/java/com/ffii/lioner/modules/lioner/entity/SearchCriteriaTemplateRepository.java Vedi File

@@ -0,0 +1,6 @@
package com.ffii.lioner.modules.lioner.entity;
import com.ffii.core.support.AbstractRepository;

public interface SearchCriteriaTemplateRepository extends AbstractRepository<SearchCriteriaTemplate, Long> {
}


+ 111
- 0
src/main/java/com/ffii/lioner/modules/lioner/entity/TodoReminder.java Vedi File

@@ -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;
}

}


+ 141
- 0
src/main/java/com/ffii/lioner/modules/lioner/entity/TodoReminderEmailLog.java Vedi File

@@ -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;
}

}


+ 6
- 0
src/main/java/com/ffii/lioner/modules/lioner/entity/TodoReminderEmailLogRepository.java Vedi File

@@ -0,0 +1,6 @@
package com.ffii.lioner.modules.lioner.entity;
import com.ffii.core.support.AbstractRepository;

public interface TodoReminderEmailLogRepository extends AbstractRepository<TodoReminderEmailLog, Long> {
}


+ 66
- 0
src/main/java/com/ffii/lioner/modules/lioner/entity/TodoReminderNoted.java Vedi File

@@ -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;
}
}


+ 6
- 0
src/main/java/com/ffii/lioner/modules/lioner/entity/TodoReminderNotedRepository.java Vedi File

@@ -0,0 +1,6 @@
package com.ffii.lioner.modules.lioner.entity;
import com.ffii.core.support.AbstractRepository;

public interface TodoReminderNotedRepository extends AbstractRepository<TodoReminderNoted, Long> {
}


+ 6
- 0
src/main/java/com/ffii/lioner/modules/lioner/entity/TodoReminderRepository.java Vedi File

@@ -0,0 +1,6 @@
package com.ffii.lioner.modules.lioner.entity;
import com.ffii.core.support.AbstractRepository;

public interface TodoReminderRepository extends AbstractRepository<TodoReminder, Long> {
}


Dato che sono stati cambiati molti file in questo diff, alcuni di essi non verranno mostrati

Caricamento…
Annulla
Salva