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 + } +}