Anna Ho преди 3 години
родител
ревизия
bb32e9625d
променени са 100 файла, в които са добавени 8374 реда и са изтрити 0 реда
  1. +20
    -0
      .classpath
  2. Двоични данни
      .gradle/5.6.3/executionHistory/executionHistory.bin
  3. Двоични данни
      .gradle/5.6.3/executionHistory/executionHistory.lock
  4. Двоични данни
      .gradle/5.6.3/fileChanges/last-build.bin
  5. Двоични данни
      .gradle/5.6.3/fileContent/fileContent.lock
  6. Двоични данни
      .gradle/5.6.3/fileHashes/fileHashes.bin
  7. Двоични данни
      .gradle/5.6.3/fileHashes/fileHashes.lock
  8. +0
    -0
      .gradle/5.6.3/gc.properties
  9. Двоични данни
      .gradle/5.6.3/javaCompile/javaCompile.lock
  10. Двоични данни
      .gradle/5.6.3/javaCompile/taskHistory.bin
  11. Двоични данни
      .gradle/buildOutputCleanup/buildOutputCleanup.lock
  12. +2
    -0
      .gradle/buildOutputCleanup/cache.properties
  13. Двоични данни
      .gradle/buildOutputCleanup/outputFiles.bin
  14. +0
    -0
      .gradle/vcs-1/gc.properties
  15. +47
    -0
      .project
  16. +8
    -0
      a-clean-deploy-2fi-db.bat
  17. +8
    -0
      a-clean-deploy-local-db.bat
  18. +6
    -0
      a-clean-undeploy.bat
  19. +5
    -0
      a-create-war-for-prod.bat
  20. +89
    -0
      build.gradle
  21. +295
    -0
      conf/schema/schema_update.sql
  22. +12
    -0
      conf/schema/trigger.sql
  23. +6
    -0
      conf/schema/truncate.sql
  24. +3
    -0
      copy-js.sh
  25. +3
    -0
      create-resources-link.bat
  26. +4
    -0
      deploy.sh
  27. +47
    -0
      docs/SMS Functions.docx
  28. Двоични данни
      docs/SMS Schema.xlsx
  29. +5
    -0
      gradle/wrapper/gradle-wrapper.properties
  30. +188
    -0
      gradlew
  31. +100
    -0
      gradlew.bat
  32. +3
    -0
      off.bat
  33. +3
    -0
      off.sh
  34. +2
    -0
      on.bat
  35. +3
    -0
      on.sh
  36. +5
    -0
      prod.sh
  37. +12
    -0
      publish.sh
  38. +3
    -0
      setenv.bat
  39. +2
    -0
      setenv.sh
  40. +1
    -0
      settings.gradle
  41. +135
    -0
      src/main/java/com/ffii/core/BaseEntity.java
  42. +107
    -0
      src/main/java/com/ffii/core/IBaseEntity.java
  43. +36
    -0
      src/main/java/com/ffii/core/Session.java
  44. +149
    -0
      src/main/java/com/ffii/core/Settings.java
  45. +264
    -0
      src/main/java/com/ffii/core/User.java
  46. +160
    -0
      src/main/java/com/ffii/core/dao/HibernateDao.java
  47. +125
    -0
      src/main/java/com/ffii/core/dao/IHibernateDao.java
  48. +260
    -0
      src/main/java/com/ffii/core/dao/JdbcDao.java
  49. +56
    -0
      src/main/java/com/ffii/core/engine/FreeMarkerEngine.java
  50. +316
    -0
      src/main/java/com/ffii/core/engine/MailEngine.java
  51. +109
    -0
      src/main/java/com/ffii/core/i18n/support/JdbcMessageSource.java
  52. +52
    -0
      src/main/java/com/ffii/core/security/authentication/AuthToken.java
  53. +49
    -0
      src/main/java/com/ffii/core/security/authentication/TokenAuthenticationProvider.java
  54. +74
    -0
      src/main/java/com/ffii/core/security/filter/AuthTokenFilter.java
  55. +65
    -0
      src/main/java/com/ffii/core/security/service/LoginLogService.java
  56. +143
    -0
      src/main/java/com/ffii/core/security/service/TokenUserDetailsService.java
  57. +239
    -0
      src/main/java/com/ffii/core/security/userdetails/UserDetailsServiceImpl.java
  58. +100
    -0
      src/main/java/com/ffii/core/security/web/authentication/JsonAuthenticationFailureHandler.java
  59. +119
    -0
      src/main/java/com/ffii/core/security/web/authentication/JsonAuthenticationSuccessHandler.java
  60. +64
    -0
      src/main/java/com/ffii/core/setting/Setting.java
  61. +169
    -0
      src/main/java/com/ffii/core/setting/service/SettingsService.java
  62. +45
    -0
      src/main/java/com/ffii/core/support/SimpleDateEditor.java
  63. +38
    -0
      src/main/java/com/ffii/core/support/SimpleDecimalEditor.java
  64. +37
    -0
      src/main/java/com/ffii/core/support/SimpleDoubleEditor.java
  65. +37
    -0
      src/main/java/com/ffii/core/support/SimpleIntegerEditor.java
  66. +37
    -0
      src/main/java/com/ffii/core/support/SimpleLongEditor.java
  67. +41
    -0
      src/main/java/com/ffii/core/support/SimpleStringTrimToEmptyEditor.java
  68. +42
    -0
      src/main/java/com/ffii/core/support/SimpleStringTrimToNullEditor.java
  69. +17
    -0
      src/main/java/com/ffii/core/utils/ArgsBuilder.java
  70. +67
    -0
      src/main/java/com/ffii/core/utils/BooleanUtils.java
  71. +66
    -0
      src/main/java/com/ffii/core/utils/CheckDigitUtils.java
  72. +119
    -0
      src/main/java/com/ffii/core/utils/CriteriaArgsBuilder.java
  73. +191
    -0
      src/main/java/com/ffii/core/utils/CriteriaUtils.java
  74. +103
    -0
      src/main/java/com/ffii/core/utils/DataBindUtils.java
  75. +1021
    -0
      src/main/java/com/ffii/core/utils/DateUtils.java
  76. +139
    -0
      src/main/java/com/ffii/core/utils/Encoding.java
  77. +675
    -0
      src/main/java/com/ffii/core/utils/ExcelUtils.java
  78. +122
    -0
      src/main/java/com/ffii/core/utils/FileUtils.java
  79. +26
    -0
      src/main/java/com/ffii/core/utils/ImageUtils.java
  80. +68
    -0
      src/main/java/com/ffii/core/utils/JsonUtils.java
  81. +26
    -0
      src/main/java/com/ffii/core/utils/ListUtils.java
  82. +47
    -0
      src/main/java/com/ffii/core/utils/LocaleUtils.java
  83. +34
    -0
      src/main/java/com/ffii/core/utils/MapBuilder.java
  84. +45
    -0
      src/main/java/com/ffii/core/utils/MapUtils.java
  85. +471
    -0
      src/main/java/com/ffii/core/utils/NumberUtils.java
  86. +81
    -0
      src/main/java/com/ffii/core/utils/Params.java
  87. +225
    -0
      src/main/java/com/ffii/core/utils/SecurityUtils.java
  88. +20
    -0
      src/main/java/com/ffii/core/utils/SqlUtils.java
  89. +71
    -0
      src/main/java/com/ffii/core/utils/StringUtils.java
  90. +54
    -0
      src/main/java/com/ffii/core/utils/ZXingUtils.java
  91. +114
    -0
      src/main/java/com/ffii/core/utils/sql/QueryBuilder.java
  92. +336
    -0
      src/main/java/com/ffii/core/utils/web/ServletRequestUtils.java
  93. +72
    -0
      src/main/java/com/ffii/core/utils/web/ServletResponseUtils.java
  94. +17
    -0
      src/main/java/com/ffii/core/web/AbstractController.java
  95. +17
    -0
      src/main/java/com/ffii/core/web/AbstractService.java
  96. +10
    -0
      src/main/java/com/ffii/core/web/AppConfig.java
  97. +35
    -0
      src/main/java/com/ffii/core/web/ControllerAdvice.java
  98. +37
    -0
      src/main/java/com/ffii/core/web/RestResponseEntityExceptionHandler.java
  99. +52
    -0
      src/main/java/com/ffii/core/web/filter/AjaxSessionExpirationFilter.java
  100. +46
    -0
      src/main/java/com/ffii/core/web/view/AbstractView.java

+ 20
- 0
.classpath Целия файл

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="bin/main" path="src/main/java">
<attributes>
<attribute name="gradle_scope" value="main"/>
<attribute name="gradle_used_by_scope" value="main,test"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="bin/test" path="src/test/java">
<attributes>
<attribute name="gradle_scope" value="test"/>
<attribute name="gradle_used_by_scope" value="test"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11/"/>
<classpathentry kind="con" path="org.eclipse.jst.j2ee.internal.web.container"/>
<classpathentry kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer"/>
<classpathentry kind="output" path="bin/default"/>
</classpath>

Двоични данни
.gradle/5.6.3/executionHistory/executionHistory.bin Целия файл


Двоични данни
.gradle/5.6.3/executionHistory/executionHistory.lock Целия файл


Двоични данни
.gradle/5.6.3/fileChanges/last-build.bin Целия файл


Двоични данни
.gradle/5.6.3/fileContent/fileContent.lock Целия файл


Двоични данни
.gradle/5.6.3/fileHashes/fileHashes.bin Целия файл


Двоични данни
.gradle/5.6.3/fileHashes/fileHashes.lock Целия файл


+ 0
- 0
.gradle/5.6.3/gc.properties Целия файл


Двоични данни
.gradle/5.6.3/javaCompile/javaCompile.lock Целия файл


Двоични данни
.gradle/5.6.3/javaCompile/taskHistory.bin Целия файл


Двоични данни
.gradle/buildOutputCleanup/buildOutputCleanup.lock Целия файл


+ 2
- 0
.gradle/buildOutputCleanup/cache.properties Целия файл

@@ -0,0 +1,2 @@
#Wed May 25 11:46:18 CST 2022
gradle.version=5.6.3

Двоични данни
.gradle/buildOutputCleanup/outputFiles.bin Целия файл


+ 0
- 0
.gradle/vcs-1/gc.properties Целия файл


+ 47
- 0
.project Целия файл

@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>TBMS</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.wst.common.project.facet.core.builder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.wst.validation.validationbuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.buildship.core.gradleprojectbuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.wst.common.project.facet.core.nature</nature>
<nature>org.eclipse.wst.common.modulecore.ModuleCoreNature</nature>
<nature>org.eclipse.jem.workbench.JavaEMFNature</nature>
<nature>org.eclipse.buildship.core.gradleprojectnature</nature>
</natures>
<filteredResources>
<filter>
<id>0</id>
<name></name>
<type>30</type>
<matcher>
<id>org.eclipse.core.resources.regexFilterMatcher</id>
<arguments>node_modules|.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__</arguments>
</matcher>
</filter>
</filteredResources>
</projectDescription>

+ 8
- 0
a-clean-deploy-2fi-db.bat Целия файл

@@ -0,0 +1,8 @@
@echo off
copy /Y src\main\webapp\WEB-INF\classes\log4j.properties.prod src\main\webapp\WEB-INF\classes\log4j.properties
copy /Y src\main\webapp\WEB-INF\app.properties.local src\main\webapp\WEB-INF\app.properties
copy /Y src\main\webapp\META-INF\context.xml.2fi src\main\webapp\META-INF\context.xml
call a-clean-undeploy
call gradlew clean war
copy build\libs\%PROJECT_NAME%.war "%TC_BASE%\webapps"
call on

+ 8
- 0
a-clean-deploy-local-db.bat Целия файл

@@ -0,0 +1,8 @@
@echo off
copy /Y src\main\webapp\WEB-INF\classes\log4j.properties.prod src\main\webapp\WEB-INF\classes\log4j.properties
copy /Y src\main\webapp\WEB-INF\app.properties.local src\main\webapp\WEB-INF\app.properties
copy /Y src\main\webapp\META-INF\context.xml.local src\main\webapp\META-INF\context.xml
call a-clean-undeploy
call gradlew clean war
copy build\libs\%PROJECT_NAME%.war "%TC_BASE%\webapps"
call on

+ 6
- 0
a-clean-undeploy.bat Целия файл

@@ -0,0 +1,6 @@
@echo off
call setenv
call off
del "%TC_BASE%\webapps\%PROJECT_NAME%.war"
rd /s /q "%TC_BASE%\webapps\%PROJECT_NAME%"
rd /s /q "%TC_BASE%\work\Catalina\localhost\%PROJECT_NAME%"

+ 5
- 0
a-create-war-for-prod.bat Целия файл

@@ -0,0 +1,5 @@
@echo off
copy /Y src\main\webapp\WEB-INF\classes\log4j.properties.prod src\main\webapp\WEB-INF\classes\log4j.properties
copy /Y src\main\webapp\WEB-INF\app.properties.prod src\main\webapp\WEB-INF\app.properties
copy /Y src\main\webapp\META-INF\context.xml.2fi src\main\webapp\META-INF\context.xml
call gradlew clean war

+ 89
- 0
build.gradle Целия файл

@@ -0,0 +1,89 @@
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'war'

sourceCompatibility = 11
targetCompatibility = 11

tasks.withType(JavaCompile) {
options.encoding = 'UTF-8'
}

repositories {
jcenter()
mavenCentral()
maven {
url 'http://jaspersoft.jfrog.io/jaspersoft/third-party-ce-artifacts/'
}
}

war {
archiveName = 'tbms.war'
classpath = classpath - sourceSets.main.output
from (jar) {
into 'WEB-INF/lib'
}
}

dependencies {

// for JDK 11
compile 'javax.xml.bind:jaxb-api:2.3.1'
compile 'javax.annotation:javax.annotation-api:1.3.2'
compile 'com.sun.activation:javax.activation:1.2.0'

compile 'org.springframework:spring-context:4.3.14.RELEASE'
compile 'org.springframework:spring-context-support:4.3.14.RELEASE'
compile 'org.springframework:spring-orm:4.3.14.RELEASE'
compile 'org.springframework:spring-webmvc:4.3.14.RELEASE'

compile 'org.springframework.security:spring-security-config:4.2.3.RELEASE'
compile 'org.springframework.security:spring-security-web:4.2.3.RELEASE'

compile 'org.apache.commons:commons-lang3:3.5'
compile 'commons-fileupload:commons-fileupload:1.3.2'
compile 'org.apache.poi:poi:3.17'
compile 'org.apache.poi:poi-ooxml:3.17'

compile 'org.freemarker:freemarker:2.3.23'

compile 'org.hibernate:hibernate-core:5.2.12.Final'
compile 'org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.2.Final'

compile 'com.fasterxml.jackson.core:jackson-core:2.8.8'
compile 'com.fasterxml.jackson.core:jackson-annotations:2.8.8'
compile 'com.fasterxml.jackson.core:jackson-databind:2.8.8.1'

compile 'org.imgscalr:imgscalr-lib:4.2'

compile(group: 'net.sf.jasperreports', name: 'jasperreports', version:'6.5.1') {
exclude(module: 'bcmail-jdk14')
exclude(module: 'bcprov-jdk14')
exclude(module: 'bctsp-jdk14')
exclude(module: 'castor-xml')
exclude(module: 'jackson-core')
exclude(module: 'jackson-annotations')
exclude(module: 'jackson-databind')
exclude(module: 'lucene-core')
exclude(module: 'lucene-analyzers-common')
exclude(module: 'lucene-queryparser')
exclude(module: 'olap4j')
}

//providedCompile 'org.slf4j:slf4j-api:1.7.25'
//providedCompile 'org.slf4j:slf4j-log4j12:1.7.25'

providedCompile 'javax.servlet:javax.servlet-api:3.1.0'
providedCompile 'mysql:mysql-connector-java:8.0.15'

//testCompile 'junit:junit:4.12'

compile 'com.google.zxing:javase:3.2.1'

//mail
compile 'javax.mail:mail:1.4.7'
compile group: 'org.liquibase', name: 'liquibase-core', version: '3.5.3'

fileTree(dir: 'lib', include: '*.jar')

}

+ 295
- 0
conf/schema/schema_update.sql Целия файл

@@ -0,0 +1,295 @@
-- anna 20/03/2020
--

INSERT INTO `tbmsdb`.`authorities` (`authority`, `module`, `name`) VALUES ('MEETING_MAINTAIN', 'Meeting', 'Maintain Meeting');
INSERT INTO `tbmsdb`.`users_authorities` (`userId`, `authority`) VALUES ('1', 'MEETING_MAINTAIN');
ALTER TABLE `tbmsdb`.`meeting` CHANGE COLUMN `datetime` `date` DATE NOT NULL ;

-- anna 23/03/2020
--
INSERT INTO `tbmsdb`.`authorities` (`authority`, `module`, `name`) VALUES ('ORDER_MAINTAIN', 'order', 'Maintain order');
UPDATE `tbmsdb`.`authorities` SET `module`='customer' WHERE `authority`='CUSTOMER_MAINTAIN';
UPDATE `tbmsdb`.`authorities` SET `module`='meeting' WHERE `authority`='MEETING_MAINTAIN';
INSERT INTO `tbmsdb`.`users_authorities` (`userId`, `authority`) VALUES ('1', 'ORDER_MAINTAIN');
ALTER TABLE `tbmsdb`.`order` RENAME TO `tbmsdb`.`orders` ;

-- anna 31/03/2020
--

ALTER TABLE `tbmsdb`.`orders`
ADD COLUMN `refNo` VARCHAR(100) NULL DEFAULT NULL AFTER `ref`;

-- anna 07/04/2020
--

ALTER TABLE `tbmsdb`.`files`
ADD COLUMN `skey` VARCHAR(16) NOT NULL AFTER `description`;

-- anna 09/04/2020
--

ALTER TABLE `tbmsdb`.`customer`
ADD COLUMN `companyName` VARCHAR(255) NULL AFTER `phone2`;

-- anna 14/04/2020
--
ALTER TABLE `tbmsdb`.`customer`
CHANGE COLUMN `surname` `surname` VARCHAR(255) NULL DEFAULT NULL ,
CHANGE COLUMN `firstName` `firstName` VARCHAR(255) NULL DEFAULT NULL ;

-- anna 11/05/2020
--

ALTER TABLE `tbmsdb`.`orders`
ADD INDEX `cust_order` (`id` ASC, `custId` ASC);
ALTER TABLE `tbmsdb`.`orders`
ADD INDEX `order_ref` (`id` ASC, `ref` ASC);
ALTER TABLE `tbmsdb`.`customer`
ADD INDEX `cust_ref` (`id` ASC, `ref` ASC);

-- anna 15/05/2020
--

INSERT INTO `tbmsdb`.`authorities` (`authority`, `module`, `name`, `description`) VALUES ('AMOUNT_REMIND', 'order', 'Order Amount Remind', NULL);

