How do I avoid getting duplicate key errors when using findOrSaveBy dynamic finders in Grails? - grails

In a concurrent situation (asynchronous file uploads) where I try to create (or reuse) and attach keyword tags to images in an image library application i get a Unique index or primary key violation when creating Tag domain objects.
The controller calls a service method to add an image to the library and also the keyword tags that belongs to it.
Am I going about this the wrong way or am I missing something?
I'm currently using Grails 2.3.5.
Code:
class ImageAssetService {
def addTagToImage(ImageAsset imageAsset, String kw, String locale) {
def tagName = kw.toLowerCase(Locale.forLanguageTag(locale))
// OFFENDING LINE NEXT
def tag = Tag.findOrSaveByNameAndLocale(tagName, locale, [lock: true])
imageAsset.addToTags(tag)
if(!imageAsset.save()) {
throw new ImageAssetException()
}
imageAsset
}
def addTagsToImage(ImageAsset imageAsset, Set<String> keywords, String locale) {
keywords.each { kw ->
addTagToImage(imageAsset, kw, locale)
}
imageAsset
}
// CONTROLLER CALLS THIS METHOD
def addImageAsset(InputStream inputStream, String filename, long fileSize, long authorId, String timeZoneOriginal, String heading, String description, Set tags, String imageCollectionName) {
// Create the ImageAsset domain object
ImageAsset imageAsset = new ImageAsset(
filename: filename,
fileSize: fileSize,
author: Author.get(authorId),
timeZoneOriginal: TimeZone.getTimeZone(timeZoneOriginal),
heading: heading,
description: description
).save()
// Add any tags
addTagsToImage(imageAsset, tags, 'en')
/*
...
CODE REMOVED FOR BREVITY
....
*/
return imageAsset
}
}
class Tag {
String locale
String name
static hasMany = [ translations : Tag, synonyms : Tag ]
void setName(String name) { this.#name = name.toLowerCase() }
static constraints = {
locale unique: ['name']
}
static belongsTo = ImageAsset
static searchable = {
except = ['locale']
name boost: 2.0
translations component: [prefix: 'translations_', maxDepth: 2]
synonyms component: [prefix: 'synonyms_', maxDepth: 2]
}
static mapping = {
table 'tags'
}
}
class ImageAsset {
String filename
String heading
String description
String place
String city
String country
String gps
long fileSize = 0
int pixelWidth = 0
int pixelHeight = 0
Date dateTimeOriginal
TimeZone timeZoneOriginal
boolean enabled = true
Date dateCreated
static belongsTo = [
Author,
ConceptualImageCategory,
RepresentativeImageCategory,
ImageCollection
]
static hasOne = [ author : Author ]
static hasMany = [
conceptualCategories : ConceptualImageCategory,
representativeCategories : RepresentativeImageCategory,
collections : ImageCollection,
metadata : Metadata,
tags : Tag
]
static constraints = {
filename blank: false
heading nullable: true
description nullable: true
place nullable: true
city nullable: true
country nullable: true
gps nullable: true
pixelWidth nullable: true
pixelHeight nullable: true
dateTimeOriginal nullable: true
timeZoneOriginal nullable: true
}
static mapping = {
description type: 'text'
}
static searchable = {
//only = ['filename', 'heading', 'description', 'tags', 'metadata']
author component: [prefix: 'author_']
tags component: [prefix: 'tags_']
metadata component: [prefix: 'metadata_']
}
}
Error message:
Unique index or primary key violation: "CONSTRAINT_INDEX_27 ON PUBLIC.TAGS(NAME, LOCALE) VALUES ( /* key:11 */ 895, 0, 'en', 'work')"; SQL statement:
insert into tags (id, version, locale, name) values (null, ?, ?, ?) [23505-173]. Stacktrace follows:
Message: Unique index or primary key violation: "CONSTRAINT_INDEX_27 ON PUBLIC.TAGS(NAME, LOCALE) VALUES ( /* key:11 */ 895, 0, 'en', 'work')"; SQL statement:
insert into tags (id, version, locale, name) values (null, ?, ?, ?) [23505-173]
Line | Method
->> 331 | getJdbcSQLException in org.h2.message.DbException
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
| 171 | get in ''
| 148 | get . . . . . . . . . . in ''
| 101 | getDuplicateKeyException in org.h2.index.BaseIndex
| 68 | add . . . . . . . . . . in org.h2.index.TreeIndex
| 52 | add in org.h2.index.MultiVersionIndex
| 125 | addRow . . . . . . . . . in org.h2.table.RegularTable
| 127 | insertRows in org.h2.command.dml.Insert
| 86 | update . . . . . . . . . in ''
| 79 | update in org.h2.command.CommandContainer
| 235 | executeUpdate . . . . . in org.h2.command.Command
| 154 | executeUpdateInternal in org.h2.jdbc.JdbcPreparedStatement
| 140 | executeUpdate . . . . . in ''
| 102 | doCall in org.grails.datastore.gorm.GormStaticApi$_methodMissing_closure2
| 105 | addTagToImage . . . . . in se.originalab.imagedb.ImageAssetService
| 94 | doCall in se.originalab.imagedb.ImageAssetService$_addTagsToImage_closure3
| 93 | addTagsToImage . . . . . in se.originalab.imagedb.ImageAssetService
| 45 | addImageAsset in ''
| 31 | upload . . . . . . . . . in se.originalab.imagedb.UploadController
| 195 | doFilter in grails.plugin.cache.web.filter.PageFragmentCachingFilter
| 63 | doFilter . . . . . . . . in grails.plugin.cache.web.filter.AbstractFilter
| 53 | doFilter in grails.plugin.springsecurity.web.filter.GrailsAnonymousAuthenticationFilter
| 49 | doFilter . . . . . . . . in grails.plugin.springsecurity.web.authentication.RequestHolderAuthenticationFilter
| 82 | doFilter in grails.plugin.springsecurity.web.authentication.logout.MutableLogoutFilter
| 1110 | runWorker . . . . . . . in java.util.concurrent.ThreadPoolExecutor
| 603 | run in java.util.concurrent.ThreadPoolExecutor$Worker
^ 722 | run . . . . . . . . . . in java.lang.Thread

I found a solution myself. I had to break out the creation of a Tag into it's own transaction. Each call to Tag.findOrSaveByNameLocale() is now called through a synchronized service method from the controller. Then I add them to the ImageAsset afterwards.
One problem with this is that if the creation of the ImageAsset fails the Tags will still be persisted, but in my use case this is not a big problem.

Related

Grails 3.1.7 Spring Security Issue when create new User by GORM Scaffolding

I am doing my firsts steps in Grails (version 3.1.7), and I am doing an application which needs user authentication. This app is a web application that also provide some REST functionality, so I need a web login and a "rest" login with token.
I am using spring-security-core:3.1.0 and spring-security-rest:2.0.0.M2 for these propose and both logins are working properly.
Now I am having some troubles when I try to create a new user via the CRUD generated with grails generate-all package.User, I generated the view properly (I have a client class that the user could have or not so if the user has it I provide the client fields in the same create section too because a client can have only one user). When I save it I got a internal server error 500:
URI /web/webUser/save, Class java.lang.NullPointerException, Message null
in the file: \web\WebUserController.groovy
and the line is: webUser.save flush:true
the trace is:
Line | Method
80 | doFilter in grails.plugin.springsecurity.rest.RestLogoutFilter
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
| 64 | doFilter in grails.plugin.springsecurity.web.UpdateRequestContextHolderExceptionTranslationFilter
| 53 | doFilter in grails.plugin.springsecurity.web.filter.GrailsAnonymousAuthenticationFilter
| 143 | doFilter in grails.plugin.springsecurity.rest.RestAuthenticationFilter
| 62 | doFilter in grails.plugin.springsecurity.web.authentication.logout.MutableLogoutFilter
| 58 | doFilter in grails.plugin.springsecurity.web.SecurityRequestHolderFilter
| 1145 | runWorker in java.util.concurrent.ThreadPoolExecutor
| 615 | run in java.util.concurrent.ThreadPoolExecutor$Worker
^ 745 | run . . . in java.lang.Thread
Caused by NullPointerException: null
->> 47 | $tt__save in WebUserController.groovy
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
| 96 | doInTransaction in grails.transaction.GrailsTransactionTemplate$2
| 93 | execute . in grails.transaction.GrailsTransactionTemplate
| 96 | doInTransaction in grails.transaction.GrailsTransactionTemplate$2
| 93 | execute . in grails.transaction.GrailsTransactionTemplate
| 80 | doFilter in grails.plugin.springsecurity.rest.RestLogoutFilter
| 64 | doFilter in grails.plugin.springsecurity.web.UpdateRequestContextHolderExceptionTranslationFilter
| 53 | doFilter in grails.plugin.springsecurity.web.filter.GrailsAnonymousAuthenticationFilter
| 143 | doFilter in grails.plugin.springsecurity.rest.RestAuthenticationFilter
| 62 | doFilter in grails.plugin.springsecurity.web.authentication.logout.MutableLogoutFilter
| 58 | doFilter in grails.plugin.springsecurity.web.SecurityRequestHolderFilter
| 1145 | runWorker in java.util.concurrent.ThreadPoolExecutor
| 615 | run . . . in java.util.concurrent.ThreadPoolExecutor$Worker
^ 745 | run in java.lang.Thread
I am not sure if I have something wrong in the security plugin configuration or in the user and client domain class, or in the userController.
Edit: Updete info!
#Transactional(readOnly = true)
class WebUserController {
static allowedMethods = [save: "POST", update: "PUT", delete: "DELETE"]
static namespace = 'web'
def springSecurityService
#Transactional
def save(WebUser webUser) {
if (webUser == null) {
transactionStatus.setRollbackOnly()
notFound()
return
}
if (webUser.hasErrors()) {
transactionStatus.setRollbackOnly()
respond webUser.errors, view:'create'
return
}
webUser.save flush:true // line where the NullPointerException is threw
if (webUser.isAdmin){
UserRole.create webUser, Role.findByAuthority('ROLE_ADMIN')
} else {
UserRole.create webUser, Role.findByAuthority('ROLE_CLIENT')
}
request.withFormat {
form multipartForm {
flash.message = message(code: 'default.created.message', args: [message(code: 'webUser.label', default: 'WebUser'), webUser.id])
redirect webUser
}
'*' { respond webUser, [status: CREATED] }
}
}
And the code in the domin:
transient springSecurityService
String username
String password
boolean enabled = true
boolean accountExpired
boolean accountLocked
boolean passwordExpired
static transients = ['isAdmin', 'springSecurityService']
boolean isAdmin
Client client
String name
String email
Date lastVisit
static hasMany = [orders: BOrder]
static constraints = {
username size: 5..15, blank: false, unique: true
password size: 5..15, blank: false, password: true
email email: true, blank: false, unique: true
name size: 0..50, nullable: true
lastVisit nullable: true
client nullable: true
}
The error happens when the client is either null or not. And debugging if I add a afterInsert in the WebUser domain class, I can see the Client id generated if it is not null, but the WebUser id is not generated, and I do not see any error message at that point.
Well I found where was the issue.
The problem was that I added a constraint to the password size, and the validate were executed before the password was hashed, so the validate pass, then the password was hashed and it doesn't have less than the 15 characters, so it failed when it try to persist the user in the database and then it threw the null pointer exception.
Removing or changing the max length restriction to the password, the problem were solved.
The only weird thing to me is the error message that I got, which didn't tell nothing relevant in my opinion.

Grails 2.4.4 file upload error when the file exists

I have an action that receives a file correctly and saves it to a destination folder without problems.
When the destination folder has a file with the same name, the method transferTo, first deletes the existing file, then copies the new one (http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/multipart/commons/CommonsMultipartFile.html#transferTo-java.io.File-).
But if the destination file exists, Grails is throwing this error, and the existing file is deleted but the uploaded one is not copied.
I'm working in WinXP, so I don't think it is a permissions issues (the file is deleted so I guess has nothing to do with permissions).
| Error 2015-05-24 23:47:58,199 [http-bio-8090-exec-3] ERROR errors.GrailsExceptionResolver - FileNotFoundException occurred when processing request: [POST] /ehr/test/upload - parameters:
doit: upload
overwrite: true
SYNCHRONIZER_TOKEN: 8deaf46b-b6ff-4362-ac70-7223f37ae806
SYNCHRONIZER_URI: /ehr/test/upload
opts\Signos.opt (Access is denied). Stacktrace follows:
Message: opts\Signos.opt (Access is denied)
Line | Method
->> 221 | <init> in java.io.FileOutputStream
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
| 171 | <init> in ''
| 417 | write . . in org.apache.commons.fileupload.disk.DiskFileItem
| 85 | upload in test.TestController
| 198 | doFilter in grails.plugin.cache.web.filter.PageFragmentCachingFilter
| 63 | doFilter in grails.plugin.cache.web.filter.AbstractFilter
| 1145 | runWorker in java.util.concurrent.ThreadPoolExecutor
| 615 | run in java.util.concurrent.ThreadPoolExecutor$Worker
^ 744 | run . . . in java.lang.Thread
The upload action looks like:
def upload(boolean overwrite)
{
if (params.doit)
{
def errors = []
def f = request.getFile('opt')
def xml = new String( f.getBytes() )
def destination = config.opt_repo + f.getOriginalFilename()
File fileDest = new File( destination )
if (!overwrite && fileDest.exists())
{
errors << "The OPT already exists, do you want to overwrite?"
return [errors: errors, ask_overwrite: true]
}
// Some validation logic here ...
if (errors.size() == 0)
{
// http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/multipart/commons/CommonsMultipartFile.html#transferTo-java.io.File-
// If the file exists, it will be deleted first
f.transferTo(fileDest)
}
}
}

Grails/GORM BigInteger Unsigned Rejected

I'm getting the following error:
| Error 2014-08-18 11:25:00,324 [localhost-startStop-1] ERROR context.GrailsContextLoaderListener - Error initializing the application: Validation Error(s) occurred during save():
- Field error in object 'my.package.Content' on field 'fileNameLookup': rejected value [16731516642733300018]; codes [my.package.Content.fileNameLookup.typeMismatch.error,my.package.Content.fileNameLookup.typeMismatch,content.fileNameLookup.typeMismatch.error,content.fileNameLookup.typeMismatch,typeMismatch.my.package.Content.fileNameLookup,typeMismatch.fileNameLookup,typeMismatch.java.lang.Long,typeMismatch]; arguments [fileNameLookup]; default message [Could not convert number [16731516642733300018] of type [java.math.BigInteger] to target class [java.lang.Long]: overflow]
Message: Validation Error(s) occurred during save():
- Field error in object 'my.package.Content' on field 'fileNameLookup': rejected value [16731516642733300018]; codes [my.package.Content.fileNameLookup.typeMismatch.error,my.package.Content.fileNameLookup.typeMismatch,content.fileNameLookup.typeMismatch.error,content.fileNameLookup.typeMismatch,typeMismatch.my.package.Content.fileNameLookup,typeMismatch.fileNameLookup,typeMismatch.java.lang.Long,typeMismatch]; arguments [fileNameLookup]; default message [Could not convert number [16731516642733300018] of type [java.math.BigInteger] to target class [java.lang.Long]: overflow]
Line | Method
->> 6 | doCall in BootStrap$_closure1
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
| 327 | evaluateEnvironmentSpecificBlock in grails.util.Environment
| 320 | executeForEnvironment . . . . . in ''
| 296 | executeForCurrentEnvironment in ''
| 266 | run . . . . . . . . . . . . . . in java.util.concurrent.FutureTask
| 1142 | runWorker in java.util.concurrent.ThreadPoolExecutor
| 617 | run . . . . . . . . . . . . . . in java.util.concurrent.ThreadPoolExecutor$Worker
^ 745 | run in java.lang.Thread
Error |
Forked Grails VM exited with error
Domain class:
package my.package
// need to fix constraints
class Content {
// int id
Long fileNameLookup
static mapping = {
version false
fileNameLookup column: 'file_name_lookup', type:org.hibernate.type.LongType, class: Long
}
static constraints = {
fileNameLookup(nullable:true, display:false, editable: false)
}
}
Bootstrap.groovy:
import my.package.Content
class BootStrap {
def init = { servletContext ->
new Content(fileNameLookup:16731516642733300018).save(failOnError:true)
}
def destroy = {
}
}
I tried Long, BigInteger, Integer, etc... and burnt hours on this trying to figure out how to get this bigint(20) unsigned to save into the test db. How do i tell Grails/Gorm that the number is a bigint(20) so that it can handle it properly regardless of the database that i'm using?
Long.MAX_VALUE is 9,223,372,036,854,775,807. Your number is too big for it. Using BigDecimal should fix this.
BigDecimal fileNameLookup

Grails groovy boolean being returned as null

I have this function which returns two integers and a boolean in a Service class:
def setEntityRecordBalance(EntityRecord entRec, Map params) {
float totalBalance = 0
int redIssues = 0, yellowIssues = 0
boolean insured = false
/* For each account owned by this entity, get its figure in USD and then add to running total. */
if (entRec.accounts == []) {
redIssues++
setCleanFlag(entRec.redIssues, 'No accounts found.')
return [redIssues, yellowIssues]
}
else {
entRec.accounts.each {
def account = AccountRecord.findWhere(uniqueId: it.uniqueId, accountId: it.accountId,
batchID: params.selectedBatch.id)
if (account.amount == null)
totalBalance += 0
else
if (account.currencyType == null || account.currencyType.equalsIgnoreCase('USD'))
totalBalance += account.amount
else
totalBalance += getUSDamount(account)
if (account.insurance != null && (account.insurance.equalsIgnoreCase('Y') || account.insurance.equalsIgnoreCase('YES')))
insured = true
}
}
entRec.balance = totalBalance
return [redIssues, yellowIssues, insured]
}
Now, in the same Service class, I have another statement in a function that call this function like this:
def (redFlags, yellowFlags, insured) = setEntityRecordBalance(newEntityRecord, params)
println "<><><> Value of insured: " + insured + " " + redFlags + " " + yellowFlags
def (redFlgs, yellowFlgs, isReportable) = setEntityRecordBalanceFlags (newEntityRecord, insured)
I get the two integers but the boolean comes back as null, why?
This is the error I get:
<><><> Value of insured: null 1 0
| Error 2014-08-16 18:34:34,857 [http-bio-8080-exec-10] ERROR errors.GrailsExceptionResolver - MissingMethodException occurred when processing request: [POST] /FatcaOne_0/customer/saveNewEntityRecord - parameters:
status:
entityJurisdiction:
countryCode:
taxIdNumber:
uniqueId: 123
entityName: asdf
generalComments:
secondaryId: 234
address:
subStatus:
cityTown:
telephone:
giin:
No signature of method: com.twc.fatcaone.FileImportService.setEntityRecordBalanceFlags() is applicable for argument types: (com.twc.fatcaone.EntityRecord, null) values: [com.twc.fatcaone.EntityRecord : (unsaved), ...]
Possible solutions: setEntityRecordBalanceFlags(com.twc.fatcaone.EntityRecord, boolean). Stacktrace follows:
Message: No signature of method: com.twc.fatcaone.FileImportService.setEntityRecordBalanceFlags() is applicable for argument types: (com.twc.fatcaone.EntityRecord, null) values: [com.twc.fatcaone.EntityRecord : (unsaved), ...]
Possible solutions: setEntityRecordBalanceFlags(com.twc.fatcaone.EntityRecord, boolean)
Line | Method
->> 1672 | $tt__createNewEntityRecord in com.twc.fatcaone.FileImportService$$EOn7yRsm
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
| 323 | saveNewEntityRecord in com.twc.fatcaone.CustomerController$$EOn7xmG8
| 198 | doFilter . . . . . . . . . in grails.plugin.cache.web.filter.PageFragmentCachingFilter
| 63 | doFilter in grails.plugin.cache.web.filter.AbstractFilter
| 1145 | runWorker . . . . . . . . in java.util.concurrent.ThreadPoolExecutor
| 615 | run in java.util.concurrent.ThreadPoolExecutor$Worker
^ 744 | run . . . . . . . . . . . in java.lang.Thread
Because here
return [redIssues, yellowIssues]
You only return 2 elements, with no third boolean

Grails "don't flush the Session after an exception occurs" error message

I see that this error message has been posted several times already in the context of hibernate.
I am getting this error while using grails service and a domain class, any help will be really appreciated
Domain class
class Coupon {
Date dateCreated
Date lastUpdated
String code
String email
String address
String state
String city
String zip
def couponCodeGeneratorService
def beforeValidate() {
println code+"---------8-"
code = couponCodeGeneratorService.generate()
println code+"----------"
}
static constraints = {
email blank:false,email:true
address blank:false
state blank:false
city blank:false
zip blank:false
}
}
Service
class CouponCodeGeneratorService {
Random randomGenerator = new Random()
def serviceMethod() {
}
def generate(){
def group1 = randomGenerator.nextInt(9999)+"";
def group2 = randomGenerator.nextInt(9999)+"";
def group3 = randomGenerator.nextInt(9999)+"";
def group4 = randomGenerator.nextInt(9999)+"";
return group1.padLeft(4,"0") +group2.padLeft(4,"0")+group3.padLeft(4,"0")+group4.padLeft(4,"0")
}
}
The error I am getting is
---------8-
4844634041715590----------
4844634041715590---------8-
| Error 2012-09-10 11:32:54,938 [http-bio-8080-exec-7] ERROR hibernate.AssertionFailure - an assertion failure occured (this may indicate a bug in Hibernate, but is more likely due to unsafe use of the session)
Message: null id in com.easytha.Coupon entry (don't flush the Session after an exception occurs)
Line | Method
->> 19 | beforeValidate in com.easytha.Coupon
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
| 46 | onApplicationEvent in org.grails.datastore.mapping.engine.event.AbstractPersistenceEventListener
| 24 | save . . . . . . . in com.easytha.CouponController
| 186 | doFilter in grails.plugin.cache.web.filter.PageFragmentCachingFilter
| 63 | doFilter . . . . . in grails.plugin.cache.web.filter.AbstractFilter
| 886 | runTask in java.util.concurrent.ThreadPoolExecutor$Worker
| 908 | run . . . . . . . in ''
^ 662 | run in java.lang.Thread
| Error 2012-09-10 11:32:54,944 [http-bio-8080-exec-7] ERROR errors.GrailsExceptionResolver - AssertionFailure occurred when processing request: [POST] /EasyTha/coupon/save - parameters:
zip: asdf
address: asd
email: s.s#s.xom
state: asd
code:
create: Create
city: asdf
null id in com.easytha.Coupon entry (don't flush the Session after an exception occurs). Stacktrace follows:
Message: null id in com.easytha.Coupon entry (don't flush the Session after an exception occurs)
Line | Method
->> 19 | beforeValidate in com.easytha.Coupon
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
| 46 | onApplicationEvent in org.grails.datastore.mapping.engine.event.AbstractPersistenceEventListener
| 24 | save . . . . . . . in com.easytha.CouponController
| 186 | doFilter in grails.plugin.cache.web.filter.PageFragmentCachingFilter
| 63 | doFilter . . . . . in grails.plugin.cache.web.filter.AbstractFilter
| 886 | runTask in java.util.concurrent.ThreadPoolExecutor$Worker
| 908 | run . . . . . . . in ''
^ 662 | run in java.lang.Thread
I am not very familiar with Hibernate, also is this a correct way to create a coupon code that looks like a credit card number?
I suspect the problem may be that the CouponCodeGeneratorService is transactional. Therefore, when you call the service method from inside your beforeValidate you're opening and closing a transaction (even though you don't touch the database inside the method), which among other things will cause another flush of the session.
Try making the service non-transactional:
static transactional = false

Resources