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

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.

Related

Grails Overriding domain.addTo

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

Grails default value for domain class attribute of user defined type

I have a domain class Plan which is like:
class Plan{
String name
static constraints={
name(unique:true,nullable:false)
}
}
Another domain class is User:
class User{
// Other attribures and code
// ....
Plan plan
static constraints = {
// other constraints..
plan(nullable:false, defaultValue: Plan.findByName("default"))
}
}
Above code gives me error:
Caused by: java.lang.IllegalStateException: Method on class [mypackage.Plan] was used outside of a Grails application. If running in the context of a test using the mocking API or bootstrap Grails correctly.
at mypackage.User$__clinit__closure1.doCall(User.groovy:31)
The line above in error is
plan(nullable:true,defaultValue: Plan.findByName("default"))
Also I have defined the default plan in BootStrap.groovy:
if(!Plan.findByName("default")){
new Plan(name: "default",brandPartner: null,secRole: null).save(failOnError: true, flush: true)
log.info("initPlans: No default plan found, hence created a new default plan!")
}
So how should I set the default value for plan attribute(which is of user defined type)?
Use an interceptor instead of constraint:
class User{
Plan plan
static beforeInsert = {
if( !plan ) Plan.withTransaction {
plan = Plan.findByName 'default'
}
}
}

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.

Role based domain class field access in grails

I am developing a grails application.In that some cases I want to control the domain class fields based on the role.So that in each call to getter setter method of domain class I want to apply some filter based on role(Logged in user's role).I am assuming that grails will create getter setter method at runtime for the domin classes.So while writing grails code is it possible to apply this logic.If it is possible then how to apply?
Example:
Domain Class :
class Book{
String name;
double price;
}
Controller:
def index={
Book book=Book.get(1);
println book.name;
println book.price;
}
In the above code "println book.price;" this line should work only for particular role.For some other role it should throw some exception.
Is it possible achieve?Is there any plugin to do this?
Please give some help on this....Thanks
You can create get/set methods for the properties you want to control access to and put your security logic there. Assuming you've written your own security service or are using a security plugin like the Spring Security (Acegi) plugin you would:
class Book{
String name;
double price;
def authenticateService
void setPrice(double price) {
if(!authenticateService.ifAllGranted('ROLE_PRICE_FIXER')) {
throw new Exception("You are not authorized to set book prices")
}
this.price = price
}
double getPrice() {
if(!authenticateService.ifAllGranted('ROLE_PRICE_FIXER')) {
throw new Exception("You are not authorized to get book prices")
}
return this.price
}
}
I am not aware of any plugin that allows access controls to be put on domain properties.
You could also consider using a custom validator or a spring errors object to catch attempts to set a field before saving it.
EDIT: Here is an example of what I was thinking. You could generalize quite a bit more and the code here hasn't been tested so it probably won't run as is.
class securedDomain {
String securedField
def fieldSetBy = [:]
def previousValue = [:]
static transients = ['fieldSetBy', 'previousValue']
static constraints = {
securedField(validator: { v, o ->
def access = User.findByName(fieldSetBy['securedField']).hasAccess('securedField')
if(!access) securedField = previousValue['securedField']
return access
})
void setProperty(String name, value) {
if(name == "securedField") {
fieldSetBy['securedField'] = session.user
previousValue['securedField'] = securedField
securedField = value
} else {
super(name, value)
}
}

Resources