Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions grails-data-hibernate5/core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,6 @@ dependencies {
testImplementation 'org.apache.groovy:groovy-json'
testImplementation 'org.apache.tomcat:tomcat-jdbc'
testImplementation 'org.spockframework:spock-core'
testImplementation 'org.yakworks:hibernate-groovy-proxy', {
// groovy proxy fixes bytebuddy to be a bit smarter when it comes to groovy metaClass
exclude group: 'org.codehaus.groovy', module: 'groovy'
}

testRuntimeOnly 'org.hibernate:hibernate-ehcache', {
// exclude javax variant of hibernate-core 5.6
exclude group: 'org.hibernate', module: 'hibernate-core'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
import org.hibernate.boot.registry.selector.spi.StrategySelector;
import org.hibernate.boot.spi.MetadataContributor;
import org.hibernate.bytecode.spi.ProxyFactoryFactory;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.cfg.Configuration;
import org.hibernate.cfg.Environment;
Expand Down Expand Up @@ -80,6 +81,7 @@
import org.grails.orm.hibernate.HibernateEventListeners;
import org.grails.orm.hibernate.MetadataIntegrator;
import org.grails.orm.hibernate.access.TraitPropertyAccessStrategy;
import org.grails.orm.hibernate.proxy.GrailsBytecodeProvider;

/**
* A Configuration that uses a MappingContext to configure Hibernate
Expand Down Expand Up @@ -315,6 +317,13 @@ public void sessionFactoryClosed(SessionFactory factory) {
StandardServiceRegistryBuilder standardServiceRegistryBuilder = createStandardServiceRegistryBuilder(bootstrapServiceRegistry)
.applySettings(getProperties());

// Groovy-aware entity proxies: without this, any Groovy MOP dispatch through a proxy
// (proxy.id, getMetaClass(), truthy checks) initializes it. PojoEntityTuplizer resolves
// the ProxyFactoryFactory from the service registry, so a provided service wins over the
// default initiator. Hibernate 7 gets the same behavior via GrailsBytecodeProvider.
standardServiceRegistryBuilder.addService(ProxyFactoryFactory.class,
new GrailsBytecodeProvider().getProxyFactoryFactory());

StandardServiceRegistry serviceRegistry = standardServiceRegistryBuilder.build();
sessionFactory = super.buildSessionFactory(serviceRegistry);
this.serviceRegistry = serviceRegistry;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.grails.orm.hibernate.proxy;

import java.io.Serializable;
import java.lang.reflect.Method;

import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor;
import org.hibernate.type.CompositeType;

/**
* A ByteBuddy interceptor that avoids initializing the proxy for Groovy-specific methods.
* Mirrors the Hibernate 7 implementation in grails-data-hibernate7.
*
* @since 8.0
*/
public class ByteBuddyGroovyInterceptor extends ByteBuddyInterceptor {

private static final String GET_ID_METHOD = "getId";
private static final String GET_IDENTIFIER_METHOD = "getIdentifier";

@SuppressWarnings({ "unchecked", "rawtypes" })
public ByteBuddyGroovyInterceptor(
String entityName,
Class<?> persistentClass,
Class<?>[] interfaces,
Serializable id,
Method getIdentifierMethod,
Method setIdentifierMethod,
CompositeType componentIdType,
SharedSessionContractImplementor session,
boolean overridesEquals) {
super(
entityName,
(Class) persistentClass,
(Class[]) interfaces,
id,
getIdentifierMethod,
setIdentifierMethod,
componentIdType,
session,
overridesEquals);
}

@Override
public Object intercept(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();

// Answer identifier access from the LazyInitializer so it never initializes the proxy
if ((getIdentifierMethod != null && methodName.equals(getIdentifierMethod.getName())) ||
GET_ID_METHOD.equals(methodName) ||
GET_IDENTIFIER_METHOD.equals(methodName)) {
return getIdentifier();
}

if (isUninitialized()) {
GroovyProxyInterceptorLogic.InterceptorState state = new GroovyProxyInterceptorLogic.InterceptorState(
getEntityName(), persistentClass, getIdentifier());
Object result = GroovyProxyInterceptorLogic.handleUninitialized(state, methodName, args);
if (result != GroovyProxyInterceptorLogic.INVOKE_IMPLEMENTATION) { // NOPMD: sentinel comparison
return result;
}
}

return super.intercept(proxy, method, args);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.grails.orm.hibernate.proxy;

import java.io.Serial;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.Set;

import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.internal.util.ReflectHelper;
import org.hibernate.proxy.HibernateProxy;
import org.hibernate.proxy.ProxyConfiguration;
import org.hibernate.proxy.pojo.bytebuddy.ByteBuddyProxyFactory;
import org.hibernate.proxy.pojo.bytebuddy.ByteBuddyProxyHelper;
import org.hibernate.type.CompositeType;

import static org.hibernate.internal.util.collections.ArrayHelper.EMPTY_CLASS_ARRAY;

/**
* A ProxyFactory implementation for ByteBuddy that uses {@link ByteBuddyGroovyInterceptor}.
* Mirrors the Hibernate 7 implementation in grails-data-hibernate7.
*
* @since 8.0
*/
public class ByteBuddyGroovyProxyFactory extends ByteBuddyProxyFactory {

@Serial
private static final long serialVersionUID = 1L;

private final transient ByteBuddyProxyHelper byteBuddyProxyHelper;
private Class<?> persistentClass;
private String entityName;
private Class<?>[] interfaces;
private transient Method getIdentifierMethod;
private transient Method setIdentifierMethod;
private transient CompositeType componentIdType;
private boolean overridesEquals;
private Class<?> proxyClass;

public ByteBuddyGroovyProxyFactory(ByteBuddyProxyHelper byteBuddyProxyHelper) {
super(byteBuddyProxyHelper);
this.byteBuddyProxyHelper = byteBuddyProxyHelper;
}

@Override
@SuppressWarnings({ "unchecked", "rawtypes" })
public void postInstantiate(
String entityName,
Class persistentClass,
Set<Class> interfaces,
Method getIdentifierMethod,
Method setIdentifierMethod,
CompositeType componentIdType)
throws HibernateException {
this.entityName = entityName;
this.persistentClass = persistentClass;
this.interfaces = interfaces == null ? EMPTY_CLASS_ARRAY : (Class<?>[]) interfaces.toArray(EMPTY_CLASS_ARRAY);
this.getIdentifierMethod = getIdentifierMethod;
this.setIdentifierMethod = setIdentifierMethod;
this.componentIdType = componentIdType;
this.overridesEquals = ReflectHelper.overridesEquals(persistentClass);

this.proxyClass = byteBuddyProxyHelper.buildProxy(persistentClass, this.interfaces);

// do NOT call super.postInstantiate: the stock factory keeps private state for its own
// getProxy(), which is fully replaced here
}

@Override
public HibernateProxy getProxy(Serializable id, SharedSessionContractImplementor session) throws HibernateException {
try {
final ByteBuddyGroovyInterceptor interceptor = new ByteBuddyGroovyInterceptor(
entityName,
persistentClass,
interfaces,
id,
getIdentifierMethod,
setIdentifierMethod,
componentIdType,
session,
overridesEquals);

final HibernateProxy hibernateProxy =
(HibernateProxy) proxyClass.getDeclaredConstructor().newInstance();

if (hibernateProxy instanceof ProxyConfiguration) {
((ProxyConfiguration) hibernateProxy).$$_hibernate_set_interceptor(interceptor);
}

return hibernateProxy;
} catch (Exception e) {
throw new HibernateException("Unable to generate proxy for " + entityName, e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.grails.orm.hibernate.proxy;

import org.hibernate.bytecode.internal.bytebuddy.BytecodeProviderImpl;
import org.hibernate.bytecode.spi.ProxyFactoryFactory;

/**
* A {@link org.hibernate.bytecode.spi.BytecodeProvider} for Hibernate 5 that provides
* Groovy-aware entity proxies via {@link ByteBuddyGroovyProxyFactory}. Extends the stock
* ByteBuddy provider so enhancement and reflection optimization behave exactly as standard
* Hibernate. Mirrors the Hibernate 7 implementation in grails-data-hibernate7.
*
* @since 8.0
*/
public class GrailsBytecodeProvider extends BytecodeProviderImpl {

@Override
public ProxyFactoryFactory getProxyFactoryFactory() {
return new GrailsProxyFactoryFactory(this, super.getProxyFactoryFactory());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.grails.orm.hibernate.proxy;

import java.io.Serial;

import org.hibernate.bytecode.spi.BasicProxyFactory;
import org.hibernate.bytecode.spi.ProxyFactoryFactory;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.proxy.ProxyFactory;

/**
* A {@link ProxyFactoryFactory} implementation for Hibernate 5 that provides Groovy-aware
* entity proxies. Basic (non-entity) proxies are delegated to the stock implementation.
* Mirrors the Hibernate 7 implementation in grails-data-hibernate7.
*
* @since 8.0
*/
public class GrailsProxyFactoryFactory implements ProxyFactoryFactory, java.io.Serializable {

@Serial
private static final long serialVersionUID = 1L;

private final GrailsBytecodeProvider grailsBytecodeProvider;
private final transient ProxyFactoryFactory basicProxyDelegate;

public GrailsProxyFactoryFactory(GrailsBytecodeProvider grailsBytecodeProvider, ProxyFactoryFactory basicProxyDelegate) {
this.grailsBytecodeProvider = grailsBytecodeProvider;
this.basicProxyDelegate = basicProxyDelegate;
}

@Override
public ProxyFactory buildProxyFactory(SessionFactoryImplementor sessionFactory) {
return new ByteBuddyGroovyProxyFactory(grailsBytecodeProvider.getByteBuddyProxyHelper());
}

@Override
@SuppressWarnings("rawtypes")
public BasicProxyFactory buildBasicProxyFactory(Class superClass, Class[] interfaces) {
return basicProxyDelegate.buildBasicProxyFactory(superClass, interfaces);
}

@Override
@SuppressWarnings("rawtypes")
public BasicProxyFactory buildBasicProxyFactory(Class superClassOrInterface) {
return basicProxyDelegate.buildBasicProxyFactory(superClassOrInterface);
}
}
Loading