I know that using Class.forName to load a grails domain class does not work, but I'm not sure why that is. I'm guessing there is some sort of grails magic happening but it would be nice to understand what it is.
I ended up using
GrailsDomainClass dc = grailsApplication.getDomainClass('mypack.myclass' )
def newDomainObject = dc.clazz.newInstance()
But I'm not sure why just doing Class.forName('mypack.myclass') doesn't work.
Grails uses custom classloaders, so you need to use the 3-arg variant with the classloader that Grails uses and registers as the context classloader:
Class clazz = Class.forName('mypack.myclass', true, Thread.currentThread().contextClassLoader)
def newDomainObject = clazz.newInstance()
Related
I'm running into an issue where a unit test that extends HibernateSpec is failing due to the sessionFactory not being injected into the service under test. Whenever a method on the sessionFactory is called during the test, I get a NullPointerException (e.g. java.lang.NullPointerException: Cannot invoke method getClassMetadata() on null object) and the test subsequently fails.
I'm using Grails 3.2.4 and Hibernate 5.
This was working when the test used #TestMixin(HibernateTestMixin), but it looks like with some updates, the mixin is deprecated and suggests using HibernateSpec instead.
Here's a snippet from my test:
class TestDatabaseServiceSpec extends HibernateSpec {
def setup() {
}
def cleanup() {
}
void "test method"() {
when:
service.method()
then:
true
}
}
And here is a snippet from the service under test:
void method() {
...
TABLE_NAMES.add(sessionFactory.getClassMetadata('MyDomain').tableName)
...
}
I have tried to set service.sessionFactory in setup method as well as setupSpec method with the sessionFactory available in the test, but that did not help unfortunately. I have thought about using an integration test, but I would really like to see if I can continue to have this unit test work as it was before. Does anyone know if I am I doing something incorrectly or if there is a workaround/solution for this?
Thank you!
Viewing source code of HibernateSpec could give some hint. Could be there is no getSessionFactory() method in Hibernate 5 grails plugin that you are using.
I am using org.grails.plugins:hibernate5:6.0.6 with grails 3.2.5 and sessionFactory is not null for me in unit tests.
I understand that it is not always possible to upgrade, but it could give an idea what is the source of the problem.
Update:
service.sessionFactory = sessionFactory
also must be called in test setup.
What worked for me was to add the annotation #FreshRuntime to the spec and to add a doWithSpring closure to the test that sets up the sessionFactory for me (i.e. sessionFactory(InstanceFactoryBean, sessionFactory, SessionFactory)). Also, I made sure to add both service.sessionFactory = sessionFactory and service.transactionManager = transactionManager to the setupSpec method. Finally, I needed to override getDomainClasses() to work with all of the domain objects I wanted to in the test. Thanks for all the help!
The Test Mixins have been deprecated for the new traits since 3.2.3.
https://docs.grails.org/latest/guide/testing.html
Since Grails 3.3, the Grails Testing Support Framework is used for all
unit tests. This support provides a set of traits.
However, I still find myself using HibernateSpec as I can get multiple Domain classes from that, and the ServiceUnitTest for the service as well. The HibernateSpec has the needed sessionFactory.
E.g.
class MyServiceSpec extends HibernateSpec implements ServiceUnitTest<MyService>{
List<Class> getDomainClasses() { [Tag, TagLink, TagLinkValue, TagValue, TestDomain] }
...
def setup() {
service.sessionFactory = sessionFactory
...
}
// tests here with all those domain classes available and the service
}
The DataTest allows for multiple domain classes, but not the service as well.
https://testing.grails.org/latest/guide/index.html#unitTestingDomainClasses
the service test allows for the service, but not multiple domain classes.
https://testing.grails.org/latest/guide/index.html#unitTestingServices
You could use both of them together, but neither one of them has the session factory (that I could see), I'm on Grails 4.0.1. I looked through Github code for the latest and still don't see it available. Plus, the DataTest doesn't a hibernate session, it uses a SimpleMap instead so you can't use hql with it, which I wanted.
My Service was using the statelessSession
def statelessSession = sessionFactory.openStatelessSession()
String queryString = "select DISTINCT(t.tagRef) from com.fmr.aps.taggable.TagLink t group by t.tagRef order by t.tagRef ASC"
Query query = statelessSession.createQuery(queryString)
query.setReadOnly(true);
ScrollableResults results = query.scroll(ScrollMode.FORWARD_ONLY);
So I needed a unit test trait or base class with it and I spent several hours trying to make the new traits give me what I wanted. Even I began subclassing the DataTest class to try and make it give me the sessionFactory, but eventually just went with the HibernateSpec and it's nice to be able to run that one test class with several Domains, the Service and the sessionFactory.
I have a compiled grails project, and from a separate groovy project, I reflectively load a domain class as follows
Class clazz = Class.forName('mypack.myclass', true, Thread.currentThread().contextClassLoader)
def newDomainObject = clazz.newInstance()
When running from outisde of grails (in my separate groovy project), the object is not recognized as a GroovyObject.
println newDomainObject instanceof GroovyObject // false
Since I'm running from outside of grails, I would think that groovy would treat the domain class just like any other class (and from looking at the class file, it does implement GroovyObject).
My best guess is that it has something to do with how grails compiles the domain object, but I'm not sure what's going on here.
Note this is related to Why doesn't Class.forName work on grails domain classes, but not the same.
This seems impossible - just like javac changing classes that don't explicitly extend a base class to extend java.lang.Object, groovyc changes all Groovy classes to implement groovy.lang.GroovyObject.
Are you looking at a class compiled in the Groovy project from a shared .groovy class, or a compiled class in a jar?
instanceof is tricky though due to Groovy's order of evaluation; try adding parens:
println (newDomainObject instanceof GroovyObject)
If that still prints false, try recursively dumping all of the implemented interfaces:
while (clazz != Object) {
def interfaces = clazz.interfaces
if (interfaces) {
clazz.interfaces.each { println "$clazz.name implements $it.name" }
}
else {
println "$clazz.name doesn't directly implement any interfaces"
}
clazz = clazz.superclass
}
I am trying to use the Class.forName('com.mypack.MyDomain').newInstance()
to create an instance of the grails domain from its qualified name.
But Its throwing a ClassNotFoundException.
I assume this is because the .forName('') expects the class to be a java class instead of a groovy class?
How to make this work in grails, or is there another method to create the domain object from the class name in String format.
Thanks
Priyank
Another route to try would be to do:
GrailsDomainClass dc = grailsApplication.getDomainClass( 'com.mypack.MyDomain' )
def newDomainObject = dc.clazz.newInstance()
Try
GrailsClass clazz = grailsApplication.getArtefactByLogicalPropertyName(DomainClassArtefactHandler.TYPE, className)
clazz.clazz.newInstance()
I would like to make use of the g.message() functionality in the toString method of my domain class, but the g.-namespace is not accessible by default.
I doubt that a import g.* will do the trick.
I already know that I can use the messageSource functionality, but it would be nicer to use the same syntax as in the views.
You can use:
class MyDomain {
def someMethod() {
def g = ApplicationHolder.application.mainContext.getBean( 'org.codehaus.groovy.grails.plugins.web.taglib.ApplicationTagLib' )
return g.message(....)
}
}
or else you can get messageSource directly: ApplicationHolder.application.mainContext.getBean('messageSource')
Using g.render in a grails service has some hints how to use "g:" in a service. I have not tested this, but it should work mostly the same in domain classes, with one important exception: a domain class cannot use InitializingBean since it's not a bean residing in the application context.
For an arbitrary object what is the easiest way to determine if the type of the object is a Grails domain class?
You can use the GrailsApplication for that. Add a dependency injection to your controller or service:
def grailsApplication
and then you can use it like this:
def foo = ...
if (grailsApplication.isDomainClass(foo.getClass()) {
...
}
Found the following snippet at https://svn.intuitive-collaboration.com/RiskAnalytics/trunk/riskanalytics-grails/src/java/org/codehaus/groovy/grails/web/binding/GrailsDataBinder.java
DomainClassArtefactHandler.isDomainClass(clazz)
The javadoc is here: http://grails.org/doc/latest/api/org/codehaus/groovy/grails/commons/DomainClassArtefactHandler.html#isDomainClass(java.lang.Class)