DROP TABLE IF EXISTS `audit_log`;
CREATE TABLE `audit_log` (
`tableName` varchar(30) NOT NULL,
`recordId` int(11) NOT NULL,
`modifiedBy` int(11) DEFAULT NULL,
`modified` datetime DEFAULT NULL,
`oldData` json DEFAULT NULL,
`newData` json DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


-- anna 28/05/2020
ALTER TABLE `tbmsdb`.`meeting`
CHANGE COLUMN `date` `date` DATETIME NOT NULL ;


-- Matthew
-- 09/06/2020
INSERT INTO `tbmsdb`.`authorities` (`authority`, `module`, `name`) VALUES ('ORDER_CANCEL', 'order', 'Order Cancel');
INSERT INTO `tbmsdb`.`authorities` (`authority`, `module`, `name`) VALUES ('MEETING_DELETE', 'meeting', 'Meeting Delete');
INSERT INTO `tbmsdb`.`authorities` (`authority`, `module`, `name`) VALUES ('CUSTOMER_DELETE', 'customer', 'Customer Delete');
INSERT INTO `tbmsdb`.`authorities` (`authority`, `module`, `name`) VALUES ('FILE_DELETE', 'file', 'File Delete');
INSERT INTO `tbmsdb`.`authorities` (`authority`, `module`, `name`) VALUES ('PAYMENT_REPORT', 'report', 'Payment Report Auth');

-- Jason Lam
-- 10/06/2020
ALTER TABLE `tbmsdb`.`orders`
ADD COLUMN `outDate` DATE NULL AFTER `status`,
ADD COLUMN `outRemark` VARCHAR(255) NULL AFTER `outDate`,
ADD COLUMN `inDate` DATE NULL AFTER `outRemark`,
ADD COLUMN `inRemark` VARCHAR(255) NULL AFTER `inDate`;

--Jason Lam
--11/06/2020
ALTER TABLE `tbmsdb`.`orders`
ADD COLUMN `ignore` TINYINT(1) '0' AFTER `inRemark`;
ALTER TABLE `tbmsdb`.`orders`
CHANGE COLUMN `ignore` `ignoreC` TINYINT(1) NULL DEFAULT '0' ;


-- anna
-- 15/06/2020
ALTER TABLE `tbmsdb`.`audit_log`
DROP COLUMN `oldData`,
CHANGE COLUMN `newData` `dataStr` JSON NULL DEFAULT NULL ,
ADD COLUMN `actionStr` VARCHAR(255) NULL DEFAULT NULL AFTER `modified`,
ADD COLUMN `refId` INT(11) NULL DEFAULT NULL AFTER `dataStr`,
ADD COLUMN `refType` VARCHAR(255) NULL DEFAULT NULL AFTER `refId`;

-- anna
-- 23/06/2020
ALTER TABLE `tbmsdb`.`orders`
ADD COLUMN `workshopStatus` INT(11) NULL DEFAULT NULL AFTER `ignoreC`;


-- anna
-- 29/06/2020

INSERT INTO `tbmsdb`.`authorities` (`authority`, `module`, `name`) VALUES ('ORDER_ITEM_DELETE', 'order', 'Order Item Delete');
INSERT INTO `tbmsdb`.`authorities` (`authority`, `module`, `name`) VALUES ('PAYMENT_DELETE', 'order', 'Payment Delete');
INSERT INTO `tbmsdb`.`authorities` (`authority`, `module`, `name`) VALUES ('WORKSHOP_MAINTAIN', 'workshop', 'Maintain Workshop');
INSERT INTO `tbmsdb`.`authorities` (`authority`, `module`, `name`) VALUES ('CALENDAR_VIEW', 'calendar', 'View Calendar');


-- anna
-- 13/07/2020
ALTER TABLE `tbmsdb`.`orders`
ADD COLUMN `targetCompleteDate` DATE NULL DEFAULT NULL AFTER `workshopStatus`;

-- anna
-- 21/07/2020
ALTER TABLE `tbmsdb`.`customer`
ADD COLUMN `tag` VARCHAR(255) NULL DEFAULT NULL AFTER `ref`;

-- matthew
-- 07/09/2020
INSERT INTO `tbmsdb`.`settings` (`name`, `value`) VALUES ('JS.version', '1');


-- anna
-- 01/12/2020
ALTER TABLE `tbmsdb`.`meeting`
ADD COLUMN `staffName` VARCHAR(45) NULL DEFAULT NULL AFTER `remarks`;

ALTER TABLE `tbmsdb`.`orders`
ADD COLUMN `inStatus` INT(11) NULL DEFAULT NULL AFTER `targetCompleteDate`,
ADD COLUMN `outStatus` INT(11) NULL DEFAULT NULL AFTER `inStatus`;

-- anna
-- 10/06/2021
INSERT INTO `tbmsdb`.`authorities` (`authority`, `module`, `name`) VALUES ('MATERIAL_MAINTAIN', 'price_list', 'Maintain Material');
INSERT INTO `tbmsdb`.`authorities` (`authority`, `module`, `name`) VALUES ('BAND_DELETE', 'price_list', 'Delete Band');
INSERT INTO `tbmsdb`.`authorities` (`authority`, `module`, `name`) VALUES ('VIEW_EXPECTED_PRICE', 'price_list', 'View Expected Price');

-- anna
-- 29/06/2021
INSERT INTO `tbmsdb`.`settings` (`name`, `value`) VALUES ('MAIL.temp.subject', '[INVOICE] Order No.: {$orderNo}');

-- anna
-- 06/07/2021
INSERT INTO `tbmsdb`.`authorities` (`authority`, `module`, `name`) VALUES ('MATERIAL_MAIL_TEMPLATE', 'setup', 'Maintain Mail Template');
INSERT INTO `tbmsdb`.`authorities` (`authority`, `module`, `name`) VALUES ('PRICE_LIST_VIEW', 'price_list', 'View Price List');
UPDATE `tbmsdb`.`authorities` SET `authority`='PRICE_LIST_MAINTAIN', `name`='Maintain Price List' WHERE `authority`='MATERIAL_MAINTAIN';

-- anna
-- 19/07/2021
CREATE TABLE `material_band` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`versionId` mediumint(8) unsigned NOT NULL DEFAULT '0',
`deleted` tinyint(1) NOT NULL DEFAULT '0',
`createdBy` int(11) DEFAULT NULL,
`modifiedBy` int(11) DEFAULT NULL,
`created` datetime DEFAULT NULL,
`modified` datetime DEFAULT NULL,
`name` varchar(255) NOT NULL,
`fileId` int(11) DEFAULT NULL,
`expectedPriceFormula` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

CREATE TABLE `material_item` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`versionId` mediumint(8) unsigned NOT NULL DEFAULT '0',
`deleted` tinyint(1) NOT NULL DEFAULT '0',
`createdBy` int(11) DEFAULT NULL,
`modifiedBy` int(11) DEFAULT NULL,
`created` datetime DEFAULT NULL,
`modified` datetime DEFAULT NULL,
`bandId` int(11) DEFAULT NULL,
`name` varchar(255) DEFAULT NULL,
`bunch` varchar(255) DEFAULT NULL,
`itemNo` varchar(255) DEFAULT NULL,
`cost` decimal(18,4) DEFAULT '0.0000',
`suitPrice` decimal(18,4) DEFAULT NULL,
`jacketPrice` decimal(18,4) DEFAULT NULL,
`overcoatPrice` decimal(18,4) DEFAULT NULL,
`pantsPrice` decimal(18,4) DEFAULT NULL,
`otherPrice` decimal(18,4) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=22 DEFAULT CHARSET=utf8;

-- anna
-- 10/01/2022

ALTER TABLE `tbmsdb`.`material_item`
ADD COLUMN `fabricNum` VARCHAR(255) NULL DEFAULT NULL AFTER `bunch`;

-- anna
-- 4/03/2022

ALTER TABLE `tbmsdb`.`material_item`
ADD COLUMN `orderIdx` INT(11) NOT NULL DEFAULT 0 AFTER `otherPrice`;
CREATE TABLE `user_settings` (
`userId` int(11) NOT NULL,
`name` varchar(255) NOT NULL,
`value` varchar(1000) DEFAULT NULL,
`category` varchar(50) DEFAULT NULL,
`type` varchar(45) DEFAULT NULL,
PRIMARY KEY (`userId`,`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- anna
-- 22/03/2022

CREATE TABLE `sys_groups` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`deleted` tinyint(1) NOT NULL,
`versionId` mediumint(8) unsigned NOT NULL,
`created` datetime DEFAULT NULL,
`createdBy` int(11) DEFAULT NULL,
`modified` datetime DEFAULT NULL,
`modifiedBy` int(11) DEFAULT NULL,
`name` varchar(50) NOT NULL,
`description` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

ALTER TABLE `tbmsdb`.`groups`
ADD COLUMN `sysGroupId` INT(11) NOT NULL DEFAULT 0 AFTER `id`;

ALTER TABLE `tbmsdb`.`users`
ADD COLUMN `sysGroupId` INT(11) NOT NULL DEFAULT 0 AFTER `id`;

ALTER TABLE `tbmsdb`.`customer`
ADD COLUMN `sysGroupId` INT(11) NOT NULL DEFAULT 0 AFTER `id`;

ALTER TABLE `tbmsdb`.`files`
ADD COLUMN `sysGroupId` INT(11) NOT NULL DEFAULT 0 AFTER `id`;

ALTER TABLE `tbmsdb`.`files_blob`
ADD COLUMN `sysGroupId` INT(11) NOT NULL DEFAULT 0 AFTER `id`;

ALTER TABLE `tbmsdb`.`files_ref`
ADD COLUMN `sysGroupId` INT(11) NOT NULL DEFAULT 0 AFTER `id`;

ALTER TABLE `tbmsdb`.`material_band`
ADD COLUMN `sysGroupId` INT(11) NOT NULL DEFAULT 0 AFTER `id`;

ALTER TABLE `tbmsdb`.`material_item`
ADD COLUMN `sysGroupId` INT(11) NOT NULL DEFAULT 0 AFTER `id`;

ALTER TABLE `tbmsdb`.`meeting`
ADD COLUMN `sysGroupId` INT(11) NOT NULL DEFAULT 0 AFTER `id`;

ALTER TABLE `tbmsdb`.`order_item`
ADD COLUMN `sysGroupId` INT(11) NOT NULL DEFAULT 0 AFTER `id`;

ALTER TABLE `tbmsdb`.`orders`
ADD COLUMN `sysGroupId` INT(11) NOT NULL DEFAULT 0 AFTER `id`;

ALTER TABLE `tbmsdb`.`payment`
ADD COLUMN `sysGroupId` INT(11) NOT NULL DEFAULT 0 AFTER `id`;

ALTER TABLE `tbmsdb`.`message`
ADD COLUMN `sysGroupId` INT(11) NOT NULL DEFAULT 0 AFTER `id`;

-- anna
-- 18/05/2022

ALTER TABLE `tbmsdb`.`sys_groups`
ADD COLUMN `mailBody` TEXT NULL DEFAULT NULL AFTER `description`,
ADD COLUMN `mailSubject` TEXT NULL DEFAULT NULL AFTER `mailBody`;

ALTER TABLE `tbmsdb`.`sys_groups`
ADD COLUMN `companyName` VARCHAR(255) NULL DEFAULT NULL AFTER `mailSubject`,
ADD COLUMN `phone` VARCHAR(255) NULL DEFAULT NULL AFTER `companyName`,
ADD COLUMN `fax` VARCHAR(255) NULL DEFAULT NULL AFTER `phone`,
ADD COLUMN `email` VARCHAR(255) NULL DEFAULT NULL AFTER `fax`,
ADD COLUMN `address` VARCHAR(500) NULL DEFAULT NULL AFTER `email`;

-- anna
-- 23/05/2022

ALTER TABLE `tbmsdb`.`sys_groups`
ADD COLUMN `smtp_host` VARCHAR(255) NULL DEFAULT NULL AFTER `address`,
ADD COLUMN `smtp_port` VARCHAR(5) NULL DEFAULT NULL AFTER `smtp_host`,
ADD COLUMN `smtp_username` VARCHAR(255) NULL DEFAULT NULL AFTER `smtp_port`,
ADD COLUMN `smtp_password` VARCHAR(255) NULL DEFAULT NULL AFTER `smtp_username`;



+ 12
- 0
conf/schema/trigger.sql Целия файл

@@ -0,0 +1,12 @@
--
-- ON stock_ledger INSERT: SET part.balance = stock_ledger.balance
--
DROP TRIGGER IF EXISTS `tbmsdb`.`stock_ledger_AFTER_INSERT`;

DELIMITER $$
USE `tbmsdb`$$
CREATE DEFINER = `root`@`localhost` TRIGGER `tbmsdb`.`stock_ledger_AFTER_INSERT` AFTER INSERT ON `stock_ledger` FOR EACH ROW
BEGIN
UPDATE `part` SET `balance` = NEW.`balance` WHERE `id` = NEW.`partId`;
END$$
DELIMITER ;

+ 6
- 0
conf/schema/truncate.sql Целия файл

@@ -0,0 +1,6 @@
-- SET FOREIGN_KEY_CHECKS=0;

-- TRUNCATE `job`;


-- SET FOREIGN_KEY_CHECKS=1;

+ 3
- 0
copy-js.sh Целия файл

@@ -0,0 +1,3 @@
#!/bin/bash
source ./setenv.sh
cp -r src/main/webapp/resources/js/* ${tc_instance}/webapps/sms/resources/js/

+ 3
- 0
create-resources-link.bat Целия файл

@@ -0,0 +1,3 @@
@ECHO OFF
rd /S /Q C:\Tomcat\webapps\tbms\resources
mklink /J C:\Tomcat\webapps\tbms\resources C:\workspace\TBMS\src\main\webapp\resources

+ 4
- 0
deploy.sh Целия файл

@@ -0,0 +1,4 @@
#!/bin/bash
bash ./undeploy.sh
bash ./publish.sh
bash ./on.sh

+ 47
- 0
docs/SMS Functions.docx Целия файл

@@ -0,0 +1,47 @@
Manage Master Data
Vendor
Equipment Type
Equipment
Equipment Part
Equipment and Parts relation

Manage Preventive Maintenance (PM) Plan
Setup Preventive Maintenance Plan
Equipment
Plan name and description (e.g. checklist)
Frequency (e.g. Daily, Weekly, Monthly, Yearly)
Resp. Team/Person
Preferred timeslot (or Shift)
Preferred day of week (e.g. Monday, Tuesday)
Time required to perform the PM
Support file attachments
Provide daily and upcoming pending PM plan (i.e. Pending to do list)
Display all the details above, plus Last Performed Date
Upon PM completion, maintenance staff will create Service Log to record, then PM plan is completed (by matching Service Log with corresponding PM plan)
Incident Log should be created if any issues found during PM

Manage Incident
Search Incident Logs
Create/Edit Incident Logs
Support file attachments
If part replacement is required, it's used as the base for Part Stock Out

Manage Problem
Search Problem Logs
Create/Edit Problem Logs
Support linking to related Incident Logs
Support file attachments

Manage Parts Inventory
Stock In/Out and Adjustment
Search Parts
Create/Edit Parts
Support re-order level, can filter list of parts that the balance is below re-order level
Support First-In-First-Out for Stock Out ???
Parts I/O Ledger

Android App
View PM Plans
Input Service and Incident Logs
Receive Incident service requests


Двоични данни
docs/SMS Schema.xlsx Целия файл


+ 5
- 0
gradle/wrapper/gradle-wrapper.properties Целия файл

@@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.3-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

+ 188
- 0
gradlew Целия файл

@@ -0,0 +1,188 @@
#!/usr/bin/env sh

#
# Copyright 2015 the original author or 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
#
# http://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 UN*X
##
##############################################################################

# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null

APP_NAME="Gradle"
APP_BASE_NAME=`basename "$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 "$*"
}

die () {
echo
echo "$*"
echo
exit 1
}

# 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
;;
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" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi

# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi

# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`

# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option

if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi

# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")

# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"

# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi

exec "$JAVACMD" "$@"

+ 100
- 0
gradlew.bat Целия файл

@@ -0,0 +1,100 @@
@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 http://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 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%" == "0" goto init

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 init

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

:init
@rem Get command-line arguments, handling Windows variants

if not "%OS%" == "Windows_NT" goto win9xME_args

:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2

:win9xME_args_slurp
if "x%~1" == "x" goto execute

set CMD_LINE_ARGS=%*

: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 %CMD_LINE_ARGS%

:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="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!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1

:mainEnd
if "%OS%"=="Windows_NT" endlocal

:omega

+ 3
- 0
off.bat Целия файл

@@ -0,0 +1,3 @@
@echo off
sc stop Tomcat8
ping 127.0.0.1 > nul

+ 3
- 0
off.sh Целия файл

@@ -0,0 +1,3 @@
#!/bin/bash
source ./setenv.sh
bash ${tc_instance}/bin/catalina.sh stop >> /dev/null 2>&1

+ 2
- 0
on.bat Целия файл

@@ -0,0 +1,2 @@
@echo off
sc start Tomcat8

+ 3
- 0
on.sh Целия файл

@@ -0,0 +1,3 @@
#!/bin/bash
source ./setenv.sh
bash ${tc_instance}/bin/catalina.sh start >> /dev/null 2>&1

+ 5
- 0
prod.sh Целия файл

@@ -0,0 +1,5 @@
#!/bin/bash
cp src/main/webapp/WEB-INF/classes/log4j.properties.prod src/main/webapp/WEB-INF/classes/log4j.properties
cp src/main/webapp/WEB-INF/app.properties.prod src/main/webapp/WEB-INF/app.properties
rm -rf src/main/webapp/META-INF/context.xml
bash ./gradlew clean war

+ 12
- 0
publish.sh Целия файл

@@ -0,0 +1,12 @@
#!/bin/bash
source ./setenv.sh
cp src/main/webapp/WEB-INF/classes/log4j.properties.local.mac src/main/webapp/WEB-INF/classes/log4j.properties
cp src/main/webapp/WEB-INF/app.properties.local.mac src/main/webapp/WEB-INF/app.properties
cp src/main/webapp/META-INF/context.xml.2fi src/main/webapp/META-INF/context.xml


#mvn clean package
#cp target/*.war ${tc_instance}/webapps/

bash ./gradlew clean war
cp build/libs/*.war ${tc_instance}/webapps/

+ 3
- 0
setenv.bat Целия файл

@@ -0,0 +1,3 @@
@echo off
set PROJECT_NAME=tbms
set TC_BASE=C:\Tomcat

+ 2
- 0
setenv.sh Целия файл

@@ -0,0 +1,2 @@
project=tbms
tc_instance=/Users/2fiadmin/tomcat

+ 1
- 0
settings.gradle Целия файл

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

+ 135
- 0
src/main/java/com/ffii/core/BaseEntity.java Целия файл

@@ -0,0 +1,135 @@
/*******************************************************************************
* 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;

import java.io.Serializable;
import java.util.Date;

import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import javax.persistence.Version;

@MappedSuperclass
public abstract class BaseEntity<PK extends Serializable> implements Serializable, Cloneable {

private static final long serialVersionUID = -7211267047474252631L;

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private PK id;

@Column(columnDefinition = "int(11)")
private Integer sysGroupId;

@Version
@Column(columnDefinition = "mediumint unsigned", nullable = false)
private int versionId; // hibernate optimistic lock version id

@Column(columnDefinition = "boolean", nullable = false)
private boolean deleted; // defaults to false

@Column(updatable = false)
private Integer createdBy; // User ID that created this record

@Column
private Integer modifiedBy; // User ID that last modified this record

@Column(updatable = false, nullable = false)
private Date created; // date created

@Column(nullable = false)
private Date modified; // last modified date

/** Default constructor */
public BaseEntity() {

}

/*
* implements Cloneable
*/
@Override
protected Object clone() throws CloneNotSupportedException {
BaseEntity<?> clone = (BaseEntity<?>) super.clone();
clone.id = null;
return clone;
}

/*
* getters and setters
*/
public PK getId() {
return id;
}

@SuppressWarnings("unchecked")
public void setId(Serializable id) {
this.id = (PK) id;
}

public int getVersionId() {
return versionId;
}

public void setVersionId(int versionId) {
this.versionId = versionId;
}

public boolean isDeleted() {
return deleted;
}

public void setDeleted(boolean deleted) {
this.deleted = deleted;
}

public Integer getCreatedBy() {
return createdBy;
}

public void setCreatedBy(Integer createdBy) {
this.createdBy = createdBy;
}

public Integer getModifiedBy() {
return modifiedBy;
}

public void setModifiedBy(Integer modifiedBy) {
this.modifiedBy = modifiedBy;
}

public Date getCreated() {
return created;
}

public void setCreated(Date created) {
this.created = created;
}

public Date getModified() {
return modified;
}

public void setModified(Date modified) {
this.modified = modified;
}

public Integer getSysGroupId() {
return sysGroupId;
}

public void setSysGroupId(Integer sysGroupId) {
this.sysGroupId = sysGroupId;
}

}

+ 107
- 0
src/main/java/com/ffii/core/IBaseEntity.java Целия файл

@@ -0,0 +1,107 @@
/*******************************************************************************
* 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;

import java.io.Serializable;
import java.util.Date;

public interface IBaseEntity<PK extends Serializable> extends Serializable, Cloneable {

/**
* @return the entity id
*/
public PK getId();

/**
* Set the entity id
*
* @param id
* the entity id
*/
public void setId(PK id);

/**
* @return hibernate version id
*/
public int getVersionId();

/**
* Set the hibernate version id
*
* @param versionId
* the hibernate version id
*/
public void setVersionId(int versionId);

/**
* @return entity's deleted flag
*/
public boolean isDeleted();

/**
* Set the entity's deleted flag
*
* @param deleted
* the entity's deleted flag
*/
public void setDeleted(boolean deleted);

/**
* @return created by User ID
*/
public Integer getCreatedBy();

/**
* Set the created by User ID
*
* @param createdBy
* the created by User ID
*/
public void setCreatedBy(Integer createdBy);

/**
* @return the last modified by User ID
*/
public Integer getModifiedBy();

/**
* Set the last modified by User ID
*
* @param modifiedBy
* the last modified by User ID
*/
public void setModifiedBy(Integer modifiedBy);

/**
* @return created date
*/
public Date getCreated();

/**
* Set the created date
*
* @param created
* the created date
*/
public void setCreated(Date created);

/**
* @return the last modified date
*/
public Date getModified();

/**
* Set the last modified date
*
* @param modified
* the last modified date
*/
public void setModified(Date modified);

}

+ 36
- 0
src/main/java/com/ffii/core/Session.java Целия файл

@@ -0,0 +1,36 @@
/*******************************************************************************
* 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;

import javax.servlet.http.HttpServletRequest;

/**
* Get or Set Session Attributes
*
* @author Patrick
*/
public class Session {

public static final String AVAILABLE_LOCALES = "availableLocales";

/**
* Get session attribute
*/
public static Object getAttribute(HttpServletRequest request, String name) {
return request.getSession().getAttribute(name);
}

/**
* Set session attribute
*/
public static void setAttribute(HttpServletRequest request, String name, Object value) {
request.getSession().setAttribute(name, value);
}

}

+ 149
- 0
src/main/java/com/ffii/core/Settings.java Целия файл

@@ -0,0 +1,149 @@
/*******************************************************************************
* 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;

import com.ffii.core.setting.service.SettingsService;
import com.ffii.core.utils.BooleanUtils;

public abstract class Settings {

/*
* System-wide settings
*/

/** Define all available language names as comma separated string */
public static final String SYS_AVAILABLE_LANGUAGES = "SYS.availableLanguages";

/** Define all available locales as comma separated string */
public static final String SYS_AVAILABLE_LOCALES = "SYS.availableLocales";

/** Define the system default locale as string */
public static final String SYS_DEFAULT_LOCALE = "SYS.defaultLocale";

/** Define the system available currencies as comma separated string */
public static final String SYS_CURRENCIES = "SYS.currencies";

/** Define the system modules (authorities.module) */
public static final String SYS_ROLE_MODULES = "SYS.modules";

