Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HibernateProxy and hibernate-groovy-bytebuddy lib #624

Merged
merged 4 commits into from Nov 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
27 changes: 27 additions & 0 deletions examples/grails-hibernate-groovy-proxy/build.gradle
@@ -0,0 +1,27 @@
group "examples"

dependencies {
implementation "org.yakworks:hibernate-groovy-proxy:$hibernateGroovyProxy"

implementation "org.springframework.boot:spring-boot-starter-logging"
implementation "org.springframework.boot:spring-boot-autoconfigure"
// implementation "javax.servlet:javax.servlet-api:$servletApiVersion"
implementation "org.grails:grails-core:$grailsVersion"
implementation "org.grails:grails-dependencies:$grailsVersion", {
exclude module:'grails-datastore-simple'
}
implementation "org.grails:grails-web-boot:$grailsVersion"
implementation project(":grails-plugin")

implementation "org.hibernate:hibernate-core:$hibernate5Version"

// runtimeOnly "com.bertramlabs.plugins:asset-pipeline-grails:$assetPipelineVersion"
runtimeOnly "com.h2database:h2"
runtimeOnly "org.yaml:snakeyaml:$snakeyamlVersion"
runtimeOnly "org.apache.tomcat:tomcat-jdbc:$tomcatVersion"
// runtimeOnly "org.grails.plugins:fields:$fieldsVersion"
// runtimeOnly "org.grails.plugins:scaffolding:$scaffoldingVersion"

testImplementation "org.grails:grails-gorm-testing-support:$testingSupportVersion"

}
@@ -0,0 +1,25 @@
---
grails:
profile: web
codegen:
defaultPackage: datasources
info:
app:
name: '@info.app.name@'
version: '@info.app.version@'
grailsVersion: '@info.app.grailsVersion@'
spring:
groovy:
template:
check-template-location: false
main:
allow-circular-references: true

---
dataSource:
pooled: true
jmxExport: true
driverClassName: org.h2.Driver
dbCreate: create-drop
url: jdbc:h2:mem:books;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE

17 changes: 17 additions & 0 deletions examples/grails-hibernate-groovy-proxy/grails-app/conf/logback.xml
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>

<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" />
<conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" />

<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<charset>UTF-8</charset>
<pattern>'%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%5p) %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n%wex'</pattern>
</encoder>
</appender>

<root level="error">
<appender-ref ref="STDOUT" />
</root>
</configuration>
@@ -0,0 +1,20 @@
package example

import grails.compiler.GrailsCompileStatic
import grails.persistence.Entity

@Entity
@GrailsCompileStatic
class Customer implements Serializable {

String name

Customer(Long id, String name) {
this.id = id
this.name = name
}

static mapping = {
id generator: 'assigned'
}
}
@@ -0,0 +1,15 @@
package datasources

import grails.boot.GrailsApp
import grails.boot.config.GrailsAutoConfiguration
import groovy.transform.CompileStatic
import org.springframework.boot.autoconfigure.EnableAutoConfiguration
import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration

//@EnableAutoConfiguration(exclude = DataSourceTransactionManagerAutoConfiguration)
@CompileStatic
class Application extends GrailsAutoConfiguration {
static void main(String[] args) {
GrailsApp.run(Application)
}
}
@@ -0,0 +1,40 @@
package example

import org.hibernate.Hibernate

import grails.gorm.transactions.Rollback
import grails.test.hibernate.HibernateSpec

/**
* Tests Proxy with hibernate-groovy-proxy
*/
class ProxySpec extends HibernateSpec {

@Rollback
void "Test Proxy"() {
when:
new Customer(1, "Bob").save(failOnError: true, flush: true)
hibernateDatastore.currentSession.clear()

def proxy
Customer.withNewSession {
proxy = Customer.load(1)
}

then:
//without ByteBuddyGroovyInterceptor this would normally cause the proxy to init
proxy
proxy.metaClass
proxy.getMetaClass()
!Hibernate.isInitialized(proxy)
//id calls
proxy.id == 1
proxy.getId() == 1
proxy["id"] == 1
!Hibernate.isInitialized(proxy)
// gorms trait implements in the class so no way to tell
// proxy.toString() == "Customer : 1 (proxy)"
// !Hibernate.isInitialized(proxy)
}

}
Expand Up @@ -2,8 +2,10 @@ package functional.tests

