I'm trying to use ElasticSearch 1.0.0.2 in Grails 3.1.6.
The domain class is:
class Post {
String content
Date dateCreated
static belongsTo = [user: User]
static hasMany = [tags: Tag]
static searchable = {
tags component : true
user component: true
}
}
I've injected ElasticSearchService in my SearchController and trying to obtain search results as:
try {
def searchResult = elasticSearchService.search("${params.q}")
// def searchResult = Post.search("${params.q}")
println("search result: "+searchResult)
return [searchResult: searchResult]
}catch (e){
println e.message
return [searchError: true]
}
But getting error like this:
ERROR grails.plugins.elasticsearch.conversion.unmarshall.DomainClassUnmarshaller - Error unmarshalling property 'user' of Class Post with id 4
java.lang.IllegalStateException: Property Post.user is not mapped as [component], but broken search hit found.
at sun.reflect.GeneratedConstructorAccessor116.newInstance(Unknown Source)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:422)
at org.springsource.loaded.ri.ReflectiveInterceptor.jlrConstructorNewInstance(ReflectiveInterceptor.java:1075)
at org.codehaus.groovy.reflection.CachedConstructor.invoke(CachedConstructor.java:83)
at org.codehaus.groovy.reflection.CachedConstructor.doConstructorInvoke(CachedConstructor.java:77)
at org.codehaus.groovy.runtime.callsite.ConstructorSite$ConstructorSiteNoUnwrap.callConstructor(ConstructorSite.java:84)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:247)
at grails.plugins.elasticsearch.conversion.unmarshall.DomainClassUnmarshaller.unmarshallProperty(DomainClassUnmarshaller.groovy:206)
at sun.reflect.GeneratedMethodAccessor339.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at org.springsource.loaded.ri.ReflectiveInterceptor.jlrMethodInvoke(ReflectiveInterceptor.java:1432)
at org.codehaus.groovy.runtime.callsite.PogoMetaMethodSite$PogoCachedMethodSiteNoUnwrapNoCoerce.invoke(PogoMetaMethodSite.java:210)
at org.codehaus.groovy.runtime.callsite.PogoMetaMethodSite.callCurrent(PogoMetaMethodSite.java:59)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallCurrent(CallSiteArray.java:52)
at org.codehaus.groovy.runtime.callsite.PogoMetaMethodSite.callCurrent(PogoMetaMethodSite.java:64)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:190)
Would someone please tell me what mistake i'm making. Thanks.
Had the same problem, used this piece from the elastisearch plugin documentation to change my component to references which fixed my problem:
3.8.1. Searchable Reference
The searchable-reference mapping mode is the default mode used for association, and requires the searchable class of the association to be root-mapped in order to have its own index. With this mode, the associated domains are not completely marshalled in the resulting JSON document: only the id and the type of the instances are kept. When the document is retrieved from the index, the plugin will automatically rebuild the association from the indices using the stored id.
Example
class MyDomain {
// odom is an association with the OtherDomain class, set as a reference
OtherDomain odom
static searchable = {
odom reference:true
}
}
// The OtherDomain definition, with default searchable
class OtherDomain {
static searchable = true
String field1 = "val1"
String field2 = "val2"
String field3 = "val3"
String field4 = "val4"
}
When indexing an instance of MyDomain, the resulting JSON documents will be sent to ElasticSearch:
{
"mydomain": {
"_id":1,
"odom": { "id":1 }
}
}
{
"otherdomain": {
"_id":1,
"field1":"val1",
"field2":"val2",
"field3":"val3",
"field4":"val4"
}
}
Here is the link to the documentation
http://noamt.github.io/elasticsearch-grails-plugin/docs/index.html#searchableComponentReference
Related
I have migrated my application from Grails-3.x to Grails-4.1.1
Most of my Domain classes implemented the following Traits class (DynamicProperties), which has an implementation of GormEntity for some reason - to override the propertyMissing method.
trait DynamicProperties<D> implements GormEntity<D> {
def dynamic = [:]
def propertyMissing(String name, value) {
if (!propertyIsDatasource(name)) {
dynamic[name] = value
}
}
def propertyMissing(String name) {
if (propertyIsDatasource(name)) {
super.propertyMissing(name)
} else {
dynamic[name]
}
}
boolean propertyIsDatasource(String name) {
false
}
}
The above trait has been implemented by many domain classes like this
class Customer implements DynamicProperties<Customer> {
String customerCode
String customerName
Address address
....
}
Now, when I run my application, It is throwing the following exception
HHH000142: Bytecode enhancement failed: com.apps.billing.Customer.
Caused by: java.lang.NullPointerException
at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.call(PogoMetaClassSite.java:37)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:47)
at org.codehaus.groovy.runtime.callsite.PogoMetaMethodSite.call(PogoMetaMethodSite.java:75)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:127)
at **com.apps.billing.common.DynamicProperties$Trait$Helper.$init$**(DynamicProperties.groovy:7)
It used to work fine with Grails3.x
I had a similar problem when migrating a project to Grails 4.0.13.
I tracked the trigger of this problem down to having default values for member variables in a trait class. I see you have the same with the property dynamic being initialized with the empty map.
I recommend lazy initializing it in one of your propertyMissing methods.
I'd like to do some work when a new object is added to a domain with a hasMany relationship.
like for example, for a Person hasMany Hobby do some work in the interceptor for addToHobby() and removeFromHobby() as follows:
class Person {
String name
boolean likesFishing
static hasMany = [hobby: Hobby]
addToHobby(Hobby h) {
super.addToHobby(h) //*throws missingMethod exception
if (h.name="Fishing") {this.likesFishing=true;}
}
removeFromHobby(Hobby h) {
super removeFromHobby(h)
if (h.name="Fishing") {this.likesFishing=false;}
}
}
For some reason an error is thrown which I guess has something to do with some magic work being done by Gorm that isn't done when the method is overridden. Anyway around this? I can put this kind of thing in beforeUpdate or something like that, but that's a lot more general and will catch every update, not just an addition or removal from the list.
Note the error thrown is not the null pointer exception referred to in the post on this topic 7 years ago (apparently caused by faiilure to initiate the Set), but rather a InvocationTargetException preventing the super method from beeing called.
No signature of method: testapp.Person.addToHobby() is applicable for argument types: (testapp.Hobby) values: [testapp.Hobby : (unsaved)]
Possible solutions: addToHobby(testapp.Hobby), addToHobby(java.lang.Object), getHobby(). Stacktrace follows:
java.lang.reflect.InvocationTargetException: null
at org.grails.core.DefaultGrailsControllerClass$ReflectionInvoker.invoke(DefaultGrailsControllerClass.java:210)
at org.grails.core.DefaultGrailsControllerClass.invoke(DefaultGrailsControllerClass.java:187)
at org.grails.web.mapping.mvc.UrlMappingsInfoHandlerAdapter.handle(UrlMappingsInfoHandlerAdapter.groovy:90)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:963)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:897)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:861)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
at org.springframework.boot.web.filter.ApplicationContextHeaderFilter.doFilterInternal(ApplicationContextHeaderFilter.java:55)
at org.grails.web.servlet.mvc.GrailsWebRequestFilter.doFilterInternal(GrailsWebRequestFilter.java:77)
at org.grails.web.filters.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:67)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
The domain methods are injected into your classes by meta-programming or similar technique, so there's no such thing as super.addTo*().
The easiest way for you would be to use interceptors as you mentioned, or you can add your own meta-methods to override GORM's default addTo's.
For example:
class BootStrap {
def init = { servletContext ->
def oldAddToHobby = Person.metaClass.getMetaMethod 'addToHobby'
Person.metaClass.addToHobby{ Hobby h ->
oldAddToHobby.invoke delegate, h
println 'blah 2'
}
}
def destroy = {}
}
Based on injecteer's comment above, below is a generic method to add a handler after addTo (or any other dynamic method which takes a single parameter).
public class Meta {
static boolean setAfterHandler(String methodName, Class sourceClass, Class paramClass) {
//get the method
MetaMethod mymethod = sourceClass.metaClass.getMetaMethod(methodName)
if (mymethod==null) return false
//if after_ handler exists, update the metaClass to call the
// dynamic method, and then the after_ method handler. r
if (sourceClass.newInstance().respondsTo("after_${methodName}")) {
sourceClass.metaClass."${methodName}" {myParam ->
mymethod.invoke delegate, myParam
delegate."after_${methodName}"(myParam);
}
return true
} else {
return false
}
}
To use this, call the setAfterHandler method somewhere in Bootstrap or elsewhere before you try to use it, for example
def success = Meta.setAfterHandler("addToHobby", Person.class, Hobby.class)
In your domain class, insert your handler with the method name after_addToCollectionName as in the example below:
class Person {
String first
String last
Integer age
boolean likesFishing=false
static hasMany = [hobby: Hobby]
static constraints = {
}
void after_addToHobby(Hobby h) {
if (h.name=="Fishing") {
this.likesFishing=true;
}
}
}
You'll probably have more fun attacking this problem from the other side:...
class Hobby {
String name
static belongsTo = [person: Person]
setPerson(Person p) {
this.person = p
if (name="Fishing") {p.likesFishing=true;}
}
Person getPerson() {
return this.person
}
}
I am trying to clear out the data from the userDeptses set but when I call the clear() method and try to save I get the following error
Caused by: java.lang.UnsupportedOperationException: queued clear cannot be used with orphan delete
I have the cascading set properly, I have even tried using just delete-orphan but still have issues. All relevant classes have equals and hashCode methods implemented: AppSystemUser UserDepts Department
All documentation and articles I have read online say that using the combination of clear() and all-delete-orphan is supposed to work, but not for me. Any help is greatly appreciated.
Grails 3.1.4
Controller:
AppSystemUser user = AppSystemUser.findBySystemUserid(cmd.netid);
user.userDeptses.clear();
userMgmtService.saveAppSystemUser(user);
AppSystemUser:
class AppSystemUser {
String systemUserid
String email
String fullName
Date lastLogin
Boolean active
Set appSystemUserRoles = [];
Set userCollegeses = [];
Set userDeptses = [];
static hasMany = [appSystemUserRoles: AppSystemUserRole,
applicationExtensions: ApplicationExtension,
userCollegeses: UserColleges,
userDeptses: UserDepts]
static mapping = {
version false
fullName column: 'fullName'
lastLogin column: 'lastLogin'
id name: "systemUserid", generator: "assigned"
appSystemUserRoles cascade: "save-update, all-delete-orphan"
userCollegeses cascade: "save-update, all-delete-orphan"
userDeptses cascade: "save-update, all-delete-orphan"
}
....
UserDept:
class UserDepts {
Boolean active
AppSystemUser appSystemUser
Department department
static belongsTo = [AppSystemUser, Department]
static mapping = {
version false
appSystemUser column: "system_userid"
}
....
UserMgmtService:
#Transactional
class UserMgmtService {
def saveAppSystemUser(AppSystemUser user) {
user.save();
}
}
I was not able to find out how to successfully use the clear() method of the Set so I just created a simple workaround that does the trick
def static hibernateSetClear(Set data) {
if(data) {
Iterator i = data.iterator();
while (i.hasNext() && i.next()) {
i.remove();
}
}
}
Just iterate through the Set and remove each item individually. This works perfect and I just call this method instead of clear() whenever I need to clear a Set
Grails 2.3.7, Java 1.7, tomcat
For no apparent reason(that I can see) the reviewResults property of the Review domain sometimes saves as the ordinal value of the enum.
The database schema says that the column is a varchar type.
Sometimes NA is saved as NA, however, sometimes it is saved as 0.
This is the same with passed, except passed is saved as 1 sometimes.
Any ideas why this might be happening?
abstract class Work {
// Nothing defined in here related
// to the review domain enum.
}
The Domain class in question.
class Review extends Work {
Results reviewResults
String notes
enum Results {
NA,
Passed,
Error
}
static constraints = {
reviewResults(nullable: true, enumType: 'string')
}
// Is this redundant if it is already declared in the constraints?
static mapping = {
reviewResults enumType: 'string'
}
}
The Domains related controller.
// Reviews created with Quartz job, results property is not set.
class ReviewController {
def reviewService
def show(Long id) {
def reviewInstance = Review.get(id)
def reviewResultsOptions = []
reviewResultsOptions.addAll(com.mycompany.app.Review.Results.values())
[reviewInstance: reviewInstance, reviewResultsOptions: reviewResultsOptions]
}
def closeWork(Long id, String reviewResults, String notes) {
def review = Review.get(id)
review.reviewResults = Review.Results.valueOf(reviewResults)
def result = reviewService.closeReview(review, notes)
}
}
The service for the controller/domain.
class ReviewService {
def workService
Review closeReview(Review work, String notes) {
work.notes = notes
workService.closeWork(work)
return work.errors.allErrors.empty ? work : null
}
}
The service where the Review object is finally saved.
class WorkService {
Tracking closeWork(Work workInstance) {
def tracked = new Tracking()
workInstance.setStatus(status)
workIntance.setClosed(new Date())
WorkInstance.save(flush: true)
// Set some tracking properties and save and return the tracking object.
}
}
I am trying to use this super good plugin:
https://github.com/robfletcher/grails-gson/blob/master/test/apps/gson-test/grails-app/controllers/grails/plugin/gson/test/AlbumController.groovy
because the default GRAILS JSON does not expand the related items.
However when I try it, it fails.
Now, when i do this, it works:
def levelJson() {
render ToolType.list(params) as JSON
}
This fails:
def levelJson() {
render ToolType.list(params) as GSON
}
Error:
ERROR errors.GrailsExceptionResolver - UnsupportedOperationException occurred when processing request: [GET] /authtools/toolType/levelJson
Attempted to serialize java.lang.Class: org.hibernate.proxy.HibernateProxy. Forgot to register a type adapter?. Stacktrace follows:
Message: Attempted to serialize java.lang.Class: org.hibernate.proxy.HibernateProxy. Forgot to register a type adapter?
Classes:
class Artist {
String name
static hasMany = [albums: Album]
}
class Album {
String title
static belongsTo = Artist
}
The solution as dmahapatro pointed out was to modify the data class belongsTo
Before (not working):
class Album {
String title
static belongsTo = Artist
}
After (working):
class Album {
String title
static belongsTo = [artist: Artist]
}