/*
* 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";

public static final String SYS_PASSWORD_RULE_MIN = "SYS.password.rule.length.min";
public static final String SYS_PASSWORD_RULE_MAX = "SYS.password.rule.length.max";
public static final String SYS_PASSWORD_RULE_NUMBER = "SYS.password.rule.number";
public static final String SYS_PASSWORD_RULE_UPPER_ENG = "SYS.password.rule.upper.eng";
public static final String SYS_PASSWORD_RULE_LOWER_ENG = "SYS.password.rule.lower.eng";
public static final String SYS_PASSWORD_RULE_SPECIAL = "SYS.password.rule.special";

public static final String JS_VERSION = "JS.version";

public static class MailSMTP {
private String host;
private String port;
private String username;
private String password;

public MailSMTP(SettingsService settingsService) {
if (settingsService == null) throw new NullPointerException("SettingsService cannot be null");

this.host = settingsService.getString(MAIL_SMTP_HOST);
this.port = settingsService.getString(MAIL_SMTP_PORT);
this.username = settingsService.getString(MAIL_SMTP_USERNAME);
this.password = settingsService.getString(MAIL_SMTP_PASSWORD);
}

public String getHost() {
return host;
}

public String getPort() {
return port;
}

public String getUsername() {
return username;
}

public String getPassword() {
return password;
}

}

public static class PasswordRule {
private int min;
private int max;

private boolean number;
private boolean upperEng;
private boolean lowerEng;
private boolean specialChar;

public PasswordRule(SettingsService settingsService) {
if (settingsService == null) throw new NullPointerException("SettingsService cannot be null");

this.min = settingsService.getInt(SYS_PASSWORD_RULE_MIN);
this.max = settingsService.getInt(SYS_PASSWORD_RULE_MAX);
this.number = BooleanUtils.isTrue(settingsService.getString(SYS_PASSWORD_RULE_NUMBER));
this.upperEng = BooleanUtils.isTrue(settingsService.getString(SYS_PASSWORD_RULE_UPPER_ENG));
this.lowerEng = BooleanUtils.isTrue(settingsService.getString(SYS_PASSWORD_RULE_LOWER_ENG));
this.specialChar = BooleanUtils.isTrue(settingsService.getString(SYS_PASSWORD_RULE_SPECIAL));
}

public int getMin() {
return min;
}

public int getMax() {
return max;
}

public boolean needNumberChar() {
return number;
}

public boolean needUpperEngChar() {
return upperEng;
}

public boolean needLowerEngChar() {
return lowerEng;
}

public boolean needSpecialChar() {
return specialChar;
}

public String getWrongMsg() {
StringBuilder msg = new StringBuilder("Please Following Password Rule.\n");
msg.append("Minimum " + getMin() + " Characters\n");
msg.append("Maximum " + getMax() + " Characters\n");
if (needNumberChar()) msg.append("Numbers\n");
if (needLowerEngChar()) msg.append("Lower-Case Letters\n");
if (needUpperEngChar()) msg.append("Capital Letters\n");
if (needSpecialChar()) msg.append("Symbols\n");
return msg.toString();
}

}
}

+ 264
- 0
src/main/java/com/ffii/core/User.java Целия файл

@@ -0,0 +1,264 @@
/*******************************************************************************
* 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;

import java.util.List;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
import javax.persistence.Transient;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

@Entity
@Table(name = "users")
public class User extends BaseEntity<Integer> implements UserDetails {

private static final long serialVersionUID = -9036483598847846102L;

/** Company ID */
@Column(nullable = false)
private int companyId;

/** Customer ID, optional, for End User's customers */
@Column(nullable = true)
private Integer customerId;

/** username for login */
@Column(columnDefinition = "varchar(32)", nullable = false)
private String username;

/** password for login */
@Column(columnDefinition = "varchar(64)", nullable = false)
private String password;

/** is user expired? (not implemented) */
@Column(columnDefinition = "boolean", nullable = false)
private boolean expired;

/** is user locked? (not implemented) */
@Column(columnDefinition = "boolean", nullable = false)
private boolean locked;

/** User default locale (en / zh_TW / zh_CN) */
@Column(columnDefinition = "varchar(5)")
private String locale;

/** Full name for display */
@Column(columnDefinition = "varchar(90)")
private String fullname;

/* User profile info (optional fields) START */

/** First name */
@Column(columnDefinition = "varchar(45)")
private String firstname;

/** Last name */
@Column(columnDefinition = "varchar(30)")
private String lastname;

/** Department */
@Column(nullable = true)
private Integer deptId;

/** Job title */
@Column(columnDefinition = "varchar(60)")
private String title;

/** Email */
@Column(columnDefinition = "varchar(120)")
private String email;

/** Phone 1 */
@Column(columnDefinition = "varchar(30)")
private String phone1;

/** Phone 2 */
@Column(columnDefinition = "varchar(30)")
private String phone2;

/** Remarks */
@Column(columnDefinition = "varchar(600)")
private String remarks;

/* User profile info (optional fields) END */

/** Transient authorities (populated during user authentication) */
@Transient
private List<GrantedAuthority> authorities;

/*
* getters and setters
*/

public int getCompanyId() {
return companyId;
}

public void setCompanyId(int companyId) {
this.companyId = companyId;
}

public Integer getCustomerId() {
return customerId;
}

public void setCustomerId(Integer customerId) {
this.customerId = customerId;
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}

public boolean isExpired() {
return expired;
}

public void setExpired(boolean expired) {
this.expired = expired;
}

public boolean isLocked() {
return locked;
}

public void setLocked(boolean locked) {
this.locked = locked;
}

public String getLocale() {
return locale;
}

public void setLocale(String locale) {
this.locale = locale;
}

public String getFullname() {
return fullname;
}

public void setFullname(String fullname) {
this.fullname = fullname;
}

public String getFirstname() {
return firstname;
}

public void setFirstname(String firstname) {
this.firstname = firstname;
}

public String getLastname() {
return lastname;
}

public void setLastname(String lastname) {
this.lastname = lastname;
}

public Integer getDeptId() {
return deptId;
}

public void setDeptId(Integer deptId) {
this.deptId = deptId;
}

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 void setAuthorities(List<GrantedAuthority> authorities) {
this.authorities = authorities;
}

/*
* Override
*/

@Override
public List<GrantedAuthority> getAuthorities() {
return authorities;
}

@Override
public boolean isAccountNonExpired() {
return !expired;
}

@Override
public boolean isAccountNonLocked() {
return !locked;
}

@Override
public boolean isCredentialsNonExpired() {
return true;
}

@Override
public boolean isEnabled() {
return true;
}

}

+ 160
- 0
src/main/java/com/ffii/core/dao/HibernateDao.java Целия файл

@@ -0,0 +1,160 @@
/*******************************************************************************
* 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.dao;

import java.io.Serializable;
import java.util.Date;
import java.util.List;

import com.ffii.core.BaseEntity;
import com.ffii.core.User;
import com.ffii.core.utils.SecurityUtils;

import org.hibernate.query.Query;
import org.springframework.orm.hibernate5.support.HibernateDaoSupport;

/**
* Implementation of the {@link IHibernateDao} interface.
*
* @author Patrick
*/
public abstract class HibernateDao<T extends BaseEntity<PK>, PK extends Serializable> extends HibernateDaoSupport
implements IHibernateDao<T, PK> {

private final Class<T> type;

public HibernateDao(final Class<T> type) {
this.type = type;
}

/**
* Automatically set the {@code createdBy}, {@code created}, {@code modifiedBy},
* and {@code modified} fields of the object instance.
*
* @param instance the object instance
* @param isNew {@code true} if the instance is new, and need to set the
* {@code createdBy} and {@code created} fields
*/
private void autoSetCreateAndLastModifyFields(final T instance, boolean isNew) {
// try to get the authenticated User ID from session (not available in Mapp)
User user = SecurityUtils.getUser();
Integer userId = null;
Integer sysGroupId = null;
if (user != null){
userId = user.getId();
sysGroupId = user.getSysGroupId();
}

Date now = new Date();

if (isNew) {
if (instance.getCreatedBy() == null && userId != null)
instance.setCreatedBy(userId);
if (userId != null)
instance.setModifiedBy(userId);
if (instance.getCreated() == null)
instance.setCreated(now);
if(instance.getSysGroupId() == null)
instance.setSysGroupId(sysGroupId);
instance.setModified(now);
} else {
if (userId != null)
instance.setModifiedBy(userId);
instance.setModified(now);
}
}

@SuppressWarnings("unchecked")
@Override
public PK save(final T instance) {
autoSetCreateAndLastModifyFields(instance, true);
return (PK) getHibernateTemplate().save(instance);
}

@SuppressWarnings("unchecked")
@Override
public PK saveAs(final T instance) {
return (PK) getHibernateTemplate().save(instance);
}

@Override
public void update(final T instance) {
autoSetCreateAndLastModifyFields(instance, false);
getHibernateTemplate().update(instance);
}

@Override
public PK saveOrUpdate(final T instance) {
Serializable id = instance.getId();
if (id instanceof Long) {
if (((Long) id).longValue() < 0L)
id = null;
} else if (id instanceof Integer) {
if (((Integer) id).intValue() < 0)
id = null;
}
if (id == null)
return save(instance);
else {
update(instance);
return (PK) instance.getId();
}
}

@Override
public boolean delete(final T instance) {
try {
getHibernateTemplate().delete(instance);
return true;
} catch (Exception e) {
return false;
}
}

@Override
public void deleteAll(List<T> instances) {
getHibernateTemplate().deleteAll(instances);
}

@Override
public void flush() {
getHibernateTemplate().flush();
}

@Override
public T find(final PK id) {
return getHibernateTemplate().get(type, id);
}

@Override
@SuppressWarnings("unchecked")
public T findByQuery(final String queryString, final Serializable... args) {
return getHibernateTemplate().execute(session -> {
Query<T> query = session.createQuery(queryString);
for (int i = 0; i < args.length; i++) {
query.setParameter(i, args[i]);
}
query.setMaxResults(1);
return query.uniqueResult();
});
}

@Override
@SuppressWarnings("unchecked")
public List<T> listByQuery(final String queryString, final Serializable... args) {
return getHibernateTemplate().execute(session -> {
Query<T> query = session.createQuery(queryString);
for (int i = 0; i < args.length; i++) {
query.setParameter(i, args[i]);
}
return query.list();
});
}

}

+ 125
- 0
src/main/java/com/ffii/core/dao/IHibernateDao.java Целия файл

@@ -0,0 +1,125 @@
/*******************************************************************************
* Copyright 2019 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.dao;

import java.io.Serializable;
import java.util.List;

import org.springframework.orm.hibernate5.HibernateTemplate;

/**
* Hibernate DAO interface. For use with business services to perform actual operations.
*
* @param <T>
* The class this DAO is taking care of.
* @param <PK>
* The type of the primary key of class instances.
*
* @author Patrick
*/
public interface IHibernateDao<T extends Serializable, PK extends Serializable> {

/**
* Returns the underlying {@link HibernateTemplate}
*/
public HibernateTemplate getHibernateTemplate();

/**
* Saves the instance to the database.
*
* @param instance
* the object instance to save
*
* @return the object instance's primary key in database
*/
public PK save(T instance);

/**
* Saves the instance to the database without setting the created/updated fields.
*
* @param instance
* the object instance to save
*
* @return the object instance's primary key in database
*/
public PK saveAs(T instance);

/**
* Updates the record in the database with the given instance.
*
* @param instance
* modified object instance
*/
public void update(T instance);

/**
* Saves or updates the record in the database with the given instance.
*
* @param instance
* new or modified object instance
*
* @return the object instance's primary key in database
*/
public PK saveOrUpdate(T instance);

/**
* Deletes the object instance from the database.
*
* @param instance
* the object instance to remove
*/
public boolean delete(T instance);

/**
* Remove a collection of instances from the database.
*
* @param instances
* The object instances to remove
*/
public void deleteAll(List<T> instances);

/**
* Finds the object with the specified identity from the database.
*
* @param id
* the object's primary key in database
*
* @return the mapped object instance from the database
*/
public T find(PK id);

/**
* Finds the object using the specified query (HQL) and arguments from the database.
*
* @param queryString
* the query string using HQL
* @param args
* variable number of arguments to pass to the query. Note you must have the exact number of arguments as specified in the query.
*
* @return the object instance from the database
*/
public T findByQuery(String queryString, Serializable... args);

/**
* Returns a {@code List} of {@code Object}s using the specified query (HQL) and arguments from the database.
*
* @param queryString
* the query string using HQL
* @param args
* variable number of arguments to pass to the query. Note you must have the exact number of arguments as specified in the query.
*
* @return {@code List} of object instances from the database
*/
public List<T> listByQuery(String queryString, Serializable... args);

public void flush();

}

+ 260
- 0
src/main/java/com/ffii/core/dao/JdbcDao.java Целия файл

@@ -0,0 +1,260 @@
/*******************************************************************************
* 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.dao;

import java.io.Serializable;
import java.util.List;
import java.util.Map;

import javax.persistence.Table;

import com.ffii.core.BaseEntity;
import com.ffii.core.utils.MapUtils;
import com.ffii.core.utils.Params;

import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcDaoSupport;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.core.simple.SimpleJdbcInsert;

/**
* JDBC DAO implementation which extends Spring {@link NamedParameterJdbcDaoSupport}
*
* @author Patrick
*/
public class JdbcDao extends NamedParameterJdbcDaoSupport {

/**
* Return an instance of SimpleJdbcInsert with the specified table name
*/
private SimpleJdbcInsert getJdbcInsert(String tableName, String idName) {
SimpleJdbcInsert jdbcInsert = new SimpleJdbcInsert(getJdbcTemplate());
if (idName != null)
return jdbcInsert.withTableName(tableName).usingGeneratedKeyColumns(idName);
else
return jdbcInsert.withTableName(tableName);
}

/**
* Query for a {@code List} of {@code Object}s of type {@code T} using the supplied {@code class} to map the query results. Uses SQL with the named
* parameter support provided by the {@code NamedParameterJdbcTemplate}.
*
* @param sql
* the SQL query to run
* @param mappedClass
* the class to use for result mapping
* @param args
* the map containing the arguments for the query
* @param maxRows
* the maximum results to return
*/
public <T> List<T> query(String sql, Class<T> mappedClass, Map<String, ?> args, int maxRows) {
((JdbcTemplate) getNamedParameterJdbcTemplate().getJdbcOperations()).setMaxRows(maxRows);
return getNamedParameterJdbcTemplate().query(sql, args, BeanPropertyRowMapper.newInstance(mappedClass));
}

/**
* Execute the supplied query with the supplied arguments.
* <p>
* The query is expected to be a single row query (otherwise, {@code null} will be returned); the result row will be mapped to a Map<String, Object> (one
* entry for each column, using the column name as the key). Uses SQL with the named parameter support provided by the {@link NamedParameterJdbcTemplate}
* </p>
*
* @param sql
* the SQL query to run
* @param args
* the map containing the arguments for the query
*/
public Map<String, Object> queryForMap(String sql, Map<String, ?> args) {
try {
return getNamedParameterJdbcTemplate().queryForMap(sql, args);
} catch (IncorrectResultSizeDataAccessException e) {
logger.debug("Unexpected row count for queryForMap()", e);
return null;
}
}

/**
* Query for a {@code List} of {@code Map}s, each element in the returned {@code List} is constructed as a {@code Map} as described in
* {@link JdbcOperations#queryForMap}. Uses SQL with the named parameter support provided by the {@code NamedParameterJdbcTemplate}.
*
* @param sql
* the SQL query to run
* @param args
* the map containing the arguments for the query
* @param maxRows
* the maximum results to return
*/
public List<Map<String, Object>> queryForList(String sql, Map<String, ?> args, int maxRows) {
((JdbcTemplate) getNamedParameterJdbcTemplate().getJdbcOperations()).setMaxRows(maxRows);
return getNamedParameterJdbcTemplate().queryForList(sql, args);
}

/**
* Query for a {@code List} of {@code Map}s, each element in the returned {@code List} is constructed as a {@code Map} as described in
* {@link JdbcOperations#queryForMap}. Uses SQL with the named parameter support provided by the {@code NamedParameterJdbcTemplate}.
*
* @param sql
* the SQL query to run
* @param args
* the map containing the arguments for the query
*/
public List<Map<String, Object>> queryForList(String sql, Map<String, ?> args) {
return queryForList(sql, args, 0);
}

/**
* Query for an {@code int} passing in a SQL query using the named parameter support provided by the {@code NamedParameterJdbcTemplate} and a map containing
* the arguments.
* <p>
* The query is expected to be a single row/single column query; the returned result will be directly mapped to an {@code int}.
*
* @param sql
* the SQL query to run
* @param args
* the map containing the arguments for the query
*/
public int queryForInt(String sql, Map<String, ?> args) {
Integer value = getNamedParameterJdbcTemplate().queryForObject(sql, args, Integer.class);
return (value != null ? value.intValue() : 0);
}

/**
* Query for a {@code String} passing in a SQL query using the named parameter support provided by the {@code NamedParameterJdbcTemplate} and a map
* containing the arguments.
* <p>
* The query is expected to be a single row/single column query; the returned result will be directly mapped to a {@code String}.
*
* @param sql
* the SQL query to run
* @param args
* the map containing the arguments for the query
*/
public String queryForString(String sql, Map<String, ?> args) {
return getNamedParameterJdbcTemplate().queryForObject(sql, args, String.class);
}

/**
* Executes a batch using the supplied SQL statement with the batch of supplied arguments using the named parameter support provided by the
* {@code NamedParameterJdbcTemplate}
*
* @param sql
* the SQL statement to execute
* @param batchValues
* the array of Maps containing the batch of arguments for the query
*
* @return an array containing the numbers of rows affected by each update in the batch
*/
public int[] executeBatchUpdate(String sql, Map<String, ?>[] batchValues) {
return getNamedParameterJdbcTemplate().batchUpdate(sql, batchValues);
}

/**
* Execute the supplied SQL statement with (optional) supplied arguments using the named parameter support provided by the
* {@code NamedParameterJdbcTemplate}
*
* @param sql
* the SQL statement to execute
* @param args
* the map containing the arguments for the query
*
* @return the number of rows affected by the update
*/
public int executeUpdate(String sql, Map<String, ?> args) {
return getNamedParameterJdbcTemplate().update(sql, args);
}

/**
* Execute the supplied SQL statement with no arguments
*
* @param sql
* the SQL statement to execute
*
* @return the number of rows affected by the update
*/
public int executeUpdate(String sql) {
return getJdbcTemplate().update(sql);
}

/**
* Executes SQL UPDATE statement with the supplied class and its ID and keyValuePairs
*
* @param clazz
* any class that extends BaseEntity
* @param keyValuePairs
* key value pairs, must include the ID field
*
* @return the number of rows affected by the update
*/
public int executeUpdateByTable(String tableName, Map<String, ?> args) {

StringBuilder sql = new StringBuilder("UPDATE `" + tableName + "` SET ");
int i = 0;
for (String key : args.keySet()) {
if (i > 0) sql.append(", ");
sql.append(key + " = :" + key);
i++;
}
sql.append(" WHERE id = :id");

return executeUpdate(sql.toString(), args);
}

/**
* Execute SQL Insert statement with the supplied arguments
*
* @param tableName
* the database table name
* @param args
* the map containing the arguments for the insert statement in the form of column name and its value
*
* @return the number of rows affected as returned by the JDBC driver
*/
public int executeInsert(String tableName, Map<String, Object> args) {
return getJdbcInsert(tableName, null).execute(args);
}

/**
* Execute SQL Insert statement with the supplied arguments and return the generated key value
*
* @param tableName
* the database table name
* @param idName
* the name of ID column that has auto generated key
* @param args
* the map containing the arguments for the insert statement in the form of column name and its value
*
* @return the generated key value
*/
public Number executeInsertAndReturnKey(String tableName, String idName, Map<String, Object> args) {
return getJdbcInsert(tableName, idName).executeAndReturnKey(args);
}

/**
* Executes SQL Delete statement with the supplied class and its ID
*
* @param clazz
* any class that extends BaseEntity
* @param id
* the entity's ID
*
* @return the number of rows affected by the delete
*/
public int executeDelete(Class<? extends BaseEntity<Serializable>> clazz, Serializable id) {
Table table = clazz.getAnnotation(Table.class);

String sql = "DELETE FROM " + table.name() + " WHERE id = :id";

return executeUpdate(sql, MapUtils.toHashMap(Params.ID, id));
}

}

+ 56
- 0
src/main/java/com/ffii/core/engine/FreeMarkerEngine.java Целия файл

@@ -0,0 +1,56 @@
/*******************************************************************************
* Copyright 2019 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.engine;

import java.io.IOException;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.ui.freemarker.FreeMarkerTemplateUtils;
import org.springframework.web.context.support.WebApplicationObjectSupport;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;

import freemarker.template.Template;
import freemarker.template.TemplateException;

/**
* FreeMarker template engine - a Spring Component Bean
*
* @author Patrick
*/
@Component
public class FreeMarkerEngine extends WebApplicationObjectSupport {

@Autowired
private FreeMarkerConfigurer freemarkerConfig;

/**
* Generates content from FreeMarker template
*
* @param template
* the FreeMarker template name (e.g. {@code "example/example.ftl"})
* @param model
* the model to be applied to the FreeMarker template, may be null
* @return the generated content as a {@code String}, or {@code null} if exceptions occured
*/
public String generateFreeMarkerContent(String template, Object model) {
try {
Template t = freemarkerConfig.getConfiguration().getTemplate(template);
return FreeMarkerTemplateUtils.processTemplateIntoString(t, model);
} catch (TemplateException e) {
logger.error("Error while processing FreeMarker template ", e);
} catch (IOException e) {
logger.error("IOException occured ", e);
}
return null;
}

}

+ 316
- 0
src/main/java/com/ffii/core/engine/MailEngine.java Целия файл

