Index: .gitignore
===================================================================
diff -u
--- .gitignore (revision 0)
+++ .gitignore (revision 75c1874d6dd0213527d1449d619d18ef22f15263)
@@ -0,0 +1,16 @@
+/build/
+/.gradle/
+
+### Eclipse files
+.classpath
+.project
+.settings/
+/bin
+
+### IDEA Files
+.idea
+*.iml
+*.ipr
+*.iws
+out
+
Index: build.gradle
===================================================================
diff -u
--- build.gradle (revision 0)
+++ build.gradle (revision 75c1874d6dd0213527d1449d619d18ef22f15263)
@@ -0,0 +1,142 @@
+buildscript {
+ repositories {
+ mavenLocal()
+ maven { url 'http://workhorse.lemanscorp.com/nexus/content/groups/public/' }
+ }
+ dependencies {
+ classpath "org.grails:grails-gradle-plugin:$grailsVersion"
+ classpath "org.grails.plugins:hibernate5:6.0.5"
+ classpath "org.grails.plugins:views-gradle:1.1.2"
+
+ // Lemans
+ classpath 'net.saliman:gradle-cobertura-plugin:2.3.2'
+ }
+}
+
+//version "0.1" - leave this out to get no version no. in the war filename - kkrebs
+group 'com.lemans.services'
+
+defaultTasks 'clean', 'check', 'cobertura'
+
+apply plugin:"eclipse"
+apply plugin:"idea"
+apply plugin:"war"
+apply plugin: "org.grails.grails-web"
+apply plugin: "org.grails.plugins.views-json"
+
+// Lemans
+apply plugin: 'net.saliman.cobertura'
+apply plugin: 'codenarc'
+
+repositories {
+ mavenLocal()
+ maven { url 'http://workhorse.lemanscorp.com/nexus/content/groups/public/' }
+}
+
+dependencyManagement {
+ imports {
+ mavenBom "org.grails:grails-bom:$grailsVersion"
+ }
+ applyMavenExclusions false
+}
+
+configurations {
+ compile.exclude module: 'commons-logging'
+}
+
+
+dependencies {
+ testCompile "org.springframework.boot:spring-boot-starter-logging"
+ compile "org.springframework.boot:spring-boot-autoconfigure"
+ compile "org.grails:grails-core"
+
+ compile "org.springframework.boot:spring-boot-starter-actuator"
+ compile "org.springframework.boot:spring-boot-actuator-docs"
+
+ provided "org.springframework.boot:spring-boot-starter-tomcat"
+
+ compile "org.grails:grails-plugin-url-mappings"
+ compile "org.grails:grails-plugin-rest"
+ compile "org.grails:grails-plugin-codecs"
+ compile "org.grails:grails-plugin-interceptors"
+ compile "org.grails:grails-plugin-services"
+ compile "org.grails:grails-plugin-datasource"
+ compile "org.grails:grails-plugin-databinding"
+ compile "org.grails:grails-plugin-async"
+ compile "org.grails:grails-web-boot"
+ compile "org.grails:grails-logging"
+ compile "org.grails.plugins:cache"
+ compile "org.grails.plugins:hibernate5"
+ compile "org.hibernate:hibernate-core:5.1.2.Final"
+ compile "org.hibernate:hibernate-ehcache:5.1.2.Final"
+ compile "org.grails.plugins:views-json"
+ compile "org.grails.plugins:views-json-templates"
+ console "org.grails:grails-console"
+ profile "org.grails.profiles:rest-api"
+ compile "org.codehaus.groovy:groovy-ant"
+
+ provided "org.slf4j:slf4j-api:1.7.20"
+ provided "org.slf4j:jcl-over-slf4j:1.7.21"
+ provided "org.slf4j:jul-to-slf4j:1.7.21"
+ provided "org.slf4j:log4j-over-slf4j:1.7.21"
+ provided "ch.qos.logback:logback-core"
+ provided "ch.qos.logback:logback-classic"
+
+ testCompile "org.grails:grails-plugin-testing"
+// testCompile "org.grails:grails-datastore-rest-client"
+
+ // Lemans
+
+ compile 'org.apache.httpcomponents:httpclient:4.2.1'
+ compile 'org.codehaus.groovy.modules.http-builder:http-builder:0.7.2'
+
+ compile 'javax.mail:mail:1.4.5'
+
+ provided 'net.sourceforge.jtds:jtds:1.3.1'
+
+
+ compile 'com.lemans.grails.plugins:lemans-core:0.1.0'
+ compile 'com.lemans.grails.plugins:lemans-security:0.1.2c'
+ compile 'com.lemans.grails.plugins:lemans-rest:0.1.0c'
+
+ testCompile 'com.lemans.grails.plugins:lemans-testing:0.1.0'
+
+ compile 'dumbster:dumbster:1.6'
+}
+
+task wrapper(type: Wrapper) {
+ gradleVersion = gradleWrapperVersion
+}
+
+grails {
+ // deals with 'command line too long' issue when running Grails commands in Idea
+ pathingJar = true
+}
+
+// Lemans
+
+tasks.withType(Test) {
+ testLogging {
+ showStandardStreams = true
+ }
+ systemProperties System.properties
+}
+
+tasks.withType(JavaExec) {
+ systemProperties System.properties
+}
+
+codenarc {
+ toolVersion = '0.26.0'
+ configFile = file("${project.projectDir}/codenarc/rules.groovy")
+}
+
+cobertura {
+ coverageSourceDirs = [project.sourceSets.main.groovy.srcDirs]
+ coverageExcludes = ['.*Application.*']
+
+// NOTE: It looks like coverage has false negatives. This may fix it - kkrebs
+// coverageMergeDatafiles = [file("${project.buildDir.path}/cobertura/cobertura.ser"),
+// file("${project.buildDir.path}/cobertura/coberturaInput.ser")]
+}
+
Index: codenarc/rules.groovy
===================================================================
diff -u
--- codenarc/rules.groovy (revision 0)
+++ codenarc/rules.groovy (revision 75c1874d6dd0213527d1449d619d18ef22f15263)
@@ -0,0 +1,75 @@
+ruleset {
+ ruleset('rulesets/basic.xml')
+ ruleset('rulesets/braces.xml')
+ ruleset('rulesets/concurrency.xml')
+ ruleset('rulesets/convention.xml') {
+ exclude 'NoDef'
+ exclude 'NoTabCharacter'
+ exclude 'TrailingComma'
+ }
+ ruleset('rulesets/design.xml') {
+ exclude 'AbstractClassWithoutAbstractMethod'
+ exclude 'EmptyMethodInAbstractClass'
+ }
+ ruleset('rulesets/dry.xml') {
+ exclude 'DuplicateListLiteral'
+ exclude 'DuplicateNumberLiteral'
+ exclude 'DuplicateMapLiteral'
+ exclude 'DuplicateStringLiteral'
+ }
+ ruleset('rulesets/enhanced.xml')
+ ruleset('rulesets/exceptions.xml')
+ ruleset('rulesets/formatting.xml') {
+ exclude 'ClassJavadoc'
+ exclude 'ConsecutiveBlankLines'
+ LineLength {
+ length = 140 // should fit in a tweet - kkrebs ;=}
+ }
+ SpaceAroundMapEntryColon {
+ characterAfterColonRegex = /\s/
+ }
+ exclude 'TrailingWhitespace'
+ }
+ ruleset('rulesets/generic.xml')
+ ruleset('rulesets/grails.xml') {
+ exclude 'GrailsDomainHasEquals'
+ exclude 'GrailsDomainHasToString'
+ }
+ ruleset('rulesets/groovyism.xml')
+ ruleset('rulesets/imports.xml')
+ ruleset('rulesets/jdbc.xml')
+ ruleset('rulesets/junit.xml')
+ ruleset('rulesets/logging.xml')
+ ruleset('rulesets/naming.xml') {
+ exclude 'FactoryMethodName'
+ MethodName { // helps Spock
+ regex = /[a-zA-Z#][_#\w\s'"\(\)]*/
+ }
+ PropertyName {
+ regex = /[a-z][_a-zA-Z0-9]*/
+ }
+ }
+ ruleset('rulesets/security.xml') {
+ exclude 'JavaIoPackageAccess'
+ }
+ ruleset('rulesets/serialization.xml')
+ ruleset('rulesets/size.xml') {
+ CyclomaticComplexity { // Requires the GMetrics jar
+ maxMethodComplexity = 10
+ }
+ MethodSize {
+ maxLines = 26
+ doNotApplyToClassNames = '*Spec'
+ }
+ MethodSize {
+ maxLines = 41
+ applyToClassNames = '*Spec'
+ }
+ }
+ ruleset('rulesets/unnecessary.xml') {
+ UnnecessaryBooleanExpression {
+ doNotApplyToClassNames = '*Spec*'
+ }
+ }
+ ruleset('rulesets/unused.xml')
+}
\ No newline at end of file
Index: gradle.properties
===================================================================
diff -u
--- gradle.properties (revision 0)
+++ gradle.properties (revision 75c1874d6dd0213527d1449d619d18ef22f15263)
@@ -0,0 +1,2 @@
+grailsVersion=3.2.4
+gradleWrapperVersion=3.1
Index: gradle/wrapper/gradle-wrapper.jar
===================================================================
diff -u
Binary files differ
Index: gradle/wrapper/gradle-wrapper.properties
===================================================================
diff -u
--- gradle/wrapper/gradle-wrapper.properties (revision 0)
+++ gradle/wrapper/gradle-wrapper.properties (revision 75c1874d6dd0213527d1449d619d18ef22f15263)
@@ -0,0 +1,6 @@
+#Tue Jan 10 09:43:32 CST 2017
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-3.1-all.zip
Index: gradlew
===================================================================
diff -u
--- gradlew (revision 0)
+++ gradlew (revision 75c1874d6dd0213527d1449d619d18ef22f15263)
@@ -0,0 +1,169 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## 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=""
+
+# 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
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+# 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" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
Index: gradlew.bat
===================================================================
diff -u
--- gradlew.bat (revision 0)
+++ gradlew.bat (revision 75c1874d6dd0213527d1449d619d18ef22f15263)
@@ -0,0 +1,84 @@
+@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=
+
+@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
Index: grails-app/conf/application.groovy
===================================================================
diff -u
--- grails-app/conf/application.groovy (revision 0)
+++ grails-app/conf/application.groovy (revision 75c1874d6dd0213527d1449d619d18ef22f15263)
@@ -0,0 +1 @@
+grails.databinding.dateFormats = ["yyyy-MM-dd'T'hh:mm:ss", 'yyyy-MM-dd']
\ No newline at end of file
Index: grails-app/conf/application.properties
===================================================================
diff -u
--- grails-app/conf/application.properties (revision 0)
+++ grails-app/conf/application.properties (revision 75c1874d6dd0213527d1449d619d18ef22f15263)
@@ -0,0 +1,2 @@
+# Spring Boot config for jmx to avoid naming conflicts when multiple apps in same Tomcat
+spring.jmx.default-domain=correspondence-service
Index: grails-app/conf/application.yml
===================================================================
diff -u
--- grails-app/conf/application.yml (revision 0)
+++ grails-app/conf/application.yml (revision 75c1874d6dd0213527d1449d619d18ef22f15263)
@@ -0,0 +1,100 @@
+---
+grails:
+ profile: rest-api
+ codegen:
+ defaultPackage: com.lemans.correspondence
+ spring:
+ transactionManagement:
+ proxies: false
+info:
+ app:
+ name: '@info.app.name@'
+ version: '@info.app.version@'
+ grailsVersion: '@info.app.grailsVersion@'
+spring:
+ groovy:
+ template:
+ check-template-location: false
+# Spring Actuator Endpoints are Disabled by Default
+endpoints:
+ enabled: false
+ jmx:
+ enabled: true
+
+---
+grails:
+ mime:
+ disable:
+ accept:
+ header:
+ userAgents:
+ - Gecko
+ - WebKit
+ - Presto
+ - Trident
+ types:
+ json:
+ - application/json
+ - text/json
+ hal:
+ - application/hal+json
+ - application/hal+xml
+ xml:
+ - text/xml
+ - application/xml
+ atom: application/atom+xml
+ css: text/css
+ csv: text/csv
+ js: text/javascript
+ rss: application/rss+xml
+ text: text/plain
+ all: '*/*'
+ urlmapping:
+ cache:
+ maxsize: 1000
+ controllers:
+ defaultScope: singleton
+ converters:
+ encoding: UTF-8
+
+---
+hibernate:
+ naming_strategy: org.hibernate.cfg.DefaultNamingStrategy
+ cache:
+ queries: false
+ use_second_level_cache: false
+ use_query_cache: false
+ region.factory_class: org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory
+ format_sql: true
+ dialect: org.hibernate.dialect.SQLServer2008Dialect
+dataSource:
+ dbCreate: none
+ pooled: true
+ jmxExport: true
+ driverClassName: net.sourceforge.jtds.jdbc.Driver
+
+environments:
+ development:
+ dataSource:
+ url: jdbc:jtds:sqlserver://dev-dbprod02vm/AppSecurity
+ username: appsecurity_user
+ password: DevPassword1
+ logSql: true
+ test:
+ dataSource:
+ url: jdbc:jtds:sqlserver://dev-dbprod02vm/AppSecurity
+ username: appsecurity_user
+ password: DevPassword1
+ logSql: true
+ production:
+ dataSource:
+ jndiName: java:comp/env/jdbc/appSecurity
+ logSql: false
+
+---
+---
+server:
+ contextPath: /correspondence-service
+
+management:
+ context_path: /admin
Index: grails-app/conf/logback.groovy
===================================================================
diff -u
--- grails-app/conf/logback.groovy (revision 0)
+++ grails-app/conf/logback.groovy (revision 75c1874d6dd0213527d1449d619d18ef22f15263)
@@ -0,0 +1,27 @@
+import grails.util.BuildSettings
+import grails.util.Environment
+
+// See http://logback.qos.ch/manual/groovy.html for details on configuration
+appender('STDOUT', ConsoleAppender) {
+ encoder(PatternLayoutEncoder) {
+ pattern = "%level %logger - %msg%n"
+ }
+}
+
+root(ERROR, ['STDOUT'])
+
+logger 'grails.app', INFO, ['STDOUT'], false
+logger 'com.lemans', INFO, ['STDOUT'], false
+
+def targetDir = BuildSettings.TARGET_DIR
+if (Environment.isDevelopmentMode() && targetDir) {
+ appender("FULL_STACKTRACE", FileAppender) {
+ file = "${targetDir}/stacktrace.log"
+ append = true
+ encoder(PatternLayoutEncoder) {
+ pattern = "%level %logger - %msg%n"
+ }
+ }
+ logger("StackTrace", ERROR, ['FULL_STACKTRACE'], true)
+}
+
Index: grails-app/conf/spring/resources.groovy
===================================================================
diff -u
--- grails-app/conf/spring/resources.groovy (revision 0)
+++ grails-app/conf/spring/resources.groovy (revision 75c1874d6dd0213527d1449d619d18ef22f15263)
@@ -0,0 +1,40 @@
+import grails.plugin.dumbster.Dumbster
+import grails.util.Environment
+import org.apache.http.impl.client.DefaultHttpClient
+import org.apache.http.impl.conn.PoolingClientConnectionManager
+import org.springframework.mail.javamail.JavaMailSenderImpl
+import com.lemans.security.TokenVerifier
+
+// Place your Spring DSL code here
+beans = {
+
+ Environment current = Environment.current
+ if (current != Environment.PRODUCTION) {
+ authServiceContext(String, 'http://services1.dev.lemanscorp.com/auth-service/verifyRequest')
+ correspondenceEmailDomain(String, 'test.com')
+ emailHost(String, 'localhost')
+ }
+ else {
+ authServiceContext(org.springframework.jndi.JndiObjectFactoryBean) {
+ jndiName = 'java:comp/env/authServiceContext'
+ }
+ correspondenceEmailDomain(org.springframework.jndi.JndiObjectFactoryBean) {
+ jndiName = 'java:comp/env/correspondenceEmailDomain'
+ }
+ emailHost(org.springframework.jndi.JndiObjectFactoryBean) {
+ jndiName = 'java:comp/env/emailHost'
+ }
+ }
+
+ mailSender(JavaMailSenderImpl) {
+ host = ref('emailHost')
+ }
+
+ if (current != Environment.PRODUCTION) {
+ dumbster(Dumbster) { bean ->
+ grailsApplication = application
+ bean.initMethod = 'start'
+ bean.destroyMethod = 'stop'
+ }
+ }
+}
Index: grails-app/controllers/com/lemans/correspondence/ApplicationController.groovy
===================================================================
diff -u
--- grails-app/controllers/com/lemans/correspondence/ApplicationController.groovy (revision 0)
+++ grails-app/controllers/com/lemans/correspondence/ApplicationController.groovy (revision 75c1874d6dd0213527d1449d619d18ef22f15263)
@@ -0,0 +1,15 @@
+package com.lemans.correspondence
+
+import grails.core.GrailsApplication
+import grails.plugins.GrailsPluginManager
+import grails.plugins.PluginManagerAware
+
+class ApplicationController implements PluginManagerAware {
+
+ GrailsApplication grailsApplication
+ GrailsPluginManager pluginManager
+
+ def grails() {
+ [grailsApplication: grailsApplication, pluginManager: pluginManager]
+ }
+}
Index: grails-app/controllers/com/lemans/correspondence/DumbsterController.groovy
===================================================================
diff -u
--- grails-app/controllers/com/lemans/correspondence/DumbsterController.groovy (revision 0)
+++ grails-app/controllers/com/lemans/correspondence/DumbsterController.groovy (revision 75c1874d6dd0213527d1449d619d18ef22f15263)
@@ -0,0 +1,23 @@
+package com.lemans.correspondence
+
+import com.lemans.LemansApiController
+
+
+class DumbsterController extends LemansApiController {
+
+ def dumbster
+
+ def index() {
+ if (dumbster) {
+ render toJson(dumbster.messages)
+ }
+ else { notFound() }
+ }
+
+ def reset() {
+ if (dumbster) {
+ dumbster.reset()
+ }
+ else { notFound() }
+ }
+}
Index: grails-app/controllers/com/lemans/correspondence/UrlMappings.groovy
===================================================================
diff -u
--- grails-app/controllers/com/lemans/correspondence/UrlMappings.groovy (revision 0)
+++ grails-app/controllers/com/lemans/correspondence/UrlMappings.groovy (revision 75c1874d6dd0213527d1449d619d18ef22f15263)
@@ -0,0 +1,16 @@
+package com.lemans.correspondence
+
+class UrlMappings {
+
+ static mappings = {
+ "/dm/$dm/form/type"(controller: 'domainForm') { action = [GET: 'types'] }
+ "/dm/$dm/form/type/$id"(controller: 'domainForm') { action = [GET: 'typeById'] }
+
+ "/dm/$dm/domain/$domainId/form"(controller: 'domainForm') { action = [GET: 'index', POST: 'add'] }
+ "/dm/$dm/domain/$domainId/form/$formKey"(controller: 'domainForm') { action = [GET: 'show', PUT: 'update', DELETE: 'remove'] }
+ "/dm/$dm/domain/$domainId/form/$formKey/submit"(controller: 'formSubmit') { action = [POST: 'add'] }
+
+ '/admin/dumbster/message'(controller: 'dumbster', GET: 'index')
+ '/admin/dumbster/message/reset'(controller: 'dumbster', PUT: 'reset')
+ }
+}
Index: grails-app/controllers/com/lemans/correspondence/forms/DomainFormController.groovy
===================================================================
diff -u
--- grails-app/controllers/com/lemans/correspondence/forms/DomainFormController.groovy (revision 0)
+++ grails-app/controllers/com/lemans/correspondence/forms/DomainFormController.groovy (revision 75c1874d6dd0213527d1449d619d18ef22f15263)
@@ -0,0 +1,49 @@
+package com.lemans.correspondence.forms
+
+import com.lemans.LemansApiController
+
+class DomainFormController extends LemansApiController {
+
+ def domainFormService
+
+ def domainFormManagerService
+
+ def index(Integer domainId) {
+ Map criteria = common() + pagination() + [domainId: domainId]
+ Map data = domainFormService.findForms(criteria)
+ renderMany data.results, data.totalRecords
+ }
+
+ def add(Integer domainId) {
+ Map criteria = request.JSON
+ DomainForm form = domainFormManagerService.createDomainForm(criteria, auditUserName, domainId)
+ renderObject form
+ }
+
+ def show(Integer id) {
+ Map criteria = common() + [domainId: params.domainId, id: id, formKey: params.formKey]
+ renderOne domainFormService.findForm(criteria)
+ }
+
+ def update(Integer domainId, String formKey) {
+ Map criteria = request.JSON
+ DomainForm form = domainFormManagerService.updateDomainForm(criteria, formKey, auditUserName, domainId)
+ form != null ? renderObject(form) : notFound(unknownForm())
+ }
+
+ def remove(Integer domainId, String formKey) {
+ DomainForm form
+ if (formKey != null) { form = domainFormManagerService.deleteDomainForm(formKey, auditUserName, domainId) }
+ form != null ? renderDelete(form) : renderDelete(form, unknownForm())
+ }
+
+ def types() {
+ renderMany domainFormService.allFormTypes()
+ }
+
+ def typeById(Integer id) {
+ renderOne domainFormService.formTypeById(id)
+ }
+
+ private List unknownForm() { [errorMessage(unknownEntity('Domain Form'))] }
+}
Index: grails-app/controllers/com/lemans/correspondence/forms/FormSubmitController.groovy
===================================================================
diff -u
--- grails-app/controllers/com/lemans/correspondence/forms/FormSubmitController.groovy (revision 0)
+++ grails-app/controllers/com/lemans/correspondence/forms/FormSubmitController.groovy (revision 75c1874d6dd0213527d1449d619d18ef22f15263)
@@ -0,0 +1,61 @@
+package com.lemans.correspondence.forms
+
+import com.lemans.LemansApiController
+
+
+class FormSubmitController extends LemansApiController {
+
+ def formSubmitService
+
+ def domainFormService
+
+ def add(Integer domainId, String formKey) {
+ String contentType = request.contentType
+ Map formData = [:]
+ if (contentType.contains('multipart/form-data') || contentType.contains('application/x-www-form-urlencoded')) {
+ formData = extractFormData(params)
+ } else {
+ formData = extractFormData(request.JSON)
+ }
+ Map domainForm = domainFormService.findForm([domainId: domainId, formKey: formKey])
+ if (domainForm) {
+ List errors = formSubmitService.saveSubmittedForm(domainForm, formData)
+ if (errors) {
+ response.status = 400
+ renderMessages errors
+ }
+ else { renderEmpty() }
+ } else {
+ notFound(unknownDomainForm())
+ }
+ }
+
+ private Map extractFormData(Map inputData) {
+ Map sortedData = sortFormData(inputData)
+ sortedData?.collectEntries {
+ if (it.key?.startsWith('form_')) {
+ String key = "${it.key?.split('_').last()}"
+ [(key): URLDecoder.decode(it.value, 'UTF-8')]
+ } else {
+ [:]
+ }
+ }
+ }
+
+ private sortFormData(Map inputData) {
+ inputData.sort {
+ List data = it.key.split('_')
+ if (data.size() > 2) {
+ if (data[1].isNumber()) {
+ return data[1].toInteger()
+ }
+ }
+ }
+ }
+
+ private List unknownDomainForm() { [errorMessage(unknownEntity('domainForm'))] }
+}
+
+
+
+
Index: grails-app/domain/com/lemans/correspondence/Domain.groovy
===================================================================
diff -u
--- grails-app/domain/com/lemans/correspondence/Domain.groovy (revision 0)
+++ grails-app/domain/com/lemans/correspondence/Domain.groovy (revision 75c1874d6dd0213527d1449d619d18ef22f15263)
@@ -0,0 +1,17 @@
+package com.lemans.correspondence
+
+class Domain {
+
+ Integer id
+
+ String domainName
+
+ String displayName
+
+ static mapping = {
+ table 'Domain'
+ id column: 'domainId'
+ domainName updateable: false
+ displayName updateable: false
+ }
+}
Index: grails-app/domain/com/lemans/correspondence/forms/DomainForm.groovy
===================================================================
diff -u
--- grails-app/domain/com/lemans/correspondence/forms/DomainForm.groovy (revision 0)
+++ grails-app/domain/com/lemans/correspondence/forms/DomainForm.groovy (revision 75c1874d6dd0213527d1449d619d18ef22f15263)
@@ -0,0 +1,39 @@
+package com.lemans.correspondence.forms
+
+import com.lemans.correspondence.Domain
+import com.lemans.services.Auditable
+
+
+class DomainForm extends Auditable {
+
+ Integer id
+
+ Domain domain
+
+ Integer domainFormTypeId
+
+ String formKey
+
+ String formName
+
+ Date startDate
+
+ Date endDate
+
+ String formDetailXml
+
+ static constraints = {
+ formName nullable: true, maxSize: 50
+ domainFormTypeId nullable: true
+ startDate nullable: true
+ endDate nullable: true
+ }
+
+ static mapping = {
+ table 'DomainForm'
+ id column: 'domainFormId'
+ domain column: 'domainId', updateable: false
+ formKey updateable: false
+ formDetailXml sqlType: 'clob'
+ }
+}
Index: grails-app/domain/com/lemans/correspondence/forms/DomainFormType.groovy
===================================================================
diff -u
--- grails-app/domain/com/lemans/correspondence/forms/DomainFormType.groovy (revision 0)
+++ grails-app/domain/com/lemans/correspondence/forms/DomainFormType.groovy (revision 75c1874d6dd0213527d1449d619d18ef22f15263)
@@ -0,0 +1,15 @@
+package com.lemans.correspondence.forms
+
+
+class DomainFormType {
+
+ Integer id
+
+ String typeName
+
+ static mapping = {
+ table 'DomainFormType'
+ id column: 'domainFormTypeId'
+ typeName updateable: false
+ }
+}
Index: grails-app/i18n/messages.properties
===================================================================
diff -u
--- grails-app/i18n/messages.properties (revision 0)
+++ grails-app/i18n/messages.properties (revision 75c1874d6dd0213527d1449d619d18ef22f15263)
@@ -0,0 +1,61 @@
+default.doesnt.match.message={0} with value [{2}] does not match the required pattern [{3}]
+default.invalid.url.message={0} with value [{2}] is not a valid URL
+default.invalid.creditCard.message={0} with value [{2}] is not a valid credit card number
+default.invalid.email.message={0} with value [{2}] is not a valid e-mail address
+default.invalid.range.message={0} with value [{2}] does not fall within the valid range from [{3}] to [{4}]
+default.invalid.size.message={0} with value [{2}] does not fall within the valid size range from [{3}] to [{4}]
+default.invalid.max.message={0} with value [{2}] exceeds maximum value [{3}]
+default.invalid.min.message={0} with value [{2}] is less than minimum value [{3}]
+default.invalid.max.size.message={0} with value [{2}] exceeds the maximum size of [{3}]
+default.invalid.min.size.message={0} with value [{2}] is less than the minimum size of [{3}]
+default.invalid.validator.message={0} with value [{2}] does not pass custom validation
+default.not.inlist.message={0} with value [{2}] is not contained within the list [{3}]
+default.blank.message={0} is required
+default.not.equal.message={0} with value [{2}] cannot equal [{3}]
+default.null.message={0} is required
+default.not.unique.message={0} with value [{2}] must be unique
+
+default.paginate.prev=Previous
+default.paginate.next=Next
+default.boolean.true=True
+default.boolean.false=False
+default.date.format=yyyy-MM-dd HH:mm:ss z
+default.number.format=0
+
+default.created.message={0} {1} created
+default.updated.message={0} {1} updated
+default.deleted.message={0} {1} deleted
+default.not.deleted.message={0} {1} could not be deleted
+default.not.found.message={0} not found with id {1}
+default.optimistic.locking.failure=Another user has updated this {0} while you were editing
+
+default.home.label=Home
+default.list.label={0} List
+default.add.label=Add {0}
+default.new.label=New {0}
+default.create.label=Create {0}
+default.show.label=Show {0}
+default.edit.label=Edit {0}
+
+default.button.create.label=Create
+default.button.edit.label=Edit
+default.button.update.label=Update
+default.button.delete.label=Delete
+default.button.delete.confirm.message=Are you sure?
+
+# Data binding errors. Use "typeMismatch.$className.$propertyName to customize (eg typeMismatch.Book.author)
+typeMismatch.java.net.URL={0} must be a valid URL
+typeMismatch.java.net.URI={0} must be a valid URI
+typeMismatch.java.util.Date={0} must be a valid Date
+typeMismatch.java.lang.Double={0} must be a valid number
+typeMismatch.java.lang.Integer={0} must be a valid number
+typeMismatch.java.lang.Long={0} must be a valid number
+typeMismatch.java.lang.Short={0} must be a valid number
+typeMismatch.java.math.BigDecimal={0} must be a valid number
+typeMismatch.java.math.BigInteger={0} must be a valid number
+typeMismatch={0} is type-mismatched
+
+#DomainForm labels
+domainForm.userName=Form Name
+
+
Index: grails-app/i18n/messages_cs_CZ.properties
===================================================================
diff -u
--- grails-app/i18n/messages_cs_CZ.properties (revision 0)
+++ grails-app/i18n/messages_cs_CZ.properties (revision 75c1874d6dd0213527d1449d619d18ef22f15263)
@@ -0,0 +1,55 @@
+default.doesnt.match.message=Položka [{0}] třídy [{1}] o hodnotě [{2}] neodpovídá požadovanému vzoru [{3}]
+default.invalid.url.message=Položka [{0}] třídy [{1}] o hodnotě [{2}] není validní URL
+default.invalid.creditCard.message=Položka [{0}] třídy [{1}] o hodnotě [{2}] není validní číslo kreditní karty
+default.invalid.email.message=Položka [{0}] třídy [{1}] o hodnotě [{2}] není validní emailová adresa
+default.invalid.range.message=Položka [{0}] třídy [{1}] o hodnotě [{2}] není v povoleném rozmezí od [{3}] do [{4}]
+default.invalid.size.message=Položka [{0}] třídy [{1}] o hodnotě [{2}] není v povoleném rozmezí od [{3}] do [{4}]
+default.invalid.max.message=Položka [{0}] třídy [{1}] o hodnotě [{2}] překračuje maximální povolenou hodnotu [{3}]
+default.invalid.min.message=Položka [{0}] třídy [{1}] o hodnotě [{2}] je menší než minimální povolená hodnota [{3}]
+default.invalid.max.size.message=Položka [{0}] třídy [{1}] o hodnotě [{2}] překračuje maximální velikost [{3}]
+default.invalid.min.size.message=Položka [{0}] třídy [{1}] o hodnotě [{2}] je menší než minimální velikost [{3}]
+default.invalid.validator.message=Položka [{0}] třídy [{1}] o hodnotě [{2}] neprošla validací
+default.not.inlist.message=Položka [{0}] třídy [{1}] o hodnotě [{2}] není obsažena v seznamu [{3}]
+default.blank.message=Položka [{0}] třídy [{1}] nemůže být prázdná
+default.not.equal.message=Položka [{0}] třídy [{1}] o hodnotě [{2}] nemůže být stejná jako [{3}]
+default.null.message=Položka [{0}] třídy [{1}] nemůže být prázdná
+default.not.unique.message=Položka [{0}] třídy [{1}] o hodnotě [{2}] musí být unikátní
+
+default.paginate.prev=Předcházející
+default.paginate.next=Následující
+default.boolean.true=Pravda
+default.boolean.false=Nepravda
+default.date.format=dd. MM. yyyy HH:mm:ss z
+default.number.format=0
+
+default.created.message={0} {1} vytvořeno
+default.updated.message={0} {1} aktualizováno
+default.deleted.message={0} {1} smazáno
+default.not.deleted.message={0} {1} nelze smazat
+default.not.found.message={0} nenalezen s id {1}
+default.optimistic.locking.failure=Jiný uživatel aktualizoval záznam {0}, právě když byl vámi editován
+
+default.home.label=Domů
+default.list.label={0} Seznam
+default.add.label=Přidat {0}
+default.new.label=Nový {0}
+default.create.label=Vytvořit {0}
+default.show.label=Ukázat {0}
+default.edit.label=Editovat {0}
+
+default.button.create.label=Vytvoř
+default.button.edit.label=Edituj
+default.button.update.label=Aktualizuj
+default.button.delete.label=Smaž
+default.button.delete.confirm.message=Jste si jistý?
+
+# Data binding errors. Use "typeMismatch.$className.$propertyName to customize (eg typeMismatch.Book.author)
+typeMismatch.java.net.URL=Položka {0} musí být validní URL
+typeMismatch.java.net.URI=Položka {0} musí být validní URI
+typeMismatch.java.util.Date=Položka {0} musí být validní datum
+typeMismatch.java.lang.Double=Položka {0} musí být validní desetinné číslo
+typeMismatch.java.lang.Integer=Položka {0} musí být validní číslo
+typeMismatch.java.lang.Long=Položka {0} musí být validní číslo
+typeMismatch.java.lang.Short=Položka {0} musí být validní číslo
+typeMismatch.java.math.BigDecimal=Položka {0} musí být validní číslo
+typeMismatch.java.math.BigInteger=Položka {0} musí být validní číslo
Index: grails-app/i18n/messages_da.properties
===================================================================
diff -u
--- grails-app/i18n/messages_da.properties (revision 0)
+++ grails-app/i18n/messages_da.properties (revision 75c1874d6dd0213527d1449d619d18ef22f15263)
@@ -0,0 +1,56 @@
+default.doesnt.match.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] overholder ikke mønsteret [{3}]
+default.invalid.url.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] er ikke en gyldig URL
+default.invalid.creditCard.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] er ikke et gyldigt kreditkortnummer
+default.invalid.email.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] er ikke en gyldig e-mail adresse
+default.invalid.range.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] ligger ikke inden for intervallet fra [{3}] til [{4}]
+default.invalid.size.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] ligger ikke inden for størrelsen fra [{3}] til [{4}]
+default.invalid.max.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] overstiger den maksimale værdi [{3}]
+default.invalid.min.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] er under den minimale værdi [{3}]
+default.invalid.max.size.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] overstiger den maksimale størrelse på [{3}]
+default.invalid.min.size.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] er under den minimale størrelse på [{3}]
+default.invalid.validator.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] overholder ikke den brugerdefinerede validering
+default.not.inlist.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] findes ikke i listen [{3}]
+default.blank.message=Feltet [{0}] i klassen [{1}] kan ikke være tom
+default.not.equal.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] må ikke være [{3}]
+default.null.message=Feltet [{0}] i klassen [{1}] kan ikke være null
+default.not.unique.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] skal være unik
+
+default.paginate.prev=Forrige
+default.paginate.next=Næste
+default.boolean.true=Sand
+default.boolean.false=Falsk
+default.date.format=yyyy-MM-dd HH:mm:ss z
+default.number.format=0
+
+default.created.message={0} {1} oprettet
+default.updated.message={0} {1} opdateret
+default.deleted.message={0} {1} slettet
+default.not.deleted.message={0} {1} kunne ikke slettes
+default.not.found.message={0} med id {1} er ikke fundet
+default.optimistic.locking.failure=En anden bruger har opdateret denne {0} imens du har lavet rettelser
+
+default.home.label=Hjem
+default.list.label={0} Liste
+default.add.label=Tilføj {0}
+default.new.label=Ny {0}
+default.create.label=Opret {0}
+default.show.label=Vis {0}
+default.edit.label=Ret {0}
+
+default.button.create.label=Opret
+default.button.edit.label=Ret
+default.button.update.label=Opdater
+default.button.delete.label=Slet
+default.button.delete.confirm.message=Er du sikker?
+
+# Databindingsfejl. Brug "typeMismatch.$className.$propertyName for at passe til en given klasse (f.eks typeMismatch.Book.author)
+typeMismatch.java.net.URL=Feltet {0} skal være en valid URL
+typeMismatch.java.net.URI=Feltet {0} skal være en valid URI
+typeMismatch.java.util.Date=Feltet {0} skal være en valid Dato
+typeMismatch.java.lang.Double=Feltet {0} skal være et valid tal
+typeMismatch.java.lang.Integer=Feltet {0} skal være et valid tal
+typeMismatch.java.lang.Long=Feltet {0} skal være et valid tal
+typeMismatch.java.lang.Short=Feltet {0} skal være et valid tal
+typeMismatch.java.math.BigDecimal=Feltet {0} skal være et valid tal
+typeMismatch.java.math.BigInteger=Feltet {0} skal være et valid tal
+
Index: grails-app/i18n/messages_de.properties
===================================================================
diff -u
--- grails-app/i18n/messages_de.properties (revision 0)
+++ grails-app/i18n/messages_de.properties (revision 75c1874d6dd0213527d1449d619d18ef22f15263)
@@ -0,0 +1,55 @@
+default.doesnt.match.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] entspricht nicht dem vorgegebenen Muster [{3}]
+default.invalid.url.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] ist keine gültige URL
+default.invalid.creditCard.message=Das Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] ist keine gültige Kreditkartennummer
+default.invalid.email.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] ist keine gültige E-Mail Adresse
+default.invalid.range.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] ist nicht im Wertebereich von [{3}] bis [{4}]
+default.invalid.size.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] ist nicht im Wertebereich von [{3}] bis [{4}]
+default.invalid.max.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] ist größer als der Höchstwert von [{3}]
+default.invalid.min.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] ist kleiner als der Mindestwert von [{3}]
+default.invalid.max.size.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] übersteigt den Höchstwert von [{3}]
+default.invalid.min.size.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] unterschreitet den Mindestwert von [{3}]
+default.invalid.validator.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] ist ungültig
+default.not.inlist.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] ist nicht in der Liste [{3}] enthalten.
+default.blank.message=Die Eigenschaft [{0}] des Typs [{1}] darf nicht leer sein
+default.not.equal.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] darf nicht gleich [{3}] sein
+default.null.message=Die Eigenschaft [{0}] des Typs [{1}] darf nicht null sein
+default.not.unique.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] darf nur einmal vorkommen
+
+default.paginate.prev=Vorherige
+default.paginate.next=Nächste
+default.boolean.true=Wahr
+default.boolean.false=Falsch
+default.date.format=dd.MM.yyyy HH:mm:ss z
+default.number.format=0
+
+default.created.message={0} {1} wurde angelegt
+default.updated.message={0} {1} wurde geändert
+default.deleted.message={0} {1} wurde gelöscht
+default.not.deleted.message={0} {1} konnte nicht gelöscht werden
+default.not.found.message={0} mit der id {1} wurde nicht gefunden
+default.optimistic.locking.failure=Ein anderer Benutzer hat das {0} Object geändert während Sie es bearbeitet haben
+
+default.home.label=Home
+default.list.label={0} Liste
+default.add.label={0} hinzufügen
+default.new.label={0} anlegen
+default.create.label={0} anlegen
+default.show.label={0} anzeigen
+default.edit.label={0} bearbeiten
+
+default.button.create.label=Anlegen
+default.button.edit.label=Bearbeiten
+default.button.update.label=Aktualisieren
+default.button.delete.label=Löschen
+default.button.delete.confirm.message=Sind Sie sicher?
+
+# Data binding errors. Use "typeMismatch.$className.$propertyName to customize (eg typeMismatch.Book.author)
+typeMismatch.java.net.URL=Die Eigenschaft {0} muss eine gültige URL sein
+typeMismatch.java.net.URI=Die Eigenschaft {0} muss eine gültige URI sein
+typeMismatch.java.util.Date=Die Eigenschaft {0} muss ein gültiges Datum sein
+typeMismatch.java.lang.Double=Die Eigenschaft {0} muss eine gültige Zahl sein
+typeMismatch.java.lang.Integer=Die Eigenschaft {0} muss eine gültige Zahl sein
+typeMismatch.java.lang.Long=Die Eigenschaft {0} muss eine gültige Zahl sein
+typeMismatch.java.lang.Short=Die Eigenschaft {0} muss eine gültige Zahl sein
+typeMismatch.java.math.BigDecimal=Die Eigenschaft {0} muss eine gültige Zahl sein
+typeMismatch.java.math.BigInteger=Die Eigenschaft {0} muss eine gültige Zahl sein
Index: grails-app/i18n/messages_es.properties
===================================================================
diff -u
--- grails-app/i18n/messages_es.properties (revision 0)
+++ grails-app/i18n/messages_es.properties (revision 75c1874d6dd0213527d1449d619d18ef22f15263)
@@ -0,0 +1,55 @@
+default.doesnt.match.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] no corresponde al patrón [{3}]
+default.invalid.url.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] no es una URL válida
+default.invalid.creditCard.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] no es un número de tarjeta de crédito válida
+default.invalid.email.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] no es una dirección de correo electrónico válida
+default.invalid.range.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] no entra en el rango válido de [{3}] a [{4}]
+default.invalid.size.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] no entra en el tamaño válido de [{3}] a [{4}]
+default.invalid.max.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] excede el valor máximo [{3}]
+default.invalid.min.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] es menos que el valor mínimo [{3}]
+default.invalid.max.size.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] excede el tamaño máximo de [{3}]
+default.invalid.min.size.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] es menor que el tamaño mínimo de [{3}]
+default.invalid.validator.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] no es válido
+default.not.inlist.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] no esta contenido dentro de la lista [{3}]
+default.blank.message=La propiedad [{0}] de la clase [{1}] no puede ser vacía
+default.not.equal.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] no puede igualar a [{3}]
+default.null.message=La propiedad [{0}] de la clase [{1}] no puede ser nulo
+default.not.unique.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] debe ser única
+
+default.paginate.prev=Anterior
+default.paginate.next=Siguiente
+default.boolean.true=Verdadero
+default.boolean.false=Falso
+default.date.format=yyyy-MM-dd HH:mm:ss z
+default.number.format=0
+
+default.created.message={0} {1} creado
+default.updated.message={0} {1} actualizado
+default.deleted.message={0} {1} eliminado
+default.not.deleted.message={0} {1} no puede eliminarse
+default.not.found.message=No se encuentra {0} con id {1}
+default.optimistic.locking.failure=Mientras usted editaba, otro usuario ha actualizado su {0}
+
+default.home.label=Principal
+default.list.label={0} Lista
+default.add.label=Agregar {0}
+default.new.label=Nuevo {0}
+default.create.label=Crear {0}
+default.show.label=Mostrar {0}
+default.edit.label=Editar {0}
+
+default.button.create.label=Crear
+default.button.edit.label=Editar
+default.button.update.label=Actualizar
+default.button.delete.label=Eliminar
+default.button.delete.confirm.message=¿Está usted seguro?
+
+# Data binding errors. Use "typeMismatch.$className.$propertyName to customize (eg typeMismatch.Book.author)
+typeMismatch.java.net.URL=La propiedad {0} debe ser una URL válida
+typeMismatch.java.net.URI=La propiedad {0} debe ser una URI válida
+typeMismatch.java.util.Date=La propiedad {0} debe ser una fecha válida
+typeMismatch.java.lang.Double=La propiedad {0} debe ser un número válido
+typeMismatch.java.lang.Integer=La propiedad {0} debe ser un número válido
+typeMismatch.java.lang.Long=La propiedad {0} debe ser un número válido
+typeMismatch.java.lang.Short=La propiedad {0} debe ser un número válido
+typeMismatch.java.math.BigDecimal=La propiedad {0} debe ser un número válido
+typeMismatch.java.math.BigInteger=La propiedad {0} debe ser un número válido
\ No newline at end of file
Index: grails-app/i18n/messages_fr.properties
===================================================================
diff -u
--- grails-app/i18n/messages_fr.properties (revision 0)
+++ grails-app/i18n/messages_fr.properties (revision 75c1874d6dd0213527d1449d619d18ef22f15263)
@@ -0,0 +1,19 @@
+default.doesnt.match.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] ne correspond pas au pattern [{3}]
+default.invalid.url.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] n'est pas une URL valide
+default.invalid.creditCard.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] n'est pas un numéro de carte de crédit valide
+default.invalid.email.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] n'est pas une adresse e-mail valide
+default.invalid.range.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] n'est pas contenue dans l'intervalle [{3}] à [{4}]
+default.invalid.size.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] n'est pas contenue dans l'intervalle [{3}] à [{4}]
+default.invalid.max.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] est supérieure à la valeur maximum [{3}]
+default.invalid.min.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] est inférieure à la valeur minimum [{3}]
+default.invalid.max.size.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] est supérieure à la valeur maximum [{3}]
+default.invalid.min.size.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] est inférieure à la valeur minimum [{3}]
+default.invalid.validator.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] n'est pas valide
+default.not.inlist.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] ne fait pas partie de la liste [{3}]
+default.blank.message=La propriété [{0}] de la classe [{1}] ne peut pas être vide
+default.not.equal.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] ne peut pas être égale à [{3}]
+default.null.message=La propriété [{0}] de la classe [{1}] ne peut pas être nulle
+default.not.unique.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] doit être unique
+
+default.paginate.prev=Précédent
+default.paginate.next=Suivant
Index: grails-app/i18n/messages_it.properties
===================================================================
diff -u
--- grails-app/i18n/messages_it.properties (revision 0)
+++ grails-app/i18n/messages_it.properties (revision 75c1874d6dd0213527d1449d619d18ef22f15263)
@@ -0,0 +1,55 @@
+default.doesnt.match.message=La proprietà [{0}] della classe [{1}] con valore [{2}] non corrisponde al pattern [{3}]
+default.invalid.url.message=La proprietà [{0}] della classe [{1}] con valore [{2}] non è un URL valido
+default.invalid.creditCard.message=La proprietà [{0}] della classe [{1}] con valore [{2}] non è un numero di carta di credito valido
+default.invalid.email.message=La proprietà [{0}] della classe [{1}] con valore [{2}] non è un indirizzo email valido
+default.invalid.range.message=La proprietà [{0}] della classe [{1}] con valore [{2}] non rientra nell'intervallo valido da [{3}] a [{4}]
+default.invalid.size.message=La proprietà [{0}] della classe [{1}] con valore [{2}] non rientra nell'intervallo di dimensioni valide da [{3}] a [{4}]
+default.invalid.max.message=La proprietà [{0}] della classe [{1}] con valore [{2}] è maggiore di [{3}]
+default.invalid.min.message=La proprietà [{0}] della classe [{1}] con valore [{2}] è minore di [{3}]
+default.invalid.max.size.message=La proprietà [{0}] della classe [{1}] con valore [{2}] è maggiore di [{3}]
+default.invalid.min.size.message=La proprietà [{0}] della classe [{1}] con valore [{2}] è minore di [{3}]
+default.invalid.validator.message=La proprietà [{0}] della classe [{1}] con valore [{2}] non è valida
+default.not.inlist.message=La proprietà [{0}] della classe [{1}] con valore [{2}] non è contenuta nella lista [{3}]
+default.blank.message=La proprietà [{0}] della classe [{1}] non può essere vuota
+default.not.equal.message=La proprietà [{0}] della classe [{1}] con valore [{2}] non può essere uguale a [{3}]
+default.null.message=La proprietà [{0}] della classe [{1}] non può essere null
+default.not.unique.message=La proprietà [{0}] della classe [{1}] con valore [{2}] deve essere unica
+
+default.paginate.prev=Precedente
+default.paginate.next=Successivo
+default.boolean.true=Vero
+default.boolean.false=Falso
+default.date.format=dd/MM/yyyy HH:mm:ss z
+default.number.format=0
+
+default.created.message={0} {1} creato
+default.updated.message={0} {1} aggiornato
+default.deleted.message={0} {1} eliminato
+default.not.deleted.message={0} {1} non può essere eliminato
+default.not.found.message={0} non trovato con id {1}
+default.optimistic.locking.failure=Un altro utente ha aggiornato questo {0} mentre si era in modifica
+
+default.home.label=Home
+default.list.label={0} Elenco
+default.add.label=Aggiungi {0}
+default.new.label=Nuovo {0}
+default.create.label=Crea {0}
+default.show.label=Mostra {0}
+default.edit.label=Modifica {0}
+
+default.button.create.label=Crea
+default.button.edit.label=Modifica
+default.button.update.label=Aggiorna
+default.button.delete.label=Elimina
+default.button.delete.confirm.message=Si è sicuri?
+
+# Data binding errors. Usa "typeMismatch.$className.$propertyName per la personalizzazione (es typeMismatch.Book.author)
+typeMismatch.java.net.URL=La proprietà {0} deve essere un URL valido
+typeMismatch.java.net.URI=La proprietà {0} deve essere un URI valido
+typeMismatch.java.util.Date=La proprietà {0} deve essere una data valida
+typeMismatch.java.lang.Double=La proprietà {0} deve essere un numero valido
+typeMismatch.java.lang.Integer=La proprietà {0} deve essere un numero valido
+typeMismatch.java.lang.Long=La proprietà {0} deve essere un numero valido
+typeMismatch.java.lang.Short=La proprietà {0} deve essere un numero valido
+typeMismatch.java.math.BigDecimal=La proprietà {0} deve essere un numero valido
+typeMismatch.java.math.BigInteger=La proprietà {0} deve essere un numero valido
Index: grails-app/i18n/messages_ja.properties
===================================================================
diff -u
--- grails-app/i18n/messages_ja.properties (revision 0)
+++ grails-app/i18n/messages_ja.properties (revision 75c1874d6dd0213527d1449d619d18ef22f15263)
@@ -0,0 +1,55 @@
+default.doesnt.match.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、[{3}]パターンと一致していません。
+default.invalid.url.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、有効なURLではありません。
+default.invalid.creditCard.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、有効なクレジットカード番号ではありません。
+default.invalid.email.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、有効なメールアドレスではありません。
+default.invalid.range.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、[{3}]から[{4}]範囲内を指定してください。
+default.invalid.size.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、[{3}]から[{4}]以内を指定してください。
+default.invalid.max.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、最大値[{3}]より大きいです。
+default.invalid.min.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、最小値[{3}]より小さいです。
+default.invalid.max.size.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、最大値[{3}]より大きいです。
+default.invalid.min.size.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、最小値[{3}]より小さいです。
+default.invalid.validator.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、カスタムバリデーションを通過できません。
+default.not.inlist.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、[{3}]リスト内に存在しません。
+default.blank.message=[{1}]クラスのプロパティ[{0}]の空白は許可されません。
+default.not.equal.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、[{3}]と同等ではありません。
+default.null.message=[{1}]クラスのプロパティ[{0}]にnullは許可されません。
+default.not.unique.message=クラス[{1}]プロパティ[{0}]の値[{2}]は既に使用されています。
+
+default.paginate.prev=戻る
+default.paginate.next=次へ
+default.boolean.true=はい
+default.boolean.false=いいえ
+default.date.format=yyyy/MM/dd HH:mm:ss z
+default.number.format=0
+
+default.created.message={0}(id:{1})を作成しました。
+default.updated.message={0}(id:{1})を更新しました。
+default.deleted.message={0}(id:{1})を削除しました。
+default.not.deleted.message={0}(id:{1})は削除できませんでした。
+default.not.found.message={0}(id:{1})は見つかりませんでした。
+default.optimistic.locking.failure=この{0}は編集中に他のユーザによって先に更新されています。
+
+default.home.label=ホーム
+default.list.label={0}リスト
+default.add.label={0}を追加
+default.new.label={0}を新規作成
+default.create.label={0}を作成
+default.show.label={0}詳細
+default.edit.label={0}を編集
+
+default.button.create.label=作成
+default.button.edit.label=編集
+default.button.update.label=更新
+default.button.delete.label=削除
+default.button.delete.confirm.message=本当に削除してよろしいですか?
+
+# Data binding errors. Use "typeMismatch.$className.$propertyName to customize (eg typeMismatch.Book.author)
+typeMismatch.java.net.URL={0}は有効なURLでなければなりません。
+typeMismatch.java.net.URI={0}は有効なURIでなければなりません。
+typeMismatch.java.util.Date={0}は有効な日付でなければなりません。
+typeMismatch.java.lang.Double={0}は有効な数値でなければなりません。
+typeMismatch.java.lang.Integer={0}は有効な数値でなければなりません。
+typeMismatch.java.lang.Long={0}は有効な数値でなければなりません。
+typeMismatch.java.lang.Short={0}は有効な数値でなければなりません。
+typeMismatch.java.math.BigDecimal={0}は有効な数値でなければなりません。
+typeMismatch.java.math.BigInteger={0}は有効な数値でなければなりません。
Index: grails-app/i18n/messages_nb.properties
===================================================================
diff -u
--- grails-app/i18n/messages_nb.properties (revision 0)
+++ grails-app/i18n/messages_nb.properties (revision 75c1874d6dd0213527d1449d619d18ef22f15263)
@@ -0,0 +1,56 @@
+default.doesnt.match.message=Feltet [{0}] i klassen [{1}] med verdien [{2}] overholder ikke mønsteret [{3}]
+default.invalid.url.message=Feltet [{0}] i klassen [{1}] med verdien [{2}] er ikke en gyldig URL
+default.invalid.creditCard.message=Feltet [{0}] i klassen [{1}] med verdien [{2}] er ikke et gyldig kredittkortnummer
+default.invalid.email.message=Feltet [{0}] i klassen [{1}] med verdien [{2}] er ikke en gyldig epostadresse
+default.invalid.range.message=Feltet [{0}] i klassen [{1}] med verdien [{2}] er ikke innenfor intervallet [{3}] til [{4}]
+default.invalid.size.message=Feltet [{0}] i klassen [{1}] med verdien [{2}] er ikke innenfor intervallet [{3}] til [{4}]
+default.invalid.max.message=Feltet [{0}] i klassen [{1}] med verdien [{2}] overstiger maksimumsverdien på [{3}]
+default.invalid.min.message=Feltet [{0}] i klassen [{1}] med verdien [{2}] er under minimumsverdien på [{3}]
+default.invalid.max.size.message=Feltet [{0}] i klassen [{1}] med verdien [{2}] overstiger maksimumslengden på [{3}]
+default.invalid.min.size.message=Feltet [{0}] i klassen [{1}] med verdien [{2}] er kortere enn minimumslengden på [{3}]
+default.invalid.validator.message=Feltet [{0}] i klassen [{1}] med verdien [{2}] overholder ikke den brukerdefinerte valideringen
+default.not.inlist.message=Feltet [{0}] i klassen [{1}] med verdien [{2}] finnes ikke i listen [{3}]
+default.blank.message=Feltet [{0}] i klassen [{1}] kan ikke være tom
+default.not.equal.message=Feltet [{0}] i klassen [{1}] med verdien [{2}] kan ikke være [{3}]
+default.null.message=Feltet [{0}] i klassen [{1}] kan ikke være null
+default.not.unique.message=Feltet [{0}] i klassen [{1}] med verdien [{2}] må være unik
+
+default.paginate.prev=Forrige
+default.paginate.next=Neste
+default.boolean.true=Ja
+default.boolean.false=Nei
+default.date.format=dd.MM.yyyy HH:mm:ss z
+default.number.format=0
+
+default.created.message={0} {1} opprettet
+default.updated.message={0} {1} oppdatert
+default.deleted.message={0} {1} slettet
+default.not.deleted.message={0} {1} kunne ikke slettes
+default.not.found.message={0} med id {1} ble ikke funnet
+default.optimistic.locking.failure=En annen bruker har oppdatert denne {0} mens du redigerte
+
+default.home.label=Hjem
+default.list.label={0}liste
+default.add.label=Legg til {0}
+default.new.label=Ny {0}
+default.create.label=Opprett {0}
+default.show.label=Vis {0}
+default.edit.label=Endre {0}
+
+default.button.create.label=Opprett
+default.button.edit.label=Endre
+default.button.update.label=Oppdater
+default.button.delete.label=Slett
+default.button.delete.confirm.message=Er du sikker?
+
+# Data binding errors. Use "typeMismatch.$className.$propertyName to customize (eg typeMismatch.Book.author)
+typeMismatch.java.net.URL=Feltet {0} må være en gyldig URL
+typeMismatch.java.net.URI=Feltet {0} må være en gyldig URI
+typeMismatch.java.util.Date=Feltet {0} må være en gyldig dato
+typeMismatch.java.lang.Double=Feltet {0} må være et gyldig tall
+typeMismatch.java.lang.Integer=Feltet {0} må være et gyldig heltall
+typeMismatch.java.lang.Long=Feltet {0} må være et gyldig heltall
+typeMismatch.java.lang.Short=Feltet {0} må være et gyldig heltall
+typeMismatch.java.math.BigDecimal=Feltet {0} må være et gyldig tall
+typeMismatch.java.math.BigInteger=Feltet {0} må være et gyldig heltall
+
Index: grails-app/i18n/messages_nl.properties
===================================================================
diff -u
--- grails-app/i18n/messages_nl.properties (revision 0)
+++ grails-app/i18n/messages_nl.properties (revision 75c1874d6dd0213527d1449d619d18ef22f15263)
@@ -0,0 +1,55 @@
+default.doesnt.match.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] komt niet overeen met het vereiste patroon [{3}]
+default.invalid.url.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] is geen geldige URL
+default.invalid.creditCard.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] is geen geldig credit card nummer
+default.invalid.email.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] is geen geldig e-mailadres
+default.invalid.range.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] valt niet in de geldige waardenreeks van [{3}] tot [{4}]
+default.invalid.size.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] valt niet in de geldige grootte van [{3}] tot [{4}]
+default.invalid.max.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] overschrijdt de maximumwaarde [{3}]
+default.invalid.min.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] is minder dan de minimumwaarde [{3}]
+default.invalid.max.size.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] overschrijdt de maximumgrootte van [{3}]
+default.invalid.min.size.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] is minder dan minimumgrootte van [{3}]
+default.invalid.validator.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] is niet geldig
+default.not.inlist.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] komt niet voor in de lijst [{3}]
+default.blank.message=Attribuut [{0}] van entiteit [{1}] mag niet leeg zijn
+default.not.equal.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] mag niet gelijk zijn aan [{3}]
+default.null.message=Attribuut [{0}] van entiteit [{1}] mag niet leeg zijn
+default.not.unique.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] moet uniek zijn
+
+default.paginate.prev=Vorige
+default.paginate.next=Volgende
+default.boolean.true=Ja
+default.boolean.false=Nee
+default.date.format=dd-MM-yyyy HH:mm:ss z
+default.number.format=0
+
+default.created.message={0} {1} ingevoerd
+default.updated.message={0} {1} gewijzigd
+default.deleted.message={0} {1} verwijderd
+default.not.deleted.message={0} {1} kon niet worden verwijderd
+default.not.found.message={0} met id {1} kon niet worden gevonden
+default.optimistic.locking.failure=Een andere gebruiker heeft deze {0} al gewijzigd
+
+default.home.label=Home
+default.list.label={0} Overzicht
+default.add.label=Toevoegen {0}
+default.new.label=Invoeren {0}
+default.create.label=Invoeren {0}
+default.show.label=Details {0}
+default.edit.label=Wijzigen {0}
+
+default.button.create.label=Invoeren
+default.button.edit.label=Wijzigen
+default.button.update.label=Opslaan
+default.button.delete.label=Verwijderen
+default.button.delete.confirm.message=Weet je het zeker?
+
+# Data binding errors. Use "typeMismatch.$className.$propertyName to customize (eg typeMismatch.Book.author)
+typeMismatch.java.net.URL=Attribuut {0} is geen geldige URL
+typeMismatch.java.net.URI=Attribuut {0} is geen geldige URI
+typeMismatch.java.util.Date=Attribuut {0} is geen geldige datum
+typeMismatch.java.lang.Double=Attribuut {0} is geen geldig nummer
+typeMismatch.java.lang.Integer=Attribuut {0} is geen geldig nummer
+typeMismatch.java.lang.Long=Attribuut {0} is geen geldig nummer
+typeMismatch.java.lang.Short=Attribuut {0} is geen geldig nummer
+typeMismatch.java.math.BigDecimal=Attribuut {0} is geen geldig nummer
+typeMismatch.java.math.BigInteger=Attribuut {0} is geen geldig nummer
Index: grails-app/i18n/messages_pl.properties
===================================================================
diff -u
--- grails-app/i18n/messages_pl.properties (revision 0)
+++ grails-app/i18n/messages_pl.properties (revision 75c1874d6dd0213527d1449d619d18ef22f15263)
@@ -0,0 +1,59 @@
+#
+# Translated by Matthias Hryniszak - padcom@gmail.com
+#
+
+default.doesnt.match.message=Właściwość [{0}] klasy [{1}] o wartości [{2}] nie pasuje do wymaganego wzorca [{3}]
+default.invalid.url.message=Właściwość [{0}] klasy [{1}] o wartości [{2}] jest niepoprawnym adresem URL
+default.invalid.creditCard.message=Właściwość [{0}] klasy [{1}] with value [{2}] nie jest poprawnym numerem karty kredytowej
+default.invalid.email.message=Właściwość [{0}] klasy [{1}] o wartości [{2}] nie jest poprawnym adresem e-mail
+default.invalid.range.message=Właściwość [{0}] klasy [{1}] o wartości [{2}] nie zawiera się zakładanym zakresie od [{3}] do [{4}]
+default.invalid.size.message=Właściwość [{0}] klasy [{1}] o wartości [{2}] nie zawiera się w zakładanym zakresie rozmiarów od [{3}] do [{4}]
+default.invalid.max.message=Właściwość [{0}] klasy [{1}] o wartości [{2}] przekracza maksymalną wartość [{3}]
+default.invalid.min.message=Właściwość [{0}] klasy [{1}] o wartości [{2}] jest mniejsza niż minimalna wartość [{3}]
+default.invalid.max.size.message=Właściwość [{0}] klasy [{1}] o wartości [{2}] przekracza maksymalny rozmiar [{3}]
+default.invalid.min.size.message=Właściwość [{0}] klasy [{1}] o wartości [{2}] jest mniejsza niż minimalny rozmiar [{3}]
+default.invalid.validator.message=Właściwość [{0}] klasy [{1}] o wartości [{2}] nie spełnia założonych niestandardowych warunków
+default.not.inlist.message=Właściwość [{0}] klasy [{1}] o wartości [{2}] nie zawiera się w liście [{3}]
+default.blank.message=Właściwość [{0}] klasy [{1}] nie może być pusta
+default.not.equal.message=Właściwość [{0}] klasy [{1}] o wartości [{2}] nie może równać się [{3}]
+default.null.message=Właściwość [{0}] klasy [{1}] nie może być null
+default.not.unique.message=Właściwość [{0}] klasy [{1}] o wartości [{2}] musi być unikalna
+
+default.paginate.prev=Poprzedni
+default.paginate.next=Następny
+default.boolean.true=Prawda
+default.boolean.false=Fałsz
+default.date.format=yyyy-MM-dd HH:mm:ss z
+default.number.format=0
+
+default.created.message=Utworzono {0} {1}
+default.updated.message=Zaktualizowano {0} {1}
+default.deleted.message=Usunięto {0} {1}
+default.not.deleted.message={0} {1} nie mógł zostać usunięty
+default.not.found.message=Nie znaleziono {0} o id {1}
+default.optimistic.locking.failure=Inny użytkownik zaktualizował ten obiekt {0} w trakcie twoich zmian
+
+default.home.label=Strona domowa
+default.list.label=Lista {0}
+default.add.label=Dodaj {0}
+default.new.label=Utwórz {0}
+default.create.label=Utwórz {0}
+default.show.label=Pokaż {0}
+default.edit.label=Edytuj {0}
+
+default.button.create.label=Utwórz
+default.button.edit.label=Edytuj
+default.button.update.label=Zaktualizuj
+default.button.delete.label=Usuń
+default.button.delete.confirm.message=Czy jesteś pewien?
+
+# Data binding errors. Use "typeMismatch.$className.$propertyName to customize (eg typeMismatch.Book.author)
+typeMismatch.java.net.URL=Właściwość {0} musi być poprawnym adresem URL
+typeMismatch.java.net.URI=Właściwość {0} musi być poprawnym adresem URI
+typeMismatch.java.util.Date=Właściwość {0} musi być poprawną datą
+typeMismatch.java.lang.Double=Właściwość {0} musi być poprawnyą liczbą
+typeMismatch.java.lang.Integer=Właściwość {0} musi być poprawnyą liczbą
+typeMismatch.java.lang.Long=Właściwość {0} musi być poprawnyą liczbą
+typeMismatch.java.lang.Short=Właściwość {0} musi być poprawnyą liczbą
+typeMismatch.java.math.BigDecimal=Właściwość {0} musi być poprawnyą liczbą
+typeMismatch.java.math.BigInteger=Właściwość {0} musi być poprawnyą liczbą
Index: grails-app/i18n/messages_pt_BR.properties
===================================================================
diff -u
--- grails-app/i18n/messages_pt_BR.properties (revision 0)
+++ grails-app/i18n/messages_pt_BR.properties (revision 75c1874d6dd0213527d1449d619d18ef22f15263)
@@ -0,0 +1,59 @@
+#
+# Translated by Lucas Teixeira - lucastex@gmail.com
+#
+
+default.doesnt.match.message=O campo [{0}] da classe [{1}] com o valor [{2}] não atende ao padrão definido [{3}]
+default.invalid.url.message=O campo [{0}] da classe [{1}] com o valor [{2}] não é uma URL válida
+default.invalid.creditCard.message=O campo [{0}] da classe [{1}] com o valor [{2}] não é um número válido de cartão de crédito
+default.invalid.email.message=O campo [{0}] da classe [{1}] com o valor [{2}] não é um endereço de email válido.
+default.invalid.range.message=O campo [{0}] da classe [{1}] com o valor [{2}] não está entre a faixa de valores válida de [{3}] até [{4}]
+default.invalid.size.message=O campo [{0}] da classe [{1}] com o valor [{2}] não está na faixa de tamanho válida de [{3}] até [{4}]
+default.invalid.max.message=O campo [{0}] da classe [{1}] com o valor [{2}] ultrapassa o valor máximo [{3}]
+default.invalid.min.message=O campo [{0}] da classe [{1}] com o valor [{2}] não atinge o valor mínimo [{3}]
+default.invalid.max.size.message=O campo [{0}] da classe [{1}] com o valor [{2}] ultrapassa o tamanho máximo de [{3}]
+default.invalid.min.size.message=O campo [{0}] da classe [{1}] com o valor [{2}] não atinge o tamanho mínimo de [{3}]
+default.invalid.validator.message=O campo [{0}] da classe [{1}] com o valor [{2}] não passou na validação
+default.not.inlist.message=O campo [{0}] da classe [{1}] com o valor [{2}] não é um valor dentre os permitidos na lista [{3}]
+default.blank.message=O campo [{0}] da classe [{1}] não pode ficar em branco
+default.not.equal.message=O campo [{0}] da classe [{1}] com o valor [{2}] não pode ser igual a [{3}]
+default.null.message=O campo [{0}] da classe [{1}] não pode ser vazio
+default.not.unique.message=O campo [{0}] da classe [{1}] com o valor [{2}] deve ser único
+
+default.paginate.prev=Anterior
+default.paginate.next=Próximo
+default.boolean.true=Sim
+default.boolean.false=Não
+default.date.format=dd/MM/yyyy HH:mm:ss z
+default.number.format=0
+
+default.created.message={0} {1} criado
+default.updated.message={0} {1} atualizado
+default.deleted.message={0} {1} removido
+default.not.deleted.message={0} {1} não pode ser removido
+default.not.found.message={0} não foi encontrado com o id {1}
+default.optimistic.locking.failure=Outro usuário atualizou este [{0}] enquanto você tentou salvá-lo
+
+default.home.label=Principal
+default.list.label={0} Listagem
+default.add.label=Adicionar {0}
+default.new.label=Novo {0}
+default.create.label=Criar {0}
+default.show.label=Ver {0}
+default.edit.label=Editar {0}
+
+default.button.create.label=Criar
+default.button.edit.label=Editar
+default.button.update.label=Alterar
+default.button.delete.label=Remover
+default.button.delete.confirm.message=Tem certeza?
+
+# Mensagens de erro em atribuição de valores. Use "typeMismatch.$className.$propertyName" para customizar (eg typeMismatch.Book.author)
+typeMismatch.java.net.URL=O campo {0} deve ser uma URL válida.
+typeMismatch.java.net.URI=O campo {0} deve ser uma URI válida.
+typeMismatch.java.util.Date=O campo {0} deve ser uma data válida
+typeMismatch.java.lang.Double=O campo {0} deve ser um número válido.
+typeMismatch.java.lang.Integer=O campo {0} deve ser um número válido.
+typeMismatch.java.lang.Long=O campo {0} deve ser um número válido.
+typeMismatch.java.lang.Short=O campo {0} deve ser um número válido.
+typeMismatch.java.math.BigDecimal=O campo {0} deve ser um número válido.
+typeMismatch.java.math.BigInteger=O campo {0} deve ser um número válido.
Index: grails-app/i18n/messages_pt_PT.properties
===================================================================
diff -u
--- grails-app/i18n/messages_pt_PT.properties (revision 0)
+++ grails-app/i18n/messages_pt_PT.properties (revision 75c1874d6dd0213527d1449d619d18ef22f15263)
@@ -0,0 +1,34 @@
+#
+# translation by miguel.ping@gmail.com, based on pt_BR translation by Lucas Teixeira - lucastex@gmail.com
+#
+
+default.doesnt.match.message=O campo [{0}] da classe [{1}] com o valor [{2}] não corresponde ao padrão definido [{3}]
+default.invalid.url.message=O campo [{0}] da classe [{1}] com o valor [{2}] não é um URL válido
+default.invalid.creditCard.message=O campo [{0}] da classe [{1}] com o valor [{2}] não é um número válido de cartão de crédito
+default.invalid.email.message=O campo [{0}] da classe [{1}] com o valor [{2}] não é um endereço de email válido.
+default.invalid.range.message=O campo [{0}] da classe [{1}] com o valor [{2}] não está dentro dos limites de valores válidos de [{3}] a [{4}]
+default.invalid.size.message=O campo [{0}] da classe [{1}] com o valor [{2}] está fora dos limites de tamanho válido de [{3}] a [{4}]
+default.invalid.max.message=O campo [{0}] da classe [{1}] com o valor [{2}] ultrapassa o valor máximo [{3}]
+default.invalid.min.message=O campo [{0}] da classe [{1}] com o valor [{2}] não atinge o valor mínimo [{3}]
+default.invalid.max.size.message=O campo [{0}] da classe [{1}] com o valor [{2}] ultrapassa o tamanho máximo de [{3}]
+default.invalid.min.size.message=O campo [{0}] da classe [{1}] com o valor [{2}] não atinge o tamanho mínimo de [{3}]
+default.invalid.validator.message=O campo [{0}] da classe [{1}] com o valor [{2}] não passou na validação
+default.not.inlist.message=O campo [{0}] da classe [{1}] com o valor [{2}] não se encontra nos valores permitidos da lista [{3}]
+default.blank.message=O campo [{0}] da classe [{1}] não pode ser vazio
+default.not.equal.message=O campo [{0}] da classe [{1}] com o valor [{2}] não pode ser igual a [{3}]
+default.null.message=O campo [{0}] da classe [{1}] não pode ser vazio
+default.not.unique.message=O campo [{0}] da classe [{1}] com o valor [{2}] deve ser único
+
+default.paginate.prev=Anterior
+default.paginate.next=Próximo
+
+# Mensagens de erro em atribuição de valores. Use "typeMismatch.$className.$propertyName" para personalizar(eg typeMismatch.Book.author)
+typeMismatch.java.net.URL=O campo {0} deve ser um URL válido.
+typeMismatch.java.net.URI=O campo {0} deve ser um URI válido.
+typeMismatch.java.util.Date=O campo {0} deve ser uma data válida
+typeMismatch.java.lang.Double=O campo {0} deve ser um número válido.
+typeMismatch.java.lang.Integer=O campo {0} deve ser um número válido.
+typeMismatch.java.lang.Long=O campo {0} deve ser um número valido.
+typeMismatch.java.lang.Short=O campo {0} deve ser um número válido.
+typeMismatch.java.math.BigDecimal=O campo {0} deve ser um número válido.
+typeMismatch.java.math.BigInteger=O campo {0} deve ser um número válido.
Index: grails-app/i18n/messages_ru.properties
===================================================================
diff -u
--- grails-app/i18n/messages_ru.properties (revision 0)
+++ grails-app/i18n/messages_ru.properties (revision 75c1874d6dd0213527d1449d619d18ef22f15263)
@@ -0,0 +1,31 @@
+default.doesnt.match.message=Значение [{2}] поля [{0}] класса [{1}] не соответствует образцу [{3}]
+default.invalid.url.message=Значение [{2}] поля [{0}] класса [{1}] не является допустимым URL-адресом
+default.invalid.creditCard.message=Значение [{2}] поля [{0}] класса [{1}] не является допустимым номером кредитной карты
+default.invalid.email.message=Значение [{2}] поля [{0}] класса [{1}] не является допустимым e-mail адресом
+default.invalid.range.message=Значение [{2}] поля [{0}] класса [{1}] не попадает в допустимый интервал от [{3}] до [{4}]
+default.invalid.size.message=Размер поля [{0}] класса [{1}] (значение: [{2}]) не попадает в допустимый интервал от [{3}] до [{4}]
+default.invalid.max.message=Значение [{2}] поля [{0}] класса [{1}] больше чем максимально допустимое значение [{3}]
+default.invalid.min.message=Значение [{2}] поля [{0}] класса [{1}] меньше чем минимально допустимое значение [{3}]
+default.invalid.max.size.message=Размер поля [{0}] класса [{1}] (значение: [{2}]) больше чем максимально допустимый размер [{3}]
+default.invalid.min.size.message=Размер поля [{0}] класса [{1}] (значение: [{2}]) меньше чем минимально допустимый размер [{3}]
+default.invalid.validator.message=Значение [{2}] поля [{0}] класса [{1}] не допустимо
+default.not.inlist.message=Значение [{2}] поля [{0}] класса [{1}] не попадает в список допустимых значений [{3}]
+default.blank.message=Поле [{0}] класса [{1}] не может быть пустым
+default.not.equal.message=Значение [{2}] поля [{0}] класса [{1}] не может быть равно [{3}]
+default.null.message=Поле [{0}] класса [{1}] не может иметь значение null
+default.not.unique.message=Значение [{2}] поля [{0}] класса [{1}] должно быть уникальным
+
+default.paginate.prev=Предыдушая страница
+default.paginate.next=Следующая страница
+
+# Ошибки при присвоении данных. Для точной настройки для полей классов используйте
+# формат "typeMismatch.$className.$propertyName" (например, typeMismatch.Book.author)
+typeMismatch.java.net.URL=Значение поля {0} не является допустимым URL
+typeMismatch.java.net.URI=Значение поля {0} не является допустимым URI
+typeMismatch.java.util.Date=Значение поля {0} не является допустимой датой
+typeMismatch.java.lang.Double=Значение поля {0} не является допустимым числом
+typeMismatch.java.lang.Integer=Значение поля {0} не является допустимым числом
+typeMismatch.java.lang.Long=Значение поля {0} не является допустимым числом
+typeMismatch.java.lang.Short=Значение поля {0} не является допустимым числом
+typeMismatch.java.math.BigDecimal=Значение поля {0} не является допустимым числом
+typeMismatch.java.math.BigInteger=Значение поля {0} не является допустимым числом
Index: grails-app/i18n/messages_sv.properties
===================================================================
diff -u
--- grails-app/i18n/messages_sv.properties (revision 0)
+++ grails-app/i18n/messages_sv.properties (revision 75c1874d6dd0213527d1449d619d18ef22f15263)
@@ -0,0 +1,55 @@
+default.doesnt.match.message=Attributet [{0}] för klassen [{1}] med värde [{2}] matchar inte mot uttrycket [{3}]
+default.invalid.url.message=Attributet [{0}] för klassen [{1}] med värde [{2}] är inte en giltig URL
+default.invalid.creditCard.message=Attributet [{0}] för klassen [{1}] med värde [{2}] är inte ett giltigt kreditkortsnummer
+default.invalid.email.message=Attributet [{0}] för klassen [{1}] med värde [{2}] är inte en giltig e-postadress
+default.invalid.range.message=Attributet [{0}] för klassen [{1}] med värde [{2}] är inte inom intervallet [{3}] till [{4}]
+default.invalid.size.message=Attributet [{0}] för klassen [{1}] med värde [{2}] har en storlek som inte är inom [{3}] till [{4}]
+default.invalid.max.message=Attributet [{0}] för klassen [{1}] med värde [{2}] överskrider maxvärdet [{3}]
+default.invalid.min.message=Attributet [{0}] för klassen [{1}] med värde [{2}] är mindre än minimivärdet [{3}]
+default.invalid.max.size.message=Attributet [{0}] för klassen [{1}] med värde [{2}] överskrider maxstorleken [{3}]
+default.invalid.min.size.message=Attributet [{0}] för klassen [{1}] med värde [{2}] är mindre än minimistorleken [{3}]
+default.invalid.validator.message=Attributet [{0}] för klassen [{1}] med värde [{2}] är inte giltigt enligt anpassad regel
+default.not.inlist.message=Attributet [{0}] för klassen [{1}] med värde [{2}] är inte giltigt, måste vara ett av [{3}]
+default.blank.message=Attributet [{0}] för klassen [{1}] får inte vara tomt
+default.not.equal.message=Attributet [{0}] för klassen [{1}] med värde [{2}] får inte vara lika med [{3}]
+default.null.message=Attributet [{0}] för klassen [{1}] får inte vara tomt
+default.not.unique.message=Attributet [{0}] för klassen [{1}] med värde [{2}] måste vara unikt
+
+default.paginate.prev=Föregående
+default.paginate.next=Nästa
+default.boolean.true=Sant
+default.boolean.false=Falskt
+default.date.format=yyyy-MM-dd HH:mm:ss z
+default.number.format=0
+
+default.created.message={0} {1} skapades
+default.updated.message={0} {1} uppdaterades
+default.deleted.message={0} {1} borttagen
+default.not.deleted.message={0} {1} kunde inte tas bort
+default.not.found.message={0} med id {1} kunde inte hittas
+default.optimistic.locking.failure=En annan användare har uppdaterat det här {0} objektet medan du redigerade det
+
+default.home.label=Hem
+default.list.label= {0} - Lista
+default.add.label=Lägg till {0}
+default.new.label=Skapa {0}
+default.create.label=Skapa {0}
+default.show.label=Visa {0}
+default.edit.label=Ändra {0}
+
+default.button.create.label=Skapa
+default.button.edit.label=Ändra
+default.button.update.label=Uppdatera
+default.button.delete.label=Ta bort
+default.button.delete.confirm.message=Är du säker?
+
+# Data binding errors. Use "typeMismatch.$className.$propertyName to customize (eg typeMismatch.Book.author)
+typeMismatch.java.net.URL=Värdet för {0} måste vara en giltig URL
+typeMismatch.java.net.URI=Värdet för {0} måste vara en giltig URI
+typeMismatch.java.util.Date=Värdet {0} måste vara ett giltigt datum
+typeMismatch.java.lang.Double=Värdet {0} måste vara ett giltigt nummer
+typeMismatch.java.lang.Integer=Värdet {0} måste vara ett giltigt heltal
+typeMismatch.java.lang.Long=Värdet {0} måste vara ett giltigt heltal
+typeMismatch.java.lang.Short=Värdet {0} måste vara ett giltigt heltal
+typeMismatch.java.math.BigDecimal=Värdet {0} måste vara ett giltigt nummer
+typeMismatch.java.math.BigInteger=Värdet {0} måste vara ett giltigt heltal
\ No newline at end of file
Index: grails-app/i18n/messages_th.properties
===================================================================
diff -u
--- grails-app/i18n/messages_th.properties (revision 0)
+++ grails-app/i18n/messages_th.properties (revision 75c1874d6dd0213527d1449d619d18ef22f15263)
@@ -0,0 +1,55 @@
+default.doesnt.match.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] ไม่ถูกต้องตามรูปแบบที่กำหนดไว้ใน [{3}]
+default.invalid.url.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] ไม่ถูกต้องตามรูปแบบ URL
+default.invalid.creditCard.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] ไม่ถูกต้องตามรูปแบบหมายเลขบัตรเครดิต
+default.invalid.email.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] ไม่ถูกต้องตามรูปแบบอีเมล์
+default.invalid.range.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] ไม่ได้มีค่าที่ถูกต้องในช่วงจาก [{3}] ถึง [{4}]
+default.invalid.size.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] ไม่ได้มีขนาดที่ถูกต้องในช่วงจาก [{3}] ถึง [{4}]
+default.invalid.max.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] มีค่าเกิดกว่าค่ามากสุด [{3}]
+default.invalid.min.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] มีค่าน้อยกว่าค่าต่ำสุด [{3}]
+default.invalid.max.size.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] มีขนาดเกินกว่าขนาดมากสุดของ [{3}]
+default.invalid.min.size.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] มีขนาดต่ำกว่าขนาดต่ำสุดของ [{3}]
+default.invalid.validator.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] ไม่ผ่านการทวนสอบค่าที่ตั้งขึ้น
+default.not.inlist.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] ไม่ได้อยู่ในรายการต่อไปนี้ [{3}]
+default.blank.message=คุณสมบัติ [{0}] ของคลาส [{1}] ไม่สามารถเป็นค่าว่างได้
+default.not.equal.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] ไม่สามารถเท่ากับ [{3}] ได้
+default.null.message=คุณสมบัติ [{0}] ของคลาส [{1}] ไม่สามารถเป็น null ได้
+default.not.unique.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] จะต้องไม่ซ้ำ (unique)
+
+default.paginate.prev=ก่อนหน้า
+default.paginate.next=ถัดไป
+default.boolean.true=จริง
+default.boolean.false=เท็จ
+default.date.format=dd-MM-yyyy HH:mm:ss z
+default.number.format=0
+
+default.created.message=สร้าง {0} {1} เรียบร้อยแล้ว
+default.updated.message=ปรับปรุง {0} {1} เรียบร้อยแล้ว
+default.deleted.message=ลบ {0} {1} เรียบร้อยแล้ว
+default.not.deleted.message=ไม่สามารถลบ {0} {1}
+default.not.found.message=ไม่พบ {0} ด้วย id {1} นี้
+default.optimistic.locking.failure=มีผู้ใช้ท่านอื่นปรับปรุง {0} ขณะที่คุณกำลังแก้ไขข้อมูลอยู่
+
+default.home.label=หน้าแรก
+default.list.label=รายการ {0}
+default.add.label=เพิ่ม {0}
+default.new.label=สร้าง {0} ใหม่
+default.create.label=สร้าง {0}
+default.show.label=แสดง {0}
+default.edit.label=แก้ไข {0}
+
+default.button.create.label=สร้าง
+default.button.edit.label=แก้ไข
+default.button.update.label=ปรับปรุง
+default.button.delete.label=ลบ
+default.button.delete.confirm.message=คุณแน่ใจหรือไม่ ?
+
+# Data binding errors. Use "typeMismatch.$className.$propertyName to customize (eg typeMismatch.Book.author)
+typeMismatch.java.net.URL=คุณสมบัติ '{0}' จะต้องเป็นค่า URL ที่ถูกต้อง
+typeMismatch.java.net.URI=คุณสมบัติ '{0}' จะต้องเป็นค่า URI ที่ถูกต้อง
+typeMismatch.java.util.Date=คุณสมบัติ '{0}' จะต้องมีค่าเป็นวันที่
+typeMismatch.java.lang.Double=คุณสมบัติ '{0}' จะต้องมีค่าเป็นจำนวนประเภท Double
+typeMismatch.java.lang.Integer=คุณสมบัติ '{0}' จะต้องมีค่าเป็นจำนวนประเภท Integer
+typeMismatch.java.lang.Long=คุณสมบัติ '{0}' จะต้องมีค่าเป็นจำนวนประเภท Long
+typeMismatch.java.lang.Short=คุณสมบัติ '{0}' จะต้องมีค่าเป็นจำนวนประเภท Short
+typeMismatch.java.math.BigDecimal=คุณสมบัติ '{0}' จะต้องมีค่าเป็นจำนวนประเภท BigDecimal
+typeMismatch.java.math.BigInteger=คุณสมบัติ '{0}' จะต้องมีค่าเป็นจำนวนประเภท BigInteger
Index: grails-app/i18n/messages_zh_CN.properties
===================================================================
diff -u
--- grails-app/i18n/messages_zh_CN.properties (revision 0)
+++ grails-app/i18n/messages_zh_CN.properties (revision 75c1874d6dd0213527d1449d619d18ef22f15263)
@@ -0,0 +1,18 @@
+default.blank.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u4E0D\u80FD\u4E3A\u7A7A
+default.doesnt.match.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u4E0E\u5B9A\u4E49\u7684\u6A21\u5F0F [{3}]\u4E0D\u5339\u914D
+default.invalid.creditCard.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u4E0D\u662F\u4E00\u4E2A\u6709\u6548\u7684\u4FE1\u7528\u5361\u53F7
+default.invalid.email.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u4E0D\u662F\u4E00\u4E2A\u5408\u6CD5\u7684\u7535\u5B50\u90AE\u4EF6\u5730\u5740
+default.invalid.max.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u6BD4\u6700\u5927\u503C [{3}]\u8FD8\u5927
+default.invalid.max.size.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u7684\u5927\u5C0F\u6BD4\u6700\u5927\u503C [{3}]\u8FD8\u5927
+default.invalid.min.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u6BD4\u6700\u5C0F\u503C [{3}]\u8FD8\u5C0F
+default.invalid.min.size.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u7684\u5927\u5C0F\u6BD4\u6700\u5C0F\u503C [{3}]\u8FD8\u5C0F
+default.invalid.range.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u4E0D\u5728\u5408\u6CD5\u7684\u8303\u56F4\u5185( [{3}] \uFF5E [{4}] )
+default.invalid.size.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u7684\u5927\u5C0F\u4E0D\u5728\u5408\u6CD5\u7684\u8303\u56F4\u5185( [{3}] \uFF5E [{4}] )
+default.invalid.url.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u4E0D\u662F\u4E00\u4E2A\u5408\u6CD5\u7684URL
+default.invalid.validator.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u672A\u80FD\u901A\u8FC7\u81EA\u5B9A\u4E49\u7684\u9A8C\u8BC1
+default.not.equal.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u4E0E[{3}]\u4E0D\u76F8\u7B49
+default.not.inlist.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u4E0D\u5728\u5217\u8868\u7684\u53D6\u503C\u8303\u56F4\u5185
+default.not.unique.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u5FC5\u987B\u662F\u552F\u4E00\u7684
+default.null.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u4E0D\u80FD\u4E3Anull
+default.paginate.next=\u4E0B\u9875
+default.paginate.prev=\u4E0A\u9875
Index: grails-app/init/BootStrap.groovy
===================================================================
diff -u
--- grails-app/init/BootStrap.groovy (revision 0)
+++ grails-app/init/BootStrap.groovy (revision 75c1874d6dd0213527d1449d619d18ef22f15263)
@@ -0,0 +1,29 @@
+import com.dumbster.smtp.SmtpMessage
+
+class BootStrap {
+
+ @SuppressWarnings(['Println'])
+ def init = { servletContext ->
+ enhanceSmtpMessageClass()
+ }
+
+ def destroy = {
+ }
+
+ private void enhanceSmtpMessageClass() {
+ MetaClass mc = SmtpMessage.metaClass
+ Closure split = { it.split(',')*.trim() }
+ mc.with {
+ getSubject = { -> delegate.getHeaderValue('Subject') }
+ getDate = { -> delegate.getHeaderValue('Date') }
+ getTo = { -> delegate.tos[0] }
+ getTos = { -> split(delegate.getHeaderValue('To')) }
+ getFrom = { -> delegate.froms[0] }
+ getFroms = { -> split(delegate.getHeaderValue('From')) }
+ getCc = { -> delegate.ccs[0] }
+ getCcs = { -> split(delegate.getHeaderValue('Cc')) }
+ getBcc = { -> delegate.bccs[0] }
+ getBccs = { -> split(delegate.getHeaderValue('Bcc')) }
+ }
+ }
+}
Index: grails-app/init/correspondence/service/Application.groovy
===================================================================
diff -u
--- grails-app/init/correspondence/service/Application.groovy (revision 0)
+++ grails-app/init/correspondence/service/Application.groovy (revision 75c1874d6dd0213527d1449d619d18ef22f15263)
@@ -0,0 +1,10 @@
+package correspondence.service
+
+import grails.boot.GrailsApp
+import grails.boot.config.GrailsAutoConfiguration
+
+class Application extends GrailsAutoConfiguration {
+ static void main(String[] args) {
+ GrailsApp.run(Application, args)
+ }
+}
Index: grails-app/services/com/lemans/correspondence/forms/DomainFormManagerService.groovy
===================================================================
diff -u
--- grails-app/services/com/lemans/correspondence/forms/DomainFormManagerService.groovy (revision 0)
+++ grails-app/services/com/lemans/correspondence/forms/DomainFormManagerService.groovy (revision 75c1874d6dd0213527d1449d619d18ef22f15263)
@@ -0,0 +1,83 @@
+package com.lemans.correspondence.forms
+
+import grails.transaction.Transactional
+
+import com.lemans.correspondence.Domain
+import com.lemans.services.LemansManager
+
+@Transactional
+class DomainFormManagerService extends LemansManager {
+
+ private final DomainFormFormTransformer transformer = new DomainFormFormTransformer()
+
+ private final DomainFormFormValidator validator = new DomainFormFormValidator()
+
+ /**
+ * Creates a new DomainForm.
+ *
+ * @param values
+ * @param username
+ * @param domainId
+ *
+ * @return DomainForm which may contain errors or be null
+ */
+ DomainForm createDomainForm(Map values, String username, Integer domainId) {
+ DomainForm domainForm = new DomainForm(values)
+ domainForm.domain = Domain.read(domainId)
+ domainForm.formDetailXml = transformer.formToXml(values.form)
+ domainForm.validate()
+ validator.validate(values.form, domainForm)
+ saveOrDiscardDomain(domainForm, username)
+ }
+
+ /**
+ * Updates an existing DomainForm.
+ *
+ * @param values
+ * @param formKey
+ * @param username
+ * @param domainId
+ *
+ * @return DomainForm which may errors or be null
+ */
+ DomainForm updateDomainForm(Map values, String formKey, String username, Integer domainId) {
+ DomainForm domainForm = findDomainForm(formKey, domainId)
+ if (domainForm) {
+ domainForm.properties = values
+ Map form = values.form
+ if (form) {
+ domainForm.formDetailXml = transformer.formToXml(form)
+ validator.validate(form, domainForm)
+ }
+ domainForm.validate()
+ saveOrDiscardDomain(domainForm, username)
+ }
+ }
+
+ /**
+ * Soft deletes a DomainForm.
+ *
+ * @param formKey
+ * @param username
+ * @param domainId
+ *
+ * @return DomainForm which may errors or be null
+ */
+ DomainForm deleteDomainForm(String formKey, String username, Integer domainId) {
+ DomainForm form = findDomainForm(formKey, domainId)
+ if (form) { softDelete(form, username) }
+ form
+ }
+
+ /**
+ * Finds an active or inactive DomainForm by formKey and domainId.
+ *
+ * @param formKey
+ * @param domainId
+ *
+ * @return DomainForm or null
+ */
+ DomainForm findDomainForm(String formKey, Integer domainId) {
+ DomainForm.findByFormKeyAndDomain(formKey, Domain.get(domainId))
+ }
+}
Index: grails-app/services/com/lemans/correspondence/forms/DomainFormService.groovy
===================================================================
diff -u
--- grails-app/services/com/lemans/correspondence/forms/DomainFormService.groovy (revision 0)
+++ grails-app/services/com/lemans/correspondence/forms/DomainFormService.groovy (revision 75c1874d6dd0213527d1449d619d18ef22f15263)
@@ -0,0 +1,77 @@
+package com.lemans.correspondence.forms
+
+import grails.transaction.Transactional
+
+import java.sql.Clob
+
+import com.lemans.services.LemansService
+
+@Transactional(readOnly = true)
+class DomainFormService extends LemansService {
+
+ private final DomainFormFormTransformer transformer = new DomainFormFormTransformer()
+
+ /**
+ * Finds a form for a domain by formKey.
+ *
+ * @param criteria containing domainId and formKey
+ *
+ * @return Map containing form data
+ */
+ Map findForm(Map criteria) {
+ criteria.sorting = criteria.sorting ?: 'formName'
+ Map data = dqx(criteria).executeOneFrom('dbo.vwDomainForm', formClauses(criteria))
+ transformformDetailXmlClobToMap(data.results)
+ data.results[0]
+ }
+
+ /**
+ * Finds forms for a domain.
+ *
+ * @param criteria containing domainId
+ *
+ * @return Map containing results and totalRecords
+ */
+ Map findForms(Map criteria) {
+ criteria.sorting = criteria.sorting ?: 'formName'
+ Map data = dqx(criteria).executeFrom('dbo.vwDomainForm', ['domainId = :domainId'])
+ transformformDetailXmlClobToMap(data.results)
+ data
+ }
+
+ /**
+ * Finds all the supported formTypes.
+ *
+ * @return List of formTypes
+ */
+ List allFormTypes() {
+ sql().rows('SELECT * FROM dbo.vwDomainFormType')
+ }
+
+ /**
+ * Finds a formType by id.
+ *
+ * @param id
+ *
+ * @return Map with formType data
+ */
+ Map formTypeById(Integer id) {
+ sql().firstRow('SELECT * FROM dbo.vwDomainFormType WHERE domainFormTypeId = ?', id)
+ }
+
+ private List formClauses(Map criteria) {
+ List clauses = ['domainId = :domainId']
+ if (criteria.formKey) { clauses << 'formKey = :formKey' }
+ if (criteria.id) { clauses << 'domainFormId = :id' }
+ clauses
+ }
+
+ private void transformformDetailXmlClobToMap(List forms) {
+ forms.each { Map form ->
+ Clob clob = form.formDetailXml
+ String xml = clob.characterStream.text
+ form.form = transformer.formFromXml(xml)
+ form.remove 'formDetailXml'
+ }
+ }
+}
Index: grails-app/services/com/lemans/correspondence/forms/EmailService.groovy
===================================================================
diff -u
--- grails-app/services/com/lemans/correspondence/forms/EmailService.groovy (revision 0)
+++ grails-app/services/com/lemans/correspondence/forms/EmailService.groovy (revision 75c1874d6dd0213527d1449d619d18ef22f15263)
@@ -0,0 +1,55 @@
+package com.lemans.correspondence.forms
+
+import grails.transaction.Transactional
+import groovy.text.markup.MarkupTemplateEngine
+import groovy.text.markup.TemplateConfiguration
+
+import javax.mail.internet.InternetAddress
+import javax.mail.internet.MimeMessage
+import javax.mail.internet.MimeMessage.RecipientType
+
+@Transactional
+class EmailService {
+
+
+ def mailSender
+
+ def formTemplate = '''
+ html {
+ head {
+ title('formSubmit')
+ }
+ body{
+ h2("$formName form:")
+ table(border:1){
+ tr{
+ th("FieldName")
+ th("FieldData")
+ }
+ formData.each { data ->
+ tr{
+ td("$data.key")
+ td("$data.value")
+ }
+ }
+ }
+
+ }
+ }
+ '''
+
+ def sendFormSubmitEmail(Map formData, Map emailData) {
+ TemplateConfiguration config = new TemplateConfiguration()
+ MarkupTemplateEngine engine = new MarkupTemplateEngine(config)
+ Map templateData = [formData: formData, formName: emailData.subject]
+ String body = engine.createTemplate(formTemplate).make(templateData).writeTo(new StringWriter())
+ MimeMessage mimeMessage = mailSender.createMimeMessage()
+ mimeMessage.setRecipient(RecipientType.TO, new InternetAddress(emailData.to))
+ mimeMessage.setFrom(new InternetAddress(emailData.from))
+ mimeMessage.setSubject(emailData.subject)
+ mimeMessage.setContent(body, 'text/html')
+ mailSender.send(mimeMessage)
+ }
+
+
+}
Index: grails-app/services/com/lemans/correspondence/forms/FormSubmitService.groovy
===================================================================
diff -u
--- grails-app/services/com/lemans/correspondence/forms/FormSubmitService.groovy (revision 0)
+++ grails-app/services/com/lemans/correspondence/forms/FormSubmitService.groovy (revision 75c1874d6dd0213527d1449d619d18ef22f15263)
@@ -0,0 +1,67 @@
+package com.lemans.correspondence.forms
+
+import com.lemans.services.LemansService
+import grails.transaction.Transactional
+import groovy.xml.MarkupBuilder
+
+@Transactional
+class FormSubmitService extends LemansService {
+
+ def emailService
+
+ def correspondenceEmailDomain
+
+ private final DomainFormSubmissionValidator validator = new DomainFormSubmissionValidator()
+
+ static final String FORM_INSERT_SQL = '''
+ EXEC dbo.spInsertFormRequest
+ @domainFormId = :domainFormId,
+ @domainId = :domainId,
+ @userId = :userId,
+ @emailAddress = :emailAddress,
+ @requestXml = :xml
+ '''
+
+ List saveSubmittedForm(Map domainForm, Map formData) {
+ List errors = validator.validate(domainForm.form.fields, formData)
+ if (errors) { return errors }
+
+ String formXml = submitFromToXML(formData)
+ // store form data to database
+ Map criteria = [
+ domainFormId: domainForm.domainFormId,
+ domainId: domainForm.domainId,
+ emailAddress: formData.emailAddress,
+ xml: formXml
+ ]
+
+ List response = sql().rows(criteria, FORM_INSERT_SQL)
+ //send formData as email to domainForm recipients.
+ Map emailData = constructEmailData(response[0], domainForm)
+ emailService.sendFormSubmitEmail(formData, emailData)
+
+ errors
+ }
+
+ private Map constructEmailData(Map dbResponse, Map domainForm) {
+ [
+ to: dbResponse.responderGuid + '@' + correspondenceEmailDomain,
+ from: dbResponse.requesterGuid + '@' + correspondenceEmailDomain,
+ subject: domainForm.formName
+ ]
+
+ }
+
+ private String submitFromToXML(domainForm) {
+
+ StringWriter writer = new StringWriter()
+ new MarkupBuilder(writer).form {
+ domainForm.each { key, value ->
+ "$key"(value)
+ }
+ }
+ writer.toString()
+ }
+
+
+}
Index: grails-app/views/index.gson
===================================================================
diff -u
--- grails-app/views/index.gson (revision 0)
+++ grails-app/views/index.gson (revision 75c1874d6dd0213527d1449d619d18ef22f15263)
@@ -0,0 +1,3 @@
+json {
+ results 'correspondence-service'
+}
\ No newline at end of file
Index: src/integration-test/groovy/com/lemans/correspondence/CorrespondenceFuncSpec.groovy
===================================================================
diff -u
--- src/integration-test/groovy/com/lemans/correspondence/CorrespondenceFuncSpec.groovy (revision 0)
+++ src/integration-test/groovy/com/lemans/correspondence/CorrespondenceFuncSpec.groovy (revision 75c1874d6dd0213527d1449d619d18ef22f15263)
@@ -0,0 +1,19 @@
+package com.lemans.correspondence
+
+
+import com.lemans.testing.LemansApiFunctionalSpec
+import correspondence.service.Application
+import grails.test.mixin.integration.Integration
+import groovyx.net.http.HTTPBuilder
+
+@Integration(applicationClass = Application)
+abstract class CorrespondenceFuncSpec extends LemansApiFunctionalSpec {
+
+ @Override
+ final String serviceName() { 'correspondence-service' }
+
+ def setup() {
+ port = serverPort
+ http = new HTTPBuilder(url())
+ }
+}
Index: src/integration-test/groovy/com/lemans/correspondence/forms/DomainFormFuncSpec.groovy
===================================================================
diff -u
--- src/integration-test/groovy/com/lemans/correspondence/forms/DomainFormFuncSpec.groovy (revision 0)
+++ src/integration-test/groovy/com/lemans/correspondence/forms/DomainFormFuncSpec.groovy (revision 75c1874d6dd0213527d1449d619d18ef22f15263)
@@ -0,0 +1,160 @@
+package com.lemans.correspondence.forms
+
+import com.lemans.correspondence.CorrespondenceFuncSpec
+
+class DomainFormFuncSpec extends CorrespondenceFuncSpec {
+
+ @Override
+ String resourceName() { 'form' }
+
+ private static final String UPDATE_FORMKEY = 'THOR_CU'
+ private static final String DELETE_FORMKEY = 'THOR_CU___OLD'
+
+ def 'can create a valid DomainForm'() {
+ given:
+ int domain = 18
+ int typeId = 1
+ String key = 'zzz_' + new Date()
+ String name = 'Thor Contact Us'
+ path(domain: domain)
+ String json =
+"""
+{
+ "domainFormTypeId": "$typeId",
+ "formName": "$name",
+ "formKey": "$key",
+ "form": {
+ "fields": [
+ { "name": "firstName", "required": false },
+ { "name": "lastName", "required": true, "matches": "[A-Za-z0-9]+", "min": 3, "max": 50 },
+ { "name": "emailAddress", "type": "EMAIL" }
+ ],
+ "recipients": [
+ { "name": "Buddy Guy", "email": "bguy@parts-unltd.com" },
+ { "name": "Jonny Lang", "email": "jlang@parts-unltd.com" }
+ ]
+ }
+}
+"""
+ ok()
+
+ when:
+ post(json)
+
+ then:
+ with(payload.results) {
+ domainId == domain
+ domainFormTypeId == typeId
+ formName == name
+ formKey == key
+ form.fields.size() == 3
+ form.recipients.size() == 2
+ }
+ }
+
+ def 'can NOT create an invalid DomainForm'() {
+ given:
+ int domain = 18
+ path(domain: domain)
+ String json = '''{ "domainFormTypeId": 1, "formKey": "whatever", "form": { } }'''
+ invalid()
+
+ when:
+ post(json)
+
+ then:
+ with(payload.messages) {
+ it[0].type == 'error'
+ it[0].text == 'At least 1 field is required'
+ it[1].type == 'error'
+ it[1].text == 'At least 1 recipient is required'
+ size() == 2
+ }
+ }
+
+ def 'can NOT create a DomainForm with duplicate field names'() {
+ given:
+ int domain = 18
+ path(domain: domain)
+ String json =
+'''
+{
+ "domainFormTypeId": 1,
+ "formKey": "whatever",
+ "formName": "what ever",
+ "form": {
+ "fields": [
+ { "name": "firstName" },
+ { "name": "firstName" },
+ ],
+ "recipients": [
+ { "name": "Buddy Guy", "email": "bguy@parts-unltd.com" },
+ { "name": "Jonny Lang", "email": "bguy@parts-unltd.com" }
+ ]
+ }
+}
+'''
+ invalid()
+
+ when:
+ post(json)
+
+ then:
+ with(payload.messages) {
+ it[0].type == 'error'
+ it[0].text == 'Field name must be unique'
+ it[1].type == 'error'
+ it[1].text == 'Recipient email must be unique'
+ size() == 2
+ }
+ }
+
+ def 'can NOT update an invalid DomainForm'() {
+ given:
+ path(domain: 18, UPDATE_FORMKEY)
+ String json = '{ "formName": "01234567890123456789012345678901234567890123456789______" }'
+ invalid()
+
+ when:
+ put(json)
+
+ then:
+ with(payload.messages[0]) {
+ type == 'error'
+ field == 'formName'
+ text.contains 'exceeds the maximum size'
+ }
+ payload.messages.size() == 1
+ }
+
+ def 'can update a valid DomainForm'() {
+ given:
+ int domain = 18
+ String name = 'XYZ_' + new Date()
+ path(domain: domain, UPDATE_FORMKEY)
+ String json = """{ "formName": "$name" }"""
+ ok()
+
+ when:
+ put(json)
+
+ then:
+ with(payload.results) {
+ formName == name
+ formKey == UPDATE_FORMKEY
+ domainId == domain
+ }
+ }
+
+ def 'can delete a DomainForm'() {
+ given:
+ path(domain: 18, DELETE_FORMKEY)
+ ok()
+
+ when:
+ delete()
+
+ then:
+ !payload
+ }
+}
Index: src/integration-test/groovy/com/lemans/correspondence/forms/DomainFormQueryFuncSpec.groovy
===================================================================
diff -u
--- src/integration-test/groovy/com/lemans/correspondence/forms/DomainFormQueryFuncSpec.groovy (revision 0)
+++ src/integration-test/groovy/com/lemans/correspondence/forms/DomainFormQueryFuncSpec.groovy (revision 75c1874d6dd0213527d1449d619d18ef22f15263)
@@ -0,0 +1,62 @@
+package com.lemans.correspondence.forms
+
+import com.lemans.correspondence.CorrespondenceFuncSpec
+
+class DomainFormQueryFuncSpec extends CorrespondenceFuncSpec {
+
+ @Override
+ String resourceName() { 'form' }
+
+ def 'can find all the forms for a domain'() {
+ given:
+ int domainId = 18
+ path(domain: domainId)
+ ok()
+
+ when:
+ get()
+
+ then:
+ with(payload.results) {
+ size() >= 1
+ domainFormId.every { it != null }
+ domainId.every { it == domainId }
+ formKey.every { it != null }
+ domainFormTypeId.every { it != null }
+ form.every { it }
+ }
+ payload.meta.totalRecords >= 1
+ }
+
+ def 'can find a form for a domain by formKey'() {
+ given:
+ int theDomainId = 18
+ String key = 'THOR_CU'
+ path(domain: theDomainId, "$key")
+ ok()
+
+ when:
+ get()
+
+ then:
+ with(payload.results) {
+ domainId == theDomainId
+ formKey == key
+ form.fields.size()
+ form.recipients.size()
+ }
+ }
+
+ def 'can NOT find a form for a domain by id that does not exist'() {
+ given:
+ path(domain: 18, '_ffg_')
+ notFound()
+
+ when:
+ get()
+
+ then:
+ payload == [:]
+ }
+}
+
Index: src/integration-test/groovy/com/lemans/correspondence/forms/DomainFormTypeFuncSpec.groovy
===================================================================
diff -u
--- src/integration-test/groovy/com/lemans/correspondence/forms/DomainFormTypeFuncSpec.groovy (revision 0)
+++ src/integration-test/groovy/com/lemans/correspondence/forms/DomainFormTypeFuncSpec.groovy (revision 75c1874d6dd0213527d1449d619d18ef22f15263)
@@ -0,0 +1,50 @@
+package com.lemans.correspondence.forms
+
+import com.lemans.correspondence.CorrespondenceFuncSpec
+
+class DomainFormTypeFuncSpec extends CorrespondenceFuncSpec {
+
+ @Override
+ String resourceName() { 'form/type' }
+
+ def 'can find all the formTypes'() {
+ given:
+ path()
+ ok()
+
+ when:
+ get()
+
+ then:
+ with(payload) {
+ results.size() >= 1
+ }
+ }
+
+ def 'can find a formType by id'() {
+ given:
+ path('1')
+ ok()
+
+ when:
+ get()
+
+ then:
+ with(payload) {
+ results.domainFormTypeId == 1
+ results.typeName == 'Contact Us'
+ }
+ }
+
+ def 'can NOT find a formType by id that does not exist'() {
+ given:
+ path('-1')
+ notFound()
+
+ when:
+ get()
+
+ then:
+ payload == [:]
+ }
+}
Index: src/integration-test/groovy/com/lemans/correspondence/forms/EmailServiceIntegrationSpec.groovy
===================================================================
diff -u
--- src/integration-test/groovy/com/lemans/correspondence/forms/EmailServiceIntegrationSpec.groovy (revision 0)
+++ src/integration-test/groovy/com/lemans/correspondence/forms/EmailServiceIntegrationSpec.groovy (revision 75c1874d6dd0213527d1449d619d18ef22f15263)
@@ -0,0 +1,41 @@
+package com.lemans.correspondence.forms
+
+import grails.test.mixin.integration.Integration
+import spock.lang.Shared
+import spock.lang.Specification
+
+import com.dumbster.smtp.SmtpMessage
+
+import correspondence.service.Application
+
+@Integration(applicationClass = Application)
+class EmailServiceIntegrationSpec extends Specification {
+
+ @Shared
+ def emailService
+
+ @Shared
+ def dumbster
+
+ def setup() { dumbster.reset() }
+
+ def 'can send a test email'() {
+ given:
+ Map formData = [stuff: 'yipee', moreStuff: 'kaiya']
+ Map emailData = [to: 'handler@lemans.com', from: 'initiator@lemans.com', subject: 'contactUs']
+
+ when:
+ emailService.sendFormSubmitEmail(formData, emailData)
+ SmtpMessage msg = dumbster.messages[0]
+
+ then:
+ dumbster.messageCount == 1
+ msg.subject == 'contactUs'
+ msg.tos == ['handler@lemans.com']
+ msg.froms == ['initiator@lemans.com']
+ msg.body.contains 'stuff'
+ msg.body.contains 'yipee'
+ msg.body.contains 'moreStuff'
+ msg.body.contains 'kaiya'
+ }
+}
Index: src/integration-test/groovy/com/lemans/correspondence/forms/FormSubmitFuncSpec.groovy
===================================================================
diff -u
--- src/integration-test/groovy/com/lemans/correspondence/forms/FormSubmitFuncSpec.groovy (revision 0)
+++ src/integration-test/groovy/com/lemans/correspondence/forms/FormSubmitFuncSpec.groovy (revision 75c1874d6dd0213527d1449d619d18ef22f15263)
@@ -0,0 +1,73 @@
+package com.lemans.correspondence.forms
+
+import com.lemans.correspondence.CorrespondenceFuncSpec
+
+/**
+ * Created by arajaraman on 2/18/2016.
+ */
+class FormSubmitFuncSpec extends CorrespondenceFuncSpec {
+
+ @Override
+ String resourceName() { 'submit' }
+
+ private final static String UNKNOWN_USER = 'domainForm is unknown'
+
+ def 'can not create a DomainForm with invalid formKey'() {
+ given:
+ int domain = 18
+ String formKey = 'AVBC'
+ path(domain: domain, form: formKey)
+ String json = '''{ "form_contactName": "test", "form_emailAddress": "att.com" }'''
+
+ notFound()
+
+ when:
+ post(json)
+
+ then:
+ with(payload) {
+ messages.size() == 1
+ messages[0].type == 'error'
+ messages[0].text == UNKNOWN_USER
+ }
+ }
+
+ def 'can not create a DomainForm with invalid data'() {
+ given:
+ int domain = 5
+ String formKey = 'MOOSE_CU'
+ path(domain: domain, form: formKey)
+ String json = '''{ "form_contactName": "test", "form_emailAddress": "att.com" }'''
+
+ invalid()
+
+ when:
+ post(json)
+
+ then:
+ with(payload) {
+ messages.size() == 1
+ messages[0].type == 'error'
+ messages[0].text == 'emailAddress att.com is not a valid email address'
+ messages[0].field == 'emailAddress'
+ }
+ }
+
+
+ def 'can create a DomainForm with valid data'() {
+ given:
+ int domain = 5
+ String formKey = 'MOOSE_CU'
+ path(domain: domain, form: formKey)
+ String json = '''{ "form_1_emailAddress": "att@tt.com", "form_10_contactName": "test", "form_contactMessage": "test" }'''
+
+ ok()
+
+ when:
+ post(json)
+
+ then:
+ payload == [:]
+ }
+
+}
Index: src/integration-test/groovy/com/lemans/security/TokenVerifierFuncSpec.groovy
===================================================================
diff -u
--- src/integration-test/groovy/com/lemans/security/TokenVerifierFuncSpec.groovy (revision 0)
+++ src/integration-test/groovy/com/lemans/security/TokenVerifierFuncSpec.groovy (revision 75c1874d6dd0213527d1449d619d18ef22f15263)
@@ -0,0 +1,22 @@
+package com.lemans.security
+
+import com.lemans.correspondence.CorrespondenceFuncSpec
+
+class TokenVerifierFuncSpec extends CorrespondenceFuncSpec {
+
+ def 'can NOT act without a valid security token'() {
+ given:
+ System.setProperty('ignoreToken', '')
+ domain = null
+ path()
+ expectedStatusCode = 401
+
+ when:
+ get()
+
+ then:
+ with(payload) {
+ messages[0].text == 'no credentials'
+ }
+ }
+}
Index: src/main/groovy/com/lemans/correspondence/forms/DomainFormFieldValidator.groovy
===================================================================
diff -u
--- src/main/groovy/com/lemans/correspondence/forms/DomainFormFieldValidator.groovy (revision 0)
+++ src/main/groovy/com/lemans/correspondence/forms/DomainFormFieldValidator.groovy (revision 75c1874d6dd0213527d1449d619d18ef22f15263)
@@ -0,0 +1,81 @@
+package com.lemans.correspondence.forms
+
+import java.util.regex.PatternSyntaxException
+
+
+class DomainFormFieldValidator {
+
+ static final int NAME_MIN = 3
+ static final int NAME_MAX = 50
+ static final String NAME_REGEX = /[a-zA-Z0-9]+/
+
+ static final String STRING = 'STRING'
+ static final String EMAIL = 'EMAIL'
+ static final String NUMBER = 'NUMBER'
+ static final String BOOLEAN = 'BOOLEAN'
+
+ static final Set TYPES = [STRING, EMAIL, NUMBER, BOOLEAN].asImmutable()
+
+ List validate(Map field) {
+ List errors = []
+ validateName(field, errors)
+ validateType(field, errors)
+ validateMatches(field, errors)
+ if (field.min != null || field.max != null) { validateMinMax(field, errors) }
+ errors
+ }
+
+ private validateName(Map field, List errors) {
+ String name = field.name?.trim()
+ if (!name) { errors << 'name is required' }
+ if (name?.size() < NAME_MIN) { errors << "name minimum length is $NAME_MIN" }
+ if (name?.size() > NAME_MAX) { errors << "name maximum length is $NAME_MAX" }
+ if (!(name ==~ NAME_REGEX)) { errors << 'name may only contain letters and numbers' }
+ }
+
+ private validateType(Map field, List errors) {
+ String type = type(field)
+ if (!(type in TYPES)) {
+ errors << "type $type is not supported [${TYPES.join(',')}]"
+ }
+ }
+
+ private validateMatches(Map field, List errors) {
+ String matches = field.matches?.trim()
+ String type = type(field)
+ if (matches) {
+ if (type == STRING) {
+ try { '' ==~ matches }
+ catch (PatternSyntaxException x) { errors << 'matches REGEX is invalid' }
+ }
+ else { errors << "matches is not supported for type $type" }
+ }
+ }
+
+ private validateMinMax(Map field, List errors) {
+ String type = type(field)
+ if (type == STRING) { validateStringLengths(field, errors) }
+ else if (type == NUMBER) { validateMinMaxNumbers(field, errors) }
+ else { errors << "min and max are not supported for type $type" }
+ }
+
+ private validateStringLengths(Map field, List errors) {
+ Integer min = field.min
+ Integer max = field.max
+ if (min != null && min < 1) { errors << 'min length must be > 0' }
+ if (max != null && max < 1) { errors << 'max length must be > 0' }
+ if (min != null && max != null && max < min) {
+ errors << 'max length must be >= min length'
+ }
+ }
+
+ private validateMinMaxNumbers(Map field, List errors) {
+ Number min = field.min
+ Number max = field.max
+ if (min != null && max != null && max < min) {
+ errors << 'max must be >= min'
+ }
+ }
+
+ private String type(Map field) { field.type?.trim() ?: STRING }
+}
Index: src/main/groovy/com/lemans/correspondence/forms/DomainFormFormTransformer.groovy
===================================================================
diff -u
--- src/main/groovy/com/lemans/correspondence/forms/DomainFormFormTransformer.groovy (revision 0)
+++ src/main/groovy/com/lemans/correspondence/forms/DomainFormFormTransformer.groovy (revision 75c1874d6dd0213527d1449d619d18ef22f15263)
@@ -0,0 +1,49 @@
+package com.lemans.correspondence.forms
+
+import groovy.xml.MarkupBuilder
+
+class DomainFormFormTransformer {
+
+ @SuppressWarnings('UnnecessaryCollectCall')
+ Map formFromXml(String xml) {
+ Node formNode = new XmlParser().parseText(xml)
+ List xmlFields = formNode.fields.collect { it*.attributes() }
+ List xmlRecipients = formNode.recipients.collect { it*.attributes() }
+ [fields: xmlFields[0].collect { createFieldFromXml(it) }, recipients: xmlRecipients[0]]
+ }
+
+ String formToXml(Map form) {
+ StringWriter writer = new StringWriter()
+ new MarkupBuilder(writer).form {
+ fields {
+ form.fields.each { field(createSparselyPopulatedXmlField(it)) }
+ }
+ recipients {
+ form.recipients.each { recipient(name: it.name, email: it.email ) }
+ }
+ }
+ writer
+ }
+
+ private Map createFieldFromXml(xmlField) {
+ Map field = [name: xmlField.name, type: xmlField.type]
+ if (xmlField.required) { field.required = xmlField.required.toBoolean() }
+ if (xmlField.matches) { field.matches = xmlField.matches }
+ if (xmlField.min != null) {
+ field.min = (field.type == 'STRING' ? xmlField.min.toInteger() : xmlField.min.toBigDecimal())
+ }
+ if (xmlField.max != null) {
+ field.max = (xmlField.type == 'STRING' ? xmlField.max.toInteger() : xmlField.max.toBigDecimal())
+ }
+ field
+ }
+
+ private Map createSparselyPopulatedXmlField(Map field) {
+ Map sparseField = [name: field.name, type: field.type ?: DomainFormFieldValidator.STRING]
+ if (field.required) { sparseField.required = field.required }
+ if (field.matches) { sparseField.matches = field.matches }
+ if (field.min != null) { sparseField.min = field.min.toString() }
+ if (field.max != null) { sparseField.max = field.max.toString() }
+ sparseField
+ }
+}
Index: src/main/groovy/com/lemans/correspondence/forms/DomainFormFormValidator.groovy
===================================================================
diff -u
--- src/main/groovy/com/lemans/correspondence/forms/DomainFormFormValidator.groovy (revision 0)
+++ src/main/groovy/com/lemans/correspondence/forms/DomainFormFormValidator.groovy (revision 75c1874d6dd0213527d1449d619d18ef22f15263)
@@ -0,0 +1,48 @@
+package com.lemans.correspondence.forms
+
+import org.springframework.validation.Errors
+
+class DomainFormFormValidator {
+
+ private final DomainFormFieldValidator fieldValidator = new DomainFormFieldValidator()
+
+ private final DomainFormRecipientValidator recipientValidator = new DomainFormRecipientValidator()
+
+ /**
+ * Validates a DomainForm form definition, adding any errors to the global errors on the DomainForm.
+ *
+ * @param form Map
+ * @param domainForm
+ */
+ void validate(Map form, DomainForm domainForm) {
+ Errors errors = domainForm.errors
+ validateFields(form.fields, errors)
+ validateRecipients(form.recipients, errors)
+ }
+
+ private validateFields(List fields, Errors errors) {
+ if (fields) {
+ fields.eachWithIndex { Map field, Integer index ->
+ List msgs = fieldValidator.validate(field)
+ msgs.each { String msg -> reject "Field[$index] $msg", errors }
+ }
+ Map fieldsByName = fields.collectEntries { [(it.name): it] }
+ if (fields.size() > fieldsByName.size()) { reject 'Field name must be unique', errors }
+ }
+ else { reject 'At least 1 field is required', errors }
+ }
+
+ private validateRecipients(List recipients, Errors errors) {
+ if (recipients) {
+ recipients.eachWithIndex { Map recipient, Integer index ->
+ List msgs = recipientValidator.validate(recipient)
+ msgs.each { String msg -> reject "Recipient[$index] $msg", errors }
+ }
+ Map recipientsByEmail = recipients.collectEntries { [(it.email): it] }
+ if (recipients.size() > recipientsByEmail.size()) { reject 'Recipient email must be unique', errors }
+ }
+ else { reject 'At least 1 recipient is required', errors }
+ }
+
+ private reject(String msg, Errors errors) { errors.reject msg, msg }
+}
Index: src/main/groovy/com/lemans/correspondence/forms/DomainFormRecipientValidator.groovy
===================================================================
diff -u
--- src/main/groovy/com/lemans/correspondence/forms/DomainFormRecipientValidator.groovy (revision 0)
+++ src/main/groovy/com/lemans/correspondence/forms/DomainFormRecipientValidator.groovy (revision 75c1874d6dd0213527d1449d619d18ef22f15263)
@@ -0,0 +1,29 @@
+package com.lemans.correspondence.forms
+
+import org.apache.commons.validator.routines.EmailValidator
+
+class DomainFormRecipientValidator {
+
+ static final int NAME_MIN = 5
+ static final int NAME_MAX = 50
+
+ List validate(Map recipient) {
+ List errors = []
+ validateName(recipient, errors)
+ validateEmail(recipient, errors)
+ errors
+ }
+
+ private validateName(Map recipient, List errors) {
+ String name = recipient.name?.trim()
+ if (!name) { errors << 'name is required' }
+ if (name?.size() < NAME_MIN) { errors << "name minimum length is $NAME_MIN" }
+ if (name?.size() > NAME_MAX) { errors << "name maximum length is $NAME_MAX" }
+ }
+
+ private validateEmail(Map recipient, List errors) {
+ String email = recipient.email?.trim()
+ if (!email) { errors << 'email is required' }
+ if (!EmailValidator.instance.isValid(email)) { errors << 'email is invalid' }
+ }
+}
Index: src/main/groovy/com/lemans/correspondence/forms/DomainFormSubmissionValidator.groovy
===================================================================
diff -u
--- src/main/groovy/com/lemans/correspondence/forms/DomainFormSubmissionValidator.groovy (revision 0)
+++ src/main/groovy/com/lemans/correspondence/forms/DomainFormSubmissionValidator.groovy (revision 75c1874d6dd0213527d1449d619d18ef22f15263)
@@ -0,0 +1,83 @@
+package com.lemans.correspondence.forms
+
+import org.apache.commons.validator.routines.EmailValidator
+
+class DomainFormSubmissionValidator {
+
+ /**
+ * Validates a DomainForm submission using the configured validation rules.
+ *
+ * @param fields configuration from the DomainForm
+ * @param submission Map with the form submission data
+ *
+ * @return List of errors which may be empty
+ */
+ List validate(List fields, Map submission) {
+ List errors = []
+ submission.entrySet().each {
+ if (!fields.find { Map field -> field.name == it.key } ) {
+ errors << reject(it.key, 'Unknown field')
+ }
+ }
+ fields.each { Map field ->
+ String name = field.name
+ String value = submission[name]
+ if (field.required && !value) { errors << reject(name, "$name is required") }
+ if (value) {
+ String type = field.type
+ if (type == DomainFormFieldValidator.STRING ) { validateString(errors, field, value) }
+ else if (type == DomainFormFieldValidator.EMAIL) { validateEmail(errors, field, value) }
+ else if (type == DomainFormFieldValidator.NUMBER ) { validateNumber(errors, field, value) }
+ else if (type == DomainFormFieldValidator.BOOLEAN) { validateBoolean(errors, field, value) }
+ }
+ }
+ errors
+ }
+
+ private void validateString(List errors, Map field, String value) {
+ String matches = field.matches
+ if (matches && !(value ==~ matches)) {
+ errors << reject(field.name, "$field.name $value is invalid")
+ }
+ Integer min = field.min?.toInteger()
+ if (min != null && value.size() < min) {
+ errors << reject(field.name, "$field.name $value length must be at least $field.min")
+ }
+ Integer max = field.max?.toInteger()
+ if (max != null && value.size() > max) {
+ errors << reject(field.name, "$field.name $value length must be at most $field.max")
+ }
+ }
+
+ private void validateEmail(List errors, Map field, String value) {
+ if (!EmailValidator.instance.isValid(value)) {
+ errors << reject(field.name, "$field.name $value is not a valid email address")
+ }
+ }
+
+ private void validateNumber(List errors, Map field, String value) {
+ if (value.isBigDecimal()) {
+ BigDecimal number = value.toBigDecimal()
+ BigDecimal min = field.min?.toBigDecimal()
+ if (min != null && number < min) {
+ errors << reject(field.name, "$field.name $value must be at least $field.min")
+ }
+ BigDecimal max = field.max?.toBigDecimal()
+ if (max != null && number > max) {
+ errors << reject(field.name, "$field.name $value must be at most $field.max")
+ }
+ }
+ else { errors << reject(field.name, "$field.name $value must be a number") }
+ }
+
+ private void validateBoolean(List errors, Map field, String value) {
+ if (value != 'true' && value != 'false') {
+ errors << reject(field.name, "$field.name must be true or false")
+ }
+ }
+
+
+ private Map reject(String name, String message) {
+ [type: 'error', field: name, text: message]
+ }
+}
Index: src/main/groovy/grails/plugin/dumbster/Dumbster.groovy
===================================================================
diff -u
--- src/main/groovy/grails/plugin/dumbster/Dumbster.groovy (revision 0)
+++ src/main/groovy/grails/plugin/dumbster/Dumbster.groovy (revision 75c1874d6dd0213527d1449d619d18ef22f15263)
@@ -0,0 +1,92 @@
+package grails.plugin.dumbster
+
+import org.springframework.mail.javamail.JavaMailSenderImpl
+
+import com.dumbster.smtp.SimpleSmtpServer
+import com.dumbster.smtp.SmtpMessage
+
+/**
+ * @author Burt Beckwith
+ */
+class Dumbster {
+
+ def grailsApplication
+
+ Integer port
+
+ protected SimpleSmtpServer server
+
+ /**
+ * Starts the server; called by Spring, so shouldn't be called directly.
+ */
+ @SuppressWarnings('Println')
+ void start() {
+
+ def conf = grailsApplication.config.dumbster
+
+ if (conf.port) {
+ port = conf.port
+ }
+ else {
+ port = 1025
+ while (true) {
+ try {
+ new ServerSocket(port).close()
+
+ // update the mail plugin's JavaMailSender if available
+ if (grailsApplication.mainContext.containsBean('mailSender')) {
+ JavaMailSenderImpl mailSender = grailsApplication.mainContext.mailSender
+ mailSender.port = port
+ }
+
+ break
+ }
+ catch (IOException e) {
+ port++
+ if (port > 10000) {
+ throw new BindException('Cannot find open port for Dumbster SMTP server')
+ }
+ }
+ }
+ }
+ println "Dumbster is using port $port"
+ server = SimpleSmtpServer.start(port)
+ }
+
+ /**
+ * Stops the server; called by Spring, so shouldn't be called directly.
+ */
+ void stop() {
+ server?.stop()
+ }
+
+ /**
+ * Remove all sent emails. Call this in the setUp() method in your integration tests.
+ */
+ void reset() {
+ if (!server) { return }
+
+ for (Iterator iter = server.receivedEmail; iter.hasNext(); ) {
+ iter.next()
+ iter.remove()
+ }
+ }
+
+ /**
+ * Check if stopped.
+ * @return true
if the server was stopped (or never started)
+ */
+ boolean isStopped() { server ? server.stopped : true }
+
+ /**
+ * Get all current messages.
+ * @return the messages
+ */
+ List getMessages() { server ? server.receivedEmail.collect { it } : [] }
+
+ /**
+ * Get the number of sent messages.
+ * @return the number
+ */
+ int getMessageCount() { server ? server.receivedEmailSize : 0 }
+}
Index: src/test/groovy/com/lemans/correspondence/forms/DomainFormFieldValidatorSpec.groovy
===================================================================
diff -u
--- src/test/groovy/com/lemans/correspondence/forms/DomainFormFieldValidatorSpec.groovy (revision 0)
+++ src/test/groovy/com/lemans/correspondence/forms/DomainFormFieldValidatorSpec.groovy (revision 75c1874d6dd0213527d1449d619d18ef22f15263)
@@ -0,0 +1,142 @@
+package com.lemans.correspondence.forms
+
+
+import spock.lang.Specification
+import spock.lang.Unroll
+
+@SuppressWarnings('CyclomaticComplexity')
+class DomainFormFieldValidatorSpec extends Specification {
+
+ DomainFormFieldValidator validator = new DomainFormFieldValidator()
+
+ Map field = [name: 'ab1']
+
+ def 'a valid recipient has no errors'() {
+ expect:
+ validator.validate(field) == []
+ }
+
+ @Unroll
+ def 'name with value #value validation error is #error'() {
+ given:
+ field.name = value
+
+ expect:
+ error == findError(validator.validate(field), error)
+
+ where:
+ value || error
+ null || 'name is required'
+ ' ' || 'name is required'
+ 'a1' || 'name minimum length is 3'
+ 'ab*' || 'name may only contain letters and numbers'
+ 'ab' || 'name minimum length is 3'
+ '50chars'.padRight(50, 'X') || null
+ '51chars'.padRight(51, 'X') || 'name maximum length is 50'
+ }
+
+ @Unroll
+ def 'type with value #value validation error is #error'() {
+ given:
+ field.type = value
+
+ expect:
+ error == findError(validator.validate(field), error)
+
+ where:
+ value || error
+ null || null
+ 'STRING' || null
+ 'EMAIL' || null
+ 'NUMBER' || null
+ 'BOOLEAN' || null
+ 'WHATEVER' || 'type WHATEVER is not supported [STRING,EMAIL,NUMBER,BOOLEAN]'
+ }
+
+ @Unroll
+ def 'matches with value #value and type #type validation error is #error'() {
+ given:
+ field.type = type
+ field.matches = value
+
+ expect:
+ error == findError(validator.validate(field), error)
+
+ where:
+ value || type || error
+ null || null || null
+ /a/ || null || null
+ /[/ || null || 'matches REGEX is invalid'
+ null || 'STRING' || null
+ /a/ || 'STRING' || null
+ /[/ || 'STRING' || 'matches REGEX is invalid'
+ null || 'EMAIL' || null
+ /a/ || 'EMAIL' || 'matches is not supported for type EMAIL'
+ null || 'NUMBER' || null
+ /a/ || 'NUMBER' || 'matches is not supported for type NUMBER'
+ null || 'BOOLEAN' || null
+ /a/ || 'BOOLEAN' || 'matches is not supported for type BOOLEAN'
+ }
+
+ @Unroll
+ def 'min #min and max #max and type STRING validation error is #error'() {
+ given:
+ field.min = min
+ field.max = max
+
+ expect:
+ error == findError(validator.validate(field), error)
+
+ where:
+ min || max || error
+ null || null || null
+ 0 || null || 'min length must be > 0'
+ 1 || null || null
+ null || 0 || 'max length must be > 0'
+ null || 1 || null
+ 50 || 49 || 'max length must be >= min length'
+ }
+
+ @Unroll
+ def 'min #min and max #max and type NUMBER validation error is #error'() {
+ given:
+ field.type = 'NUMBER'
+ field.min = min
+ field.max = max
+
+ expect:
+ error == findError(validator.validate(field), error)
+
+ where:
+ min || max || error
+ null || null || null
+ 100 || null || null
+ 0 || null || null
+ -1 || null || null
+ null || 100 || null
+ null || 0 || null
+ null || -1 || null
+ 50 || 49 || 'max must be >= min'
+ -50 || -51 || 'max must be >= min'
+ }
+
+ @Unroll
+ def 'min #min and max #max for unsupported type validation error is #error'() {
+ given:
+ field.type = type
+ field.min = 10
+ field.max = 50
+
+ expect:
+ error == findError(validator.validate(field), error)
+
+ where:
+ type || error
+ 'EMAIL' || 'min and max are not supported for type EMAIL'
+ 'BOOLEAN' || 'min and max are not supported for type BOOLEAN'
+ }
+
+ private String findError(List errors, String error) {
+ errors.find { it.contains(error ?: '') }
+ }
+}
Index: src/test/groovy/com/lemans/correspondence/forms/DomainFormRecipientValidatorSpec.groovy
===================================================================
diff -u
--- src/test/groovy/com/lemans/correspondence/forms/DomainFormRecipientValidatorSpec.groovy (revision 0)
+++ src/test/groovy/com/lemans/correspondence/forms/DomainFormRecipientValidatorSpec.groovy (revision 75c1874d6dd0213527d1449d619d18ef22f15263)
@@ -0,0 +1,54 @@
+package com.lemans.correspondence.forms
+
+
+import spock.lang.Specification
+import spock.lang.Unroll
+
+class DomainFormRecipientValidatorSpec extends Specification {
+
+ DomainFormRecipientValidator validator = new DomainFormRecipientValidator()
+
+ Map recipient = [name: 'abcde', email: 'abc@xyz.com']
+
+ def 'a valid recipient has no errors'() {
+ expect:
+ validator.validate(recipient) == []
+ }
+
+ @Unroll
+ def 'name with value #value validation error is #error'() {
+ given:
+ recipient.name = value
+
+ expect:
+ error == findError(validator.validate(recipient), error)
+
+ where:
+ value || error
+ null || 'name is required'
+ ' ' || 'name is required'
+ 'abcd' || 'name minimum length is 5'
+ '50chars'.padRight(50, '_') || null
+ '51chars'.padRight(51, '_') || 'name maximum length is 50'
+ }
+
+ @Unroll
+ def 'email with value #value validation error is #error'() {
+ given:
+ recipient.email = value
+
+ expect:
+ error == findError(validator.validate(recipient), error)
+
+ where:
+ value || error
+ null || 'email is required'
+ ' ' || 'email is required'
+ 'xxx@whatever.com' || null
+ 'whatever' || 'email is invalid'
+ }
+
+ private String findError(List errors, String error) {
+ errors.find { it.contains(error ?: '') }
+ }
+}
Index: src/test/groovy/com/lemans/correspondence/forms/DomainFormSpec.groovy
===================================================================
diff -u
--- src/test/groovy/com/lemans/correspondence/forms/DomainFormSpec.groovy (revision 0)
+++ src/test/groovy/com/lemans/correspondence/forms/DomainFormSpec.groovy (revision 75c1874d6dd0213527d1449d619d18ef22f15263)
@@ -0,0 +1,44 @@
+package com.lemans.correspondence.forms
+
+import com.lemans.testing.Auditable32Spec
+import grails.test.mixin.TestFor
+import spock.lang.Unroll
+
+import com.lemans.correspondence.Domain
+
+@TestFor(DomainForm)
+class DomainFormSpec extends Auditable32Spec {
+
+ @Override
+ protected getAuditable() { form }
+
+ DomainForm form = new DomainForm(
+ domain: new Domain(),
+ formKey: 'whatever',
+ formDetailXml: ''
+ )
+
+ def 'a valid DomainForm has no errors'() {
+ expect:
+ auditable.validate()
+ }
+
+ @Unroll
+ def 'formName with value #value validation error is #error'() {
+ given:
+ auditable.formName = value
+
+ when:
+ auditable.validate()
+
+ then:
+ auditable.errors['formName']?.code == error
+
+ where:
+ value || error
+ null || null
+ 'a' || null
+ '50chars'.padRight(50, '_') || null
+ '51chars'.padRight(51, '_') || MAX_SIZE
+ }
+}
Index: src/test/groovy/com/lemans/correspondence/forms/DomainFormSubmissionValidatorSpec.groovy
===================================================================
diff -u
--- src/test/groovy/com/lemans/correspondence/forms/DomainFormSubmissionValidatorSpec.groovy (revision 0)
+++ src/test/groovy/com/lemans/correspondence/forms/DomainFormSubmissionValidatorSpec.groovy (revision 75c1874d6dd0213527d1449d619d18ef22f15263)
@@ -0,0 +1,236 @@
+package com.lemans.correspondence.forms
+
+import spock.lang.Specification
+import spock.lang.Unroll
+
+@SuppressWarnings('CyclomaticComplexity')
+class DomainFormSubmissionValidatorSpec extends Specification {
+
+ DomainFormSubmissionValidator validator = new DomainFormSubmissionValidator()
+
+ def 'can NOT submit with unknown submission fields'() {
+ given:
+ Map field0 = [name: 'abc']
+ Map field1 = [name: 'def']
+ List fields = [field0, field1]
+ Map submission = [tuv: '0', xyz: 'A']
+
+ when:
+ List errors = validator.validate(fields, submission)
+
+ then:
+ errors[0] == [type: 'error', field: 'tuv', text: 'Unknown field']
+ errors[1] == [type: 'error', field: 'xyz', text: 'Unknown field']
+ errors.size() == 2
+ }
+
+ @Unroll('field of type #type with value #value and required #required validation error is #error')
+ def 'can validate a required value in a submission'() {
+ given:
+ String fieldName = 'abc'
+ Map field = [name: fieldName, type: type, required: required]
+ Map submission = [(fieldName): value]
+
+ expect:
+ List errors = validator.validate([field], submission)
+ errors.find { it.field == fieldName }?.text == error
+
+ where:
+ value | type | required || error
+ '' | 'STRING' | false || null
+ '' | 'STRING' | true || 'abc is required'
+ 'x' | 'STRING' | true || null
+ '' | 'EMAIL' | false || null
+ '' | 'EMAIL' | true || 'abc is required'
+ 'a@b.co' | 'EMAIL' | true || null
+ '' | 'NUMBER' | false || null
+ '' | 'NUMBER' | true || 'abc is required'
+ '6.9' | 'NUMBER' | true || null
+ '' | 'BOOLEAN' | false || null
+ '' | 'BOOLEAN' | true || 'abc is required'
+ 'false' | 'BOOLEAN' | true || null
+ 'true' | 'BOOLEAN' | true || null
+ }
+
+ @Unroll('field of type STRING matches a regex with value #value validation error is #error')
+ def 'can validate a STRING value matches a regex in a submission'() {
+ given:
+ String fieldName = 'whatever'
+ Map field = [name: fieldName, type: 'STRING', matches: /[A-Z]+/]
+ Map submission = [(fieldName): value]
+
+ expect:
+ List errors = validator.validate([field], submission)
+ errors.find { it.field == fieldName }?.text == error
+
+ where:
+ value || error
+ '' || null
+ ' ' || 'whatever is invalid'
+ 'A' || null
+ '0' || 'whatever 0 is invalid'
+ '$' || 'whatever $ is invalid'
+ 'Z' || null
+ }
+
+ @Unroll('field of type STRING has a min length with value #value validation error is #error')
+ def 'can validate a STRING value with a min length in a submission'() {
+ given:
+ String fieldName = 'whatever'
+ Map field = [name: fieldName, type: 'STRING', min: min]
+ Map submission = [(fieldName): value]
+
+ expect:
+ List errors = validator.validate([field], submission)
+ errors.find { it.field == fieldName }?.text == error
+
+ where:
+ value | min || error
+ '' | null || null
+ ' ' | 3 || 'whatever length must be at least 3'
+ 'ab' | 3 || 'whatever ab length must be at least 3'
+ 'abc' | 3 || null
+ 'abcd' | 3 || null
+ }
+
+ @Unroll('field of type STRING has a max length with value #value validation error is #error')
+ def 'can validate a STRING value with a max length in a submission'() {
+ given:
+ String fieldName = 'whatever'
+ Map field = [name: fieldName, type: 'STRING', max: max]
+ Map submission = [(fieldName): value]
+
+ expect:
+ List errors = validator.validate([field], submission)
+ errors.find { it.field == fieldName }?.text == error
+
+ where:
+ value | max || error
+ '' | null || null
+ 'abcd' | 3 || 'whatever abcd length must be at most 3'
+ 'abcdef' | 3 || 'whatever abcdef length must be at most 3'
+ 'abc' | 3 || null
+ ' ' | 3 || null
+ }
+
+ @Unroll('field of type EMAIL with value #value validation error is #error')
+ def 'can validate an EMAIL value in a submission'() {
+ given:
+ String fieldName = 'emailAddress'
+ Map field = [name: fieldName, type: 'EMAIL']
+ Map submission = [(fieldName): value]
+
+ expect:
+ List errors = validator.validate([field], submission)
+ errors.find { it.field == fieldName }?.text == error
+
+ where:
+ value || error
+ '' || null
+ ' ' || 'emailAddress is not a valid email address'
+ 'x' || 'emailAddress x is not a valid email address'
+ 'a@b.co' || null
+ }
+
+ @Unroll('field of type BOOLEAN value #value validation error is #error')
+ def 'can validate a BOOLEAN value in a submission'() {
+ given:
+ String fieldName = 'interested'
+ Map field = [name: fieldName, type: 'BOOLEAN']
+ Map submission = [(fieldName): value]
+
+ expect:
+ List errors = validator.validate([field], submission)
+ errors.find { it.field == fieldName }?.text == error
+
+ where:
+ value || error
+ '' || null
+ '0' || 'interested must be true or false'
+ '1' || 'interested must be true or false'
+ 'f' || 'interested must be true or false'
+ 't' || 'interested must be true or false'
+ 'false' || null
+ 'true' || null
+ }
+
+ @Unroll('field of type NUMBER with value #value validation error is #error')
+ def 'can validate a NUMBER value in a submission'() {
+ given:
+ String fieldName = 'count'
+ Map field = [name: fieldName, type: 'NUMBER']
+ Map submission = [(fieldName): value]
+
+ expect:
+ List errors = validator.validate([field], submission)
+ errors.find { it.field == fieldName }?.text == error
+
+ where:
+ value || error
+ '' || null
+ ' ' || 'count must be a number'
+ 'a' || 'count a must be a number'
+ '-10' || null
+ '-10.6' || null
+ '0' || null
+ '0.0' || null
+ '10' || null
+ '10.6' || null
+ }
+
+ @Unroll('field of type NUMBER with a min #min and value #value validation error is #error')
+ def 'can validate a min NUMBER value in a submission'() {
+ given:
+ String fieldName = 'val'
+ Map field = [name: fieldName, type: 'NUMBER', min: min]
+ Map submission = [(fieldName): value]
+
+ expect:
+ List errors = validator.validate([field], submission)
+ errors.find { it.field == fieldName }?.text == error
+
+ where:
+ value | min || error
+ '' | -10 || null
+ '' | 0 || null
+ '' | 10 || null
+ '-10.1' | -10.0 || 'val -10.1 must be at least -10.0'
+ '-10.0' | -10.0 || null
+ '-9.9' | -10.0 || null
+ '-0.1' | 0 || 'val -0.1 must be at least 0'
+ '0.0' | 0 || null
+ '0.1' | 0 || null
+ '9.9' | 10 || 'val 9.9 must be at least 10'
+ '10.0' | 10 || null
+ '10.1' | 10 || null
+ }
+
+ @Unroll('field of type NUMBER with a max #max and value #value validation error is #error')
+ def 'can validate a max NUMBER value in a submission'() {
+ given:
+ String fieldName = 'val'
+ Map field = [name: fieldName, type: 'NUMBER', max: max]
+ Map submission = [(fieldName): value]
+
+ expect:
+ List errors = validator.validate([field], submission)
+ errors.find { it.field == fieldName }?.text == error
+
+ where:
+ value | max || error
+ '' | -10 || null
+ '' | 0 || null
+ '' | 10 || null
+ '-10.1' | -10.0 || null
+ '-10.0' | -10.0 || null
+ '-9.9' | -10.0 || 'val -9.9 must be at most -10.0'
+ '-0.1' | 0 || null
+ '0.0' | 0.0 || null
+ '0.1' | 0 || 'val 0.1 must be at most 0'
+ '9.9' | 10 || null
+ '10.0' | 10 || null
+ '10.1' | 10 || 'val 10.1 must be at most 10'
+ }
+
+
+}
Index: src/test/groovy/com/lemans/correspondence/forms/DomainFormTransformerSpec.groovy
===================================================================
diff -u
--- src/test/groovy/com/lemans/correspondence/forms/DomainFormTransformerSpec.groovy (revision 0)
+++ src/test/groovy/com/lemans/correspondence/forms/DomainFormTransformerSpec.groovy (revision 75c1874d6dd0213527d1449d619d18ef22f15263)
@@ -0,0 +1,89 @@
+package com.lemans.correspondence.forms
+
+import groovy.json.JsonSlurper
+import spock.lang.Specification
+
+class DomainFormTransformerSpec extends Specification {
+
+ private static final String FORM = '''
+{
+ "fields": [
+ { "name": "name", "type": "STRING", "required": true, "min": 5, "max": 50 },
+ { "name": "nickName", "type": "STRING", "matches": "[a-zA-z]*" },
+ { "name": "email", "type": "EMAIL" },
+ { "name": "senior", "type": "BOOLEAN" },
+ { "name": "age", "type": "NUMBER", "min": 0, "max": 125 },
+ { "name": "salary", "type": "NUMBER", "min": 20000, "max": 10000000.00 }
+ ],
+ "recipients": [
+ { "name": "Buddy Guy", "email": "bguy@parts-unltd.com" },
+ { "name": "Jonny Lang", "email": "jlang@parts-unltd.com" }
+ ]
+}
+'''
+
+ DomainFormFormTransformer transformer = new DomainFormFormTransformer()
+
+ def 'can transform a form from jsonToXml and xmlToJson'() {
+ when:
+ Map formFromJson = new JsonSlurper().parseText(FORM)
+ String xml = transformer.formToXml(formFromJson)
+
+ then:
+ assertForm(formFromJson)
+ xml
+
+ when:
+ Map formFromXml = transformer.formFromXml(xml)
+
+ then:
+ assertForm(formFromXml)
+ }
+
+ def 'can transform a field without a type as a STRING'() {
+ given:
+ String jsonForm = '{ "fields": [ { "name": "a" } ] }'
+ Map formFromJson = new JsonSlurper().parseText(jsonForm)
+
+ when:
+ String xml = transformer.formToXml(formFromJson)
+ Map formFromXml = transformer.formFromXml(xml)
+
+ then:
+ formFromXml.fields[0].type == 'STRING'
+ }
+
+ private void assertForm(Map form) {
+ with(form) {
+ with(fields) {
+ size() == 6
+ assertField([name: 'name', type: 'STRING', required: true, min: 5, max: 50], it[0])
+ assertField([name: 'nickName', type: 'STRING', matches: '[a-zA-z]*'], it[1])
+ assertField([name: 'email', type: 'EMAIL'], it[2])
+ assertField([name: 'senior', type: 'BOOLEAN'], it[3])
+ assertField([name: 'age', type: 'NUMBER', min: 0, max: 125], it[4])
+ assertField([ name: 'salary', type: 'NUMBER', min: 20000, max: 10000000.00], it[5])
+ }
+ with(recipients) {
+ size() == 2
+ it[0].name == 'Buddy Guy'
+ it[0].email == 'bguy@parts-unltd.com'
+ it[1].name == 'Jonny Lang'
+ it[1].email == 'jlang@parts-unltd.com'
+ }
+ }
+ true
+ }
+
+ private void assertField(Map expected, Map actual) {
+ with(expected) {
+ name == actual.name
+ type == actual.type
+ required == actual.required
+ matches == actual.matches
+ min == actual.min
+ max == actual.max
+ }
+ true
+ }
+}