Grails Overriding domain.addTo - grails

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

Related

grails4 migration Traits lazy load issue - HHH000142: Bytecode enhancement failed

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.

Grails 3 - assign controller's variable in interceptor

I'm upgrading by Grails 2.5.1 web-app to grails 3, but I'm stuck with this problem: in my controllers I was using beforeInterceptors to pre-calculate a set of variables to be used in their action methods.
class MyController {
def myVar
def beforeInterceptor = {
myVar = calculateMyVarFromParams(params)
}
def index() {
/* myVar is already initialized */
}
}
Now that with Grails 3 interceptors are more powerful and on separate files, how can I achieve the same result? To avoid using request-scope variables, I tried with the following code
class MyInterceptor {
boolean before() {
MyController.myVar = calculateMyVarFromParams(params)
MyController.myVar != null // also block execution if myVar is still null
}
boolean after() { true }
void afterView() { /* nothing */ }
}
class MyController {
def myVar
def index() {
println('myVar: '+myVar)
}
}
but I get
ERROR org.grails.web.errors.GrailsExceptionResolver - MissingPropertyException occurred when processing request: [GET] /my/index
No such property: myVar for class: com.usablenet.utest.MyController
Possible solutions: myVar. Stacktrace follows:
groovy.lang.MissingPropertyException: No such property: myVar for class: com.usablenet.utest.MyController
Possible solutions: myVar
at com.usablenet.utest.MyInterceptor.before(MyInterceptor.groovy:15) ~[main/:na]
I assumed (wrongly, apparently) that this would be feasible. Is there a solution? Thanks in advance!
Note: in my case MyController is an abstract class extended by all other controllers
What I was missing is to declare myVar as static, as simple as that!
Update
If for any reason you cannot define the variable as static, you can set it as attribute on request object in the interceptor, and read it from there in the controller
// Interceptor
request.setAttribute('myVar', calculateMyVarFromParams(params))
// Controller
request.getAttribute('myVar')

Inherited grails domain classes missing dynamic properties