@@ -0,0 +1,316 @@
/*******************************************************************************
* Copyright 2018 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.engine;

import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import javax.mail.MessagingException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;

import com.ffii.core.Settings;
import com.ffii.core.setting.service.SettingsService;
import com.ffii.core.utils.SecurityUtils;
import com.ffii.tbms.user.service.SysGroupService;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.InputStreamSource;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Component;
import org.springframework.web.context.support.WebApplicationObjectSupport;

/**
* Mail Engine is a Spring Component Bean that handles E-mail operations.
*
* @author Patrick
*/
@Component
public class MailEngine extends WebApplicationObjectSupport {

@Autowired
private FreeMarkerEngine freeMarkerEngine;

private JavaMailSender mailSender;

@Autowired
private SettingsService settingsService;

@Autowired
private SysGroupService sysGroupService;

/**
* Send HTML mail message
*
* @param from
* the from address, must not be null
* @param replyTo
* the reply to address, may be null
* @param to
* the to addresses, must not be null
* @param cc
* the cc addresses, may be null
* @param bcc
* the bcc addresses, may be null
* @param subject
* the subject, may be null
* @param html
* the html message body, must not be null
* @param attachmentFilename
* the name of the attachment as it will appear in the mail
* @param file
* the File to attach
*/

private JavaMailSender getJavaMailSender(int sysGroupId){
//if(mailSender == null){
Map<String, Object> sysGroup = sysGroupService.find(sysGroupId);

String smtp_host = "";
int smtp_port = 0;
String smtp_username = "";
String smtp_password = "";
logger.info(sysGroup);
if(sysGroup != null){
smtp_host = sysGroup.get("smtp_host")==null?"":sysGroup.get("smtp_host").toString();
smtp_port = sysGroup.get("smtp_port")==null?0:Integer.parseInt(sysGroup.get("smtp_port").toString());
smtp_username = sysGroup.get("smtp_username")==null?"":sysGroup.get("smtp_username").toString();
smtp_password = sysGroup.get("smtp_password")==null?"":sysGroup.get("smtp_password").toString();
}

JavaMailSenderImpl jmsi = new JavaMailSenderImpl();
jmsi.setHost(smtp_host);
jmsi.setPort(smtp_port);
jmsi.setUsername(smtp_username);
jmsi.setPassword(smtp_password);

Properties props = jmsi.getJavaMailProperties();
props.put("mail.transport.protocol", "smtp");
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.starttbms.enable", "true");
props.put("mail.debug", "true");
mailSender = jmsi;
//}
return mailSender;
}

public void sendHtmlMail(int sysGroupId, InternetAddress from, InternetAddress replyTo, InternetAddress[] to, InternetAddress[] cc, InternetAddress[] bcc,
String subject, String html, String attachmentFilename, File file) {

MimeMessage mimeMessage = getJavaMailSender(sysGroupId).createMimeMessage();

try {
MimeMessageHelper message = new MimeMessageHelper(mimeMessage, true, "UTF-8");
message.setFrom(from);
if (replyTo != null) message.setReplyTo(replyTo);
message.setTo(to);
if (cc != null) message.setCc(cc);
if (bcc != null) message.setBcc(bcc);
if (subject != null) message.setSubject(subject);
message.setText(html, true);
if (attachmentFilename != null && file != null) message.addAttachment(attachmentFilename, file);
} catch (MessagingException e) {
logger.error("Failed to send HTML mail", e);
}

getJavaMailSender(sysGroupId).send(mimeMessage);
}

/**
* Send HTML mail message
*
* @param from
* the from address, must not be null
* @param replyTo
* the reply to address, may be null
* @param to
* the to addresses, must not be null
* @param subject
* the subject, may be null
* @param html
* the html message body, must not be null
* @param attachmentFilename
* the name of the attachment as it will appear in the mail
* @param file
* the File to attach
*/
public void sendHtmlMail(int sysGroupId, InternetAddress from, InternetAddress replyTo, InternetAddress[] to,
String subject, String html, String attachmentFilename, File file) {
sendHtmlMail(sysGroupId, from, replyTo, to, null, null, subject, html, attachmentFilename, file);
}

/**
* Send HTML mail message
*
* @param from
* the from address, must not be null
* @param replyTo
* the reply to address, may be null
* @param to
* the to addresses, must not be null
* @param subject
* the subject, may be null
* @param html
* the html message body, must not be null
*/
public void sendHtmlMail(int sysGroupId,InternetAddress from, InternetAddress replyTo, InternetAddress[] to, String subject, String html) {
sendHtmlMail(sysGroupId, from, replyTo, to, null, null, subject, html, null, null);
}

/**
* Send HTML mail message generated from FreeMarker template
*
* @param from
* the from address, must not be null
* @param replyTo
* the reply to address, may be null
* @param to
* the to addresses, must not be null
* @param cc
* the cc addresses, may be null
* @param bcc
* the bcc addresses, may be null
* @param subject
* the subject, may be null
* @param template
* the FreeMarker template name, must not be null
* @param model
* the model to be applied to the FreeMarker template, may be null
* @param attachmentFilename
* the name of the attachment as it will appear in the mail
* @param file
* the File to attach
*/
public void sendFreeMarkerHtmlMail(int sysGroupId,InternetAddress from, InternetAddress replyTo, InternetAddress[] to, InternetAddress[] cc, InternetAddress[] bcc,
String subject, String template, Map<?, ?> model, String attachmentFilename, File file) {

String html = freeMarkerEngine.generateFreeMarkerContent(template, model);

sendHtmlMail(sysGroupId,from, replyTo, to, cc, bcc, subject, html, attachmentFilename, file);
}

/**
* Send HTML mail message generated from FreeMarker template
*
* @param from
* the from address, must not be null
* @param replyTo
* the reply to address, may be null
* @param to
* the to addresses, must not be null
* @param subject
* the subject, may be null
* @param template
* the FreeMarker template name, must not be null
* @param model
* the model to be applied to the FreeMarker template, may be null
*/
public void sendFreeMarkerHtmlMail(int sysGroupId, InternetAddress from, InternetAddress replyTo, InternetAddress[] to, InternetAddress[] cc, InternetAddress[] bcc,
String subject, String template, Map<?, ?> model) {
sendFreeMarkerHtmlMail(sysGroupId, from, replyTo, to, cc, bcc, subject, template, model, null, null);
}

/**
* Send HTML mail message generated from FreeMarker template
*
* @param from
* the from address, must not be null
* @param replyTo
* the reply to address, may be null
* @param to
* the to addresses, must not be null
* @param subject
* the subject, may be null
* @param template
* the FreeMarker template name, must not be null
* @param model
* the model to be applied to the FreeMarker template, may be null
* @param attachmentFilename
* the name of the attachment as it will appear in the mail
* @param file
* the File to attach
*/
public void sendFreeMarkerHtmlMail(int sysGroupId,InternetAddress from, InternetAddress replyTo, InternetAddress[] to,
String subject, String template, Map<?, ?> model, String attachmentFilename, File file) {
sendFreeMarkerHtmlMail(sysGroupId, from, replyTo, to, null, null, subject, template, model, attachmentFilename, file);
}

/**
* Send HTML mail message generated from FreeMarker template
*
* @param from
* the from address, must not be null
* @param replyTo
* the reply to address, may be null
* @param to
* the to addresses, must not be null
* @param subject
* the subject, may be null
* @param template
* the FreeMarker template name, must not be null
* @param model
* the model to be applied to the FreeMarker template, may be null
* @param attachmentFilename
* the name of the attachment as it will appear in the mail
* @param file
* the File to attach
*/

public void sendFreeMarkerHtmlMail(int sysGroupId, InternetAddress from, InternetAddress replyTo, InternetAddress[] to,
String subject, String template, Map<?, ?> model, String attachmentFilename, InputStreamSource file) {

String html = freeMarkerEngine.generateFreeMarkerContent(template, model);

MimeMessage mimeMessage = getJavaMailSender(sysGroupId).createMimeMessage();
try {
MimeMessageHelper message = new MimeMessageHelper(mimeMessage, true, "UTF-8");
message.setFrom(from);
if (replyTo != null) message.setReplyTo(replyTo);
message.setTo(to);
if (subject != null) message.setSubject(subject);
message.setText(html, true);
if (attachmentFilename != null && file != null) message.addAttachment(attachmentFilename, file);
} catch (MessagingException e) {
logger.error("Failed to send HTML mail", e);
}

getJavaMailSender(sysGroupId).send(mimeMessage);
}


/**
* Send HTML mail message generated from FreeMarker template
*
* @param from
* the from address, must not be null
* @param replyTo
* the reply to address, may be null
* @param to
* the to addresses, must not be null
* @param subject
* the subject, may be null
* @param template
* the FreeMarker template name, must not be null
* @param model
* the model to be applied to the FreeMarker template, may be null
*/
public void sendFreeMarkerHtmlMail(int sysGroupId, InternetAddress from, InternetAddress replyTo, InternetAddress[] to,
String subject, String template, Map<?, ?> model) {
sendFreeMarkerHtmlMail(sysGroupId,from, replyTo, to, null, null, subject, template, model, null, null);
}

}

+ 109
- 0
src/main/java/com/ffii/core/i18n/support/JdbcMessageSource.java Целия файл

@@ -0,0 +1,109 @@
/*******************************************************************************
* Copyright 2019 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.i18n.support;

import java.text.MessageFormat;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.support.AbstractMessageSource;

import com.ffii.core.dao.JdbcDao;

/**
* Jdbc MessageSource implementation.
*
* <pre>
* CREATE TABLE `i18n` (
* `locale` varchar(10) NOT NULL,
* `code` varchar(100) NOT NULL,
* `value` varchar(500) DEFAULT NULL,
* PRIMARY KEY (`locale`,`code`)
* );
* </pre>
*
* @author Patrick
*/
public class JdbcMessageSource extends AbstractMessageSource {

@Autowired
private JdbcDao jdbcDao;

@Override
protected String resolveCodeWithoutArguments(String code, Locale locale) {
return getMessages(locale.toString()).get(code);
}

@Override
protected MessageFormat resolveCode(String code, Locale locale) {
String msg = getMessages(locale.toString()).get(code);
return createMessageFormat(msg, locale);
}

/** i18n messages cache */
private static Map<String, Map<String, String>> MESSAGES = new HashMap<String, Map<String, String>>();

/**
* Load a list of messages from i18n table in database
*
* @param locale
* the locale String (e.g. {@code en_US})
*/
public List<Map<String, Object>> loadMessages(String locale) {
/* prep args */
Map<String, Object> args = new HashMap<String, Object>();
args.put("locale", locale);

/* build sql */
String sql = "SELECT locale, code, value FROM i18n WHERE locale = :locale";

/* do query and return */
return jdbcDao.queryForList(sql, args);
}

/**
* Get i18n messages map
*
* @param locale
* the locale String (e.g. {@code en_US})
*/
public Map<String, String> getMessages(String locale) {
Map<String, String> messages = MESSAGES.get(locale);

/* if not loaded yet, load it from database */
if (messages == null) {
messages = new HashMap<String, String>();

/* convert the list to a simple messages map */
List<Map<String, Object>> rawMessages = loadMessages(locale);
for (Map<String, Object> rm : rawMessages) {
messages.put((String) rm.get("code"), (String) rm.get("value"));
}

/* cache the messages */
MESSAGES.put(locale, messages);
}

return messages;
}

/**
* Reset the i18n messages cache
*/
public void resetMessagesCache() {
/* simply create a new map */
MESSAGES = new HashMap<String, Map<String, String>>();
}

}

+ 52
- 0
src/main/java/com/ffii/core/security/authentication/AuthToken.java Целия файл

@@ -0,0 +1,52 @@
package com.ffii.core.security.authentication;

import com.ffii.core.User;

import org.springframework.security.authentication.AbstractAuthenticationToken;

/**
* Authentication Token
*
* @author Patrick
*/
public class AuthToken extends AbstractAuthenticationToken {

private static final long serialVersionUID = 3383254131623375507L;

private final String token;
private final User user;

public AuthToken(String token) {
super(null);

this.token = token;
this.user = null;
setAuthenticated(false);
}

public AuthToken(String token, User user) {
super(user.getAuthorities());

this.token = token;
this.user = user;
setAuthenticated(true);
}

@Override
public Object getCredentials() {
return getToken();
}

@Override
public Object getPrincipal() {
return getUser();
}

public String getToken() {
return token;
}

public User getUser() {
return user;
}
}

+ 49
- 0
src/main/java/com/ffii/core/security/authentication/TokenAuthenticationProvider.java Целия файл

@@ -0,0 +1,49 @@
/*******************************************************************************
* 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.security.authentication;

import com.ffii.core.User;
import com.ffii.core.security.service.TokenUserDetailsService;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Component;

/**
* Token Authentication Provider
*
* @author Patrick
*/
@Component
public class TokenAuthenticationProvider implements AuthenticationProvider {

@Autowired
private TokenUserDetailsService tokenUserDetailsService;

@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
final AuthToken authToken = (AuthToken) authentication;
final String token = authToken.getToken();

User user = tokenUserDetailsService.findUserByToken(token);

if (user == null)
throw new BadCredentialsException("No user found for token - " + token);

return new AuthToken(token, user);
}

@Override
public boolean supports(Class<?> authentication) {
return authentication.equals(AuthToken.class);
}
}

+ 74
- 0
src/main/java/com/ffii/core/security/filter/AuthTokenFilter.java Целия файл

@@ -0,0 +1,74 @@
package com.ffii.core.security.filter;

import java.io.IOException;
import java.util.Collections;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.ffii.core.security.authentication.AuthToken;
import com.ffii.core.utils.StringUtils;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.RequestMatcher;

/**
* Authentication Token Filter
*
* @author Patrick
*/
public class AuthTokenFilter extends AbstractAuthenticationProcessingFilter {

public static final String TOKEN_HEADER = "x-auth-token";

public AuthTokenFilter(RequestMatcher requestMatcher) {
super(requestMatcher);
}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
final String tokenValue = getTokenValue((HttpServletRequest) request);

// This filter only applies if the header is present
if (StringUtils.isEmpty(tokenValue)) {
chain.doFilter(request, response);
return;
}

// On success keep going on the chain
this.setAuthenticationSuccessHandler((request1, response1, authentication) -> {
chain.doFilter(request1, response1);
});

super.doFilter(request, response, chain);
}

@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException, IOException, ServletException {
final String tokenValue = getTokenValue(request);

if (StringUtils.isEmpty(tokenValue)) {
return null;
}

AuthToken token = new AuthToken(tokenValue);
token.setDetails(authenticationDetailsSource.buildDetails(request));

return this.getAuthenticationManager().authenticate(token);
}

private String getTokenValue(HttpServletRequest request) {
return Collections.list(request.getHeaderNames()).stream()
.filter(header -> TOKEN_HEADER.equalsIgnoreCase(header)).map(header -> request.getHeader(header))
.findFirst().orElse(null);
}

}

+ 65
- 0
src/main/java/com/ffii/core/security/service/LoginLogService.java Целия файл

@@ -0,0 +1,65 @@
/*******************************************************************************
* 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.security.service;

import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.ffii.core.dao.JdbcDao;
import com.ffii.core.utils.MapUtils;
import com.ffii.core.web.AbstractService;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;

/**
* Login Log Service
*
* @author Patrick
*/
@Service
public class LoginLogService extends AbstractService {

@Autowired
private JdbcDao jdbcDao;

/**
* Create Login Log record
*
* @param username
* the username
* @param remoteAddr
* request.getRemoteAddr()
* @param success
* true if the login is successful, else false
*
* @return true if the number of rows inserted is 1
*/
@Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = false)
public boolean createLoginLog(String username, String remoteAddr, boolean 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.executeInsert("user_login_log", 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));
}

}

+ 143
- 0
src/main/java/com/ffii/core/security/service/TokenUserDetailsService.java Целия файл

@@ -0,0 +1,143 @@
/*******************************************************************************
* 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.security.service;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import com.ffii.core.User;
import com.ffii.core.dao.JdbcDao;
import com.ffii.core.utils.MapUtils;
import com.ffii.core.web.AbstractService;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;

/**
* Token User Details Service
*
* @author Patrick
*/
@Service
public class TokenUserDetailsService extends AbstractService {

@Autowired
private JdbcDao jdbcDao;

public static final String FIND_USER_BY_DEVICE_TOKEN_SQL = "SELECT u.* FROM users u LEFT JOIN access_token at ON u.id = at.userId"
+ " WHERE u.deleted = 0 AND at.token = :token";

public static final String LOAD_AUTHORITIES_BY_USERNAME_SQL = "SELECT u.username, ua.authority"
+ " FROM `users_authorities` ua" + " LEFT JOIN `users` u ON ua.userId = u.id"
+ " WHERE u.deleted = 0 AND u.username = ?";

public static final String LOAD_GROUP_AUTHORITIES_BY_USERNAME_SQL = "SELECT g.id, g.name, ga.authority"
+ " FROM `groups_authorities` ga" + " LEFT JOIN `groups` g ON ga.groupId = g.id AND g.deleted = 0"
+ " LEFT JOIN `groups_users` gu ON ga.groupId = gu.groupId" + " LEFT JOIN `users` u ON gu.userId = u.id"
+ " WHERE u.deleted = 0 AND u.username = ?";

protected final MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();

// ~ Constructors
public TokenUserDetailsService() {
}

// ~ Methods

/**
* Allows subclasses to add their own granted authorities to the list to be
* returned in the <tt>UserDetails</tt>.
*
* @param username the username, for use by finder methods
* @param authorities the current granted authorities, as populated from the
* <code>authoritiesByUsername</code> mapping
*/
protected void addCustomAuthorities(String username, List<GrantedAuthority> authorities) {
// add ROLE_USER for basic access
authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
}

public UserDetails loadUserByToken(String token) {

User user = findUserByToken(token); // contains no GrantedAuthority[]

// build GrantedAuthority[]
Set<GrantedAuthority> authoritiesSet = new HashSet<>();

// aadd all user's authorities
authoritiesSet.addAll(loadUserAuthorities(user.getUsername()));

// add all user's groups' authorities
authoritiesSet.addAll(loadGroupAuthorities(user.getUsername()));

// convert to List
List<GrantedAuthority> authoritiesList = new ArrayList<GrantedAuthority>(authoritiesSet);

addCustomAuthorities(user.getUsername(), authoritiesList);

user.setAuthorities(authoritiesList);

return user;
}

@Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true)
public User findUserByToken(String token) {
List<User> users = jdbcDao.query(FIND_USER_BY_DEVICE_TOKEN_SQL, User.class, MapUtils.toHashMap("token", token),
1);
if (users.size() == 1)
return users.get(0);
else
return null;
}

/**
* Loads user authorities by executing SQL
*
* @return a list of GrantedAuthority objects for the user
*/
@Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true)
protected List<GrantedAuthority> loadUserAuthorities(String username) {
return jdbcDao.getNamedParameterJdbcTemplate().query(LOAD_AUTHORITIES_BY_USERNAME_SQL,
MapUtils.toHashMap("username", username), new RowMapper<GrantedAuthority>() {
@Override
public GrantedAuthority mapRow(ResultSet rs, int rowNum) throws SQLException {
return new SimpleGrantedAuthority(rs.getString("authority"));
}
});
}

/**
* Loads group authorities by executing SQL
*
* @return a list of GrantedAuthority objects from the user's groups
*/
@Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true)
protected List<GrantedAuthority> loadGroupAuthorities(String username) {
return jdbcDao.getNamedParameterJdbcTemplate().query(LOAD_GROUP_AUTHORITIES_BY_USERNAME_SQL,
MapUtils.toHashMap("username", username), new RowMapper<GrantedAuthority>() {
@Override
public GrantedAuthority mapRow(ResultSet rs, int rowNum) throws SQLException {
return new SimpleGrantedAuthority(rs.getString("authority"));
}
});
}

}

+ 239
- 0
src/main/java/com/ffii/core/security/userdetails/UserDetailsServiceImpl.java Целия файл

@@ -0,0 +1,239 @@
/*******************************************************************************
* 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.security.userdetails;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import com.ffii.core.User;

import org.springframework.context.ApplicationContextException;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.util.Assert;


public class UserDetailsServiceImpl extends JdbcDaoSupport implements UserDetailsService {

// ~ Static fields/initializers =====================================================================================

public static final String DEF_USERS_BY_USERNAME_QUERY = "SELECT *"
+ " FROM `users` WHERE deleted = 0 AND username = ?";

public static final String DEF_AUTHORITIES_BY_USERNAME_QUERY = "SELECT u.username, ua.authority"
+ " FROM `users_authorities` ua"
+ " LEFT JOIN `users` u ON ua.userId = u.id"
+ " WHERE u.deleted = 0 AND u.username = ?";

public static final String DEF_GROUP_AUTHORITIES_BY_USERNAME_QUERY = "SELECT g.id, g.name, ga.authority"
+ " FROM `groups_authorities` ga"
+ " LEFT JOIN `groups` g ON ga.groupId = g.id AND g.deleted = 0"
+ " LEFT JOIN `groups_users` gu ON ga.groupId = gu.groupId"
+ " LEFT JOIN `users` u ON gu.userId = u.id"
+ " WHERE u.deleted = 0 AND u.username = ?";

// ~ Instance fields ================================================================================================

protected final MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();

private String usersByUsernameQuery;
private String authoritiesByUsernameQuery;
private String groupAuthoritiesByUsernameQuery;

private boolean enableAuthorities = true;
private boolean enableGroups;

// ~ Constructors ===================================================================================================

public UserDetailsServiceImpl() {
usersByUsernameQuery = DEF_USERS_BY_USERNAME_QUERY;
authoritiesByUsernameQuery = DEF_AUTHORITIES_BY_USERNAME_QUERY;
groupAuthoritiesByUsernameQuery = DEF_GROUP_AUTHORITIES_BY_USERNAME_QUERY;
}

// ~ Methods ========================================================================================================

/**
* Allows subclasses to add their own granted authorities to the list to be returned in the <tt>UserDetails</tt>.
*
* @param username
* the username, for use by finder methods
* @param authorities
* the current granted authorities, as populated from the <code>authoritiesByUsername</code> mapping
*/
protected void addCustomAuthorities(String username, List<GrantedAuthority> authorities) {
// add ROLE_USER for basic access
authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
}

public String getUsersByUsernameQuery() {
return usersByUsernameQuery;
}

@Override
protected void initDao() throws ApplicationContextException {
Assert.isTrue(enableAuthorities || enableGroups, "Use of either authorities or groups must be enabled");
}

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

List<User> users = loadUsersByUsername(username);

if (users.size() == 0) {
logger.debug("Query returned no results for user '" + username + "'");

throw new UsernameNotFoundException(
messages.getMessage("JdbcDaoImpl.notFound", new Object[] { username }, "Username {0} not found"));
}

User user = users.get(0); // contains no GrantedAuthority[]

Set<GrantedAuthority> dbAuthsSet = new HashSet<GrantedAuthority>();

if (enableAuthorities) {
dbAuthsSet.addAll(loadUserAuthorities(user.getUsername()));
}

if (enableGroups) {
dbAuthsSet.addAll(loadGroupAuthorities(user.getUsername()));
}

List<GrantedAuthority> dbAuths = new ArrayList<GrantedAuthority>(dbAuthsSet);

addCustomAuthorities(user.getUsername(), dbAuths);

if (dbAuths.size() == 0) {
logger.debug("User '" + username + "' has no authorities and will be treated as 'not found'");

throw new UsernameNotFoundException(
messages.getMessage("USER.noAuthority", new Object[] { username }, "User {0} has no GrantedAuthority"));
}

user.setAuthorities(dbAuths);
return user;
}

