I want to add a date expression validator in my command object, but I'm not sure what's the correct syntax...
class UserController {
…
}
class DateServiceCommand {
String date //valid format is DD-MMM-YYYY, 01-APR-2011
static constraints = {
date(blank:false, ??? )
}
}
You can use a custom validator:
import java.text.*
class DateServiceCommand {
String date
static constraints = {
date blank: false, validator: { v ->
def df = new SimpleDateFormat('dd-MMM-yyyy')
df.lenient = false
// parse will return null if date was unparseable
return df.parse(v, new ParsePosition(0)) ? true : false
}
}
}
Related
I began learn Groovy, and faced the challenge.
I have this code, that stores meta-data to object:
class Meta {
final MetaItem name
final MetaItem description
// ...
// And more fields with type MetaItem
// ...
Meta() {
name = new MetaItem("name")
description = new MetaItem("description")
}
void setName(String name) {
this.name.value = name
}
String getName() {
return this.name.value
}
void setDescription(String description) {
this.description.value = description
}
String getDescription() {
return this.description.value
}
// ...
// And more methods. Two for each field
// ...
}
class MetaItem {
private final def id
def value
MetaItem(String id) {
this.id = id
}
}
// Validating
def meta = new Meta()
assert meta.name == null
assert meta.description == null
meta.with {
name = "Name"
description = "Desc"
}
assert meta.name == "Name"
assert meta.description == "Desc"
print "Success!"
As you can see from the code, it increases quicly in volumes when new fields are added, because for each field you need to add two methods. Can this somehow be optimized? Redirect the assignment operation from object to his member. I've looked on Delegate, but this is not what I need.
P.S. I can't use access by .value because this class is used in Gradle extension and I need to configure it like this:
myExtension {
meta {
name = "Name"
description = "Desc"
// And many others
}
}
P.P.S. Sorry for my bad english, it's not my first language
I have 4 fields that takes a price (BigDecimal) but only one of them should contain a price. If price2 contains 255.95 and someone want's to enter a price in price1 Then this should be rejected with a message which says that you have to clean the other (price2 in this case)
I tried to do that in the domain but it didn't work for me.
An example:
class Author {
def String name
def String email
def BigDecimal price1
def BigDecimal price2
def BigDecimal price3
// static hasMany = [books: Book]
static constraints = {
name nullable:true
email nullable:true
price1(nullable:true,
validator: { val, obj ->
(obj.price2==null) and (obj.price3 == null) })
price2(nullable:true,
validator: { val, obj ->
(obj.price1==null) and (obj.price3 == null) })
price3(nullable:true,
validator: { val, obj ->
(obj.price1==null) and (obj.price2 == null) })
}
static mapping = {
name column: "AuthorName", sqltype:"char", length:25
}
String toString() {
return name
}
def beforeValidate() {
}
}
I get this error:
groovy.lang.MissingMethodException: No signature of method: masterdetail.Author.and() is applicable for argument types: (java.lang.Boolean) values: [true]
Possible solutions: any(), any(groovy.lang.Closure), find(), find(groovy.lang.Closure), find(java.lang.String), find(masterdetail.Author)
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.unwrap(ScriptBytecodeAdapter.java:58)
at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.callCurrent(PogoMetaClassSite.java:81)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallCurrent(CallSiteArray.java:52)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:154)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:166)
at masterdetail.Author$__clinit__closure1$_closure3.doCall(Author.groovy:16)
So, what to do?
Finally I solved the problem with some help fro doelleri.
I edited the question so it contains the final solution.
I have a Grails domain class. I would like to add a validation such that when the application is running in TEST environment, the domain class can't have more than 100 records.
I can't figure out the best way to add this type of validation. I am on Grails 2.5.3.
This is what I have so far.
class MyDomain {
String test
static constraints = {
test blank: false, nullable: false
id blank: false, validator: {value, command ->
if (Environment.current == Environment.TEST) {
//do validation for not allowing more than 100 records
}
}
}
How can I add this validation?
Solution for a single domain
What #Joshua answered is perfectly fine but there are few other ways. One of them is:
class MyDomain {
String test
void beforeInsert() {
if (Environment.current == Environment.TEST) {
MyDomain.withNewSession {
if (MyDomain.count() == 100) {
throw new Exception("Not allowing more than 100 records")
}
}
}
}
static constraints = {
test blank: false
}
}
Also, please note two things:
The blank: false on the id field of no use since it is not a string because blank constraint is applicable on a String
The nullable: false is of no use since the default value of nullable constraint is false
Generic solution for across domain TL;DR
If you want this behavior across multiple domains, copying the same code is not recommended as your code won't be DRY. For that, you can register a custom event listener:
First define a Java annotation in src/groovy:
import java.lang.annotation.Documented
import java.lang.annotation.ElementType
import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy
import java.lang.annotation.Target
#Documented
#Target([ElementType.TYPE, ElementType.FIELD])
#Retention(RetentionPolicy.RUNTIME)
public #interface LimitRowsFoo {
int value() default 100
}
Now define another Groovy class:
import grails.util.Environment
import org.grails.datastore.mapping.engine.event.PreInsertEvent
import org.grails.datastore.mapping.engine.event.AbstractPersistenceEvent
import org.grails.datastore.mapping.engine.event.AbstractPersistenceEventListener
class PreInsertEventListener extends AbstractPersistenceEventListener {
PreUpdateEventListener(final Datastore datastore) {
super(datastore)
}
#Override
protected void onPersistenceEvent(AbstractPersistenceEvent event) {
// Instance of domain which is being created
Object domainInstance = event.entityObject
if (Environment.current == Environment.TEST) {
if (domainInstance.class.isAnnotationPresent(LimitRowsFoo.class)) {
// Just using any domain reference here
MyDomain.withNewTransaction {
int maxRows = domainInstance.class.getAnnotation(LimitRowsFoo.class).value()
if (domainInstance.class.count() == maxRows) {
event.cancel()
}
}
}
}
}
#Override
boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
// Only allow PreInsert event to be listened
eventType.name == PreInsertEvent.class.name
}
}
Now register this in Bootstrap.groovy:
application.mainContext.eventTriggeringInterceptor.datastores.each { k, datastore ->
applicationContext.addApplicationListener new PreInsertEventListener(datastore)
}
Now, in your domain, all you have to do is like these:
#LimitRowsFoo
class MyDomain {
String test
static constraints = {
test blank: false
}
}
#LimitRowsFoo(value = 200) // to limit records to 200
class MyAnotherDomain {
String name
}
Wouldn't something like this do the trick for you?
class MyDomain {
String test
static constraints = {
test blank: false, nullable: false
id blank: false, validator: {value, command ->
if (Environment.current == Environment.TEST) {
//do validation for not allowing more than 100 records
if (MyDomain.count() > 100) return ['too.many.records']
}
}
}
I have created the RegisterController.groovy out of the spring ui package.
When I created it, it looked like that:
class RegisterController extends grails.plugin.springsecurity.ui.RegisterController{
}
Then I just copied the initial RegisterController.groovy into my template.
package com.testApplication.register
import grails.plugin.springsecurity.SpringSecurityUtils
import grails.plugin.springsecurity.authentication.dao.NullSaltSource
import grails.plugin.springsecurity.ui.RegistrationCode
import groovy.text.SimpleTemplateEngine
class RegisterController extends grails.plugin.springsecurity.ui.RegisterController {
// override default value from base class
static defaultAction = 'index'
// override default value from base class
static allowedMethods = [register: 'POST']
def mailService
def messageSource
def saltSource
def index() {
def copy = [:] + (flash.chainedParams ?: [:])
copy.remove 'controller'
copy.remove 'action'
[command: new RegisterCommand(copy)]
}
def register(RegisterCommand command) {
if (command.hasErrors()) {
render view: 'index', model: [command: command]
return
}
String salt = saltSource instanceof NullSaltSource ? null : command.username
def user = lookupUserClass().newInstance(email: command.email, username: command.username,
accountLocked: true, enabled: true)
RegistrationCode registrationCode = springSecurityUiService.register(user, command.password, salt)
if (registrationCode == null || registrationCode.hasErrors()) {
// null means problem creating the user
flash.error = message(code: 'spring.security.ui.register.miscError')
flash.chainedParams = params
redirect action: 'index'
return
}
String url = generateLink('verifyRegistration', [t: registrationCode.token])
def conf = SpringSecurityUtils.securityConfig
def body = conf.ui.register.emailBody
if (body.contains('$')) {
body = evaluate(body, [user: user, url: url])
}
mailService.sendMail {
to command.email
from conf.ui.register.emailFrom
subject conf.ui.register.emailSubject
html body.toString()
}
render view: 'index', model: [emailSent: true]
}
def verifyRegistration() {
def conf = SpringSecurityUtils.securityConfig
String defaultTargetUrl = conf.successHandler.defaultTargetUrl
String token = params.t
def registrationCode = token ? RegistrationCode.findByToken(token) : null
if (!registrationCode) {
flash.error = message(code: 'spring.security.ui.register.badCode')
redirect uri: defaultTargetUrl
return
}
def user
// TODO to ui service
RegistrationCode.withTransaction { status ->
String usernameFieldName = SpringSecurityUtils.securityConfig.userLookup.usernamePropertyName
user = lookupUserClass().findWhere((usernameFieldName): registrationCode.username)
if (!user) {
return
}
user.accountLocked = false
user.save(flush:true)
def UserRole = lookupUserRoleClass()
def Role = lookupRoleClass()
for (roleName in conf.ui.register.defaultRoleNames) {
UserRole.create user, Role.findByAuthority(roleName)
}
registrationCode.delete()
}
if (!user) {
flash.error = message(code: 'spring.security.ui.register.badCode')
redirect uri: defaultTargetUrl
return
}
springSecurityService.reauthenticate user.username
flash.message = message(code: 'spring.security.ui.register.complete')
redirect uri: conf.ui.register.postRegisterUrl ?: defaultTargetUrl
}
def forgotPassword() {
if (!request.post) {
// show the form
return
}
String username = params.username
if (!username) {
flash.error = message(code: 'spring.security.ui.forgotPassword.username.missing')
redirect action: 'forgotPassword'
return
}
String usernameFieldName = SpringSecurityUtils.securityConfig.userLookup.usernamePropertyName
def user = lookupUserClass().findWhere((usernameFieldName): username)
if (!user) {
flash.error = message(code: 'spring.security.ui.forgotPassword.user.notFound')
redirect action: 'forgotPassword'
return
}
def registrationCode = new RegistrationCode(username: user."$usernameFieldName")
registrationCode.save(flush: true)
String url = generateLink('resetPassword', [t: registrationCode.token])
def conf = SpringSecurityUtils.securityConfig
def body = conf.ui.forgotPassword.emailBody
if (body.contains('$')) {
body = evaluate(body, [user: user, url: url])
}
mailService.sendMail {
to user.email
from conf.ui.forgotPassword.emailFrom
subject conf.ui.forgotPassword.emailSubject
html body.toString()
}
[emailSent: true]
}
def resetPassword(ResetPasswordCommand command) {
String token = params.t
def registrationCode = token ? RegistrationCode.findByToken(token) : null
if (!registrationCode) {
flash.error = message(code: 'spring.security.ui.resetPassword.badCode')
redirect uri: SpringSecurityUtils.securityConfig.successHandler.defaultTargetUrl
return
}
if (!request.post) {
return [token: token, command: new ResetPasswordCommand()]
}
command.username = registrationCode.username
command.validate()
if (command.hasErrors()) {
return [token: token, command: command]
}
String salt = saltSource instanceof NullSaltSource ? null : registrationCode.username
RegistrationCode.withTransaction { status ->
String usernameFieldName = SpringSecurityUtils.securityConfig.userLookup.usernamePropertyName
def user = lookupUserClass().findWhere((usernameFieldName): registrationCode.username)
user.password = springSecurityUiService.encodePassword(command.password, salt)
user.save()
registrationCode.delete()
}
springSecurityService.reauthenticate registrationCode.username
flash.message = message(code: 'spring.security.ui.resetPassword.success')
def conf = SpringSecurityUtils.securityConfig
String postResetUrl = conf.ui.register.postResetUrl ?: conf.successHandler.defaultTargetUrl
redirect uri: postResetUrl
}
protected String generateLink(String action, linkParams) {
createLink(base: "$request.scheme://$request.serverName:$request.serverPort$request.contextPath",
controller: 'register', action: action,
params: linkParams)
}
protected String evaluate(s, binding) {
new SimpleTemplateEngine().createTemplate(s).make(binding)
}
static final passwordValidator = { String password, command ->
if (command.username && command.username.equals(password)) {
return 'command.password.error.username'
}
if (!checkPasswordMinLength(password, command) ||
!checkPasswordMaxLength(password, command) ||
!checkPasswordRegex(password, command)) {
return 'command.password.error.strength'
}
}
static boolean checkPasswordMinLength(String password, command) {
def conf = SpringSecurityUtils.securityConfig
int minLength = conf.ui.password.minLength instanceof Number ? conf.ui.password.minLength : 8
password && password.length() >= minLength
}
static boolean checkPasswordMaxLength(String password, command) {
def conf = SpringSecurityUtils.securityConfig
int maxLength = conf.ui.password.maxLength instanceof Number ? conf.ui.password.maxLength : 64
password && password.length() <= maxLength
}
static boolean checkPasswordRegex(String password, command) {
def conf = SpringSecurityUtils.securityConfig
String passValidationRegex = conf.ui.password.validationRegex ?:
'^.*(?=.*\\d)(?=.*[a-zA-Z])(?=.*[!##$%^&]).*$'
password && password.matches(passValidationRegex)
}
static final password2Validator = { value, command ->
if (command.password != command.password2) {
return 'command.password2.error.mismatch'
}
}
}
class RegisterCommand {
String username
String email
String password
String password2
def grailsApplication
static constraints = {
username blank: false, validator: { value, command ->
if (value) {
def User = command.grailsApplication.getDomainClass(
SpringSecurityUtils.securityConfig.userLookup.userDomainClassName).clazz
if (User.findByUsername(value)) {
return 'registerCommand.username.unique'
}
}
}
email blank: false, email: true
password blank: false, validator: RegisterController.passwordValidator
password2 validator: RegisterController.password2Validator
}
}
class ResetPasswordCommand {
String username
String password
String password2
static constraints = {
password blank: false, validator: RegisterController.passwordValidator
password2 validator: RegisterController.password2Validator
}
}
However, I am getting an error at:
class RegisterController extends grails.plugin.springsecurity.ui.RegisterController {` with the error:Cannot override the final method from RegisterController`
and at
package com.testApplication.register
Multiple markers at this line
- The type org.springframework.security.core.context.SecurityContext cannot be resolved. It is indirectly
referenced from required .class files
- The type org.springframework.security.core.GrantedAuthority cannot be resolved. It is indirectly referenced
from required .class files
- The type org.springframework.security.web.savedrequest.SavedRequest cannot be resolved. It is indirectly
referenced from required .class files
I tried to use #Override however, this does not change it. What am I doing wrong?
I appreciate your answer!
The final keyword prevents you from modifying a class if applied to the class, or a method if applied to a method.
From http://en.wikipedia.org/wiki/Final_%28Java%29 :
A final method cannot be overridden or hidden by subclasses. This is used to prevent unexpected behavior from a subclass altering a method that may be crucial to the function or consistency of the class.
If you subclass a class with a final method you need to leave that method alone. If the class is final you can't subclass it at all.
Refresh dependencies solved my issue.
I'm having some hard time figuring out what's wrong in this simple code:
This is a command I made for changing the password:
package myapp.commands
import org.springframework.web.context.request.RequestContextHolder as RCH
import myapp.User
class PasswordCommand {
String currentPassword
String password
String passwordConfirm
private u
User getUser() {
def id = RCH.requestAttributes.session?.user?.id
if (!u && id) {
u = User.get(id)
}
return u
}
static constraints = {
currentPassword blank: false, validator: { val, cmd ->
if (cmd.user && cmd.user.password != val)
return "user.password.invalid"
}
...
}
And in the appropriate controller I use this action:
def doPassword = { PasswordCommand cmd ->
if (!cmd.hasErrors()) {
User user = cmd.getUser()
...
Unfortunately, I get an Error 500 when I try to change the password:
URI: /Myapp/user/doPassword
Class: java.lang.NoSuchMethodError
Message: PasswordCommand.validate()Z
What is going on ?!
Add the #Validateable annotation:
import grails.validation.Validateable
...
#Validateable
class PasswordCommand {
...
}