Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions dependencies.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,6 @@ ext {
'graphql-java-extended-scalars.version': '24.0',
'groovy.version' : '5.0.7',
'guava.version' : '33.6.0-jre',
'hibernate-groovy-proxy.version': '1.1',
// Security: overrides spring-boot-dependencies (5.4.2). 5.4.3 fixes CVE-2026-54399 (httpcore5) and CVE-2026-54428 (httpcore5-h2).
'httpcore5.version' : '5.4.3',
// Security: overrides the transitive com.fasterxml Jackson 2.x. 2.21.5 fixes CVE-2026-54515 flagged against 2.21.4.
Expand Down Expand Up @@ -174,7 +173,6 @@ ext {
'groovy-xml' : "org.apache.groovy:groovy-xml:${bomDependencyVersions['groovy.version']}",
'groovy-yaml' : "org.apache.groovy:groovy-yaml:${bomDependencyVersions['groovy.version']}",
'guava' : "com.google.guava:guava:${bomDependencyVersions['guava.version']}",
'hibernate-groovy-proxy' : "org.yakworks:hibernate-groovy-proxy:${bomDependencyVersions['hibernate-groovy-proxy.version']}",
// Security override of spring-boot-dependencies - see httpcore5.version
'httpcore5' : "org.apache.httpcomponents.core5:httpcore5:${bomDependencyVersions['httpcore5.version']}",
'httpcore5-h2' : "org.apache.httpcomponents.core5:httpcore5-h2:${bomDependencyVersions['httpcore5.version']}",
Expand Down
1 change: 0 additions & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ openTest4jVersion=1.3.0
picocliVersion=4.7.6
slf4jVersion=2.0.17
spyMemcachedVersion=2.12.3
yakworksHibernateGroovyProxyVersion=1.1

# Build dependency versions not managed by BOMs
apacheRatVersion=0.8.1
Expand Down
5 changes: 0 additions & 5 deletions grails-data-hibernate5/core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,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,88 @@
/*
* 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";

private final boolean lazyToString;

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

@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(), lazyToString);
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,120 @@
/*
* 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;
private final boolean lazyToString;

public ByteBuddyGroovyProxyFactory(ByteBuddyProxyHelper byteBuddyProxyHelper) {
this(byteBuddyProxyHelper, false);
}

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

@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,
lazyToString);

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,70 @@
/*
* 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(),
isLazyToString(sessionFactory));
}

private static boolean isLazyToString(SessionFactoryImplementor sessionFactory) {
Object value = sessionFactory.getProperties().get(GroovyProxyInterceptorLogic.PROPERTY_LAZY_TO_STRING);
return value != null && Boolean.parseBoolean(value.toString());
}

@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
Loading