/**
* Executes the SQL <tt>usersByUsernameQuery</tt> and returns a list of UserDetails objects. There should normally only be one matching user.
*/
protected List<User> loadUsersByUsername(String username) {
return getJdbcTemplate().query(usersByUsernameQuery, new String[] { username }, BeanPropertyRowMapper.newInstance(User.class));
}

/**
* Loads authorities by executing the SQL from <tt>authoritiesByUsernameQuery</tt>.
*
* @return a list of GrantedAuthority objects for the user
*/
protected List<GrantedAuthority> loadUserAuthorities(String username) {
return getJdbcTemplate().query(authoritiesByUsernameQuery, new String[] { username }, new RowMapper<GrantedAuthority>() {
@Override
public GrantedAuthority mapRow(ResultSet rs, int rowNum) throws SQLException {
return new SimpleGrantedAuthority(rs.getString("authority"));
}
});
}

/**
* Loads authorities by executing the SQL from <tt>groupAuthoritiesByUsernameQuery</tt>.
*
* @return a list of GrantedAuthority objects for the user
*/
protected List<GrantedAuthority> loadGroupAuthorities(String username) {
return getJdbcTemplate().query(groupAuthoritiesByUsernameQuery, new String[] { username }, new RowMapper<GrantedAuthority>() {
@Override
public GrantedAuthority mapRow(ResultSet rs, int rowNum) throws SQLException {
return new SimpleGrantedAuthority(rs.getString("authority"));
}
});
}

/**
* Allows the default query string used to retrieve authorities based on username to be overridden, if default table or column names need to be changed. The
* default query is {@link #DEF_AUTHORITIES_BY_USERNAME_QUERY}; when modifying this query, ensure that all returned columns are mapped back to the same
* column names as in the default query.
*
* @param queryString
* The SQL query string to set
*/
public void setAuthoritiesByUsernameQuery(String queryString) {
authoritiesByUsernameQuery = queryString;
}

protected String getAuthoritiesByUsernameQuery() {
return authoritiesByUsernameQuery;
}

/**
* Allows the default query string used to retrieve group authorities based on username to be overridden, if default table or column names need to be
* changed. The default query is {@link #DEF_GROUP_AUTHORITIES_BY_USERNAME_QUERY}; when modifying this query, ensure that all returned columns are mapped
* back to the same column names as in the default query.
*
* @param queryString
* The SQL query string to set
*/
public void setGroupAuthoritiesByUsernameQuery(String queryString) {
groupAuthoritiesByUsernameQuery = queryString;
}

/**
* Allows the default query string used to retrieve users based on username to be overridden, if default table or column names need to be changed. The
* default query is {@link #DEF_USERS_BY_USERNAME_QUERY}; when modifying this query, ensure that all returned columns are mapped back to the same column
* names as in the default query. If the 'enabled' column does not exist in the source database, a permanent true value for this column may be returned by
* using a query similar to
*
* <pre>
* &quot;select username,password,'true' as enabled from users where username = ?&quot;
* </pre>
*
* @param usersByUsernameQueryString
* The query string to set
*/
public void setUsersByUsernameQuery(String usersByUsernameQueryString) {
this.usersByUsernameQuery = usersByUsernameQueryString;
}

protected boolean getEnableAuthorities() {
return enableAuthorities;
}

/**
* Enables loading of authorities from the authorities table. Defaults to true
*/
public void setEnableAuthorities(boolean enableAuthorities) {
this.enableAuthorities = enableAuthorities;
}

protected boolean getEnableGroups() {
return enableGroups;
}

/**
* Enables support for group authorities. Defaults to false
*
* @param enableGroups
*/
public void setEnableGroups(boolean enableGroups) {
this.enableGroups = enableGroups;
}

}

+ 100
- 0
src/main/java/com/ffii/core/security/web/authentication/JsonAuthenticationFailureHandler.java Целия файл

@@ -0,0 +1,100 @@
/*******************************************************************************
* 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.security.web.authentication;

import java.io.IOException;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;

import com.ffii.core.utils.BooleanUtils;
import com.ffii.core.utils.JsonUtils;
import com.ffii.core.utils.MapUtils;
import com.ffii.core.utils.Params;
import com.ffii.core.utils.StringUtils;
import com.ffii.core.utils.web.ServletRequestUtils;
import com.ffii.core.utils.web.ServletResponseUtils;
import com.ffii.core.web.view.AbstractView;
import com.ffii.core.security.service.LoginLogService;
import com.ffii.tbms.user.service.UserService;

/**
* <tt>AuthenticationFailureHandler</tt> implementation which return a failure JSON String upon failed authentication.
*
* @author Patrick
*/
public class JsonAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {

/** Continuous fail 5 time, lock user */
private final int TIMES = 5;

protected final Log logger = LogFactory.getLog(getClass());

@Autowired
private UserService userService;

@Autowired
private LoginLogService loginLogService;

/**
* Returns the failure JSON String to client.
*/
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception)
throws IOException, ServletException {

logger.info("Authentication Failure: " + exception.getMessage() + " [" + request.getRemoteAddr() + "]");

String username = StringUtils.left(ServletRequestUtils.getStringParameter(request, "username"), 32);

ServletResponseUtils.disableCaching(response);
response.setContentType(AbstractView.CONTENT_TYPE_JSON);
response.setCharacterEncoding("UTF-8");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); // set HTTP status to 401

boolean locked = false;
// log failed login
if (username != null) {
loginLogService.createLoginLog(username, request.getRemoteAddr(), false);

// when failed 5 times, lock account
List<Map<String, Object>> logs = loginLogService.listLastLog(username, TIMES);
if (logs.size() >= TIMES) {
boolean needLock = true;
for (Map<String, Object> log : logs) {
if (BooleanUtils.isTrue(log.get("success"))) {
needLock = false;
break;
}
}
if (needLock) {
locked = true;
userService.lockUser(username, true);
}
}

}
Map<String, Object> result = MapUtils.toHashMap(
Params.SUCCESS, Boolean.FALSE,
Params.MSG,
locked ? "Account locked (" + TIMES + " Times Failure), please contact your IT administrator." : "Invalid Username or Password.");

ServletResponseUtils.writeStringToStream(response, AbstractView.CHARSET_UTF8, JsonUtils.toJsonString(result));
}

}

+ 119
- 0
src/main/java/com/ffii/core/security/web/authentication/JsonAuthenticationSuccessHandler.java Целия файл

@@ -0,0 +1,119 @@
/*******************************************************************************
* 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.security.web.authentication;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.ffii.core.Session;
import com.ffii.core.Settings;
import com.ffii.core.security.service.LoginLogService;
import com.ffii.core.setting.service.SettingsService;
import com.ffii.core.utils.JsonUtils;
import com.ffii.core.utils.LocaleUtils;
import com.ffii.core.utils.Params;
import com.ffii.core.utils.SecurityUtils;
import com.ffii.core.utils.web.ServletResponseUtils;
import com.ffii.core.web.view.AbstractView;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.security.web.savedrequest.SavedRequest;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.i18n.SessionLocaleResolver;
import org.springframework.web.util.WebUtils;

/**
* <tt>AuthenticationSuccessHandler</tt> implementation which return a success JSON String upon successful authentication.
*
* @author Patrick
*/
public class JsonAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {

private RequestCache requestCache = new HttpSessionRequestCache();

@Autowired
private LoginLogService loginLogService;

@Autowired
private SettingsService settingsService;

/**
* Returns the successful JSON String to client, unless the account is locked.
*/
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {

logger.info("Authentication Success: " + authentication.getName() + " [" + request.getRemoteAddr() + "]");

ServletResponseUtils.disableCaching(response);
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
response.setCharacterEncoding("UTF-8");

// save available locales to session
String[] availableLocales = settingsService.getString(Settings.SYS_AVAILABLE_LOCALES).split(",");
Session.setAttribute(request, Session.AVAILABLE_LOCALES, availableLocales);

// set user's default locale
String locale = SecurityUtils.getUser().getLocale();
if (!StringUtils.isEmpty(locale))
WebUtils.setSessionAttribute(request, SessionLocaleResolver.LOCALE_SESSION_ATTRIBUTE_NAME, LocaleUtils.toLocale(locale));

// extra values to be passed to client
Map<String, Object> values = new HashMap<String, Object>();

SavedRequest savedRequest = requestCache.getRequest(request, response);
if (savedRequest != null) {
String targetUrlParameter = getTargetUrlParameter();
if (isAlwaysUseDefaultTargetUrl() || (targetUrlParameter != null && StringUtils.hasText(request.getParameter(targetUrlParameter)))) {
requestCache.removeRequest(request, response);
authenticationSuccess(request, response, null);
return;
}

// Use the DefaultSavedRequest URL
String targetUrl = savedRequest.getRedirectUrl();
logger.debug("Sending DefaultSavedRequest Url: " + targetUrl);

// target URL to redirect to (if any)
values.put("targetUrl", URLEncoder.encode(targetUrl, "UTF-8"));
}

authenticationSuccess(request, response, values);
}

protected void authenticationSuccess(HttpServletRequest request, HttpServletResponse response, Map<String, Object> values)
throws UnsupportedEncodingException, IOException {

Map<String, Object> args = new HashMap<String, Object>();
args.put(Params.SUCCESS, Boolean.TRUE);
if (values != null) args.putAll(values);

clearAuthenticationAttributes(request);
ServletResponseUtils.writeStringToStream(response, AbstractView.CHARSET_UTF8, JsonUtils.toJsonString(args));

// log successful login
loginLogService.createLoginLog(SecurityUtils.getUser().getUsername(), request.getRemoteAddr(), true);

}

}

+ 64
- 0
src/main/java/com/ffii/core/setting/Setting.java Целия файл

@@ -0,0 +1,64 @@
/*******************************************************************************
* Copyright 2019 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.setting;

import java.io.Serializable;

public class Setting implements Serializable {

private static final long serialVersionUID = 3925955875238868120L;

private String category;

private String type;

private String name;

private String value;

/** default constructor */
public Setting() {

}

public String getCategory() {
return category;
}

public void setCategory(String category) {
this.category = category;
}

public String getType() {
return type;
}

public void setType(String type) {
this.type = type;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getValue() {
return value;
}

public void setValue(String value) {
this.value = value;
}

}

+ 169
- 0
src/main/java/com/ffii/core/setting/service/SettingsService.java Целия файл

@@ -0,0 +1,169 @@
/*******************************************************************************
* Copyright 2019 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.setting.service;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;

import com.ffii.core.dao.JdbcDao;
import com.ffii.core.setting.Setting;
import com.ffii.core.utils.NumberUtils;
import com.ffii.core.utils.Params;

/**
* System Settings Service
*
* @author Patrick
*/
@Service
public class SettingsService {

private static final String SQL_LOAD_SETTINGS = "SELECT * FROM settings ORDER BY category, name";

private static final String SQL_INSERT_OR_UPDATE_SETTING = "INSERT INTO settings (`name`, `value`) VALUES (:name, :value) ON DUPLICATE KEY UPDATE `name` = VALUES(`name`), `value` = VALUES(`value`)";

/**
* Settings cache
*/
private static Map<String, Setting> SETTINGS = null;

@Autowired
private JdbcDao jdbcDao;

@Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true)
public List<Map<String, Object>> searchSettings(Map<String, Object> args) {

StringBuilder sql = new StringBuilder("SELECT * FROM settings WHERE 1=1");

if (args.containsKey("category")) sql.append(" AND category = :category");
if (args.containsKey("type")) sql.append(" AND type = :type");
if (args.containsKey("name")) sql.append(" AND name = :name");

sql.append(" ORDER BY category, name");

return jdbcDao.queryForList(sql.toString(), args);
}

/**
* Load and return a Map of all system settings from database
*/
@Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true)
public Map<String, Setting> loadSettingsMap() {
List<Setting> settings = jdbcDao.query(SQL_LOAD_SETTINGS, Setting.class, null, 0);
Map<String, Setting> settingsMap = new HashMap<String, Setting>();
for (Setting setting : settings) {
settingsMap.put(setting.getName(), setting);
}
return settingsMap;
}

/**
* @param name
* the name of the setting
* @param defaultValue
* the default value to return if setting is not found
*
* @return the String value of the setting, or the default value if not found
*/
@Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true)
public String getString(String name, String defaultValue) {
if (SETTINGS == null) SETTINGS = loadSettingsMap();
return SETTINGS.get(name) != null ? SETTINGS.get(name).getValue() : defaultValue;
}

/**
* @param name
* the name of the setting
*
* @return the String value of the setting, or {@code null} if not found
*/
@Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true)
public String getString(String name) {
return getString(name, null);
}

/**
* @param name
* the name of the setting
* @param defaultValue
* the default value to return if setting is not found
*
* @return the int value of the setting, or the default value if not found
*/
@Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true)
public int getInt(String name, int defaultValue) {
return NumberUtils.toInt(getString(name), defaultValue);
}

/**
* @param name
* the name of the setting
*
* @return the int value of the setting, or zero (0) if not found
*/
@Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true)
public int getInt(String name) {
return getInt(name, 0);
}

/**
* @param name
* the name of the setting
* @param defaultValue
* the default value to return if setting is not found
*
* @return the double value of the setting, or the default value if not found
*/
@Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true)
public double getDouble(String name, double defaultValue) {
return NumberUtils.toDouble(getString(name), defaultValue);
}

/**
* @param name
* the name of the setting
*
* @return the double value of the setting, or zero (0.0) if not found
*/
@Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true)
public double getDouble(String name) {
return getDouble(name, 0.0d);
}

/**
* @param name
* the name of the setting
* @param value
* the value of the setting
*/
@Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = false)
public int saveSetting(String name, String value) {
Map<String, Object> args = new HashMap<String, Object>(2);
args.put(Params.NAME, name);
args.put(Params.VALUE, value);
return jdbcDao.executeUpdate(SQL_INSERT_OR_UPDATE_SETTING, args);
}

/**
* Reset the settings cache
*/
public void resetSettingsCache() {
// simply set the settings cache to null
SETTINGS = null;
}

}

+ 45
- 0
src/main/java/com/ffii/core/support/SimpleDateEditor.java Целия файл

@@ -0,0 +1,45 @@
/*******************************************************************************
* Copyright 2019 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.support;

import java.beans.PropertyEditorSupport;
import java.util.Date;

import org.springframework.util.StringUtils;

import com.ffii.core.utils.DateUtils;

/**
* Property Editor for Date in the format defined by {@link DateUtils#PARSE_PATTERNS}<br>
* <p>
* Defaults to <code>null</code> if cannot be parsed, never throw an Exception
* </p>
*
* @see DateUtils#parseDateStrictly(String, String[], Date)
*
* @author Patrick
*/
public class SimpleDateEditor extends PropertyEditorSupport {

@Override
public void setAsText(String text) throws IllegalArgumentException {
if (StringUtils.hasText(text))
setValue(DateUtils.parseDateStrictly(text, DateUtils.PARSE_PATTERNS, null));
else
setValue(null);
}

@Override
public String getAsText() {
return DateUtils.formatDate((Date) getValue());
}

}

+ 38
- 0
src/main/java/com/ffii/core/support/SimpleDecimalEditor.java Целия файл

@@ -0,0 +1,38 @@
/*******************************************************************************
* Copyright 2019 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.support;

import java.beans.PropertyEditorSupport;
import java.math.BigDecimal;

import com.ffii.core.utils.NumberUtils;
import com.ffii.core.utils.StringUtils;

/**
* Property Editor for BigDecimal (Defaults to <code>null</code> if cannot be parsed, never throw an Exception)
*
* @see NumberUtils#toDecimal(String, BigDecimal)
*
* @author Patrick
*/
public class SimpleDecimalEditor extends PropertyEditorSupport {

@Override
public void setAsText(String value) throws IllegalArgumentException {
setValue(NumberUtils.toDecimal(value, null));
}

@Override
public String getAsText() {
return (getValue() == null ? StringUtils.EMPTY : getValue().toString());
}

}

+ 37
- 0
src/main/java/com/ffii/core/support/SimpleDoubleEditor.java Целия файл

@@ -0,0 +1,37 @@
/*******************************************************************************
* Copyright 2019 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.support;

import java.beans.PropertyEditorSupport;

import com.ffii.core.utils.NumberUtils;
import com.ffii.core.utils.StringUtils;

/**
* Property Editor for Double (Defaults to <code>null</code> if cannot be parsed, never throw an Exception)
*
* @see NumberUtils#toDouble(String, Double)
*
* @author Patrick
*/
public class SimpleDoubleEditor extends PropertyEditorSupport {

@Override
public void setAsText(String value) throws IllegalArgumentException {
setValue(NumberUtils.toDouble(value, null));
}

@Override
public String getAsText() {
return (getValue() == null ? StringUtils.EMPTY : getValue().toString());
}

}

+ 37
- 0
src/main/java/com/ffii/core/support/SimpleIntegerEditor.java Целия файл

@@ -0,0 +1,37 @@
/*******************************************************************************
* Copyright 2019 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.support;

import java.beans.PropertyEditorSupport;

import com.ffii.core.utils.NumberUtils;
import com.ffii.core.utils.StringUtils;

/**
* Property Editor for Integer (Defaults to <code>null</code> if cannot be parsed, never throw an Exception)
*
* @see NumberUtils#toInt(String, Integer)
*
* @author Patrick
*/
public class SimpleIntegerEditor extends PropertyEditorSupport {

@Override
public void setAsText(String value) throws IllegalArgumentException {
setValue(NumberUtils.toInt(value, null));
}

@Override
public String getAsText() {
return (getValue() == null ? StringUtils.EMPTY : getValue().toString());
}

}

+ 37
- 0
src/main/java/com/ffii/core/support/SimpleLongEditor.java Целия файл

@@ -0,0 +1,37 @@
/*******************************************************************************
* Copyright 2019 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.support;

import java.beans.PropertyEditorSupport;

import com.ffii.core.utils.NumberUtils;
import com.ffii.core.utils.StringUtils;

/**
* Property Editor for Long (Defaults to <code>null</code> if cannot be parsed, never throw an Exception)
*
* @see NumberUtils#toLong(String, Long)
*
* @author Patrick
*/
public class SimpleLongEditor extends PropertyEditorSupport {

@Override
public void setAsText(String value) throws IllegalArgumentException {
setValue(NumberUtils.toLong(value, null));
}

@Override
public String getAsText() {
return (getValue() == null ? StringUtils.EMPTY : getValue().toString());
}

}

+ 41
- 0
src/main/java/com/ffii/core/support/SimpleStringTrimToEmptyEditor.java Целия файл

@@ -0,0 +1,41 @@
/*******************************************************************************
* Copyright 2019 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.support;

import java.beans.PropertyEditorSupport;

import com.ffii.core.utils.StringUtils;

/**
* <p>
* Property Editor for String
* </p>
* <p>
* Use {@link StringUtils.trimToEmpty()} when binding the value and getting the value to display
* </p>
*
* @see StringUtils#trimToEmpty(String)
*
* @author Patrick
*/
public class SimpleStringTrimToEmptyEditor extends PropertyEditorSupport {

@Override
public void setAsText(String value) throws IllegalArgumentException {
setValue(StringUtils.trimToEmpty(value));
}

@Override
public String getAsText() {
return (getValue() == null ? StringUtils.EMPTY : StringUtils.trimToEmpty((String) getValue()));
}

}

+ 42
- 0
src/main/java/com/ffii/core/support/SimpleStringTrimToNullEditor.java Целия файл

@@ -0,0 +1,42 @@
/*******************************************************************************
* Copyright 2019 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.support;

import java.beans.PropertyEditorSupport;

import com.ffii.core.utils.StringUtils;

/**
* <p>
* Property Editor for String
* </p>
* <p>
* Use {@link StringUtils.trimToNull()} when binding the value, but use {@link StringUtils.trimToEmpty()} when getting the value to display
* </p>
*
* @see StringUtils#trimToNull(String)
* @see StringUtils#trimToEmpty(String)
*
* @author Patrick
*/
public class SimpleStringTrimToNullEditor extends PropertyEditorSupport {

@Override
public void setAsText(String value) throws IllegalArgumentException {
setValue(StringUtils.trimToNull(value));
}

@Override
public String getAsText() {
return (getValue() == null ? StringUtils.EMPTY : StringUtils.trimToEmpty((String) getValue()));
}

}

+ 17
- 0
src/main/java/com/ffii/core/utils/ArgsBuilder.java Целия файл

@@ -0,0 +1,17 @@
package com.ffii.core.utils;

import java.util.Map;

/**
* @author fung
*/
public class ArgsBuilder extends MapBuilder<String, Object> {

public ArgsBuilder() {
}

public ArgsBuilder(Map<String, Object> args) {
super(args);
}

}

+ 67
- 0
src/main/java/com/ffii/core/utils/BooleanUtils.java Целия файл

@@ -0,0 +1,67 @@
/*******************************************************************************
* Copyright 2019 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;

public class BooleanUtils extends org.apache.commons.lang3.BooleanUtils {

/**
* <p>
* For the case of {@code Number}, only if the value is {@code 1} will return {@code true}. Otherwise, {@code false} is returned.
* </p>
* <p>
* For the case of {@code String}, {@code 'true'}, {@code 'on'} or {@code 'yes'} (case insensitive) will return {@code true}. Otherwise, {@code false} is
* returned.
* </p>
* <p>
* For the case of {@code Boolean}, only if the {@code Boolean} value is {@code true} will return {@code true}, handling {@code null} by returning
* {@code false}.
* </p>
* <p>
* For any other cases including {@code null} will return {@code false}.
* </p>
*/
public static boolean isTrue(Object obj) {
if (obj instanceof Number) {
return ((Number) obj).intValue() == 1;
} else if (obj instanceof String) {
return toBoolean((String) obj);
} else {
return Boolean.TRUE.equals(obj);
}
}

/**
* <p>
* For the case of {@code Number}, only if the value is {@code 0} will return {@code true}. Otherwise, {@code false} is returned.
* </p>
* <p>
* For the case of {@code String}, {@code true} is returned unless the value is {@code 'true'}, {@code 'on'} or {@code 'yes'} (case insensitive) which will
* return {@code false}.
* </p>
* <p>
* For the case of {@code Boolean}, only if the {@code Boolean} value is {@code false} will return {@code true}, handling {@code null} by returning
* {@code false}.
* </p>
* <p>
* For any other cases including {@code null} will return {@code false}.
* </p>
*/
public static boolean isFalse(Object obj) {
if (obj instanceof Number) {
return ((Number) obj).intValue() == 0;
} else if (obj instanceof String) {
return !toBoolean((String) obj);
} else {
return Boolean.FALSE.equals(obj);
}
}

}

