Merge pull request #1079 from vorburger/SpringBootAndMariaDB4j

MIFOSX-1552 Spring Boot + MariaDB4j
This commit is contained in:
Vishwas Babu A J
2014-09-24 21:06:48 -07:00
247 changed files with 1827 additions and 967 deletions
@@ -1,3 +0,0 @@
UPDATE m_permission
SET action_name = CONCAT(action_name,'_CHECKER')
WHERE code LIKE "%_CHECKER";
+51 -53
View File
@@ -11,13 +11,15 @@ buildscript {
dependencies { dependencies {
classpath 'org.gradle.api.plugins:gradle-tomcat-plugin:1.0', classpath 'org.gradle.api.plugins:gradle-tomcat-plugin:1.0',
'nl.javadude.gradle.plugins:license-gradle-plugin:0.11.0', 'nl.javadude.gradle.plugins:license-gradle-plugin:0.11.0',
'org.zeroturnaround:gradle-jrebel-plugin:1.1.2' 'org.zeroturnaround:gradle-jrebel-plugin:1.1.2',
'org.springframework.boot:spring-boot-gradle-plugin:1.1.6.RELEASE' // also change springDataJpaVersion below
} }
} }
apply plugin: 'rebel' apply plugin: 'rebel'
apply plugin: 'license' apply plugin: 'license'
apply plugin: 'war' apply plugin: 'war'
apply plugin: 'spring-boot'
apply plugin: 'eclipse' apply plugin: 'eclipse'
apply plugin: 'idea' apply plugin: 'idea'
apply plugin: 'tomcat' apply plugin: 'tomcat'
@@ -25,14 +27,13 @@ apply plugin: 'project-report'
/* define the valid syntax level for source files */ /* define the valid syntax level for source files */
sourceCompatibility = JavaVersion.VERSION_1_7 sourceCompatibility = JavaVersion.VERSION_1_7
/* define binary compatiblity version */ /* define binary compatibility version */
targetCompatibility = JavaVersion.VERSION_1_7 targetCompatibility = JavaVersion.VERSION_1_7
project.ext.springVersion = '4.0.4.RELEASE' project.ext.springBootVersion = '1.1.6.RELEASE'
project.ext.springSecurityVersion = '3.2.3.RELEASE' project.ext.springVersion = '4.0.7.RELEASE'
project.ext.hibernateCoreVersion = '4.1.9.Final'
project.ext.jerseyVersion = '1.17' project.ext.jerseyVersion = '1.17'
project.ext.springDataJpaVersion = '1.4.1.RELEASE' project.ext.springDataJpaVersion = '1.7.0.RELEASE' // also change spring-boot-gradle-plugin version above
project.ext.mysqlUser='root' project.ext.mysqlUser='root'
project.ext.mysqlPassword='mysql' project.ext.mysqlPassword='mysql'
@@ -43,11 +44,13 @@ group = 'org.mifosplatform'
repositories { repositories {
jcenter() jcenter()
maven { maven {
url "http://repository.pentaho.org/artifactory/pentaho/" url "http://repository.pentaho.org/artifactory/pentaho/"
} }
// mavenLocal() // useful for local dev using MariaDB4j SNAPSHOTs (not needed for real-world non-SNAPHOT builds)
} }
configurations { configurations {
providedRuntime // needed for Spring Boot executable WAR
providedCompile providedCompile
compile compile
runtime runtime
@@ -55,32 +58,28 @@ configurations {
} }
dependencies { dependencies {
def tomcatVersion = '7.0.47' def tomcatVersion = '7.0.54'
tomcat "org.apache.tomcat.embed:tomcat-embed-core:${tomcatVersion}", tomcat "org.apache.tomcat.embed:tomcat-embed-core:${tomcatVersion}",
"org.apache.tomcat.embed:tomcat-embed-logging-juli:${tomcatVersion}" "org.apache.tomcat.embed:tomcat-embed-logging-log4j:${tomcatVersion}" // NOT tomcat-embed-logging-juli (http://stackoverflow.com/questions/23963049/classcircularityerror-java-util-logging-logrecord-running-gradle-webapp-with-ja)
tomcat("org.apache.tomcat.embed:tomcat-embed-jasper:${tomcatVersion}") { tomcat("org.apache.tomcat.embed:tomcat-embed-jasper:${tomcatVersion}") {
exclude group: 'org.eclipse.jdt.core.compiler', module: 'ecj' exclude group: 'org.eclipse.jdt.core.compiler', module: 'ecj'
} }
tomcat "org.apache.tomcat:tomcat-dbcp:${tomcatVersion}" tomcat "org.apache.tomcat:tomcat-dbcp:${tomcatVersion}"
providedRuntime("org.springframework.boot:spring-boot-starter-tomcat")
providedCompile( providedCompile(
[group: 'javax.servlet', name: 'servlet-api', version: '2.5'], // [group: 'javax.servlet', name: 'servlet-api', version: '2.5'],
) )
compile( compile(
[group: 'org.springframework', name: 'spring-core', version: springVersion], [group: 'ch.vorburger.mariaDB4j', name: 'mariaDB4j', version: '2.1.0'],
[group: 'org.springframework', name: 'spring-context-support', version: springVersion],
[group: 'org.springframework', name: 'spring-aspects', version: springVersion],
[group: 'org.springframework', name: 'spring-web', version: springVersion],
[group: 'org.springframework', name: 'spring-test', version: springVersion],
[group: 'org.springframework.security', name: 'spring-security-core', version: springSecurityVersion],
[group: 'org.springframework.security', name: 'spring-security-web', version: springSecurityVersion],
[group: 'org.springframework.security', name: 'spring-security-config', version: springSecurityVersion],
[group: 'org.aspectj', name: 'aspectjrt', version: '1.7.0.RELEASE'],
[group: 'org.hibernate', name: 'hibernate-core', version: hibernateCoreVersion], [group: 'org.springframework.boot', name: 'spring-boot-starter-web', version: springBootVersion],
[group: 'org.hibernate', name: 'hibernate-entitymanager', version: hibernateCoreVersion], [group: 'org.springframework.boot', name: 'spring-boot-starter-data-jpa', version: springBootVersion],
[group: 'org.hibernate.javax.persistence', name: 'hibernate-jpa-2.0-api', version: '1.0.1.Final'], [group: 'org.springframework.boot', name: 'spring-boot-starter-security', version: springBootVersion],
[group: 'org.springframework', name: 'spring-context-support', version: springVersion],
[group: 'javax.ws.rs', name: 'jsr311-api', version: '1.1.1'], [group: 'javax.ws.rs', name: 'jsr311-api', version: '1.1.1'],
[group: 'com.sun.jersey', name: 'jersey-core', version: jerseyVersion], [group: 'com.sun.jersey', name: 'jersey-core', version: jerseyVersion],
@@ -98,21 +97,15 @@ dependencies {
[group: 'com.google.code.gson', name: 'gson', version: '2.2.4'], [group: 'com.google.code.gson', name: 'gson', version: '2.2.4'],
[group: 'com.google.guava', name: 'guava', version: '15.0'], [group: 'com.google.guava', name: 'guava', version: '15.0'],
[group: 'org.springframework.data', name: 'spring-data-jpa', version: springDataJpaVersion], [group: 'joda-time', name: 'joda-time', version: '2.4'],
[group: 'joda-time', name: 'joda-time', version: '2.3'],
[group: 'net.sourceforge.javacsv', name: 'javacsv', version: '2.0'], [group: 'net.sourceforge.javacsv', name: 'javacsv', version: '2.0'],
[group: 'org.apache.commons', name: 'commons-email', version: '1.3.1'], [group: 'org.apache.commons', name: 'commons-email', version: '1.3.3'],
[group: 'org.apache.commons', name: 'commons-lang3', version: '3.1'], [group: 'org.apache.commons', name: 'commons-lang3', version: '3.3.2'],
[group: 'org.slf4j', name: 'slf4j-api', version: '1.7.5'], // no slf4j & logback here (anymore), as spring-boot-starter-logging already brings this now, better assembled (log4j-over-slf4j was originally forgotten here)
[group: 'org.slf4j', name: 'jcl-over-slf4j', version: '1.7.5'],
[group: 'ch.qos.logback', name: 'logback-core', version: '1.0.13'],
[group: 'ch.qos.logback', name: 'logback-classic', version: '1.0.13'],
[group: 'mysql', name: 'mysql-connector-java', version: '5.1.27'], [group: 'mysql', name: 'mysql-connector-java', version: '5.1.27'],
[group: 'org.apache.tomcat', name: 'tomcat-jdbc', version: '7.0.47'], [group: 'org.apache.tomcat', name: 'tomcat-jdbc', version: tomcatVersion],
[group: 'pentaho-reporting-engine', name: 'pentaho-reporting-engine-classic-core', version: '3.9.1.1'], [group: 'pentaho-reporting-engine', name: 'pentaho-reporting-engine-classic-core', version: '3.9.1.1'],
[group: 'pentaho-reporting-engine', name: 'pentaho-reporting-engine-classic-extensions', version: '3.9.1-GA'], [group: 'pentaho-reporting-engine', name: 'pentaho-reporting-engine-classic-extensions', version: '3.9.1-GA'],
@@ -143,12 +136,17 @@ dependencies {
[group: 'com.amazonaws', name: 'aws-java-sdk', version: '1.2.1'], [group: 'com.amazonaws', name: 'aws-java-sdk', version: '1.2.1'],
[group: 'net.sf.ehcache', name: 'ehcache', version: '2.7.2'], [group: 'net.sf.ehcache', name: 'ehcache', version: '2.7.2'],
[group: 'com.github.spullara.mustache.java', name: 'compiler', version: '0.8.12'], [group: 'com.github.spullara.mustache.java', name: 'compiler', version: '0.8.12'],
[group: 'com.jayway.jsonpath', name: 'json-path', version: '0.9.1']) [group: 'com.jayway.jsonpath', name: 'json-path', version: '0.9.1'],
testCompile 'junit:junit:4.11', // Although Mifos (at the time of writing) doesn't have any compile time dep. on this,
'junit:junit-dep:4.11', // it's useful to have this for the Spring Boot TestRestTemplate http://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#boot-features-rest-templates-test-utility
'org.mockito:mockito-all:1.9.5', [group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.2.3']
'com.jayway.restassured:rest-assured:1.7.2' )
testCompile 'junit:junit:4.11',
'junit:junit-dep:4.11',
'org.mockito:mockito-core:1.9.5',
'com.jayway.restassured:rest-assured:1.7.2',
[group: 'org.springframework.boot', name: 'spring-boot-starter-test', version: springBootVersion]
} }
@@ -179,7 +177,7 @@ task dist(type:Zip){
include '*.sql' include '*.sql'
into('database') into('database')
} }
from('../mifosng-db/migrations') { from('src/main/resources/sql/migrations') {
fileMode = 0755 fileMode = 0755
include '**/*' include '**/*'
into('database/migrations') into('database/migrations')
@@ -203,14 +201,11 @@ task deployPentahoReports() {
war { war {
it.dependsOn deployPentahoReports it.dependsOn deployPentahoReports
from ('../mifosng-db/migrations/core_db') {
into "WEB-INF/classes/sql"
}
} }
license { license {
header rootProject.file('../LICENSE.md') header rootProject.file('../LICENSE.md')
excludes(["**/*.mustache"]) excludes(["**/*.mustache", "**/*.sql", "**/package-info.java", "**/keystore.jks"])
strictCheck true strictCheck true
} }
@@ -229,7 +224,7 @@ tomcatRunWar {
stopPort = 8081 stopPort = 8081
stopKey= 'stopKey' stopKey= 'stopKey'
enableSSL = true enableSSL = true
keystoreFile = file('../keystore.jks') keystoreFile = file('src/main/resources/keystore.jks')
keystorePass = 'openmf' keystorePass = 'openmf'
configFile = file('src/test/resources/META-INF/context.xml') configFile = file('src/test/resources/META-INF/context.xml')
} }
@@ -306,7 +301,7 @@ buildscript {
} }
dependencies { dependencies {
classpath 'com.github.ben-manes:gradle-flyway-plugin:0.2' classpath "org.flywaydb:flyway-gradle-plugin:3.0" // version upgraded during Spring Boot & MariaDB4j work, as prev. used v0.2 didn't work well after *.sql moved from mifosng-db to mifosng-provider/src/main/resources (new version also has clearer errors, e.g. in case of missing DB)
classpath 'mysql:mysql-connector-java:5.1.22' classpath 'mysql:mysql-connector-java:5.1.22'
} }
} }
@@ -316,15 +311,12 @@ flyway {
url = "jdbc:mysql://localhost:3306/mifostenant-default" url = "jdbc:mysql://localhost:3306/mifostenant-default"
user = mysqlUser user = mysqlUser
password = mysqlPassword password = mysqlPassword
def filePath = "filesystem:$projectDir" + System.properties['file.separator'] + '..' + System.properties['file.separator'] + 'mifosng-db' + System.properties['file.separator'] + 'migrations'
locations = [filePath]
} }
task migrateTenantDB<<{ task migrateTenantDB<<{
description="Migrates a Tenant DB. Optionally can pass dbName. Defaults to 'mifostenant-default' (Example: -PdbName=someTenantDBname)" description="Migrates a Tenant DB. Optionally can pass dbName. Defaults to 'mifostenant-default' (Example: -PdbName=someTenantDBname)"
def filePath = "filesystem:$projectDir" + System.properties['file.separator'] + '..' + System.properties['file.separator'] + 'mifosng-db' + System.properties['file.separator'] + 'migrations/core_db' def filePath = "filesystem:$projectDir" + '/src/main/resources/sql/migrations/core_db'
def tenantDbName = 'mifostenant-default'; def tenantDbName = 'mifostenant-default';
if (rootProject.hasProperty("dbName")) { if (rootProject.hasProperty("dbName")) {
tenantDbName = rootProject.getProperty("dbName") tenantDbName = rootProject.getProperty("dbName")
@@ -339,7 +331,7 @@ task migrateTenantDB<<{
task showTenantDBInfo<<{ task showTenantDBInfo<<{
description="Shows the migration info for a Tenant DB. Optionally can pass dbName. Defaults to 'mifostenant-default' (Example: -PdbName=someTenantDBname)" description="Shows the migration info for a Tenant DB. Optionally can pass dbName. Defaults to 'mifostenant-default' (Example: -PdbName=someTenantDBname)"
def filePath = "filesystem:$projectDir" + System.properties['file.separator'] + '..' + System.properties['file.separator'] + 'mifosng-db' + System.properties['file.separator'] + 'migrations/core_db' def filePath = "filesystem:$projectDir" + '/src/main/resources/sql/migrations/core_db'
def tenantDbName = 'mifostenant-default'; def tenantDbName = 'mifostenant-default';
if (rootProject.hasProperty("dbName")) { if (rootProject.hasProperty("dbName")) {
tenantDbName = rootProject.getProperty("dbName") tenantDbName = rootProject.getProperty("dbName")
@@ -354,7 +346,7 @@ task showTenantDBInfo<<{
task migrateTenantListDB<<{ task migrateTenantListDB<<{
description="Migrates a Tenant List DB. Optionally can pass dbName. Defaults to 'mifosplatform-tenants' (Example: -PdbName=someDBname)" description="Migrates a Tenant List DB. Optionally can pass dbName. Defaults to 'mifosplatform-tenants' (Example: -PdbName=someDBname)"
def filePath = "filesystem:$projectDir" + System.properties['file.separator'] + '..' + System.properties['file.separator'] + 'mifosng-db' + System.properties['file.separator'] + 'migrations/list_db' def filePath = "filesystem:$projectDir" + '/src/main/resources/sql/migrations/list_db'
def tenantsDbName = 'mifosplatform-tenants'; def tenantsDbName = 'mifosplatform-tenants';
if (rootProject.hasProperty("dbName")) { if (rootProject.hasProperty("dbName")) {
tenantsDbName = rootProject.getProperty("dbName") tenantsDbName = rootProject.getProperty("dbName")
@@ -369,7 +361,7 @@ task migrateTenantListDB<<{
task showTenantListDBInfo<<{ task showTenantListDBInfo<<{
description="Shows the migration info for a List DB. Optionally can pass dbName. Defaults to 'mifosplatform-tenants' (Example: -PdbName=someDBname)" description="Shows the migration info for a List DB. Optionally can pass dbName. Defaults to 'mifosplatform-tenants' (Example: -PdbName=someDBname)"
def filePath = "filesystem:$projectDir" + System.properties['file.separator'] + '..' + System.properties['file.separator'] + 'mifosng-db' + System.properties['file.separator'] + 'migrations/list_db' def filePath = "filesystem:$projectDir" + '/src/main/resources/sql/migrations/list_db'
def tenantsDbName = 'mifosplatform-tenants'; def tenantsDbName = 'mifosplatform-tenants';
if (rootProject.hasProperty("dbName")) { if (rootProject.hasProperty("dbName")) {
tenantsDbName = rootProject.getProperty("dbName") tenantsDbName = rootProject.getProperty("dbName")
@@ -383,7 +375,7 @@ task showTenantListDBInfo<<{
task repairTenantDB<<{ task repairTenantDB<<{
description="repair migrate" description="repair migrate"
def filePath = "filesystem:$projectDir" + System.properties['file.separator'] + '..' + System.properties['file.separator'] + 'mifosng-db' + System.properties['file.separator'] + 'migrations/list_db' def filePath = "filesystem:$projectDir" + '/src/main/resources/sql/migrations/list_db'
def tenantsDbName = 'mifosplatform-tenants'; def tenantsDbName = 'mifosplatform-tenants';
if (rootProject.hasProperty("dbName")) { if (rootProject.hasProperty("dbName")) {
tenantsDbName = rootProject.getProperty("dbName") tenantsDbName = rootProject.getProperty("dbName")
@@ -412,3 +404,9 @@ def qualifyVersionIfNecessary(version) {
return version return version
} }
springBoot {
mainClass = 'org.mifosplatform.ServerWithMariaDB4jApplication'
}
bootRepackage {
mainClass = 'org.mifosplatform.ServerWithMariaDB4jApplication'
}
@@ -23,6 +23,11 @@ import com.jayway.restassured.path.json.JsonPath;
import com.jayway.restassured.specification.RequestSpecification; import com.jayway.restassured.specification.RequestSpecification;
import com.jayway.restassured.specification.ResponseSpecification; import com.jayway.restassured.specification.ResponseSpecification;
/**
* Util for RestAssured tests.
* This class here in src/integrationTest is copy/pasted to src/test; please keep them in sync.
*/
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public class Utils { public class Utils {
@@ -33,7 +38,7 @@ public class Utils {
public static void initializeRESTAssured() { public static void initializeRESTAssured() {
RestAssured.baseURI = "https://localhost"; RestAssured.baseURI = "https://localhost";
RestAssured.port = 8443; RestAssured.port = 8443;
RestAssured.keystore("../keystore.jks", "openmf"); RestAssured.keystore("src/main/resources/keystore.jks", "openmf");
} }
public static String loginIntoServerAndGetBase64EncodedAuthenticationKey() { public static String loginIntoServerAndGetBase64EncodedAuthenticationKey() {
@@ -0,0 +1,43 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform;
import org.mifosplatform.infrastructure.core.boot.AbstractApplicationConfiguration;
import org.mifosplatform.infrastructure.core.boot.ApplicationExitUtil;
import org.mifosplatform.infrastructure.core.boot.EmbeddedTomcatWithSSLConfiguration;
import org.mifosplatform.infrastructure.core.boot.db.DataSourceConfiguration;
import org.mifosplatform.infrastructure.core.boot.db.DataSourceProperties;
import org.springframework.boot.SpringApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Import;
/**
* Mifos main() application which launches Mifos X in an embedded Tomcat HTTP
* (using Spring Boot).
*
* The DataSource used is a to a "normal" external database (not use MariaDB4j).
* This DataSource can be configured with parameters, see {@link DataSourceProperties}.
*
* You can easily launch this via Debug as Java Application in your IDE -
* without needing command line Gradle stuff, no need to build and deploy a WAR,
* remote attachment etc.
*
* It's the old/classic Mifos (non-X) Workspace 2.0 reborn for Mifos X! ;-)
*
* @see DataSourceProperties about how to configure the DataSource used
* @see ServerWithMariaDB4jApplication for an alternative with an embedded DB
*/
public class ServerApplication {
@Import({ DataSourceConfiguration.class, EmbeddedTomcatWithSSLConfiguration.class })
private static class Configuration extends AbstractApplicationConfiguration { }
public static void main(String[] args) throws Exception {
ConfigurableApplicationContext ctx = SpringApplication.run(Configuration.class, args);
ApplicationExitUtil.waitForKeyPressToCleanlyExit(ctx);
}
}
@@ -0,0 +1,39 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform;
import org.mifosplatform.infrastructure.core.boot.AbstractApplicationConfiguration;
import org.mifosplatform.infrastructure.core.boot.ApplicationExitUtil;
import org.mifosplatform.infrastructure.core.boot.EmbeddedTomcatWithSSLConfiguration;
import org.mifosplatform.infrastructure.core.boot.db.MariaDB4jDataSourceConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Import;
/**
* Mifos main() application which launches Mifos X in an embedded Tomcat HTTP
* server (using Spring Boot), as well as an embedded database (using
* MariaDB4j).
*
* You can easily launch this via Debug as Java Application in your IDE -
* without needing command line Gradle stuff, no need to build and deploy a WAR,
* remote attachment etc.
*
* It's the old/classic Mifos (non-X) Workspace 2.0 reborn for Mifos X! ;-)
*
* @see ServerApplication for the same without the embedded MariaDB4j database
*/
public class ServerWithMariaDB4jApplication {
@Import({ MariaDB4jDataSourceConfiguration.class, EmbeddedTomcatWithSSLConfiguration.class })
public static class Configuration extends AbstractApplicationConfiguration { }
public static void main(String[] args) throws Exception {
ConfigurableApplicationContext ctx = SpringApplication.run(Configuration.class, args);
ApplicationExitUtil.waitForKeyPressToCleanlyExit(ctx);
}
}
@@ -0,0 +1,37 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.infrastructure.core.boot;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportResource;
/**
* Base Spring Configuration with what's common to all Configuration subclasses.
*
* Notably the EnableAutoConfiguration excludes relevant for (and often adjusted
* when upgrading versions of) Spring Boot, the "old" (pre. Spring Boot &
* MariaDB4j) Mifos X appContext.xml which all configurations need, and the
* web.xml successor WebXmlConfiguration.
*
* Should NOT include Configuration related to embedded Tomcat, data sources,
* and MariaDB4j (because those differ in the subclasses).
*/
@Configuration
@Import({ WebXmlConfiguration.class })
@ImportResource({ "classpath*:META-INF/spring/appContext.xml" })
@EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class,
HibernateJpaAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class,
FlywayAutoConfiguration.class })
public abstract class AbstractApplicationConfiguration {
}
@@ -0,0 +1,33 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.infrastructure.core.boot;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import org.springframework.context.ConfigurableApplicationContext;
public abstract class ApplicationExitUtil {
private ApplicationExitUtil() {}
public static void waitForKeyPressToCleanlyExit(ConfigurableApplicationContext ctx) throws IOException {
// NOTE: In Eclipse, the Shutdown Hooks are not invoked on exit (red
// button).. In the case of MariaDB4j that's a problem because then the
// mysqld won't be stopped, so:
// (@see https://bugs.eclipse.org/bugs/show_bug.cgi?id=38016)
System.out.println("\nHit Enter to quit...");
// NOTE: In Eclipse, System.console() is not available.. so:
// (@see https://bugs.eclipse.org/bugs/show_bug.cgi?id=122429)
BufferedReader d = new BufferedReader(new InputStreamReader(System.in));
d.readLine();
ctx.stop();
ctx.close();
}
}
@@ -0,0 +1,104 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.infrastructure.core.boot;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import org.apache.catalina.connector.Connector;
import org.apache.commons.io.FileUtils;
import org.apache.coyote.http11.Http11NioProtocol;
import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
@Configuration
public class EmbeddedTomcatWithSSLConfiguration {
// http://docs.spring.io/spring-boot/docs/1.1.5.RELEASE/reference/htmlsingle/#howto-enable-multiple-connectors-in-tomcat
@Bean
public EmbeddedServletContainerFactory servletContainer() {
TomcatEmbeddedServletContainerFactory tomcat = new TomcatEmbeddedServletContainerFactory();
tomcat.setContextPath(getContextPath());
tomcat.addAdditionalTomcatConnectors(createSslConnector());
return tomcat;
}
private String getContextPath() {
return "/mifosng-provider";
}
protected Connector createSslConnector() {
Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
Http11NioProtocol protocol = (Http11NioProtocol) connector.getProtocolHandler();
try {
File keystore = getFile(getKeystore());
File truststore = keystore;
connector.setScheme("https");
connector.setSecure(true);
connector.setPort(getHTTPSPort());
protocol.setSSLEnabled(true);
protocol.setKeystoreFile(keystore.getAbsolutePath());
protocol.setKeystorePass(getKeystorePass());
protocol.setTruststoreFile(truststore.getAbsolutePath());
protocol.setTruststorePass(getKeystorePass());
// ? protocol.setKeyAlias("apitester");
return connector;
} catch (IOException ex) {
throw new IllegalStateException("can't access keystore: [" + "keystore" + "] or truststore: [" + "keystore" + "]", ex);
}
}
protected int getHTTPSPort() {
// TODO This shouldn't be hard-coded here, but configurable
return 8443;
}
protected String getKeystorePass() {
return "openmf";
}
protected Resource getKeystore() {
return new ClassPathResource("/keystore.jks");
}
public File getFile(Resource resource) throws IOException {
try {
return resource.getFile();
} catch (IOException e) {
// Uops.. OK, try again (below)
}
try {
URL url = resource.getURL();
// If this creates filenames that are too long on Win,
// then could just use resource.getFilename(),
// even though not unique, real risk prob. min.bon
String tempDir = System.getProperty("java.io.tmpdir");
tempDir = tempDir + "/" + getClass().getSimpleName() + "/";
String path = url.getPath();
String uniqName = path.replace("file:/", "").replace('!', '_');
String tempFullPath = tempDir + uniqName;
// instead of File.createTempFile(prefix?, suffix?);
File targetFile = new File(tempFullPath);
long len = resource.contentLength();
if (!targetFile.exists() || targetFile.length() != len) { // Only copy new files
FileUtils.copyURLToFile(url, targetFile);
}
return targetFile;
} catch (IOException e) {
// Uops.. erm, give up:
throw new IOException("Cannot obtain a File for Resource: "
+ resource.toString(), e);
}
}
}
@@ -0,0 +1,40 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.infrastructure.core.boot;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.web.SpringBootServletInitializer;
import org.springframework.context.annotation.ImportResource;
/**
* Required to still have a working legacy "classic" WAR.
*
* Configuration just adds the JNDI-based DataSource lookup to its
* AbstractApplicationConfiguration.
*
* This (intentionally) only configures the original (pre-Spring Boot &
* MariaDB4j) Mifos X Spring Beans, and does NOT include the embedded Tomcat
* (incl. TomcatSSLConfiguration) nor the MariaDB4jSetupService or
* MariaDB4jDataSourceConfiguration, and not even the DataSourceConfiguration
* (as it uses "classic" JNDI) - we want the WAR to "work like before".
*
* @see <a
* href="http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto-convert-an-existing-application-to-spring-boot">#howto-convert-an-existing-application-to-spring-boot</a>
*/
public class WarWebApplicationInitializer extends SpringBootServletInitializer {
@ImportResource({ "classpath*:META-INF/spring/jndi.xml" })
private static class Configuration extends AbstractApplicationConfiguration {
}
@Override
protected SpringApplicationBuilder configure(
SpringApplicationBuilder application) {
// let's share Spring Boot Love, so no showBanner(false)
return application.sources(Configuration.class);
}
}
@@ -0,0 +1,50 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.infrastructure.core.boot;
import javax.servlet.Filter;
import javax.servlet.Servlet;
import org.mifosplatform.infrastructure.core.filters.ResponseCorsFilter;
import org.springframework.boot.context.embedded.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.DelegatingFilterProxy;
import com.sun.jersey.spi.spring.container.servlet.SpringServlet;
/**
* This Configuration replaces what formerly was in web.xml.
*
* @see <a
* href="http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto-convert-an-existing-application-to-spring-boot">#howto-convert-an-existing-application-to-spring-boot</a>
*/
@Configuration
public class WebXmlConfiguration {
@Bean
public Filter springSecurityFilterChain() {
return new DelegatingFilterProxy();
}
@Bean
public ServletRegistrationBean jersey() {
Servlet jerseyServlet = new SpringServlet();
ServletRegistrationBean jerseyServletRegistration = new ServletRegistrationBean();
jerseyServletRegistration.setServlet(jerseyServlet);
jerseyServletRegistration.addUrlMappings("/api/v1/*");
jerseyServletRegistration.setName("jersey-servlet");
jerseyServletRegistration.setLoadOnStartup(1);
jerseyServletRegistration.addInitParameter("com.sun.jersey.api.json.POJOMappingFeature", "true");
jerseyServletRegistration.addInitParameter("com.sun.jersey.spi.container.ContainerResponseFilters",
ResponseCorsFilter.class.getName());
// debugging for development:
// jerseyServletRegistration.addInitParameter("com.sun.jersey.spi.container.ContainerRequestFilters",
// LoggingFilter.class.getName());
return jerseyServletRegistration;
}
}
@@ -0,0 +1,41 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.infrastructure.core.boot.db;
import javax.sql.DataSource;
import org.apache.tomcat.jdbc.pool.PoolConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Configuration for a DataSource.
* @see DataSourceProperties about how to configure this DS
*/
@Configuration
public class DataSourceConfiguration {
private static final Logger logger = LoggerFactory.getLogger(DataSourceConfiguration.class);
@Bean
public DataSourceProperties dataSourceProperties() {
return new DataSourceProperties();
}
@Bean
public DataSource tenantDataSourceJndi() {
PoolConfiguration p = getProperties();
org.apache.tomcat.jdbc.pool.DataSource ds = new org.apache.tomcat.jdbc.pool.DataSource(p);
logger.info("Created new DataSource; url=" + p.getUrl());
return ds;
}
protected DataSourceProperties getProperties() {
return dataSourceProperties();
}
}
@@ -0,0 +1,146 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.infrastructure.core.boot.db;
import javax.validation.constraints.NotNull;
import org.apache.tomcat.jdbc.pool.PoolProperties;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.util.StringUtils;
/**
* Configurable DataSource. Properties have sensible defaults, but end-users can
* override those via the Spring Values listed below; i.e. via -D Java System
* properties, or main() command line arguments, OS environment variables, from
* JNDI, or application.properties (thanks Spring Boot). For example:
* -Dmifos.datasource.port=3307.
*/
// NOT a @Component - we do not want this to picked up by component scan, only explicitly declared in DataSourceConfiguration (if that's active)
public class DataSourceProperties extends PoolProperties {
public final static String PORT = "mifos.datasource.port";
public final static String HOST = "mifos.datasource.host";
public final static String DB = "mifos.datasource.db";
public final static String UID = "mifos.datasource.username";
public final static String PWD = "mifos.datasource.password";
public final static String PROTOCOL = "mifos.datasource.protocol";
public final static String SUBPROTOCOL = "mifos.datasource.subprotocol";
@Value("${" + PORT + ":3306}")
private volatile @NotNull int port;
@Value("${" + HOST + ":localhost}")
private volatile @NotNull String hostname;
@Value("${" + DB + ":mifosplatform-tenants}")
private volatile @NotNull String dbName;
@Value("${" + UID + ":root}")
private volatile @NotNull String username;
@Value("${" + PWD + ":mysql}")
private volatile @NotNull String password;
@Value("${" + PROTOCOL + ":jdbc}")
private volatile @NotNull String jdbcProtocol;
@Value("${" + SUBPROTOCOL + ":mysql}")
private volatile @NotNull String jdbcSubprotocol;
public DataSourceProperties() {
super();
// default to save us from re-specifying this; note that it can still be
// overridden
setDriverClassName(com.mysql.jdbc.Driver.class.getName());
setMifosDefaults();
}
/**
* as per (some of..) INSTALL.md and
* org.mifosplatform.infrastructure.core.service
* .TomcatJdbcDataSourcePerTenantService
* .createNewDataSourceFor(MifosPlatformTenant)
*/
protected void setMifosDefaults() {
setInitialSize(3);
// setMaxIdle(6); -- strange, why?
// setMinIdle(3); -- JavaDoc says default is initialSize.. so shouldn't
// be needed
if (getValidationQuery() == null) setValidationQuery("SELECT 1");
setTestOnBorrow(true);
setTestOnReturn(true);
setTestWhileIdle(true);
setTimeBetweenEvictionRunsMillis(30000);
setTimeBetweenEvictionRunsMillis(60000);
setLogAbandoned(true);
setSuspectTimeout(60);
setJdbcInterceptors("org.apache.tomcat.jdbc.pool.interceptor.ConnectionState;"
+ "org.apache.tomcat.jdbc.pool.interceptor.StatementFinalizer;org.apache.tomcat.jdbc.pool.interceptor.SlowQueryReport");
}
@Override
public void setUrl(@SuppressWarnings("unused") String url) {
throw new UnsupportedOperationException("Use setHost/Port/DB() instead of setURL()");
}
@Override
public String getUrl() {
String url = super.getUrl();
if (StringUtils.hasText(url)) {
throw new IllegalStateException();
}
return jdbcProtocol + ":" + jdbcSubprotocol + "://" + getHost() + ":" + getPort() + "/" + getDBName();
}
public String getHost() {
return hostname;
}
public int getPort() {
return port;
}
public String getDBName() {
return dbName;
}
public void setPort(int port) {
this.port = port;
}
public void setHost(String hostname) {
this.hostname = hostname;
}
public void setDBName(String dbName) {
this.dbName = dbName;
}
@Override
public String getUsername() {
return this.username;
}
@Override
public void setUsername(String username) {
this.username = username;
}
@Override
public String getPassword() {
return this.password;
}
@Override
public void setPassword(String password) {
this.password = password;
}
}
@@ -0,0 +1,49 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.infrastructure.core.boot.db;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import ch.vorburger.mariadb4j.springframework.MariaDB4jSpringService;
@Configuration
public class MariaDB4jDataSourceConfiguration extends DataSourceConfiguration {
@Bean
public MariaDB4jSetupService mariaDB4jSetUp() {
return new MariaDB4jSetupService(mariaDB4j().getDB());
}
@Bean
public MariaDB4jSpringService mariaDB4j() {
MariaDB4jSpringService mariaDB4jSpringService = new MariaDB4jSpringService();
mariaDB4jSpringService.setDefaultBaseDir("build/db/bin");
mariaDB4jSpringService.setDefaultDataDir("build/db/data");
return mariaDB4jSpringService;
}
@Override
// NOT @Bean @Override dataSourceProperties() - doesn't work :(
protected DataSourceProperties getProperties() {
DataSourceProperties p = super.getProperties();
String dbName = mariaDB4jSetUp().getTenantDBName();
// Do not use p.setUrl(mariaDB4j().getConfiguration().getURL(dbName));
// Because TenantDataSourcePortFixService needs separate
// host/port/db/uid/pwd:
// (DataSourceProperties getUrl() creates the correct JDBC URL from it)
// This intentionally overrides any mifos.datasource.* settings, because
// in this configuration, logically the mariaDB4j settings take
// precedence:
p.setHost("localhost");
p.setPort(mariaDB4j().getConfiguration().getPort());
p.setDBName(dbName);
// TODO p.setUsername(mariaDB4j().getConfiguration().getUsername());
// TODO p.setPassword(mariaDB4j().getConfiguration().getPassword());
return p;
}
}
@@ -0,0 +1,41 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.infrastructure.core.boot.db;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import org.springframework.beans.factory.annotation.Autowired;
import ch.vorburger.exec.ManagedProcessException;
import ch.vorburger.mariadb4j.DB;
public class MariaDB4jSetupService {
private DB db;
@Autowired
public MariaDB4jSetupService(DB db) {
this.db = db;
}
@PostConstruct
protected void setUpMifosDBs() throws ManagedProcessException {
db.createDB(getTenantDBName());
db.createDB("mifostenant-default");
// Note that we don't need to initialize the DBs, because
// the TenantDatabaseUpgradeService will do this in just a moment.
}
public String getTenantDBName() {
return "mifosplatform-tenants";
}
@PreDestroy
protected void stop() throws ManagedProcessException {
db = null;
}
}
@@ -0,0 +1,83 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.infrastructure.core.boot.db;
import javax.sql.DataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
/**
* Service which "fixes up" the schemaServerPort in the tenants table of the
* mifosplatform-tenants schema. It sets it to the actual current port of
* MariaDB4j, instead of the default 3306 which is hard-coded in the initial
* set-up SQL script (V1__mifos-platform-shared-tenants.sql).
*
* This service is called by the TenantDatabaseUpgradeService
* "just at the right time" during start-up, that is AFTER the initialization of
* the mifosplatform-tenants but BEFORE Flyway runs on all tenant schemas.
*
* It's difficult to achieve the same goal elsewhere, because we cannot do this
* e.g. in MariaDB4jSetupService, as that's too early
* (TenantDatabaseUpgradeService has not yet run), nor e.g. in
* TomcatJdbcDataSourcePerTenantService because in there we do not have access
* to and should not depend on MariaDB4j configuration.
*/
@Service
public class TenantDataSourcePortFixService {
private static final Logger logger = LoggerFactory.getLogger(TenantDataSourcePortFixService.class);
/**
* While what this class does is convenient in 96% you may would like to
* completely disable it if you've hand tuned your tenants table for an
* advanced configuration and use e.g. different databases maybe on
* different hosts for different tenants.
*/
public final static String ENABLED = "mifos.tenantdb.fixup";
@Value("${" + ENABLED + ":true}")
private boolean enabled;
// required=false is important here, because in
// WebApplicationInitializerConfiguration for classic WAR there
// is (intentionally) no MariaDB4j nor a DataSourceProperties
// bean (because in the WAR we're using a DS from JNDI)
private @Autowired(required = false) DataSourceProperties dsp;
private JdbcTemplate jdbcTemplate;
@Autowired
public TenantDataSourcePortFixService(@Qualifier("tenantDataSourceJndi") final DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
public void fixUpTenantsSchemaServerPort() {
if (!enabled) {
logger.info("No schema_server_port UPDATE made to tenants table of the mifosplatform-tenants schema, because " + ENABLED + " = false");
return;
}
if (dsp == null) {
// we don't have any generic mechanism to know the DB port, given just a tenant DataSource
logger.debug("No schema_server_port UPDATE made to tenants table of the mifosplatform-tenants schema (because neither MariaDB4j nor our own Spring Boot DataSourceConfiguration is used in a traditional WAR)");
return;
}
int r = jdbcTemplate
.update("UPDATE tenants SET schema_server = ?, schema_server_port = ?, schema_username = ?, schema_password = ?",
dsp.getHost(), dsp.getPort(), dsp.getUsername(), dsp.getPassword());
if ( r == 0 )
logger.warn("UPDATE tenants SET ... did not update ANY rows - something is probably wrong");
else
logger.info("Upated "
+ r
+ " rows in the tenants table of the mifosplatform-tenants schema to the real current host: "
+ dsp.getHost() + ", port: " + dsp.getPort());
}
}
@@ -12,18 +12,22 @@ import java.util.Enumeration;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationListener; import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextStoppedEvent; import org.springframework.context.event.ContextClosedEvent;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import com.mysql.jdbc.AbandonedConnectionCleanupThread; import com.mysql.jdbc.AbandonedConnectionCleanupThread;
@Service @Service
public class AbandonedConnectionCleanupShutdownListener implements ApplicationListener<ContextStoppedEvent> { public class AbandonedConnectionCleanupShutdownListener implements ApplicationListener<ContextClosedEvent> {
private final static Logger logger = LoggerFactory.getLogger(AbandonedConnectionCleanupShutdownListener.class); private final static Logger logger = LoggerFactory.getLogger(AbandonedConnectionCleanupShutdownListener.class);
/**
* @see JobRegisterServiceImpl#onApplicationEvent(ContextClosedEvent) doc
* re. why we use ContextClosedEvent instead of ContextStoppedEvent
*/
@Override @Override
public void onApplicationEvent(@SuppressWarnings("unused") ContextStoppedEvent event) { public void onApplicationEvent(@SuppressWarnings("unused") ContextClosedEvent event) {
shutDowncleanUpThreadAndDeregisterJDBCDriver(); shutDowncleanUpThreadAndDeregisterJDBCDriver();
} }
@@ -8,13 +8,17 @@ package org.mifosplatform.infrastructure.core.service;
import java.util.List; import java.util.List;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import javax.sql.DataSource;
import org.mifosplatform.infrastructure.core.boot.db.TenantDataSourcePortFixService;
import org.mifosplatform.infrastructure.core.domain.MifosPlatformTenant; import org.mifosplatform.infrastructure.core.domain.MifosPlatformTenant;
import org.mifosplatform.infrastructure.security.service.TenantDetailsService; import org.mifosplatform.infrastructure.security.service.TenantDetailsService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import com.googlecode.flyway.core.Flyway; import com.googlecode.flyway.core.Flyway;
import com.googlecode.flyway.core.api.FlywayException;
/** /**
* A service that picks up on tenants that are configured to auto-update their * A service that picks up on tenants that are configured to auto-update their
@@ -24,23 +28,52 @@ import com.googlecode.flyway.core.Flyway;
public class TenantDatabaseUpgradeService { public class TenantDatabaseUpgradeService {
private final TenantDetailsService tenantDetailsService; private final TenantDetailsService tenantDetailsService;
protected final DataSource tenantDataSource;
protected final TenantDataSourcePortFixService tenantDataSourcePortFixService;
@Autowired @Autowired
public TenantDatabaseUpgradeService(final TenantDetailsService detailsService) { public TenantDatabaseUpgradeService(
final TenantDetailsService detailsService,
@Qualifier("tenantDataSourceJndi") final DataSource dataSource,
TenantDataSourcePortFixService tenantDataSourcePortFixService)
{
this.tenantDetailsService = detailsService; this.tenantDetailsService = detailsService;
this.tenantDataSource = dataSource;
this.tenantDataSourcePortFixService = tenantDataSourcePortFixService;
} }
@PostConstruct @PostConstruct
public void upgradeAllTenants() { public void upgradeAllTenants() {
upgradeTenantDB();
final List<MifosPlatformTenant> tenants = this.tenantDetailsService.findAllTenants(); final List<MifosPlatformTenant> tenants = this.tenantDetailsService.findAllTenants();
for (final MifosPlatformTenant tenant : tenants) { for (final MifosPlatformTenant tenant : tenants) {
if (tenant.isAutoUpdateEnabled()) { if (tenant.isAutoUpdateEnabled()) {
final Flyway flyway = new Flyway(); final Flyway flyway = new Flyway();
flyway.setDataSource(tenant.databaseURL(), tenant.getSchemaUsername(), tenant.getSchemaPassword()); flyway.setDataSource(tenant.databaseURL(), tenant.getSchemaUsername(), tenant.getSchemaPassword());
flyway.setLocations("sql"); flyway.setLocations("sql/migrations/core_db");
flyway.setOutOfOrder(true); flyway.setOutOfOrder(true);
flyway.migrate(); try {
flyway.migrate();
} catch (FlywayException e) {
String betterMessage = e.getMessage()
+ "; for Tenant DB URL: " + tenant.databaseURL()
+ ", username: " + tenant.getSchemaUsername();
throw new FlywayException(betterMessage, e.getCause());
}
} }
} }
} }
/**
* Initializes, and if required upgrades (using Flyway) the Tenant DB itself.
*/
private void upgradeTenantDB() {
final Flyway flyway = new Flyway();
flyway.setDataSource(tenantDataSource);
flyway.setLocations("sql/migrations/list_db");
flyway.setOutOfOrder(true);
flyway.migrate();
tenantDataSourcePortFixService.fixUpTenantsSchemaServerPort();
}
} }
@@ -64,6 +64,8 @@ public class TomcatJdbcDataSourcePerTenantService implements RoutingDataSourceSe
// see // see
// http://www.tomcatexpert.com/blog/2010/04/01/configuring-jdbc-pool-high-concurrency // http://www.tomcatexpert.com/blog/2010/04/01/configuring-jdbc-pool-high-concurrency
// see also org.mifosplatform.DataSourceProperties.setMifosDefaults()
final String jdbcUrl = tenant.databaseURL(); final String jdbcUrl = tenant.databaseURL();
final PoolConfiguration poolConfiguration = new PoolProperties(); final PoolConfiguration poolConfiguration = new PoolProperties();
@@ -36,7 +36,7 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener; import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextStoppedEvent; import org.springframework.context.event.ContextClosedEvent;
import org.springframework.scheduling.quartz.CronTriggerFactoryBean; import org.springframework.scheduling.quartz.CronTriggerFactoryBean;
import org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean; import org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean;
import org.springframework.scheduling.quartz.SchedulerFactoryBean; import org.springframework.scheduling.quartz.SchedulerFactoryBean;
@@ -48,7 +48,7 @@ import org.springframework.stereotype.Service;
* {@link CronTriggerFactoryBean} * {@link CronTriggerFactoryBean}
*/ */
@Service @Service
public class JobRegisterServiceImpl implements JobRegisterService, ApplicationListener<ContextStoppedEvent> { public class JobRegisterServiceImpl implements JobRegisterService, ApplicationListener<ContextClosedEvent> {
private final static Logger logger = LoggerFactory.getLogger(JobRegisterServiceImpl.class); private final static Logger logger = LoggerFactory.getLogger(JobRegisterServiceImpl.class);
@@ -230,8 +230,14 @@ public class JobRegisterServiceImpl implements JobRegisterService, ApplicationLi
return !this.schedularWritePlatformService.retriveSchedulerDetail().isSuspended(); return !this.schedularWritePlatformService.retriveSchedulerDetail().isSuspended();
} }
/**
* Need to use ContextClosedEvent instead of ContextStoppedEvent because in
* case Spring Boot fails to start-up (e.g. because Tomcat port is already
* in use) then org.springframework.boot.SpringApplication.run(String...)
* does a context.close(); and not a context.stop();
*/
@Override @Override
public void onApplicationEvent(@SuppressWarnings("unused") ContextStoppedEvent event) { public void onApplicationEvent(@SuppressWarnings("unused") ContextClosedEvent event) {
this.stopAllSchedulers(); this.stopAllSchedulers();
} }
@@ -28,14 +28,12 @@ public class PlatformRequestLog {
@SuppressWarnings("unused") @SuppressWarnings("unused")
private final String url; private final String url;
@SuppressWarnings("unused") @SuppressWarnings("unused")
private final Map<String, String> parameters; private final Map<String, String[]> parameters;
public static PlatformRequestLog from(final StopWatch task, final HttpServletRequest request) throws IOException { public static PlatformRequestLog from(final StopWatch task, final HttpServletRequest request) throws IOException {
final String requestUrl = request.getRequestURL().toString(); final String requestUrl = request.getRequestURL().toString();
@SuppressWarnings("unchecked") final Map<String, String[]> parameters = new HashMap<>(request.getParameterMap());
final Map<String, String> parameters = new HashMap<>(request.getParameterMap());
parameters.remove("password"); parameters.remove("password");
parameters.remove("_"); parameters.remove("_");
@@ -43,7 +41,7 @@ public class PlatformRequestLog {
} }
private PlatformRequestLog(final long startTime, final long time, final String method, final String requestUrl, private PlatformRequestLog(final long startTime, final long time, final String method, final String requestUrl,
final Map<String, String> parameters) { final Map<String, String[]> parameters) {
this.startTime = startTime; this.startTime = startTime;
this.totalTime = time; this.totalTime = time;
this.method = method; this.method = method;
@@ -45,6 +45,13 @@
org.mifosplatform.batch"> org.mifosplatform.batch">
<context:exclude-filter expression="org.springframework.stereotype.Controller" <context:exclude-filter expression="org.springframework.stereotype.Controller"
type="annotation" /> type="annotation" />
<!-- We do NOT want all @Configuration "beans" to be auto-detected by ComponentScan,
but we want to use / import them explicitly in Tests & Spring Boot applications,
or other import in other @Configuration, so that we could have mutually exclusive ones.
-->
<context:exclude-filter expression="org.springframework.context.annotation.Configuration"
type="annotation" />
</context:component-scan> </context:component-scan>
<bean id="auditorAware" <bean id="auditorAware"
@@ -63,9 +70,8 @@
<import resource="infrastructure.xml" /> <import resource="infrastructure.xml" />
<!-- do NOT <import resource="jndi.xml" /> here; <!-- do NOT <import resource="jndi.xml" /> here anymore;
because we want the TestDataSourceConfiguration to not have that; we'll manually import jndi.xml where we still need it (MifosServletWebApplicationInitializerConfiguration)
we'll manually list jndi.xml in web.xml
--> -->
<import resource="securityContext.xml" /> <import resource="securityContext.xml" />
@@ -24,4 +24,3 @@ INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `c
INSERT INTO `job` (`name`, `display_name`, `cron_expression`, `create_time`, `task_priority`, `group_name`, `previous_run_start_time`, `next_run_time`, `job_key`, `initializing_errorlog`, `is_active`, `currently_running`, `updates_allowed`, `scheduler_group`, `is_misfired`) VALUES ('Transfer Fee For Loans From Savings', 'Transfer Fee For Loans From Savings', '0 1 0 1/1 * ? *', now(), 5, NULL, NULL, NULL, NULL, NULL, 1, 0, 1, 0, 0); INSERT INTO `job` (`name`, `display_name`, `cron_expression`, `create_time`, `task_priority`, `group_name`, `previous_run_start_time`, `next_run_time`, `job_key`, `initializing_errorlog`, `is_active`, `currently_running`, `updates_allowed`, `scheduler_group`, `is_misfired`) VALUES ('Transfer Fee For Loans From Savings', 'Transfer Fee For Loans From Savings', '0 1 0 1/1 * ? *', now(), 5, NULL, NULL, NULL, NULL, NULL, 1, 0, 1, 0, 0);
@@ -4,4 +4,3 @@ VALUES ('portfolio', 'PROPOSEANDACCEPTTRANSFER_CLIENT', 'CLIENT', 'PROPOSEANDACC
INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
VALUES ('portfolio', 'PROPOSEANDACCEPTTRANSFER_CLIENT_CHECKER', 'CLIENT', 'PROPOSEANDACCEPTTRANSFER', 0); VALUES ('portfolio', 'PROPOSEANDACCEPTTRANSFER_CLIENT_CHECKER', 'CLIENT', 'PROPOSEANDACCEPTTRANSFER', 0);
@@ -44,41 +44,41 @@ begin
open cursor1; open cursor1;
LOOP1: loop LOOP1: loop
fetch cursor1 into v_currency_code, v_withdrawal_fee_type_enum, v_withdrawal_fee_amount_charge_def; fetch cursor1 into v_currency_code, v_withdrawal_fee_type_enum, v_withdrawal_fee_amount_charge_def;
if no_more_rows then if no_more_rows then
close cursor1; close cursor1;
leave LOOP1; leave LOOP1;
end if; end if;
-- set withdrawal fee name -- set withdrawal fee name
if(v_withdrawal_fee_type_enum = 1) then if(v_withdrawal_fee_type_enum = 1) then
set t_withdrawal_fee_name = CONCAT('Withdrawal fee-Flat-',v_currency_code); set t_withdrawal_fee_name = CONCAT('Withdrawal fee-Flat-',v_currency_code);
else else
set t_withdrawal_fee_name = CONCAT('Withdrawal fee-Percentage-',v_currency_code); set t_withdrawal_fee_name = CONCAT('Withdrawal fee-Percentage-',v_currency_code);
end if; end if;
-- get charge id if already exists -- get charge id if already exists
set t_charge_id = (select id from m_charge where name = t_withdrawal_fee_name); set t_charge_id = (select id from m_charge where name = t_withdrawal_fee_name);
if t_charge_id is null then if t_charge_id is null then
-- add withdrawal fee to charges -- add withdrawal fee to charges
INSERT INTO `m_charge` (`name`, `currency_code`, `charge_applies_to_enum`, `charge_time_enum`, `charge_calculation_enum`, `charge_payment_mode_enum`, `amount`, `is_penalty`, `is_active`, `is_deleted`) VALUES (t_withdrawal_fee_name , v_currency_code, 2, 5, v_withdrawal_fee_type_enum, 0, v_withdrawal_fee_amount_charge_def, 0, 1, 0); INSERT INTO `m_charge` (`name`, `currency_code`, `charge_applies_to_enum`, `charge_time_enum`, `charge_calculation_enum`, `charge_payment_mode_enum`, `amount`, `is_penalty`, `is_active`, `is_deleted`) VALUES (t_withdrawal_fee_name , v_currency_code, 2, 5, v_withdrawal_fee_type_enum, 0, v_withdrawal_fee_amount_charge_def, 0, 1, 0);
-- get inserted charge id -- get inserted charge id
set t_charge_id = last_insert_id(); set t_charge_id = last_insert_id();
end if; end if;
open cursor2; open cursor2;
LOOP2: loop LOOP2: loop
fetch cursor2 into v_account_id, v_withdrawal_fee_amount; fetch cursor2 into v_account_id, v_withdrawal_fee_amount;
if no_more_rows then if no_more_rows then
set no_more_rows := false; set no_more_rows := false;
close cursor2; close cursor2;
leave LOOP2; leave LOOP2;
end if; end if;
if (v_withdrawal_fee_type_enum=1) then if (v_withdrawal_fee_type_enum=1) then
set t_calculation_percentage = NULL; set t_calculation_percentage = NULL;
set t_calculation_on_amount = NULL; set t_calculation_on_amount = NULL;
else else
set t_calculation_percentage = v_withdrawal_fee_amount; set t_calculation_percentage = v_withdrawal_fee_amount;
set v_withdrawal_fee_amount = 0; set v_withdrawal_fee_amount = 0;
@@ -86,7 +86,7 @@ begin
end if; end if;
if not exists (select id from m_savings_account_charge sac where if not exists (select id from m_savings_account_charge sac where
sac.savings_account_id=v_account_id and sac.charge_id=t_charge_id and sac.charge_time_enum=5) then sac.savings_account_id=v_account_id and sac.charge_id=t_charge_id and sac.charge_time_enum=5) then
-- attach withdrawal charge to savings -- attach withdrawal charge to savings
INSERT INTO `m_savings_account_charge` (`savings_account_id`, `charge_id`, `is_penalty`, `charge_time_enum`, `charge_due_date`, `fee_on_month`, `fee_on_day`, `charge_calculation_enum`, `calculation_percentage`, `calculation_on_amount`, `amount`, `amount_paid_derived`, `amount_waived_derived`, `amount_writtenoff_derived`, `amount_outstanding_derived`, `is_paid_derived`, `waived`, `is_active`) VALUES (v_account_id, t_charge_id, 0, 5, NULL, NULL, NULL, v_withdrawal_fee_type_enum, t_calculation_percentage, t_calculation_on_amount, v_withdrawal_fee_amount, NULL, NULL, NULL, 0.000000, 0, 0, 1); INSERT INTO `m_savings_account_charge` (`savings_account_id`, `charge_id`, `is_penalty`, `charge_time_enum`, `charge_due_date`, `fee_on_month`, `fee_on_day`, `charge_calculation_enum`, `calculation_percentage`, `calculation_on_amount`, `amount`, `amount_paid_derived`, `amount_waived_derived`, `amount_writtenoff_derived`, `amount_outstanding_derived`, `is_paid_derived`, `waived`, `is_active`) VALUES (v_account_id, t_charge_id, 0, 5, NULL, NULL, NULL, v_withdrawal_fee_type_enum, t_calculation_percentage, t_calculation_on_amount, v_withdrawal_fee_amount, NULL, NULL, NULL, 0.000000, 0, 0, 1);
@@ -96,23 +96,23 @@ begin
else else
set t_savings_charge_id = (select id from m_savings_account_charge sac where sac.savings_account_id=v_account_id and sac.charge_id=t_charge_id and sac.charge_time_enum=5); set t_savings_charge_id = (select id from m_savings_account_charge sac where sac.savings_account_id=v_account_id and sac.charge_id=t_charge_id and sac.charge_time_enum=5);
end if; end if;
open cursor3; open cursor3;
LOOP3: loop LOOP3: loop
fetch cursor3 into v_savings_transaction_id, v_transaction_amount; fetch cursor3 into v_savings_transaction_id, v_transaction_amount;
if no_more_rows then if no_more_rows then
set no_more_rows := false; set no_more_rows := false;
close cursor3; close cursor3;
leave LOOP3; leave LOOP3;
end if; end if;
if not exists (select id from m_savings_account_charge_paid_by sacp where if not exists (select id from m_savings_account_charge_paid_by sacp where
sacp.savings_account_transaction_id=v_savings_transaction_id and sacp.savings_account_charge_id=t_savings_charge_id) then sacp.savings_account_transaction_id=v_savings_transaction_id and sacp.savings_account_charge_id=t_savings_charge_id) then
-- insert a record into savings account charge paid by -- insert a record into savings account charge paid by
INSERT INTO `m_savings_account_charge_paid_by` (`savings_account_transaction_id`, `savings_account_charge_id`, `amount`) VALUES(v_savings_transaction_id, t_savings_charge_id, v_transaction_amount); INSERT INTO `m_savings_account_charge_paid_by` (`savings_account_transaction_id`, `savings_account_charge_id`, `amount`) VALUES(v_savings_transaction_id, t_savings_charge_id, v_transaction_amount);
@@ -159,41 +159,41 @@ begin
open cursor1; open cursor1;
LOOP1: loop LOOP1: loop
fetch cursor1 into v_currency_code, v_annual_fee_amount_charge_def; fetch cursor1 into v_currency_code, v_annual_fee_amount_charge_def;
if no_more_rows then if no_more_rows then
close cursor1; close cursor1;
leave LOOP1; leave LOOP1;
end if; end if;
-- set annual fee name -- set annual fee name
set t_annual_fee_name = CONCAT('Annual fee - ',v_currency_code); set t_annual_fee_name = CONCAT('Annual fee - ',v_currency_code);
-- get charge id if already exists -- get charge id if already exists
set t_charge_id = (select id from m_charge where name = t_annual_fee_name); set t_charge_id = (select id from m_charge where name = t_annual_fee_name);
if t_charge_id is null then if t_charge_id is null then
-- add annual fee to charges -- add annual fee to charges
INSERT INTO `m_charge` (`name`, `currency_code`, `charge_applies_to_enum`, `charge_time_enum`, `charge_calculation_enum`, `charge_payment_mode_enum`, `amount`, `is_penalty`, `is_active`, `is_deleted`) VALUES (t_annual_fee_name, v_currency_code, 2, 6, 1, 0, v_annual_fee_amount_charge_def, 0, 1, 0); INSERT INTO `m_charge` (`name`, `currency_code`, `charge_applies_to_enum`, `charge_time_enum`, `charge_calculation_enum`, `charge_payment_mode_enum`, `amount`, `is_penalty`, `is_active`, `is_deleted`) VALUES (t_annual_fee_name, v_currency_code, 2, 6, 1, 0, v_annual_fee_amount_charge_def, 0, 1, 0);
-- get inserted charge id -- get inserted charge id
set t_charge_id = last_insert_id(); set t_charge_id = last_insert_id();
end if; end if;
open cursor2; open cursor2;
LOOP2: loop LOOP2: loop
fetch cursor2 into v_account_id, v_annual_fee_amount, v_annual_fee_on_month, v_annual_fee_on_day, v_annual_fee_next_due_date; fetch cursor2 into v_account_id, v_annual_fee_amount, v_annual_fee_on_month, v_annual_fee_on_day, v_annual_fee_next_due_date;
if no_more_rows then if no_more_rows then
set no_more_rows := false; set no_more_rows := false;
close cursor2; close cursor2;
leave LOOP2; leave LOOP2;
end if; end if;
if not exists (select id from m_savings_account_charge sac where if not exists (select id from m_savings_account_charge sac where
sac.savings_account_id=v_account_id and sac.charge_id=t_charge_id and sac.charge_time_enum=6) then sac.savings_account_id=v_account_id and sac.charge_id=t_charge_id and sac.charge_time_enum=6) then
-- attach annual charge to savings -- attach annual charge to savings
INSERT INTO `m_savings_account_charge` (`savings_account_id`, `charge_id`, `is_penalty`, `charge_time_enum`, `charge_due_date`, `fee_on_month`, `fee_on_day`, `charge_calculation_enum`, `calculation_percentage`, `calculation_on_amount`, `amount`, `amount_paid_derived`, `amount_waived_derived`, `amount_writtenoff_derived`, `amount_outstanding_derived`, `is_paid_derived`, `waived`, `is_active`) VALUES (v_account_id, t_charge_id, 0, 6, v_annual_fee_next_due_date, v_annual_fee_on_month, v_annual_fee_on_day, 1, NULL, NULL, v_annual_fee_amount, NULL, NULL, NULL, v_annual_fee_amount, 0, 0, 1); INSERT INTO `m_savings_account_charge` (`savings_account_id`, `charge_id`, `is_penalty`, `charge_time_enum`, `charge_due_date`, `fee_on_month`, `fee_on_day`, `charge_calculation_enum`, `calculation_percentage`, `calculation_on_amount`, `amount`, `amount_paid_derived`, `amount_waived_derived`, `amount_writtenoff_derived`, `amount_outstanding_derived`, `is_paid_derived`, `waived`, `is_active`) VALUES (v_account_id, t_charge_id, 0, 6, v_annual_fee_next_due_date, v_annual_fee_on_month, v_annual_fee_on_day, 1, NULL, NULL, v_annual_fee_amount, NULL, NULL, NULL, v_annual_fee_amount, 0, 0, 1);
@@ -203,22 +203,22 @@ begin
else else
set t_savings_charge_id = (select id from m_savings_account_charge sac where sac.savings_account_id=v_account_id and sac.charge_id=t_charge_id and sac.charge_time_enum=6); set t_savings_charge_id = (select id from m_savings_account_charge sac where sac.savings_account_id=v_account_id and sac.charge_id=t_charge_id and sac.charge_time_enum=6);
end if; end if;
open cursor3; open cursor3;
LOOP3: loop LOOP3: loop
fetch cursor3 into v_savings_transaction_id, v_transaction_amount; fetch cursor3 into v_savings_transaction_id, v_transaction_amount;
if no_more_rows then if no_more_rows then
set no_more_rows := false; set no_more_rows := false;
close cursor3; close cursor3;
leave LOOP3; leave LOOP3;
end if; end if;
if not exists (select id from m_savings_account_charge_paid_by sacp where if not exists (select id from m_savings_account_charge_paid_by sacp where
sacp.savings_account_transaction_id=v_savings_transaction_id and sacp.savings_account_charge_id=t_savings_charge_id) then sacp.savings_account_transaction_id=v_savings_transaction_id and sacp.savings_account_charge_id=t_savings_charge_id) then
-- insert a record into savings account charge paid by -- insert a record into savings account charge paid by
INSERT INTO `m_savings_account_charge_paid_by` (`savings_account_transaction_id`, `savings_account_charge_id`, `amount`) VALUES(v_savings_transaction_id, t_savings_charge_id, v_transaction_amount); INSERT INTO `m_savings_account_charge_paid_by` (`savings_account_transaction_id`, `savings_account_charge_id`, `amount`) VALUES(v_savings_transaction_id, t_savings_charge_id, v_transaction_amount);
@@ -22,6 +22,3 @@ Update loan transaction to add a link to transaction detail table
ALTER TABLE `m_loan_transaction` ALTER TABLE `m_loan_transaction`
ADD COLUMN `payment_detail_id` BIGINT(20) NULL AFTER `loan_id`, ADD COLUMN `payment_detail_id` BIGINT(20) NULL AFTER `loan_id`,
ADD CONSTRAINT `FK_m_loan_transaction_m_payment_detail` FOREIGN KEY (`payment_detail_id`) REFERENCES `m_payment_detail` (`id`); ADD CONSTRAINT `FK_m_loan_transaction_m_payment_detail` FOREIGN KEY (`payment_detail_id`) REFERENCES `m_payment_detail` (`id`);
@@ -5,4 +5,3 @@ ADD COLUMN `max_cap` DECIMAL(19,6) NULL AFTER `min_cap`;
ALTER TABLE `m_loan_charge` ALTER TABLE `m_loan_charge`
ADD COLUMN `min_cap` DECIMAL(19,6) NULL AFTER `waived`, ADD COLUMN `min_cap` DECIMAL(19,6) NULL AFTER `waived`,
ADD COLUMN `max_cap` DECIMAL(19,6) NULL AFTER `min_cap`; ADD COLUMN `max_cap` DECIMAL(19,6) NULL AFTER `min_cap`;
@@ -24,5 +24,3 @@ ALTER TABLE m_client
ALTER TABLE m_group ALTER TABLE m_group
ADD COLUMN `status_id` INT(5) NOT NULL DEFAULT 300 AFTER `is_deleted` ; ADD COLUMN `status_id` INT(5) NOT NULL DEFAULT 300 AFTER `is_deleted` ;
@@ -7,4 +7,3 @@ ALTER TABLE `acc_gl_journal_entry`
UPDATE acc_gl_journal_entry je SET je.savings_transaction_id=je.transaction_id,je.transaction_id=Concat('S',je.transaction_id) WHERE je.entity_type_enum=2; UPDATE acc_gl_journal_entry je SET je.savings_transaction_id=je.transaction_id,je.transaction_id=Concat('S',je.transaction_id) WHERE je.entity_type_enum=2;
UPDATE acc_gl_journal_entry je SET je.loan_transaction_id=je.transaction_id,je.transaction_id=Concat('L',je.transaction_id) WHERE je.entity_type_enum=1; UPDATE acc_gl_journal_entry je SET je.loan_transaction_id=je.transaction_id,je.transaction_id=Concat('L',je.transaction_id) WHERE je.entity_type_enum=1;
@@ -3,4 +3,3 @@ UPDATE m_client mc SET mc.submittedon_date=now() where mc.submittedon_date is NU
UPDATE m_group mg SET mg.submittedon_date=mg.activation_date where mg.submittedon_date is NULL; UPDATE m_group mg SET mg.submittedon_date=mg.activation_date where mg.submittedon_date is NULL;
UPDATE m_group mg SET mg.submittedon_date=now() where mg.submittedon_date is NULL; UPDATE m_group mg SET mg.submittedon_date=now() where mg.submittedon_date is NULL;
@@ -10,5 +10,3 @@ INSERT INTO `c_configuration` (
VALUES ( VALUES (
NULL , 'penalty-wait-period', '2', '1' NULL , 'penalty-wait-period', '2', '1'
); );
@@ -28,5 +28,3 @@ INSERT INTO `r_enum_value` (`enum_name`, `enum_id`, `enum_message_property`, `en
INSERT INTO `r_enum_value` (`enum_name`, `enum_id`, `enum_message_property`, `enum_value`, `enum_type`) VALUES ('savings_transaction_type_enum', 15, 'Reject Transfer', 'Reject Transfer', 0); INSERT INTO `r_enum_value` (`enum_name`, `enum_id`, `enum_message_property`, `enum_value`, `enum_type`) VALUES ('savings_transaction_type_enum', 15, 'Reject Transfer', 'Reject Transfer', 0);
INSERT INTO `r_enum_value` (`enum_name`, `enum_id`, `enum_message_property`, `enum_value`, `enum_type`) VALUES ('savings_transaction_type_enum', 16, 'Written-Off', 'Written-Off', 0); INSERT INTO `r_enum_value` (`enum_name`, `enum_id`, `enum_message_property`, `enum_value`, `enum_type`) VALUES ('savings_transaction_type_enum', 16, 'Written-Off', 'Written-Off', 0);
INSERT INTO `r_enum_value` (`enum_name`, `enum_id`, `enum_message_property`, `enum_value`, `enum_type`) VALUES ('savings_transaction_type_enum', 17, 'Overdraft Interest', 'Overdraft Interest', 0); INSERT INTO `r_enum_value` (`enum_name`, `enum_id`, `enum_message_property`, `enum_value`, `enum_type`) VALUES ('savings_transaction_type_enum', 17, 'Overdraft Interest', 'Overdraft Interest', 0);
@@ -716,4 +716,3 @@ VALUES ((select sr.id From stretchy_report sr where sr.report_name='Written-Off
'endDate'); 'endDate');

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