Skip to content

Commit bd0df2d

Browse files
committed
Various fixes for not unwrapping Hibernate proxies
1 parent 6c76333 commit bd0df2d

14 files changed

Lines changed: 745 additions & 40 deletions

File tree

grails-data-hibernate5/core/build.gradle

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -73,11 +73,6 @@ dependencies {
7373
testImplementation 'org.apache.groovy:groovy-json'
7474
testImplementation 'org.apache.tomcat:tomcat-jdbc'
7575
testImplementation 'org.spockframework:spock-core'
76-
testImplementation 'org.yakworks:hibernate-groovy-proxy', {
77-
// groovy proxy fixes bytebuddy to be a bit smarter when it comes to groovy metaClass
78-
exclude group: 'org.codehaus.groovy', module: 'groovy'
79-
}
80-
8176
testRuntimeOnly 'org.hibernate:hibernate-ehcache', {
8277
// exclude javax variant of hibernate-core 5.6
8378
exclude group: 'org.hibernate', module: 'hibernate-core'

grails-data-hibernate5/core/src/main/groovy/org/grails/orm/hibernate/cfg/HibernateMappingContextConfiguration.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
4949
import org.hibernate.boot.registry.selector.spi.StrategySelector;
5050
import org.hibernate.boot.spi.MetadataContributor;
51+
import org.hibernate.bytecode.spi.ProxyFactoryFactory;
5152
import org.hibernate.cfg.AvailableSettings;
5253
import org.hibernate.cfg.Configuration;
5354
import org.hibernate.cfg.Environment;
@@ -77,6 +78,7 @@
7778
import org.grails.datastore.mapping.model.PersistentEntity;
7879
import org.grails.orm.hibernate.EventListenerIntegrator;
7980
import org.grails.orm.hibernate.GrailsSessionContext;
81+
import org.grails.orm.hibernate.proxy.GrailsBytecodeProvider;
8082
import org.grails.orm.hibernate.HibernateEventListeners;
8183
import org.grails.orm.hibernate.MetadataIntegrator;
8284
import org.grails.orm.hibernate.access.TraitPropertyAccessStrategy;
@@ -315,6 +317,13 @@ public void sessionFactoryClosed(SessionFactory factory) {
315317
StandardServiceRegistryBuilder standardServiceRegistryBuilder = createStandardServiceRegistryBuilder(bootstrapServiceRegistry)
316318
.applySettings(getProperties());
317319

320+
// Groovy-aware entity proxies: without this, any Groovy MOP dispatch through a proxy
321+
// (proxy.id, getMetaClass(), truthy checks) initializes it. PojoEntityTuplizer resolves
322+
// the ProxyFactoryFactory from the service registry, so a provided service wins over the
323+
// default initiator. Hibernate 7 gets the same behavior via GrailsBytecodeProvider.
324+
standardServiceRegistryBuilder.addService(ProxyFactoryFactory.class,
325+
new GrailsBytecodeProvider().getProxyFactoryFactory());
326+
318327
StandardServiceRegistry serviceRegistry = standardServiceRegistryBuilder.build();
319328
sessionFactory = super.buildSessionFactory(serviceRegistry);
320329
this.serviceRegistry = serviceRegistry;
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* https://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.grails.orm.hibernate.proxy;
20+
21+
import java.io.Serializable;
22+
import java.lang.reflect.Method;
23+
24+
import org.hibernate.engine.spi.SharedSessionContractImplementor;
25+
import org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor;
26+
import org.hibernate.type.CompositeType;
27+
28+
/**
29+
* A ByteBuddy interceptor that avoids initializing the proxy for Groovy-specific methods.
30+
* Mirrors the Hibernate 7 implementation in grails-data-hibernate7.
31+
*
32+
* @since 8.0
33+
*/
34+
public class ByteBuddyGroovyInterceptor extends ByteBuddyInterceptor {
35+
36+
private static final String GET_ID_METHOD = "getId";
37+
private static final String GET_IDENTIFIER_METHOD = "getIdentifier";
38+
39+
@SuppressWarnings({ "unchecked", "rawtypes" })
40+
public ByteBuddyGroovyInterceptor(
41+
String entityName,
42+
Class<?> persistentClass,
43+
Class<?>[] interfaces,
44+
Serializable id,
45+
Method getIdentifierMethod,
46+
Method setIdentifierMethod,
47+
CompositeType componentIdType,
48+
SharedSessionContractImplementor session,
49+
boolean overridesEquals) {
50+
super(
51+
entityName,
52+
(Class) persistentClass,
53+
(Class[]) interfaces,
54+
id,
55+
getIdentifierMethod,
56+
setIdentifierMethod,
57+
componentIdType,
58+
session,
59+
overridesEquals);
60+
}
61+
62+
@Override
63+
public Object intercept(Object proxy, Method method, Object[] args) throws Throwable {
64+
String methodName = method.getName();
65+
66+
// Answer identifier access from the LazyInitializer so it never initializes the proxy
67+
if ((getIdentifierMethod != null && methodName.equals(getIdentifierMethod.getName())) ||
68+
GET_ID_METHOD.equals(methodName) ||
69+
GET_IDENTIFIER_METHOD.equals(methodName)) {
70+
return getIdentifier();
71+
}
72+
73+
if (isUninitialized()) {
74+
GroovyProxyInterceptorLogic.InterceptorState state = new GroovyProxyInterceptorLogic.InterceptorState(
75+
getEntityName(), persistentClass, getIdentifier());
76+
Object result = GroovyProxyInterceptorLogic.handleUninitialized(state, methodName, args);
77+
if (result != GroovyProxyInterceptorLogic.INVOKE_IMPLEMENTATION) { // NOPMD: sentinel comparison
78+
return result;
79+
}
80+
}
81+
82+
return super.intercept(proxy, method, args);
83+
}
84+
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* https://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.grails.orm.hibernate.proxy;
20+
21+
import java.io.Serial;
22+
import java.io.Serializable;
23+
import java.lang.reflect.Method;
24+
import java.util.Set;
25+
26+
import org.hibernate.HibernateException;
27+
import org.hibernate.engine.spi.SharedSessionContractImplementor;
28+
import org.hibernate.internal.util.ReflectHelper;
29+
import org.hibernate.proxy.HibernateProxy;
30+
import org.hibernate.proxy.ProxyConfiguration;
31+
import org.hibernate.proxy.pojo.bytebuddy.ByteBuddyProxyFactory;
32+
import org.hibernate.proxy.pojo.bytebuddy.ByteBuddyProxyHelper;
33+
import org.hibernate.type.CompositeType;
34+
35+
import static org.hibernate.internal.util.collections.ArrayHelper.EMPTY_CLASS_ARRAY;
36+
37+
/**
38+
* A ProxyFactory implementation for ByteBuddy that uses {@link ByteBuddyGroovyInterceptor}.
39+
* Mirrors the Hibernate 7 implementation in grails-data-hibernate7.
40+
*
41+
* @since 8.0
42+
*/
43+
public class ByteBuddyGroovyProxyFactory extends ByteBuddyProxyFactory {
44+
45+
@Serial
46+
private static final long serialVersionUID = 1L;
47+
48+
private final transient ByteBuddyProxyHelper byteBuddyProxyHelper;
49+
private Class<?> persistentClass;
50+
private String entityName;
51+
private Class<?>[] interfaces;
52+
private transient Method getIdentifierMethod;
53+
private transient Method setIdentifierMethod;
54+
private transient CompositeType componentIdType;
55+
private boolean overridesEquals;
56+
private Class<?> proxyClass;
57+
58+
public ByteBuddyGroovyProxyFactory(ByteBuddyProxyHelper byteBuddyProxyHelper) {
59+
super(byteBuddyProxyHelper);
60+
this.byteBuddyProxyHelper = byteBuddyProxyHelper;
61+
}
62+
63+
@Override
64+
@SuppressWarnings({ "unchecked", "rawtypes" })
65+
public void postInstantiate(
66+
String entityName,
67+
Class persistentClass,
68+
Set<Class> interfaces,
69+
Method getIdentifierMethod,
70+
Method setIdentifierMethod,
71+
CompositeType componentIdType)
72+
throws HibernateException {
73+
this.entityName = entityName;
74+
this.persistentClass = persistentClass;
75+
this.interfaces = interfaces == null ? EMPTY_CLASS_ARRAY : (Class<?>[]) interfaces.toArray(EMPTY_CLASS_ARRAY);
76+
this.getIdentifierMethod = getIdentifierMethod;
77+
this.setIdentifierMethod = setIdentifierMethod;
78+
this.componentIdType = componentIdType;
79+
this.overridesEquals = ReflectHelper.overridesEquals(persistentClass);
80+
81+
this.proxyClass = byteBuddyProxyHelper.buildProxy(persistentClass, this.interfaces);
82+
83+
// do NOT call super.postInstantiate: the stock factory keeps private state for its own
84+
// getProxy(), which is fully replaced here
85+
}
86+
87+
@Override
88+
public HibernateProxy getProxy(Serializable id, SharedSessionContractImplementor session) throws HibernateException {
89+
try {
90+
final ByteBuddyGroovyInterceptor interceptor = new ByteBuddyGroovyInterceptor(
91+
entityName,
92+
persistentClass,
93+
interfaces,
94+
id,
95+
getIdentifierMethod,
96+
setIdentifierMethod,
97+
componentIdType,
98+
session,
99+
overridesEquals);
100+
101+
final HibernateProxy hibernateProxy =
102+
(HibernateProxy) proxyClass.getDeclaredConstructor().newInstance();
103+
104+
if (hibernateProxy instanceof ProxyConfiguration) {
105+
((ProxyConfiguration) hibernateProxy).$$_hibernate_set_interceptor(interceptor);
106+
}
107+
108+
return hibernateProxy;
109+
} catch (Exception e) {
110+
throw new HibernateException("Unable to generate proxy for " + entityName, e);
111+
}
112+
}
113+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* https://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.grails.orm.hibernate.proxy;
20+
21+
import org.hibernate.bytecode.internal.bytebuddy.BytecodeProviderImpl;
22+
import org.hibernate.bytecode.spi.ProxyFactoryFactory;
23+
24+
/**
25+
* A {@link org.hibernate.bytecode.spi.BytecodeProvider} for Hibernate 5 that provides
26+
* Groovy-aware entity proxies via {@link ByteBuddyGroovyProxyFactory}. Extends the stock
27+
* ByteBuddy provider so enhancement and reflection optimization behave exactly as standard
28+
* Hibernate. Mirrors the Hibernate 7 implementation in grails-data-hibernate7.
29+
*
30+
* @since 8.0
31+
*/
32+
public class GrailsBytecodeProvider extends BytecodeProviderImpl {
33+
34+
@Override
35+
public ProxyFactoryFactory getProxyFactoryFactory() {
36+
return new GrailsProxyFactoryFactory(this, super.getProxyFactoryFactory());
37+
}
38+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* https://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.grails.orm.hibernate.proxy;
20+
21+
import java.io.Serial;
22+
23+
import org.hibernate.bytecode.spi.BasicProxyFactory;
24+
import org.hibernate.bytecode.spi.ProxyFactoryFactory;
25+
import org.hibernate.engine.spi.SessionFactoryImplementor;
26+
import org.hibernate.proxy.ProxyFactory;
27+
28+
/**
29+
* A {@link ProxyFactoryFactory} implementation for Hibernate 5 that provides Groovy-aware
30+
* entity proxies. Basic (non-entity) proxies are delegated to the stock implementation.
31+
* Mirrors the Hibernate 7 implementation in grails-data-hibernate7.
32+
*
33+
* @since 8.0
34+
*/
35+
public class GrailsProxyFactoryFactory implements ProxyFactoryFactory, java.io.Serializable {
36+
37+
@Serial
38+
private static final long serialVersionUID = 1L;
39+
40+
private final GrailsBytecodeProvider grailsBytecodeProvider;
41+
private final transient ProxyFactoryFactory basicProxyDelegate;
42+
43+
public GrailsProxyFactoryFactory(GrailsBytecodeProvider grailsBytecodeProvider, ProxyFactoryFactory basicProxyDelegate) {
44+
this.grailsBytecodeProvider = grailsBytecodeProvider;
45+
this.basicProxyDelegate = basicProxyDelegate;
46+
}
47+
48+
@Override
49+
public ProxyFactory buildProxyFactory(SessionFactoryImplementor sessionFactory) {
50+
return new ByteBuddyGroovyProxyFactory(grailsBytecodeProvider.getByteBuddyProxyHelper());
51+
}
52+
53+
@Override
54+
@SuppressWarnings("rawtypes")
55+
public BasicProxyFactory buildBasicProxyFactory(Class superClass, Class[] interfaces) {
56+
return basicProxyDelegate.buildBasicProxyFactory(superClass, interfaces);
57+
}
58+
59+
@Override
60+
@SuppressWarnings("rawtypes")
61+
public BasicProxyFactory buildBasicProxyFactory(Class superClassOrInterface) {
62+
return basicProxyDelegate.buildBasicProxyFactory(superClassOrInterface);
63+
}
64+
}

0 commit comments

Comments
 (0)