+ 66
- 0
src/main/java/com/ffii/core/utils/CheckDigitUtils.java Целия файл

@@ -0,0 +1,66 @@
/*******************************************************************************
* Copyright 2019 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;

/**
* CheckDigitUtils
*
* @author Patrick
*/
public abstract class CheckDigitUtils {

/*
* ISO/IEC 7812-1 Annex B
* Check digit calc method
*/
public static int luhnCalc(String digitsString) {
int sum = 0;
boolean alternate = false;
for (int i = 0; i < digitsString.length(); i++) {
int n = Integer.parseInt(digitsString.substring(i, i + 1));
if (alternate) {
n *= 2;
if (n > 9) {
n = (n % 10) + 1;
}
}
sum += n;
alternate = !alternate;
}
return ((sum * 9) % 10);
}

/*
* ISO/IEC 7812-1 Annex B
* Check digit check method
*/
public static boolean luhnCheck(String digitsStringWithCheckDigit) {
int sum = 0;
boolean alternate = false;
for (int i = digitsStringWithCheckDigit.length() - 1; i >= 0; i--) {
try {
int n = Integer.parseInt(digitsStringWithCheckDigit.substring(i, i + 1));
if (alternate) {
n *= 2;
if (n > 9) {
n = (n % 10) + 1;
}
}
sum += n;
alternate = !alternate;
} catch (NumberFormatException nfe) {
continue;
}
}
return (sum % 10 == 0);
}

}

+ 119
- 0
src/main/java/com/ffii/core/utils/CriteriaArgsBuilder.java Целия файл

@@ -0,0 +1,119 @@
package com.ffii.core.utils;

import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.springframework.web.bind.ServletRequestBindingException;

/**
* ArgsBuilder of Criteria
*
* @see {@link CriteriaUtils}
* @author fung
*/
public class CriteriaArgsBuilder {

private Map<String, Object> args;
private HttpServletRequest request;

public CriteriaArgsBuilder(HttpServletRequest request) {
args = new HashMap<String, Object>();
this.request = request;
}

public CriteriaArgsBuilder(HttpServletRequest request, Map<String, Object> args) {
this.args = args;
this.request = request;
}

public CriteriaArgsBuilder addStringExact(String paramName) throws ServletRequestBindingException {
CriteriaUtils.addStringExact(request, args, paramName);
return this;
}

public CriteriaArgsBuilder addStringLike(String paramName) throws ServletRequestBindingException {
CriteriaUtils.addStringLike(request, args, paramName);
return this;
}

public CriteriaArgsBuilder addString(String paramName) throws ServletRequestBindingException {
CriteriaUtils.addString(request, args, paramName);
return this;
}

public CriteriaArgsBuilder addStringContains(String paramName) throws ServletRequestBindingException {
CriteriaUtils.addStringContains(request, args, paramName);
return this;
}

public CriteriaArgsBuilder addStringStartsWith(String paramName) throws ServletRequestBindingException {
CriteriaUtils.addStringStartsWith(request, args, paramName);
return this;
}

public CriteriaArgsBuilder addStringEndsWith(String paramName) throws ServletRequestBindingException {
CriteriaUtils.addStringEndsWith(request, args, paramName);
return this;
}

public CriteriaArgsBuilder addStringList(String paramName) throws ServletRequestBindingException {
CriteriaUtils.addStringList(request, args, paramName);
return this;
}

public CriteriaArgsBuilder addStringCsv(String paramName) throws ServletRequestBindingException {
CriteriaUtils.addStringCsv(request, args, paramName);
return this;
}

public CriteriaArgsBuilder addInteger(String paramName) throws ServletRequestBindingException {
CriteriaUtils.addInteger(request, args, paramName);
return this;
}

public CriteriaArgsBuilder addNonZeroInteger(String paramName) throws ServletRequestBindingException {
CriteriaUtils.addNonZeroInteger(request, args, paramName);
return this;
}

public CriteriaArgsBuilder addIntegerList(String paramName) throws ServletRequestBindingException {
CriteriaUtils.addIntegerList(request, args, paramName);
return this;
}

public CriteriaArgsBuilder addNonZeroIntegerList(String paramName) throws ServletRequestBindingException {
CriteriaUtils.addNonZeroIntegerList(request, args, paramName);
return this;
}

public CriteriaArgsBuilder addLong(String paramName) throws ServletRequestBindingException {
CriteriaUtils.addLong(request, args, paramName);
return this;
}

public CriteriaArgsBuilder addNonZeroLong(String paramName) throws ServletRequestBindingException {
CriteriaUtils.addNonZeroLong(request, args, paramName);
return this;
}

public CriteriaArgsBuilder addDate(String paramName) throws ServletRequestBindingException {
CriteriaUtils.addDate(request, args, paramName);
return this;
}

public CriteriaArgsBuilder addDateTo(String paramName) throws ServletRequestBindingException {
CriteriaUtils.addDateTo(request, args, paramName);
return this;
}

public CriteriaArgsBuilder addBoolean(String paramName) throws ServletRequestBindingException {
CriteriaUtils.addBoolean(request, args, paramName);
return this;
}

public Map<String, Object> toMap() {
return this.args;
}
}

+ 191
- 0
src/main/java/com/ffii/core/utils/CriteriaUtils.java Целия файл

@@ -0,0 +1,191 @@
/*******************************************************************************
* Copyright 2019 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.sql.Date;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.springframework.web.bind.ServletRequestBindingException;

import com.ffii.core.utils.web.ServletRequestUtils;

/**
* Utils for getting parameter values from HTTP Request and put them into a criteria map if found
*
* @author Patrick
*/
public abstract class CriteriaUtils {

/**
* Alias for {@link #addString(HttpServletRequest, Map, String)}
*/
public static void addStringExact(HttpServletRequest request, Map<String, Object> args, String paramName) throws ServletRequestBindingException {
addString(request, args, paramName);
}

/**
* Alias for {@link #addStringContains(HttpServletRequest, Map, String)}
*/
public static void addStringLike(HttpServletRequest request, Map<String, Object> args, String paramName) throws ServletRequestBindingException {
addStringContains(request, args, paramName);
}

/**
* Add a String (an exact value for using equal sign in SQL) parameter to criteria. Usually used for exact string search.
*/
public static void addString(HttpServletRequest request, Map<String, Object> args, String paramName) throws ServletRequestBindingException {
String value = ServletRequestUtils.getTrimmedStringParameter(request, paramName);
if (value != null) args.put(paramName, value);
}

/**
* Add a String (a substring value for using <code>LIKE</code> in SQL) parameter to criteria. Usually used for substring search for values that contains the
* input value.
*/
public static void addStringContains(HttpServletRequest request, Map<String, Object> args, String paramName) throws ServletRequestBindingException {
String value = ServletRequestUtils.getTrimmedStringParameter(request, paramName);
if (value != null) args.put(paramName, StringUtils.PERCENT + value + StringUtils.PERCENT);
}

/**
* Add a String (a substring value for using <code>LIKE</code> in SQL) parameter to criteria. Usually used for substring search for values that starts with
* the input value.
*/
public static void addStringStartsWith(HttpServletRequest request, Map<String, Object> args, String paramName) throws ServletRequestBindingException {
String value = ServletRequestUtils.getTrimmedStringParameter(request, paramName);
if (value != null) args.put(paramName, value + StringUtils.PERCENT);
}

/**
* Add a String (a substring value for using <code>LIKE</code> in SQL) parameter to criteria. Usually used for substring search for values that ends with
* the input value.
*/
public static void addStringEndsWith(HttpServletRequest request, Map<String, Object> args, String paramName) throws ServletRequestBindingException {
String value = ServletRequestUtils.getTrimmedStringParameter(request, paramName);
if (value != null) args.put(paramName, StringUtils.PERCENT + value);
}

/**
* Add String List parameter to criteria.
*/
public static void addStringList(HttpServletRequest request, Map<String, Object> args, String paramName) throws ServletRequestBindingException {
String[] params = ServletRequestUtils.getStringParameters(request, paramName);
if (params.length > 0) {
List<String> value = new ArrayList<String>();
for (int i = 0; i < params.length; i++)
if (StringUtils.isNotBlank(params[i])) value.add(params[i]);
if (value.size() > 0) args.put(paramName, value);
}
}

/**
* Add String CSV parameter to criteria.
*/
public static void addStringCsv(HttpServletRequest request, Map<String, Object> args, String paramName) throws ServletRequestBindingException {
String text = ServletRequestUtils.getStringParameter(request, paramName);
if (StringUtils.isNotEmpty(text)) {
String[] params = text.split(",");
List<String> values = new ArrayList<String>();
for (int i = 0; i < params.length; i++)
values.add(params[i]);
args.put(paramName, values);
}
}

/**
* Add an Integer parameter to criteria.
*/
public static void addInteger(HttpServletRequest request, Map<String, Object> args, String paramName) throws ServletRequestBindingException {
Integer value = ServletRequestUtils.getIntParameter(request, paramName, null);
if (value != null) args.put(paramName, value);
}

/**
* Add a non-zero Integer parameter to criteria.
*/
public static void addNonZeroInteger(HttpServletRequest request, Map<String, Object> args, String paramName) throws ServletRequestBindingException {
Integer value = ServletRequestUtils.getIntParameter(request, paramName, null);
if (value != null && value.intValue() != 0) args.put(paramName, value);
}

/**
* Add Integer List parameter to criteria.
*/
public static void addIntegerList(HttpServletRequest request, Map<String, Object> args, String paramName) throws ServletRequestBindingException {
int[] params = ServletRequestUtils.getIntParameters(request, paramName);
if (params.length > 0) {
List<Integer> values = new ArrayList<Integer>();
for (int i = 0; i < params.length; i++)
values.add(params[i]);
args.put(paramName, values);
}
}

/**
* Add non-zero Integer List parameter to criteria.
*/
public static void addNonZeroIntegerList(HttpServletRequest request, Map<String, Object> args, String paramName) throws ServletRequestBindingException {
int[] params = ServletRequestUtils.getIntParameters(request, paramName);
if (params.length > 0) {
List<Integer> values = new ArrayList<Integer>();
for (int i = 0; i < params.length; i++)
if (params[i] != 0) values.add(params[i]);
args.put(paramName, values);
}
}

/**
* Add a Long parameter to criteria.
*/
public static void addLong(HttpServletRequest request, Map<String, Object> args, String paramName) throws ServletRequestBindingException {
Long value = ServletRequestUtils.getLongParameter(request, paramName, null);
if (value != null) args.put(paramName, value);
}

/**
* Add a non-zero Long parameter to criteria.
*/
public static void addNonZeroLong(HttpServletRequest request, Map<String, Object> args, String paramName) throws ServletRequestBindingException {
Long value = ServletRequestUtils.getLongParameter(request, paramName, null);
if (value != null && value.longValue() != 0L) args.put(paramName, value);
}

/**
* Add a SQL Date parameter to criteria. Usually used for <code>FROM</code> date range search, or exact date search.
*/
public static void addDate(HttpServletRequest request, Map<String, Object> args, String paramName) throws ServletRequestBindingException {
Date value = ServletRequestUtils.getSqlDateParameter(request, paramName);
if (value != null) args.put(paramName, value);
}

/**
* Add a SQL Date (plus 1 day) parameter to criteria. Usually used for {@code TO} date range search.
*/
public static void addDateTo(HttpServletRequest request, Map<String, Object> args, String paramName) throws ServletRequestBindingException {
Date value = ServletRequestUtils.getSqlDateParameter(request, paramName);
if (value != null) args.put(paramName, DateUtils.add(value.getClass(), value, Calendar.DAY_OF_MONTH, 1));
}

/**
* Add a Boolean parameter to criteria if it's not {@code null}. Accepts "true", "on", "yes" (any case) and "1" as values for true; treats every other
* non-empty value as false (i.e. parses leniently).
*/
public static void addBoolean(HttpServletRequest request, Map<String, Object> args, String paramName) throws ServletRequestBindingException {
Boolean value = ServletRequestUtils.getBooleanParameter(request, paramName);
if (value != null) args.put(paramName, value);
}

}

+ 103
- 0
src/main/java/com/ffii/core/utils/DataBindUtils.java Целия файл

@@ -0,0 +1,103 @@
/*******************************************************************************
* Copyright 2019 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.Map;

import org.apache.commons.lang3.ArrayUtils;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.web.bind.WebDataBinder;

import com.ffii.core.support.SimpleDateEditor;
import com.ffii.core.support.SimpleIntegerEditor;
import com.ffii.core.support.SimpleStringTrimToNullEditor;

/**
* DataBindUtils
*
* @author Patrick
*/
public abstract class DataBindUtils {
/** coreFields */
public static final String[] coreFields = { Params.ID, "ownerId", "deleted", "createdBy", "modifiedBy", "created", "modified", "password" };

/**
*
* @param object
* @return WebDataBinder
*/
private static WebDataBinder createBinder(Object object) {
WebDataBinder binder = new WebDataBinder(object);
binder.registerCustomEditor(String.class, new SimpleStringTrimToNullEditor());
binder.registerCustomEditor(Integer.class, new SimpleIntegerEditor());
binder.registerCustomEditor(java.util.Date.class, new SimpleDateEditor());
return binder;
}

/**
* Binds data using {@link WebDataBinder} with the following custom editors:-
*
* <ul>
* <li>{@link SimpleStringTrimToNullEditor}</li>
* <li>{@link SimpleIntegerEditor}</li>
* <li>{@link SimpleDateEditor}</li>
* </ul>
*
* @param record
* record Map
* @param object
* the target object to bind onto
* @param disallowFields
* optional
*/
public static void bindRecord(Map<String, Object> record, Object object, String... disallowFields) {
WebDataBinder binder = createBinder(object);
binder.setDisallowedFields(disallowFields);
binder.bind(new MutablePropertyValues(record));
};

/**
* Binds data using {@link WebDataBinder} with the following custom editors:-
*
* <ul>
* <li>{@link SimpleStringTrimToNullEditor}</li>
* <li>{@link SimpleIntegerEditor}</li>
* <li>{@link SimpleDateEditor}</li>
* </ul>
*
* <p>
* <b>Important:</b> The following system fields will NOT be binded for security reasons.
* <ul>
* <li>id</li>
* <li>ownerId</li>
* <li>deleted</li>
* <li>createdBy</li>
* <li>modifiedBy</li>
* <li>created</li>
* <li>modified</li>
* <li>password</li>
* </ul>
* </p>
*
* @param record
* record Map
* @param object
* the target object to bind onto
* @param disallowFields
* optional
*/
public static void bindRecordWithoutCore(Map<String, Object> record, Object object, String... disallowFields) {
WebDataBinder binder = createBinder(object);

binder.setDisallowedFields(ArrayUtils.addAll(coreFields, disallowFields));
binder.bind(new MutablePropertyValues(record));
};
}

+ 1021
- 0
src/main/java/com/ffii/core/utils/DateUtils.java
Файловите разлики са ограничени, защото са твърде много
Целия файл


+ 139
- 0
src/main/java/com/ffii/core/utils/Encoding.java Целия файл