I'm having a problem where the related table id fields return 'null' from my domain objects when using inheritance. Here is an example:
In /src/groovy/
BaseClass1.groovy
class BaseClass1 {
Long id
static mapping = {
tablePerConcreteClass true
}
}
BaseClass2.groovy
class BaseClass2 extends BaseClass1 {
String someOtherProperty
static constraints = {
someOtherProperty(maxSize:200)
}
static mapping = BaseClass1.mapping
}
In /grails-app/domain
ParentClass.groovy
class ParentClass extends BaseClass2 {
ChildClass myChild
static mapping = BaseClass2.mapping << {
version false
}
}
ChildClass.groovy
class ChildClass extends BaseClass1 {
String property
static mapping = BaseClass1.mapping
}
The problem appears here:
SomeotherCode.groovy
print parentClassInstance.myChild.id // returns the value
print parentClassInstance.myChildId // returns null
Any ideas what might be going on to get those dynamic properties to break like this?
After debugging into the get(AssociationName)Id source, I found the following:
The handler for this is:
GrailsDomainConfigurationUtil.getAssociationIdentifier(Object target, String propertyName,
GrailsDomainClass referencedDomainClass) {
String getterName = GrailsClassUtils.getGetterName(propertyName);
try {
Method m = target.getClass().getMethod(getterName, EMPTY_CLASS_ARRAY);
Object value = m.invoke(target);
if (value != null && referencedDomainClass != null) {
String identifierGetter = GrailsClassUtils.getGetterName(referencedDomainClass.getIdentifier().getName());
m = value.getClass().getDeclaredMethod(identifierGetter, EMPTY_CLASS_ARRAY);
return (Serializable)m.invoke(value);
}
}
catch (NoSuchMethodException e) {
// ignore
}
catch (IllegalAccessException e) {
// ignore
}
catch (InvocationTargetException e) {
// ignore
}
return null;
}
It threw an exception on the related class (value.getClass().getDeclaredMethod), saying NoSuchMethod for the method getId(). I was unable to remove the id declaration from the base class without Grails complaining that an identifier column was required. I tried marking id as public and it also complained that it wasn't there. So, I tried this
BaseClass {
Long id
public Long getId() { return this.#id }
}
and things worked on some classes, but not on others.
When I removed the ID declaration, I go an error: "Identity property not found, but required in domain class". On a whim, I tried adding #Entity to the concrete classes and viola! everything started working.
class BaseClass {
//Don't declare id!
}
#Entity
class ParentClass {}
#Entity
class ChildClass {}
I still think it is a grails bug that it needs to be added, but at least it is easy enough to work around.
I'm not sure why you are seeing this behavior, but I'm also not sure why you are doing some of the things you are doing here. Why have a domain class extend a POGO? Domains, Controllers, and Services are heavily managed by the Grails machinery, which probably was not designed for this sort of use. Specifically, I believe Grails builds the dynamic property getters for the GrailsDomainProperty(s) of GrailsDomainClass(es), not POGO's. In this case, you have an explicitly declared id field in BaseClass1 that is not a GrailsDomainProperty. I suspect that this POGO id property is not picked up by the Grails machinery that creates the dynamic property getters for Domains.
You might try putting BaseClass1/2 in /grails-app/domain, perhaps making them abstract if you don't want them instantiated, then extending them as you are and seeing if you observe the behavior you want.

What are the alternatives to overriding asType() when writing conversion code?

It appears the convention for converting objects in Groovy is to use the as operator and override asType(). For example:
class Id {
def value
#Override
public Object asType(Class type) {
if (type == FormattedId) {
return new FormattedId(value: value.toUpperCase())
}
}
}
def formattedId = new Id(value: "test") as FormattedId
However, Grails over-writes the implementation of asType() for all objects at runtime so that it can support idioms like render as JSON.
An alternative is to re-write the asType() in the Grails Bootstrap class as follows:
def init = { servletContext ->
Id.metaClass.asType = { Class type ->
if (type == FormattedId) {
return new FormattedId(value: value.toUpperCase())
}
}
}
However, this leads to code duplication (DRY) as you now need to repeat the above in both the Bootstrap and the Id class otherwise the as FormattedId will not work outside the Grails container.
What alternatives exist to writing conversion code in Groovy/Grails that do not break good code/OO design principals like the Single Responsibility Principal or DRY? Are Mixins are good use here?
You can use the Grails support for Codecs to automatically add encodeAs* functions to your Grails archetypes:
class FormattedIdCodec {
static encode = { target ->
new FormattedId((target as String).toUpperCase()
}
}
Then you can use the following in your code:
def formattedId = new Id(value: "test").encodeAsFormattedId
My un-elegant solution is to rename the original asType(), and make a new asType() that calls it, and to also make your BootStrap overwrite astType with a call to that method:
so, your class:
class Id {
def value
#Override
public Object asType(Class type) {
return oldAsType(type);
}
public Object oldAsType(Class type) {
if (type == FormattedId) {
return new FormattedId(value: value.toUpperCase())
}
}
}
In my app, I had asType defined in a number of classes, so I ended up using a common closure in BootStrap.groovy:
def useOldAsType = {Class clazz ->
delegate.oldAsType(clazz)
}
Id.metaClass.asType = useOldAsType;
Value.metaClass.asType = useOldAsType;
OtherClass.metaClass.asType = useOldAsType;
SubclassOfValue.metaClass.asType = useOldAsType;
Note that if you have a subclass that does not override asType, but you want it to use the superclass's, you must also set it in BootStrap.

custom Grails validation

Normally for a Grails domain or command class, you declare your constraints and the framework adds a validate() method that checks whether each of these constraints is valid for the current instance e.g.
class Adult {
String name
Integer age
void preValidate() {
// Implementation omitted
}
static constraints = {
name(blank: false)
age(min: 18)
}
}
def p = new Person(name: 'bob', age: 21)
p.validate()
In my case I want to make sure that preValidate is always executed before the class is validated. I could achieve this by adding a method
def customValidate() {
preValidate()
validate()
}
But then everyone who uses this class needs to remember to call customValidate instead of validate. I can't do this either
def validate() {
preValidate()
super.validate()
}
Because validate is not a method of the parent class (it's added by metaprogramming). Is there another way to achieve my goal?
You should be able to accomplish this by using your own version of validate on the metaclass, when your domain/command class has a preValidate() method. Something similar to the below code in your BootStrap.groovy could work for you:
class BootStrap {
def grailsApplication // Set via dependency injection
def init = { servletContext ->
for (artefactClass in grailsApplication.allArtefacts) {
def origValidate = artefactClass.metaClass.getMetaMethod('validate', [] as Class[])
if (!origValidate) {
continue
}
def preValidateMethod = artefactClass.metaClass.getMetaMethod('preValidate', [] as Class[])
if (!preValidateMethod) {
continue
}
artefactClass.metaClass.validate = {
preValidateMethod.invoke(delegate)
origValidate.invoke(delegate)
}
}
}
def destroy = {
}
}
You may be able to accomplish your goal using the beforeValidate() event. It's described in the 1.3.6 Release Notes.

Resources