import grails.testing.mixin.integration.Integration
import geb.spock.GebSpec
import spock.lang.Ignore

@Integration(applicationClass = Application)
@Ignore //FAILING downloading the firefox driver
class BookControllerSpec extends GebSpec {

void "Test list books"() {
Expand Down
1 change: 1 addition & 0 deletions gradle.properties
Expand Up @@ -8,6 +8,7 @@ groovyVersion=3.0.11
h2Version=1.4.200
hibernate5Version=5.6.11.Final
hibernateValidatorVersion=6.2.5.Final
hibernateGroovyProxy=1.1
jansiVersion=2.4.0
javaParserCoreVersion=3.23.0
jaxbVersion=2.3.1
Expand Down
5 changes: 4 additions & 1 deletion grails-datastore-gorm-hibernate5/build.gradle
Expand Up @@ -50,7 +50,10 @@ dependencies {

testImplementation "net.sf.ehcache:ehcache-core:2.6.11"
testImplementation "org.hibernate:hibernate-ehcache:$hibernate5Version"


// groovy proxy fixes bytebuddy to be a bit smarter when it comes to groovy metaClass
testImplementation "org.yakworks:hibernate-groovy-proxy:$hibernateGroovyProxy"

testImplementation "org.apache.tomcat:tomcat-jdbc:$tomcatVersion"
testRuntimeOnly "org.springframework:spring-aop:$springVersion"
testRuntimeOnly "org.apache.tomcat.embed:tomcat-embed-logging-log4j:$tomcatLog4jVersion"
Expand Down
Expand Up @@ -376,7 +376,7 @@ public static void ensureCorrectGroovyMetaClass(Object target, Class<?> persiste
* @return the unproxied instance
*/
public static Object unwrapProxy(HibernateProxy proxy) {
return proxyHandler.unwrapProxy(proxy);
return proxyHandler.unwrap(proxy);
}

/**
Expand All @@ -401,8 +401,12 @@ public static boolean isInitialized(Object obj, String associationName) {
return proxyHandler.isInitialized(obj, associationName);
}

/**
* Unproxies a HibernateProxy. If the proxy is uninitialized, it automatically triggers an initialization.
* In case the supplied object is null or not a proxy, the object will be returned as-is.
*/
public static Object unwrapIfProxy(Object instance) {
return proxyHandler.unwrapIfProxy(instance);
return proxyHandler.unwrap(instance);
}

/**
Expand Down
Expand Up @@ -15,46 +15,152 @@
*/
package org.grails.orm.hibernate.proxy;


import org.hibernate.collection.internal.AbstractPersistentCollection;
import java.io.Serializable;
import org.grails.datastore.gorm.GormEnhancer;
import org.grails.datastore.mapping.core.Session;
import org.grails.datastore.mapping.engine.AssociationQueryExecutor;
import org.grails.datastore.mapping.model.PersistentEntity;
import org.grails.datastore.mapping.proxy.ProxyFactory;
import org.grails.datastore.mapping.proxy.ProxyHandler;
import org.grails.datastore.mapping.reflect.ClassPropertyFetcher;
import org.hibernate.Hibernate;
import org.hibernate.collection.spi.PersistentCollection;
import org.hibernate.proxy.HibernateProxy;
import org.hibernate.proxy.HibernateProxyHelper;

/**
* Implementation of the ProxyHandler interface for Hibernate.
* Implementation of the ProxyHandler interface for Hibernate using org.hibernate.Hibernate
* and HibernateProxyHelper where possible.
*
* @author Graeme Rocher
* @since 1.2.2
*/
public class HibernateProxyHandler extends SimpleHibernateProxyHandler {
public class HibernateProxyHandler implements ProxyHandler, ProxyFactory {

/**
* Check if the proxy or persistent collection is initialized.
* @inheritDoc
*/
@Override
public boolean isInitialized(Object o) {
if (o instanceof PersistentCollection) {
return ((PersistentCollection)o).wasInitialized();
return Hibernate.isInitialized(o);
}

/**
* Check if an association proxy or persistent collection is initialized.
* @inheritDoc
*/
@Override
public boolean isInitialized(Object obj, String associationName) {
try {
Object proxy = ClassPropertyFetcher.getInstancePropertyValue(obj, associationName);
return isInitialized(proxy);
}
catch (RuntimeException e) {
return false;
}
}

return super.isInitialized(o);
/**
* Unproxies a HibernateProxy. If the proxy is uninitialized, it automatically triggers an initialization.
* In case the supplied object is null or not a proxy, the object will be returned as-is.
* @inheritDoc
* @see Hibernate#unproxy
*/
@Override
public Object unwrap(Object object) {
if (object instanceof PersistentCollection) {
initialize(object);
return object;
}
return Hibernate.unproxy(object);
}

public Object unwrapIfProxy(Object instance) {
if (instance instanceof PersistentCollection) {
initialize(instance);
return instance;
/**
* @inheritDoc
* @see org.hibernate.proxy.AbstractLazyInitializer#getIdentifier
*/
@Override
public Serializable getIdentifier(Object o) {
if (o instanceof HibernateProxy) {
return ((HibernateProxy)o).getHibernateLazyInitializer().getIdentifier();
}
else {
//TODO seems we can get the id here if its has normal getId
// PersistentEntity persistentEntity = GormEnhancer.findStaticApi(o.getClass()).getGormPersistentEntity();
// return persistentEntity.getMappingContext().getEntityReflector(persistentEntity).getIdentifier(o);
return null;
}
}

return super.unwrapIfProxy(instance);
/**
* @inheritDoc
* @see HibernateProxyHelper#getClassWithoutInitializingProxy
*/
@Override
public Class<?> getProxiedClass(Object o) {
return HibernateProxyHelper.getClassWithoutInitializingProxy(o);
}

/**
* calls unwrap which calls unproxy
* @see #unwrap(Object)
* @deprecated use unwrap
*/
@Deprecated
public Object unwrapIfProxy(Object instance) {
return unwrap(instance);
}

/**
* @inheritDoc
*/
@Override
public boolean isProxy(Object o) {
return super.isProxy(o) || (o instanceof PersistentCollection);
return (o instanceof HibernateProxy) || (o instanceof PersistentCollection);
}

/**
* Force initialization of a proxy or persistent collection.
* @inheritDoc
*/
@Override
public void initialize(Object o) {
if (o instanceof PersistentCollection) {
final PersistentCollection col = (PersistentCollection)o;
if (!col.wasInitialized()) {
col.forceInitialization();
Hibernate.initialize(o);
}

@Override
public <T> T createProxy(Session session, Class<T> type, Serializable key) {
throw new UnsupportedOperationException("createProxy not supported in HibernateProxyHandler");
}

@Override
public <T, K extends Serializable> T createProxy(Session session, AssociationQueryExecutor<K, T> executor, K associationKey) {
throw new UnsupportedOperationException("createProxy not supported in HibernateProxyHandler");
}

/**
* @deprecated use unwrap
*/
@Deprecated
public Object unwrapProxy(Object proxy) {
return unwrap(proxy);
}

/**
* returns the proxy for an association. returns null if its not a proxy.
* Note: Only used in a test. Deprecate?
*/
public HibernateProxy getAssociationProxy(Object obj, String associationName) {
try {
Object proxy = ClassPropertyFetcher.getInstancePropertyValue(obj, associationName);
if (proxy instanceof HibernateProxy) {
return (HibernateProxy) proxy;
}
return null;
}
catch (RuntimeException e) {
return null;
}
super.initialize(o);
}
}
Expand Up @@ -34,9 +34,11 @@

/**
* Implementation of the ProxyHandler interface for Hibernate.
* Deprecated as Hibernate 5.6+ no longer supports Javassist
*
* @author Graeme Rocher
* @since 1.2.2
* @deprecated
*/
public class SimpleHibernateProxyHandler extends JavassistProxyFactory implements ProxyHandler, ProxyFactory {

Expand Down