@@ -0,0 +1,139 @@
/*******************************************************************************
* Copyright 2012 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;

public class Encoding {

// Supported Encoding Types
public static final int GB2312 = 0;
public static final int GBK = 1;
public static final int GB18030 = 2;

public static final int HZ = 3;
public static final int BIG5 = 4;
public static final int CNS11643 = 5;

public static final int UTF8 = 6;
public static final int UTF8T = 7;
public static final int UTF8S = 8;

public static final int UNICODE = 9;
public static final int UNICODET = 10;
public static final int UNICODES = 11;

public static final int ISO2022CN = 12;
public static final int ISO2022CN_CNS = 13;
public static final int ISO2022CN_GB = 14;

public static final int EUC_KR = 15;
public static final int CP949 = 16;
public static final int ISO2022KR = 17;
public static final int JOHAB = 18;
public static final int SJIS = 19;
public static final int EUC_JP = 20;
public static final int ISO2022JP = 21;
public static final int ASCII = 22;
public static final int OTHER = 23;
public static final int TOTALTYPES = 24;

// Names of the encodings as understood by Java
public static String[] javaname;

// Names of the encodings for human viewing
public static String[] nicename;

// Names of charsets as used in charset parameter of HTML Meta tag
public static String[] htmlname;

static {
javaname = new String[TOTALTYPES];
nicename = new String[TOTALTYPES];
htmlname = new String[TOTALTYPES];

// Assign encoding names
javaname[GB2312] = "GB2312";
javaname[GBK] = "GBK";
javaname[GB18030] = "GB18030";
javaname[HZ] = "ASCII"; // What to put here? Sun doesn't support HZ
javaname[ISO2022CN_GB] = "ISO2022CN_GB";
javaname[BIG5] = "BIG5";
javaname[CNS11643] = "EUC-TW";
javaname[ISO2022CN_CNS] = "ISO2022CN_CNS";
javaname[ISO2022CN] = "ISO2022CN";
javaname[UTF8] = "UTF8";
javaname[UTF8T] = "UTF8";
javaname[UTF8S] = "UTF8";
javaname[UNICODE] = "Unicode";
javaname[UNICODET] = "Unicode";
javaname[UNICODES] = "Unicode";
javaname[EUC_KR] = "EUC_KR";
javaname[CP949] = "MS949";
javaname[ISO2022KR] = "ISO2022KR";
javaname[JOHAB] = "Johab";
javaname[SJIS] = "SJIS";
javaname[EUC_JP] = "EUC_JP";
javaname[ISO2022JP] = "ISO2022JP";
javaname[ASCII] = "ASCII";
javaname[OTHER] = "ISO8859_1";

// Assign encoding names
htmlname[GB2312] = "GB2312";
htmlname[GBK] = "GBK";
htmlname[GB18030] = "GB18030";
htmlname[HZ] = "HZ-GB-2312";
htmlname[ISO2022CN_GB] = "ISO-2022-CN-EXT";
htmlname[BIG5] = "BIG5";
htmlname[CNS11643] = "EUC-TW";
htmlname[ISO2022CN_CNS] = "ISO-2022-CN-EXT";
htmlname[ISO2022CN] = "ISO-2022-CN";
htmlname[UTF8] = "UTF-8";
htmlname[UTF8T] = "UTF-8";
htmlname[UTF8S] = "UTF-8";
htmlname[UNICODE] = "UTF-16";
htmlname[UNICODET] = "UTF-16";
htmlname[UNICODES] = "UTF-16";
htmlname[EUC_KR] = "EUC-KR";
htmlname[CP949] = "x-windows-949";
htmlname[ISO2022KR] = "ISO-2022-KR";
htmlname[JOHAB] = "x-Johab";
htmlname[SJIS] = "Shift_JIS";
htmlname[EUC_JP] = "EUC-JP";
htmlname[ISO2022JP] = "ISO-2022-JP";
htmlname[ASCII] = "ASCII";
htmlname[OTHER] = "ISO8859-1";

// Assign Human readable names
nicename[GB2312] = "GB-2312";
nicename[GBK] = "GBK";
nicename[GB18030] = "GB18030";
nicename[HZ] = "HZ";
nicename[ISO2022CN_GB] = "ISO2022CN-GB";
nicename[BIG5] = "Big5";
nicename[CNS11643] = "CNS11643";
nicename[ISO2022CN_CNS] = "ISO2022CN-CNS";
nicename[ISO2022CN] = "ISO2022 CN";
nicename[UTF8] = "UTF-8";
nicename[UTF8T] = "UTF-8 (Trad)";
nicename[UTF8S] = "UTF-8 (Simp)";
nicename[UNICODE] = "Unicode";
nicename[UNICODET] = "Unicode (Trad)";
nicename[UNICODES] = "Unicode (Simp)";
nicename[EUC_KR] = "EUC-KR";
nicename[CP949] = "CP949";
nicename[ISO2022KR] = "ISO 2022 KR";
nicename[JOHAB] = "Johab";
nicename[SJIS] = "Shift-JIS";
nicename[EUC_JP] = "EUC-JP";
nicename[ISO2022JP] = "ISO 2022 JP";
nicename[ASCII] = "ASCII";
nicename[OTHER] = "OTHER";
}
}

+ 675
- 0
src/main/java/com/ffii/core/utils/ExcelUtils.java Целия файл

@@ -0,0 +1,675 @@
/*******************************************************************************
* 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.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.math.BigDecimal;
import java.security.GeneralSecurityException;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

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.Resource;
import org.springframework.core.io.ResourceLoader;

/**
* Excel Utils (for Apache POI 3.15 to 3.17)
*
* @author Patrick
* @version 2018-04-06
*/
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.getCellTypeEnum() == 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 Cell#CELL_TYPE_NUMERIC} and {@link Cell#CELL_TYPE_STRING}
*
* @return the <code>BigDecimal</code> value, or the default value if cell is
* <code>null</code> or cell type is {@link Cell#CELL_TYPE_BLANK}
*/
public static BigDecimal getDecimalValue(Cell cell, BigDecimal defaultValue) {
if (cell == null || cell.getCellTypeEnum() == CellType.BLANK)
return defaultValue;
if (cell.getCellTypeEnum() == CellType.STRING) {
return NumberUtils.toDecimal(cell.getStringCellValue());
} else {
return BigDecimal.valueOf(cell.getNumericCellValue());
}
}

/**
* Get Cell value as <code>BigDecimal</code>
* <p>
* Only support {@link Cell#CELL_TYPE_NUMERIC} and {@link Cell#CELL_TYPE_STRING}
*
* @return the <code>BigDecimal</code> value, or <code>BigDecimal.ZERO</code> if
* cell is <code>null</code> or cell type is
* {@link Cell#CELL_TYPE_BLANK}
*/
public static BigDecimal getDecimalValue(Cell cell) {
return getDecimalValue(cell, BigDecimal.ZERO);
}

/**
* Get Cell value as <code>double</code>
* <p>
* Only support {@link Cell#CELL_TYPE_NUMERIC} and {@link Cell#CELL_TYPE_STRING}
*/
public static double getDoubleValue(Cell cell) {
if (cell == null)
return 0.0;
if (cell.getCellTypeEnum() == 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 Cell#CELL_TYPE_NUMERIC} and {@link Cell#CELL_TYPE_STRING}
*/
public static int getIntValue(Cell cell) {
return (int) NumberUtils.round(getDoubleValue(cell), 0);
}

/**
* Get Cell Integer value (truncated)
*/
public static Integer getIntValue(Cell cell, Integer defaultValue) {
if (cell == null)
return defaultValue;
if (cell.getCellTypeEnum() == CellType.STRING) {
return NumberUtils.toInt(cell.getStringCellValue(), defaultValue);
} else {
return (int) cell.getNumericCellValue();
}
}

/**
* Get Cell Date value
*/
public static Date getDateValue(Cell cell) {
if (cell == null)
return null;
if (cell.getCellTypeEnum() == CellType.STRING) {
return DateUtils.parseDateStrictly(cell.getStringCellValue(), DateUtils.PARSE_PATTERNS, null);
}
if (DateUtil.isCellDateFormatted(cell)) {
try {
return DateUtil.getJavaDate(cell.getNumericCellValue());
} 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 == 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 type
newCell.setCellType(oldCell.getCellTypeEnum());

// copy the cell data value
switch (oldCell.getCellTypeEnum()) {
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);
}

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

newCell.setCellType(oldCell.getCellTypeEnum());

switch (oldCell.getCellTypeEnum()) {
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 type
newCell.setCellType(oldCell.getCellTypeEnum());

// copy the cell data value
switch (oldCell.getCellTypeEnum()) {
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) {

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
try {
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 (InvalidFormatException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (GeneralSecurityException e) {
e.printStackTrace();
}

return bytes;
}

}

+ 122
- 0
src/main/java/com/ffii/core/utils/FileUtils.java Целия файл

@@ -0,0 +1,122 @@
/*******************************************************************************
* 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("JPG", "image/jpeg");
MIMETYPES.put("png", "image/png");
MIMETYPES.put("PNG", "image/png");
MIMETYPES.put("tiff", "image/tiff");
MIMETYPES.put("tif", "image/tiff");

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("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, ".");
String mimetype = MIMETYPES.get(extension);
return mimetype != null ? mimetype : "application/octet-stream";
}

}

+ 26
- 0
src/main/java/com/ffii/core/utils/ImageUtils.java Целия файл

@@ -0,0 +1,26 @@
/*******************************************************************************
* 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.awt.image.BufferedImage;

import org.imgscalr.Scalr;

/**
* Image Utils
*
* @author Patrick
*/
public class ImageUtils {

public BufferedImage resize(BufferedImage srcImage, int targetSize) {
return Scalr.resize(srcImage, targetSize);
}

}

+ 68
- 0
src/main/java/com/ffii/core/utils/JsonUtils.java Целия файл

@@ -0,0 +1,68 @@
/*******************************************************************************
* Copyright 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.io.IOException;
import java.util.List;
import java.util.Map;

import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;

/**
* 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 {
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);

}

public static Map<String, Object> fromJsonStringAsMap(String content)
throws JsonParseException, JsonMappingException, IOException {
return mapper.readValue(content, new TypeReference<Map<String, Object>>() {
});
}

public static List<Map<String, Object>> fromJsonStringAsListOfMap(String content)
throws JsonParseException, JsonMappingException, IOException {
return mapper.readValue(content, new TypeReference<List<Map<String, Object>>>() {
});
}

}

+ 26
- 0
src/main/java/com/ffii/core/utils/ListUtils.java Целия файл

@@ -0,0 +1,26 @@
package com.ffii.core.utils;

import java.util.ArrayList;
import java.util.List;

/**
* ListUtils
*
*/
public class ListUtils {

/**
* Convert values to ArrayList
*
* @param values
*
* @return List
*/
@SuppressWarnings("unchecked")
public static <V> List<V> toArrayList(V... values) {
List<V> list = new ArrayList<V>(values.length);
for (int i = 0; i < values.length; i++)
list.add(values[i]);
return list;
}
}

+ 47
- 0
src/main/java/com/ffii/core/utils/LocaleUtils.java Целия файл

@@ -0,0 +1,47 @@
/*******************************************************************************
* 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.Locale;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.util.StringUtils;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.i18n.SessionLocaleResolver;

/**
* Utility class for performing translation between Locale and String representations.
*/
public abstract class LocaleUtils {

private static final LocaleResolver localeResolver = new SessionLocaleResolver();

public static final String toString(Locale locale) {
return locale.toString();
}

public static final Locale toLocale(String localeString) {
return StringUtils.parseLocaleString(localeString);
}

public static final Locale resolveLocale(HttpServletRequest request) {
return localeResolver.resolveLocale(request);
}

public static final String resolveLocaleString(HttpServletRequest request) {
return resolveLocale(request).toString();
}

public static final void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
localeResolver.setLocale(request, response, locale);
}

}

+ 34
- 0
src/main/java/com/ffii/core/utils/MapBuilder.java Целия файл

@@ -0,0 +1,34 @@
package com.ffii.core.utils;

import java.util.HashMap;
import java.util.Map;

/**
* @author fung
*/
public class MapBuilder<K, V> {

private Map<K, V> args;

public MapBuilder() {
args = new HashMap<K, V>();
}

public MapBuilder(Map<K, V> args) {
this.args = args;
}

public MapBuilder<K, V> add(K key, V value) {
args.put(key, value);
return this;
}

public Map<K, V> toMap() {
return args;
}

@Override
public String toString() {
return args.toString();
}
}

+ 45
- 0
src/main/java/com/ffii/core/utils/MapUtils.java Целия файл

@@ -0,0 +1,45 @@
/*******************************************************************************
* Copyright 2019 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;

/**
* 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;
}

}

+ 471
- 0
src/main/java/com/ffii/core/utils/NumberUtils.java Целия файл

@@ -0,0 +1,471 @@
/*******************************************************************************
* Copyright 2019 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.math.BigDecimal;
import java.math.RoundingMode;

import org.apache.commons.lang3.ArrayUtils;

/**
* NumberUtils extends from Apache Commons, and incl some methods from MathUtils
*
* @author Patrick
*/
public abstract class NumberUtils extends org.apache.commons.lang3.math.NumberUtils {

/**
* Round the given value to the specified number of decimal places. The value is rounded using the given method which is any method defined in
* {@link BigDecimal}.
*
* @param x
* the value to round
* @param scale
* the number of digits to the right of the decimal point
* @param roundingMethod
* the rounding method as defined in {@link RoundingMode}
* @return the rounded value
*/
public static double round(double x, int scale, RoundingMode roundingMethod) {
try {
return BigDecimal.valueOf(x).setScale(scale, roundingMethod).doubleValue();
} catch (NumberFormatException ex) {
if (Double.isInfinite(x)) {
return x;
} else {
return Double.NaN;
}
}
}

/**
* Round the given value to the specified number of decimal places. The value is rounded using the {@link RoundingMode#HALF_UP} method.
*
* @param x
* the value to round
* @param scale
* the number of digits to the right of the decimal point
* @return the rounded value
* @see org.apache.commons.math.util.MathUtils#round(double, int)
*/
public static double round(double x, int scale) {
return round(x, scale, RoundingMode.HALF_UP);
}

/**
* Round up the given value to the specified number of decimal places. The value is rounded up using the {@link RoundingMode#UP} method.
*
* @param x
* the value to round up
* @param scale
* the number of digits to the right of the decimal point
* @return the rounded up value
*/
public static double roundUp(double x, int scale) {
return round(x, scale, RoundingMode.UP);
}

/**
* Round down the given value to the specified number of decimal places. The value is rounded down using the {@link RoundingMode#DOWN} method.
*
* @param x
* the value to round down
* @param scale
* the number of digits to the right of the decimal point
* @return the rounded down value
*/
public static double roundDown(double x, int scale) {
return round(x, scale, RoundingMode.DOWN);
}

/**
* Return the {@code int} value of an {@code Object}, or the default value if the object is either {@code null} or not an instance of {@code Number}.
*
* @param obj
* the {@code Object}
* @param defaultValue
* the default value
* @return the {@code int} value of the {@code Object}, or the default if the object is either {@code null} or not an instance of {@code Number}
* @see Number#intValue()
*/
public static int intValue(Object obj, int defaultValue) {
if (obj instanceof Number) {
return ((Number) obj).intValue();
} else if (obj instanceof String) {
try {
return Integer.parseInt((String) obj);
} catch (NumberFormatException nfe) {
}
}
return defaultValue;
}

/**
* Return the {@code int} value of an {@code Object}, or {@code zero} if the object is either {@code null} or not an instance of {@code Number}.
*
* @param obj
* the {@code Object}
* @return the {@code int} value of the {@code Object}, or {@code zero} if the object is either {@code null} or not an instance of {@code Number}
* @see Number#intValue()
*/
public static int intValue(Object obj) {
return intValue(obj, 0);
}

/**
* Convert an {@code Object} to an {@code Integer} (only if the object is an instance of {@code Number} or {@code String}), returning a default value if the
* conversion fails.
* <p>
* If the object is {@code null}, the default value is returned.
*
* @param obj
* the object to convert, may be {@code null}
* @param defaultValue
* the default value, may be {@code null}
* @return the Integer represented by the object, or the default if conversion fails
*/
public static Integer toInt(Object obj, Integer defaultValue) {
if (obj instanceof Number)
return Integer.valueOf(((Number) obj).intValue());
else if (obj instanceof String)
try {
return Integer.valueOf((String) obj);
} catch (NumberFormatException nfe) {
return defaultValue;
}
else
return defaultValue;
}

/**
* Return the {@code long} value of a {@code Long} object, or a default value if the object is {@code null}.
*
* @param obj
* the object (can be {@code null})
* @param defaultValue
* the default value
* @return the {@code long} value of the object if it's a number, or the default value if the object is {@code null} or {@code NaN}
* @see Long#longValue()
*/
public static long longValue(Object obj, long defaultValue) {
return (obj instanceof Number) ? ((Number) obj).longValue() : defaultValue;
}

/**
* Return the {@code long} value of a {@code Long} object, or {@code zero} if the object is {@code null}.
*
* @param obj
* the object (can be {@code null})
* @return the {@code long} value of the object if it's a number, or {@code zero} if the object is {@code null} or {@code NaN}
* @see Long#longValue()
*/
public static long longValue(Object obj) {
return longValue(obj, 0l);
}

/**
* Convert an {@code Object} to a {@code Long} (only if the object is an instance of {@code Number} or {@code String}), returning a default value if the
* conversion fails.
* <p>
* If the object is {@code null}, the default value is returned.
*
* @param obj
* the object to convert, may be {@code null}
* @param defaultValue
* the default value, may be {@code null}
* @return the Long represented by the object, or the default if conversion fails
*/
public static Long toLong(Object obj, Long defaultValue) {
if (obj instanceof Number)
return Long.valueOf(((Number) obj).longValue());
else if (obj instanceof String)
try {
return Long.valueOf((String) obj);
} catch (NumberFormatException nfe) {
return defaultValue;
}
else
return defaultValue;
}

/**
* @param obj
* the object (can be {@code null})
* @param defaultValue
* the default value
* @return the {@code double} value of the object if it's a number, or the default value if the object is {@code null} or {@code NaN}
* @see Double#doubleValue()
*/
public static double doubleValue(Object obj, double defaultValue) {
return (obj instanceof Number) ? ((Number) obj).doubleValue() : defaultValue;
}

/**
* @param obj
* the object (can be {@code null})
* @param defaultValue
* the default value
* @return the {@code double} value of the object if it's a number, or {@code zero} if the object is {@code null} or {@code NaN}
* @see Double#doubleValue()
*/
public static double doubleValue(Object obj) {
return doubleValue(obj, 0.0d);
}

/**
* Convert an {@code Object} to a {@code Double} (only if the object is an instance of {@code Number} or {@code String}), returning a default value if the
* conversion fails.
* <p>
* If the object is {@code null}, the default value is returned.
*
* @param obj
* the object to convert, may be {@code null}
* @param defaultValue
* the default value, may be {@code null}
* @return the Double represented by the object, or the default if conversion fails
*/
public static Double toDouble(Object obj, Double defaultValue) {
if (obj instanceof Number)
return Double.valueOf(((Number) obj).doubleValue());
else if (obj instanceof String)
try {
return Double.valueOf((String) obj);
} catch (NumberFormatException nfe) {
return defaultValue;
}
else
return defaultValue;
}

/**
* Return the {@code BigDecimal} object, or {@code zero} if the object is {@code null}.
*
* @param obj
* the {@code BigDecimal} object
* @return the {@code BigDecimal} object, or {@code zero} if the object is {@code null}
*/
public static BigDecimal decimalValue(BigDecimal obj) {
return decimalValue(obj, BigDecimal.ZERO);
}

/**
* Return the {@code BigDecimal} object, or a default value if the object is {@code null}.
*
* @param obj
* the {@code BigDecimal} object
* @param defaultValue
* the default value
* @return the {@code BigDecimal} object, or the default if the object is {@code null}
*/
public static BigDecimal decimalValue(BigDecimal obj, BigDecimal defaultValue) {
return obj == null ? defaultValue : obj;
}

/**
* Convert an {@code Object} to a {@code BigDecimal}, returning {@code BigDecimal.ZERO} if the conversion fails (e.g. the object is not an instance of
* {@code Number} nor {@code String}).
* <p>
* If the object is {@code null}, {@code BigDecimal.ZERO} is returned.
*
* @param obj
* the object to convert, may be {@code null}
* @return the BigDecimal represented by the object, or {@code BigDecimal.ZERO} if conversion fails
*/
public static BigDecimal toDecimal(Object obj) {
return toDecimal(obj, BigDecimal.ZERO);
}

/**
* Convert an {@code Object} to a {@code BigDecimal}, returning a default value if the conversion fails (e.g. the object is not an instance of
* {@code Number} nor {@code String}).
* <p>
* If the object is {@code null}, the default value is returned.
*
* @param obj
* the object to convert, may be {@code null}
* @param defaultValue
* the default value, may be {@code null}
* @return the BigDecimal represented by the object, or the default if conversion fails
*/
public static BigDecimal toDecimal(Object obj, BigDecimal defaultValue) {
if (obj instanceof BigDecimal)
return (BigDecimal) obj;
else if (obj instanceof Number)
return BigDecimal.valueOf(((Number) obj).doubleValue());
else if (obj instanceof String)
try {
return new BigDecimal((String) obj);
} catch (NumberFormatException nfe) {
return defaultValue;
}
else
return defaultValue;
}

/**
* Null-safe method to check if the two {@code Integer} objects have the same value.
*
* <ol>
* <li>Returns {@code true} if {@code a} and {@code b} are both {@code null}.
* <li>Returns {@code false} if only one of them is {@code null}.
* <li>Returns {@code true} if {@code a} and {@code b} are not {@code null} and have the same {@code int} value, else returns {@code false}.
* </ol>
*
* @param a
* Integer obj, may be {@code null}
* @param b
* Integer obj, may be {@code null}
*/
public static boolean isEqual(Integer a, Integer b) {
return a == null ? (b == null ? true : false) : (b == null ? false : a.equals(b));
}

/**
* Null-safe method to check if the two {@code Integer} objects have different values.
*
* <ol>
* <li>Returns {@code false} if {@code a} and {@code b} are both {@code null}.
* <li>Returns {@code true} if only one of them is {@code null}.
* <li>Returns {@code true} if {@code a} and {@code b} are not {@code null} and have different {@code int} values, else returns {@code false}.
* </ol>
*
* @param a
* Integer obj, may be {@code null}
* @param b
* Integer obj, may be {@code null}
*/
public static boolean isNotEqual(Integer a, Integer b) {
return !isEqual(a, b);
}

/**
* Null-safe method to check if the two {@code BigDecimal} objects have the same value.
* <p>
* Two {@code BigDecimal} objects that are equal in value but have a different scale (like 2.0 and 2.00) are considered equal by this method.
*
* <ol>
* <li>Returns {@code true} if {@code a} and {@code b} are both {@code null}.
* <li>Returns {@code false} if only one of them is {@code null}.
* <li>Returns {@code true} if {@code a} and {@code b} are not {@code null} and have the same {@code decimal} value, else returns {@code false}.
* </ol>
*
* @param a
* BigDecimal obj, may be {@code null}
* @param b
* BigDecimal obj, may be {@code null}
*/
public static boolean isEqual(BigDecimal a, BigDecimal b) {
return a == null ? (b == null ? true : false) : (b == null ? false : a.compareTo(b) == 0);
}

/**
* Null-safe method to check if the two {@code BigDecimal} objects have different values.
* <p>
* Two {@code BigDecimal} objects that are equal in value but have a different scale (like 2.0 and 2.00) are considered equal by this method.
*
* <ol>
* <li>Returns {@code false} if {@code a} and {@code b} are both {@code null}.
* <li>Returns {@code true} if only one of them is {@code null}.
* <li>Returns {@code true} if {@code a} and {@code b} are not {@code null} and have different {@code decimal} values, else returns {@code false}.
* </ol>
*
* @param a
* BigDecimal obj, may be {@code null}
* @param b
* BigDecimal obj, may be {@code null}
*/
public static boolean isNotEqual(BigDecimal a, BigDecimal b) {
return !isEqual(a, b);
}

/**
* Check if {@code BigDecimal} object {@code a} is greater than {@code BigDecimal} object {@code b}.
*
* @param a
* non-{@code null} BigDecimal obj
* @param b
* non-{@code null} BigDecimal obj
*/
public static boolean isGreaterThan(BigDecimal a, BigDecimal b) {
return a.compareTo(b) > 0;
}

/**
* Check if {@code BigDecimal} object {@code a} is greater than or equals to {@code BigDecimal} object {@code b}.
*
* @param a
* non-{@code null} BigDecimal obj
* @param b
* non-{@code null} BigDecimal obj
*/
public static boolean isGreaterThanOrEqual(BigDecimal a, BigDecimal b) {
return a.compareTo(b) >= 0;
}

/**
* Check if {@code BigDecimal} object {@code a} is less than {@code BigDecimal} object {@code b}.
*
* @param a
* non-{@code null} BigDecimal obj
* @param b
* non-{@code null} BigDecimal obj
*/
public static boolean isLessThan(BigDecimal a, BigDecimal b) {
return a.compareTo(b) < 0;
}

/**
* Check if {@code BigDecimal} object {@code a} is less than or equals to {@code BigDecimal} object {@code b}.
*
* @param a
* non-{@code null} BigDecimal obj
* @param b
* non-{@code null} BigDecimal obj
*/
public static boolean isLessThanOrEqual(BigDecimal a, BigDecimal b) {
return a.compareTo(b) <= 0;
}

/**
*
* <pre>
* NumberUtils.equalsAny(null, (Integer[]) null) = false
* NumberUtils.equalsAny(null, null, null) = true
* NumberUtils.equalsAny(null, 1, 2) = false
* NumberUtils.equalsAny(1, null, 2) = false
* NumberUtils.equalsAny(1, 1, 2) = true
* </pre>
*
* @param int
* to compare, may be {@code null}.
* @param searchInts
* a int, may be {@code null}.
* @return {@code true} if the num is equal to any other element of <code>searchInts</code>; {@code false} if <code>searchInts</code> is null or contains no
* matches.
*/
public static boolean equalsAny(final int num, int... searchInts) {
if (ArrayUtils.isNotEmpty(searchInts)) {
for (int next : searchInts) {
if (num == next) {
return true;
}
}
}
return false;
}

public static double sum(double... nums) {
BigDecimal rs = BigDecimal.ZERO;
for (double num : nums)
rs = rs.add(BigDecimal.valueOf(num));
return rs.doubleValue();
}
}

+ 81
- 0
src/main/java/com/ffii/core/utils/Params.java Целия файл

@@ -0,0 +1,81 @@
/*******************************************************************************
* Copyright 2019 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;

/**
* Static strings for standard params
*
* @author Patrick
*/
public abstract class Params {

public static final String ID = "id";

public static final String TYPE = "type";

public static final String QUERY = "query";

public static final String CODE = "code";
public static final String NAME = "name";
public static final String TITLE = "title";

public static final String KEY = "key";
public static final String VALUE = "value";

public static final String SUCCESS = "success";

public static final String AUTH = "auth";

/** Short for "message" */
public static final String MSG = "msg";

public static final String DETAILS = "details";

public static final String DATA = "data";
public static final String RECORDS = "records";
public static final String ROOT = "root";
public static final String NODE = "node";
public static final String EXPANDED = "expanded";

public static final String FROM = "from";
public static final String TO = "to";

public static final String START = "start";
public static final String LIMIT = "limit";

public static final String PREFIX = "prefix";
public static final String SUFFIX = "suffix";

public static final String LENGTH = "length";

public static final String MODE = "mode";

public static final String COUNT = "count";

public static final String TOTAL = "total";

public static final String STATUS = "status";

public static final String VERSION_ID = "versionId";

public static final String REF_ID = "refId";
public static final String REF_TYPE = "refType";
public static final String REF_CODE = "refCode";

public static final String FROM_DATE = "fromDate";
public static final String TO_DATE = "toDate";

public static final String MAX_ROWS = "maxRows";

public static final String METHOD_GET = "GET";
public static final String METHOD_POST = "POST";

}

+ 225
- 0
src/main/java/com/ffii/core/utils/SecurityUtils.java Целия файл

@@ -0,0 +1,225 @@
/*******************************************************************************
* 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.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.regex.Pattern;

import com.ffii.core.User;
import com.ffii.tbms.user.service.UserService;

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;

/**
* Security Utils - for Spring Security
*
* @author Patrick
*/
public class SecurityUtils {

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

/*
* Ref: https://www.owasp.org/index.php/Password_special_characters
* without space character
*/
private static final String SPECIAL_CHARS = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~";
private static Pattern PATTERN_SPECIAL_CHARS = Pattern.compile("[!\"#$%&'()*+,-./:;<=>?@\\[\\\\\\]^_`{|}~]");

/**
* 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} ({@code User})
* @see Authentication#getPrincipal()
*/
public static final User getUser() {
try {
return (User) getSecurityContext().getAuthentication().getPrincipal();
} catch (ClassCastException e) {
// no authenticated principal
return null;
} catch (NullPointerException e) {
// no authentication information is available
return null;
}
}

/**
* Updates the Authentication Token with the user (e.g. user changed the password)
*
* @see SecurityContext#setAuthentication(Authentication)
*/
public static final void updateUserAuthentication(final User 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 (auth.getAuthority().equals("SUPERUSER")) return true;
if (role.equals(auth.getAuthority())) return true;
}
return false;
}

public static final boolean isGrantedOnly(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);
}

public static final boolean checkPwd(String pwd, int min, int max, boolean num, boolean upperEng, boolean lowerEng, boolean special) {
if (pwd == null) return false;
if (pwd.length() < min) return false;
if (pwd.length() > max) return false;

if (num && !PATTERN_DIGITS.matcher(pwd).find()) return false;
if (upperEng && !PATTERN_A2Z_UPPER.matcher(pwd).find()) return false;
if (lowerEng && !PATTERN_A2Z_LOWER.matcher(pwd).find()) return false;
if (special && !PATTERN_SPECIAL_CHARS.matcher(pwd).find()) return false;

return true;
}

public static String genPwd(int length, boolean num, boolean upperEng, boolean lowerEng, boolean special) {
if (length <= 0) return "";

StringBuilder password = new StringBuilder(length);
Random random = new Random(System.nanoTime());

List<String> charCategories = new ArrayList<>(4);
if (lowerEng) charCategories.add(A2Z_LOWER);
if (upperEng) charCategories.add(A2Z_UPPER);
if (num) charCategories.add(DIGITS);
if (special) 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 void authArgs(Map<String, Object> args){
args.put("sysGroupId", getUser().getSysGroupId());
}

}

+ 20
- 0
src/main/java/com/ffii/core/utils/SqlUtils.java Целия файл

@@ -0,0 +1,20 @@
package com.ffii.core.utils;

public class SqlUtils {

/**
* for IN() of SQL. if no parameter, it will return "''"
*
* @return 'obj1','obj2','obj3','obj4'...
*/
public static String toInString(Object... objs) {
String rs = "";
for (int i = 0; i < objs.length; i++) {
if (i != 0) rs += ",";
rs += "'" + objs[i] + "'";
}
if (StringUtils.isBlank(rs))
rs += "''";
return rs;
}
}

+ 71
- 0
src/main/java/com/ffii/core/utils/StringUtils.java Целия файл

@@ -0,0 +1,71 @@
/*******************************************************************************
* Copyright 2019 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;
}
}

+ 54
- 0
src/main/java/com/ffii/core/utils/ZXingUtils.java Целия файл

@@ -0,0 +1,54 @@
/*******************************************************************************
* Copyright 2019 2Fi Business Solutions Ltd.
*
* This code is part of the Core2 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.awt.Image;

import com.google.zxing.BarcodeFormat;
import com.google.zxing.Writer;
import com.google.zxing.WriterException;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.oned.Code128Writer;
import com.google.zxing.qrcode.QRCodeWriter;

/**
* ZXing Utils
*
* @author Patrick
*/
public abstract class ZXingUtils {

private static final Writer CODE128_WRITER = new Code128Writer();

private static final Writer QR_CODE_WRITER = new QRCodeWriter();

/**
* Encode contents (String) to Code 128 image
*
* @throws WriterException
*/
public static Image encodeToImageCode128(String contents, int width, int height) throws WriterException {
BitMatrix matrix = CODE128_WRITER.encode(contents, BarcodeFormat.CODE_128, width, height);
return MatrixToImageWriter.toBufferedImage(matrix);
}

/**
* Encode contents (String) to QR Code image
*
* @throws WriterException
*/
public static Image encodeToImageQRCode(String contents, int width, int height) throws WriterException {
BitMatrix matrix = QR_CODE_WRITER.encode(contents, BarcodeFormat.QR_CODE, width, height);
return MatrixToImageWriter.toBufferedImage(matrix);
}

}

+ 114
- 0
src/main/java/com/ffii/core/utils/sql/QueryBuilder.java Целия файл

@@ -0,0 +1,114 @@
package com.ffii.core.utils.sql;

import java.util.Map;

import com.ffii.core.utils.StringUtils;

/**
* <p>a query builder for handling paging </p>
* <p><b>suffix cannot blank</b></p>
* <p>
* paging arguments key:
* "start": integer (base at 0),
* "limit": integer
* </p>
*
* @see #setPaging(boolean)
* @see #setPrefix(String)
* @see #setSuffix(String)
*
* @@author Fung
*/
public class QueryBuilder implements java.io.Serializable {
private static final long serialVersionUID = 4433680400177304974L;

// setting
private boolean paging = false;
private Map<String, Object> args;

/** "SELECT *" */
private String prefix;

/** "FROM xxxxxxx WHERE true" */
private String suffix;

/** default disable paging */
public QueryBuilder(){}
public QueryBuilder(boolean paging){
this.paging = paging;
}
@Override
public String toString() {
return getSearchSql();
}

public String getSearchSql() {
parameterCheck();
return getPrefix() + " " + getSuffix() + (this.paging ? limitQuery() : "");
}

/** column name "count" */
public String getTotalSql() {
parameterCheck();
return "SELECT COUNT(1) 'count'" + " " + getSuffix();
}

private void parameterCheck() {
if (StringUtils.isBlank(suffix))
throw new IllegalArgumentException("suffix of sql cannot blank");
}

private String limitQuery() {
if (args == null)
return StringUtils.EMPTY;
else if (args.containsKey("start") && args.containsKey("limit"))
return " LIMIT :start,:limit";
else if (args.containsKey("limit"))
return " LIMIT :limit";
else
return StringUtils.EMPTY;
}

// setter and getter

public boolean isPaging() {
return this.paging;
}

public boolean getPaging() {
return this.paging;
}

/** true for paging query, default false*/
public void setPaging(boolean paging) {
this.paging = paging;
}

public Map<String, Object> getArgs() {
return this.args;
}

public void setArgs(Map<String, Object> args) {
this.args = args;
}

public String getPrefix() {
return StringUtils.isNotBlank(this.prefix) ? this.prefix : "SELECT *";
}

/** "SELECT *" */
public void setPrefix(String prefix) {
this.prefix = prefix;
}

public String getSuffix() {
return this.suffix;
}

/** <p>"FROM xxxxxxxx WHERE true"</p>suffix cannot blank*/
public void setSuffix(String suffix) {
this.suffix = suffix;
}

}

+ 336
- 0
src/main/java/com/ffii/core/utils/web/ServletRequestUtils.java Целия файл

@@ -0,0 +1,336 @@
/*******************************************************************************
* Copyright 2019 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.web;

import java.io.IOException;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;

import com.ffii.core.support.SimpleDateEditor;
import com.ffii.core.support.SimpleIntegerEditor;
import com.ffii.core.support.SimpleStringTrimToNullEditor;
import com.ffii.core.utils.DateUtils;
import com.ffii.core.utils.JsonUtils;
import com.ffii.core.utils.Params;
import com.ffii.core.utils.StringUtils;

import org.apache.commons.lang3.ArrayUtils;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.ServletRequestDataBinder;

/**
* ServletRequestUtils (extends from Spring Framework)
*
* @author Patrick
*/
public abstract class ServletRequestUtils extends org.springframework.web.bind.ServletRequestUtils {

/**
* Mobile User-Agent Signatures
*/
private static final String[] MOBILE_USER_AGENTS = { "Android", "iPhone", "iPad" };

/**
* Get the <b>User-Agent</b> from the request header
*
* @param request
* HttpServletRequest
*
* @return the <b>User-Agent</b> from the request header, or <code>null</code> if not found
*/
public static String getUserAgent(HttpServletRequest request) {
return request.getHeader("User-Agent");
}

/**
* Check if the <b>User-Agent</b> is mobile device
*
* @param request
* HttpServletRequest
*
* @return <code>true</code> if the <b>User-Agent</b> is mobile device
*/
public static boolean isMobileUserAgent(HttpServletRequest request) {
for (String MOBILE_USER_AGENT : MOBILE_USER_AGENTS) {
if (StringUtils.containsIgnoreCase(getUserAgent(request), MOBILE_USER_AGENT))
return true;
}
return false;
}

/**
* Get a Map of request parameters
*
* @param request
* current HTTP request
*
* @return a Map containing all of the request parameters
*/
public static Map<String, String> getParameters(ServletRequest request) {
Enumeration<String> params = request.getParameterNames();
Map<String, String> paramsMap = new HashMap<String, String>();
while (params.hasMoreElements()) {
String paramName = (String) params.nextElement();
String paramValue = request.getParameter(paramName);
paramsMap.put(paramName, paramValue);
}
return paramsMap;
}

/**
* Get an Integer parameter, with a fallback value. Never throws an exception. Can pass a distinguished value as default to enable checks of whether it was
* supplied.
*
* @param request
* current HTTP request
* @param name
* the name of the parameter
* @param defaultVal
* the default value to use as fallback
*
* @return the Integer value, or <code>defaultVal</code> if not present OR if the value cannot be parsed
*/
public static Integer getIntParameter(ServletRequest request, String name, Integer defaultVal) {
try {
return getIntParameter(request, name);
} catch (ServletRequestBindingException e) {
return defaultVal;
}
}

/**
* Get a Long parameter, with a fallback value. Never throws an exception. Can pass a distinguished value as default to enable checks of whether it was
* supplied.
*
* @param request
* current HTTP request
* @param name
* the name of the parameter
* @param defaultVal
* the default value to use as fallback
*
* @return the Long value, or <code>defaultVal</code> if not present OR if the value cannot be parsed
*/
public static Long getLongParameter(ServletRequest request, String name, Long defaultVal) {
try {
return getLongParameter(request, name);
} catch (ServletRequestBindingException e) {
return defaultVal;
}
}

/**
* Get a Double parameter, with a fallback value. Never throws an exception. Can pass a distinguished value as default to enable checks of whether it was
* supplied.
*
* @param request
* current HTTP request
* @param name
* the name of the parameter
* @param defaultVal
* the default value to use as fallback
*
* @return the Double value, or <code>defaultVal</code> if not present OR if the value cannot be parsed
*/
public static Double getDoubleParameter(ServletRequest request, String name, Double defaultVal) {
try {
return getDoubleParameter(request, name);
} catch (ServletRequestBindingException e) {
return defaultVal;
}
}

/**
* Get a trimmed (to <code>null</code> if blank) String parameter, or <code>null</code> if not present.
*
* @param request
* current HTTP request
* @param name
* the name of the parameter
*
* @return the trimmed (to <code>null</code> if blank) String value, or <code>null</code> if not present
*
* @throws ServletRequestBindingException
* a subclass of ServletException, so it doesn't need to be caught
*/
public static String getTrimmedStringParameter(ServletRequest request, String name) throws ServletRequestBindingException {
return StringUtils.trimToNull(org.springframework.web.bind.ServletRequestUtils.getStringParameter(request, name));
}

/**
* Get a trimmed (to <code>null</code> if blank) String parameter, with a fallback value. Never throws an exception. Can pass a distinguished value to
* default to enable checks of whether it was supplied.
*
* @param request
* current HTTP request
* @param name
* the name of the parameter
* @param defaultVal
* the default value to use as fallback
*
* @return the trimmed (to <code>null</code> if blank) String value, or <code>defaultVal</code> (will be trimmed to <code>null</code> if blank) if not
* present
*/
public static String getTrimmedStringParameter(ServletRequest request, String name, String defaultVal) {
return StringUtils.trimToNull(org.springframework.web.bind.ServletRequestUtils.getStringParameter(request, name, defaultVal));
}

/**
* Get a SQL Date parameter, or <code>null</code> if not present or cannot be parsed.
*
* @param request
* current HTTP request
* @param name
* the name of the parameter
* @param defaultVal
* the default value to use as fallback
*
* @return the SQL Date value, or <code>null</code> if not present or cannot be parsed
*/
public static java.sql.Date getSqlDateParameter(ServletRequest request, String name) throws ServletRequestBindingException {
return getSqlDateParameter(request, name, null);
}

/**
* Get a SQL Date parameter, with a fallback value.
*
* @param request
* current HTTP request
* @param name
* the name of the parameter
* @param defaultVal
* the default value to use as fallback
*
* @return the SQL Date value, or default value if not present or cannot be parsed
*/
public static java.sql.Date getSqlDateParameter(ServletRequest request, String name, java.sql.Date defaultVal) throws ServletRequestBindingException {
return DateUtils.toSqlDate(DateUtils.parseDateStrictly((getTrimmedStringParameter(request, name)), DateUtils.PARSE_PATTERNS, defaultVal));
}

/**
* Get a Date parameter, or <code>null</code> if not present or cannot be parsed.
*
* @param request
* current HTTP request
* @param name
* the name of the parameter
* @param defaultVal
* the default value to use as fallback
*
* @return the Date value, or <code>null</code> if not present or cannot be parsed
*/
public static Date getDateParameter(ServletRequest request, String name) throws ServletRequestBindingException {
return getDateParameter(request, name, null);
}

/**
* Get a Date parameter, with a fallback value.
*
* @param request
* current HTTP request
* @param name
* the name of the parameter
* @param defaultVal
* the default value to use as fallback
*
* @return the Date value, or default value if not present or cannot be parsed
*/
public static Date getDateParameter(ServletRequest request, String name, Date defaultVal) throws ServletRequestBindingException {
return DateUtils.parseDateStrictly((getTrimmedStringParameter(request, name)), DateUtils.PARSE_PATTERNS, defaultVal);
}

/**
* Get a List of Map from JSON string parameter.
*
* @param request
* current HTTP request
* @param name
* the name of the parameter
*
* @return a List of Map, or an empty List if the parameter is not present
*/
public static List<?> getJsonListParameter(ServletRequest request, String name) throws ServletRequestBindingException, IOException {
String content = getStringParameter(request, name);
if (content != null)
return JsonUtils.fromJsonString(content, List.class);
else
return Collections.EMPTY_LIST;
}

/**
* Get a Map from JSON string parameter.
*
* @param request
* current HTTP request
* @param name
* the name of the parameter
*
* @return a Map, or an empty Map if the parameter is not present
*/
public static Map<?,?> getJsonMapParameter(ServletRequest request, String name) throws ServletRequestBindingException, IOException {
String content = getStringParameter(request, name);
if (content != null)
return JsonUtils.fromJsonString(content, Map.class);
else
return Collections.EMPTY_MAP;
}

/**
* Binds data using {@link ServletRequestDataBinder} with the following custom editors:-
*
* <ul>
* <li>{@link SimpleStringTrimToNullEditor}</li>
* <li>{@link SimpleIntegerEditor}</li>
* <li>{@link SimpleDateEditor}</li>
* </ul>
*
* <p>
* <b>Important:</b> The following system fields will NOT be binded for security reasons.
* <ul>
* <li>id</li>
* <li>ownerId</li>
* <li>deleted</li>
* <li>createdBy</li>
* <li>modifiedBy</li>
* <li>created</li>
* <li>modified</li>
* <li>password</li>
* </ul>
* </p>
*
* @param request
* current HTTP request
* @param object
* the target object to bind onto
* @param objectName
* the name of the target object
* @param disallowFields
* optional
*/
public static Object doBind(ServletRequest request, Object object, String objectName, String... disallowFields) {
ServletRequestDataBinder binder = new ServletRequestDataBinder(object, objectName);
binder.registerCustomEditor(String.class, new SimpleStringTrimToNullEditor());
binder.registerCustomEditor(Integer.class, new SimpleIntegerEditor());
binder.registerCustomEditor(java.util.Date.class, new SimpleDateEditor());
String[] coreFields = { Params.ID, "ownerId", "deleted", "createdBy", "modifiedBy", "created", "modified", "password" };
binder.setDisallowedFields(ArrayUtils.addAll(coreFields, disallowFields));
binder.bind(request);
return object;
};

}

+ 72
- 0
src/main/java/com/ffii/core/utils/web/ServletResponseUtils.java Целия файл

@@ -0,0 +1,72 @@
/*******************************************************************************
* Copyright 2019 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.web;

import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;

/**
* ServletResponseUtils
*
* @author Patrick
*/
public abstract class ServletResponseUtils {

/**
* Writes a String to HttpServletResponse encoded with the specified charsetName, and then flush and close it.
*
* @param response
* HttpServletResponse
* @param charsetName
* The name of a supported {@link java.nio.charset.Charset </code>charset<code>}
* @param str
* String to be written
*/
public static void writeStringToStream(HttpServletResponse response, String charsetName, String str) throws UnsupportedEncodingException, IOException {
OutputStreamWriter out = new OutputStreamWriter(response.getOutputStream(), charsetName);
out.write(str);
out.flush();
out.close();
}

/**
* Writes bytes to HttpServletResponse, and then flush and close it.
*
* @param response
* HttpServletResponse
* @param bytes
* The bytes array to be written
* @throws IOException
*/
public static void writeBytesToStream(HttpServletResponse response, byte[] bytes) throws IOException {
ServletOutputStream out = response.getOutputStream();
out.write(bytes);
out.flush();
out.close();
}

/**
* Disables caching by modifying the response header.
*
* @param response
* HttpServletResponse
*/
public static void disableCaching(HttpServletResponse response) {
response.addHeader("Pragma", "no-cache");
response.addHeader("Cache-Control", "no-cache, no-store, max-age=0");
response.addDateHeader("Expires", 1L);
}

}

+ 17
- 0
src/main/java/com/ffii/core/web/AbstractController.java Целия файл

@@ -0,0 +1,17 @@
/*******************************************************************************
* Copyright 2019 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.web;

import org.springframework.web.context.support.WebApplicationObjectSupport;

public abstract class AbstractController extends WebApplicationObjectSupport {

}

+ 17
- 0
src/main/java/com/ffii/core/web/AbstractService.java Целия файл

@@ -0,0 +1,17 @@
/*******************************************************************************
* Copyright 2019 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.web;

import org.springframework.web.context.support.WebApplicationObjectSupport;

public abstract class AbstractService extends WebApplicationObjectSupport {

}

+ 10
- 0
src/main/java/com/ffii/core/web/AppConfig.java Целия файл

@@ -0,0 +1,10 @@
package com.ffii.core.web;

import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
@EnableTransactionManagement
public class AppConfig {

}

+ 35
- 0
src/main/java/com/ffii/core/web/ControllerAdvice.java Целия файл

@@ -0,0 +1,35 @@
/*******************************************************************************
* Copyright 2019 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.web;

import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;

import com.ffii.core.support.SimpleDateEditor;
import com.ffii.core.support.SimpleIntegerEditor;
import com.ffii.core.support.SimpleStringTrimToNullEditor;

/**
* ControllerAdvice is used to define @ExceptionHandler, @InitBinder, and @ModelAttribute methods that apply to all @RequestMapping methods.
*
* @author Patrick
*/
@org.springframework.web.bind.annotation.ControllerAdvice
public class ControllerAdvice {

@InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(String.class, new SimpleStringTrimToNullEditor());
binder.registerCustomEditor(Integer.class, new SimpleIntegerEditor());
binder.registerCustomEditor(java.util.Date.class, new SimpleDateEditor());
}

}

+ 37
- 0
src/main/java/com/ffii/core/web/RestResponseEntityExceptionHandler.java Целия файл

@@ -0,0 +1,37 @@
/*******************************************************************************
* Copyright 2019 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.web;

import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

/**
* @author Patrick
*/
@org.springframework.web.bind.annotation.ControllerAdvice
public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {

protected final Log logger = LogFactory.getLog(getClass());

@ExceptionHandler(value = { DataIntegrityViolationException.class })
protected ResponseEntity<Object> handleDataIntegrityViolationException(RuntimeException ex, WebRequest request) {
logger.error(ex.getMessage());
return handleExceptionInternal(ex, ExceptionUtils.getRootCauseMessage(ex), null, HttpStatus.INTERNAL_SERVER_ERROR, request);
}

}

+ 52
- 0
src/main/java/com/ffii/core/web/filter/AjaxSessionExpirationFilter.java Целия файл

@@ -0,0 +1,52 @@
/*******************************************************************************
* Copyright 2019 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.web.filter;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

public class AjaxSessionExpirationFilter implements Filter {

private int customSessionExpiredErrorCode = 403;

@Override
public void init(FilterConfig cfg) throws ServletException {
}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpSession currentSession = ((HttpServletRequest) request).getSession(false);
if (currentSession == null) {
String requestedWithHeader = ((HttpServletRequest) request).getHeader("X-Requested-With");
if ("XMLHttpRequest".equals(requestedWithHeader)) {
((HttpServletResponse) response).sendError(this.customSessionExpiredErrorCode);
} else {
chain.doFilter(request, response);
}
} else {
chain.doFilter(request, response);
}
}

@Override
public void destroy() {
}

}

+ 46
- 0
src/main/java/com/ffii/core/web/view/AbstractView.java Целия файл

@@ -0,0 +1,46 @@
/*******************************************************************************
* Copyright 2019 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.web.view;

import org.springframework.web.servlet.View;

/**
* Abstract base class extending Spring {@link AbstractView} for {@link View} implementations.
*
* @see View
* @author Patrick
*/
public abstract class AbstractView extends org.springframework.web.servlet.view.AbstractView {

public static final String CONTENT_TYPE_JSON = "application/json";

public static final String CONTENT_TYPE_JAVASCRIPT = "text/javascript";

public static final String CONTENT_TYPE_TEXT_HTML = "text/html";
public static final String CONTENT_TYPE_TEXT_PLAIN = "text/plain";
public static final String CONTENT_TYPE_XML = "text/xml";

public static final String CONTENT_TYPE_JPEG = "image/jpeg";
public static final String CONTENT_TYPE_PNG = "image/png";

public static final String CHARSET_UTF8 = "UTF-8";

protected boolean disableCaching = true;

/**
* Disables caching of the generated JSON. <br>
* Default is {@code true}, which will prevent the client from caching the generated JSON.
*/
public void setDisableCaching(boolean disableCaching) {
this.disableCaching = disableCaching;
}

}

Някои файлове не бяха показани, защото твърде много файлове са промени

Зареждане…
Отказ
Запис