This is an automated email from the ASF dual-hosted git repository. borinquenkid pushed a commit to branch 8.0.x-hibernate7 in repository https://gitbox.apache.org/repos/asf/grails-core.git
commit 70fde10c8257d029d79beef9f02446cd9376ac74 Author: Walter B Duque de Estrada <[email protected]> AuthorDate: Fri Jan 23 14:09:45 2026 -0600 update progress --- .../orm/hibernate/proxy/HibernateProxyHandler.java | 42 ++++- .../proxy/HibernateProxyHandler5Spec.groovy | 197 ++++++++++++++++++++- .../orm/hibernate/proxy/HibernateProxyHandler.java | 89 +++++++--- .../core/HIBERNATE7-UPGRADE-PROGRESS.md | 71 +------- .../orm/hibernate/proxy/HibernateProxyHandler.java | 121 ++++++++++--- .../hibernate/HibernateGormStaticApiSpec.groovy | 18 ++ 6 files changed, 414 insertions(+), 124 deletions(-) diff --git a/grails-data-hibernate5/core/src/main/groovy/org/grails/orm/hibernate/proxy/HibernateProxyHandler.java b/grails-data-hibernate5/core/src/main/groovy/org/grails/orm/hibernate/proxy/HibernateProxyHandler.java index bb5921e82a..4baa06a419 100644 --- a/grails-data-hibernate5/core/src/main/groovy/org/grails/orm/hibernate/proxy/HibernateProxyHandler.java +++ b/grails-data-hibernate5/core/src/main/groovy/org/grails/orm/hibernate/proxy/HibernateProxyHandler.java @@ -18,6 +18,8 @@ */ package org.grails.orm.hibernate.proxy; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.Serializable; import groovy.lang.GroovyObject; @@ -46,20 +48,43 @@ import org.grails.orm.hibernate.GrailsHibernateTemplate; */ public class HibernateProxyHandler implements ProxyHandler, ProxyFactory { + private static final Logger LOG = LoggerFactory.getLogger(HibernateProxyHandler.class); + /** * Check if the proxy or persistent collection is initialized. * {@inheritDoc} */ @Override public boolean isInitialized(Object o) { + if (o == null) { + LOG.info("isInitialized(Object) - object is null, returning false"); + return false; + } + LOG.info("isInitialized(Object) - checking object of type: {}", o.getClass().getName()); if (o instanceof EntityProxy) { - return ((EntityProxy)o).isInitialized(); + boolean initialized = ((EntityProxy) o).isInitialized(); + LOG.info("isInitialized(Object) - object is EntityProxy, isInitialized: {}", initialized); + return initialized; + } + if (o instanceof HibernateProxy) { + boolean initialized = !((HibernateProxy) o).getHibernateLazyInitializer().isUninitialized(); + LOG.info("isInitialized(Object) - object is HibernateProxy, isInitialized: {}", initialized); + return initialized; + } + if (o instanceof PersistentCollection) { + boolean initialized = ((PersistentCollection) o).wasInitialized(); + LOG.info("isInitialized(Object) - object is PersistentCollection, wasInitialized: {}", initialized); + return initialized; } ProxyInstanceMetaClass proxyMc = getProxyInstanceMetaClass(o); if (proxyMc != null) { - return proxyMc.isProxyInitiated(); + boolean initialized = proxyMc.isProxyInitiated(); + LOG.info("isInitialized(Object) - object is Groovy Proxy, isProxyInitiated: {}", initialized); + return initialized; } - return Hibernate.isInitialized(o); + boolean initialized = Hibernate.isInitialized(o); + LOG.info("isInitialized(Object) - Hibernate.isInitialized returned: {}", initialized); + return initialized; } /** @@ -68,11 +93,15 @@ public class HibernateProxyHandler implements ProxyHandler, ProxyFactory { */ @Override public boolean isInitialized(Object obj, String associationName) { + LOG.info("isInitialized(Object, String) - checking association '{}' on object of type: {}", associationName, obj != null ? obj.getClass().getName() : "null"); try { Object proxy = ClassPropertyFetcher.getInstancePropertyValue(obj, associationName); - return isInitialized(proxy); + boolean initialized = isInitialized(proxy); + LOG.info("isInitialized(Object, String) - association '{}' isInitialized: {}", associationName, initialized); + return initialized; } catch (RuntimeException e) { + LOG.info("isInitialized(Object, String) - RuntimeException occurred while checking association '{}', returning false", associationName); return false; } } @@ -171,15 +200,20 @@ public class HibernateProxyHandler implements ProxyHandler, ProxyFactory { } private ProxyInstanceMetaClass getProxyInstanceMetaClass(Object o) { + LOG.info("getProxyInstanceMetaClass() - checking if object is GroovyObject: {}", o != null ? o.getClass().getName() : "null"); if (o instanceof GroovyObject) { MetaClass mc = ((GroovyObject) o).getMetaClass(); + LOG.info("getProxyInstanceMetaClass() - metaClass type: {}", mc.getClass().getName()); if (mc instanceof HandleMetaClass) { mc = ((HandleMetaClass) mc).getAdaptee(); + LOG.info("getProxyInstanceMetaClass() - handleMetaClass adaptee type: {}", mc.getClass().getName()); } if (mc instanceof ProxyInstanceMetaClass) { + LOG.info("getProxyInstanceMetaClass() - found ProxyInstanceMetaClass"); return (ProxyInstanceMetaClass) mc; } } + LOG.info("getProxyInstanceMetaClass() - no ProxyInstanceMetaClass found"); return null; } diff --git a/grails-data-hibernate5/core/src/test/groovy/org/grails/orm/hibernate/proxy/HibernateProxyHandler5Spec.groovy b/grails-data-hibernate5/core/src/test/groovy/org/grails/orm/hibernate/proxy/HibernateProxyHandler5Spec.groovy index bdffcd8bfb..d7ecec2b84 100644 --- a/grails-data-hibernate5/core/src/test/groovy/org/grails/orm/hibernate/proxy/HibernateProxyHandler5Spec.groovy +++ b/grails-data-hibernate5/core/src/test/groovy/org/grails/orm/hibernate/proxy/HibernateProxyHandler5Spec.groovy @@ -4,6 +4,8 @@ import org.slf4j.Logger import org.slf4j.LoggerFactory import grails.gorm.specs.HibernateGormDatastoreSpec import org.apache.grails.data.testing.tck.domains.Location +import org.apache.grails.data.testing.tck.domains.Person +import org.apache.grails.data.testing.tck.domains.Pet import org.hibernate.Hibernate import spock.lang.Shared import org.grails.datastore.gorm.proxy.GroovyProxyFactory @@ -14,7 +16,7 @@ class HibernateProxyHandler5Spec extends HibernateGormDatastoreSpec { @Shared HibernateProxyHandler proxyHandler = new HibernateProxyHandler() void setupSpec() { - manager.addAllDomainClasses([Location]) + manager.addAllDomainClasses([Location, Person, Pet]) } void "test isInitialized for a non-proxied object"() { @@ -106,4 +108,197 @@ class HibernateProxyHandler5Spec extends HibernateGormDatastoreSpec { cleanup: manager.session.mappingContext.proxyFactory = originalFactory } + + void "test isInitialized for null"() { + expect: + proxyHandler.isInitialized(null) == false + } + + void "test isInitialized for a persistent collection"() { + given: + Person p = new Person(firstName: "Homer", lastName: "Simpson").save(flush: true) + new Pet(name: "Santa's Little Helper", owner: p).save(flush: true) + manager.session.clear() + manager.hibernateSession.clear() + + Person loaded = Person.get(p.id) + def pets = loaded.pets + + expect: + proxyHandler.isInitialized(pets) == false + + when: + pets.size() + + then: + proxyHandler.isInitialized(pets) == true + } + + void "test isInitialized for association name"() { + given: + Person p = new Person(firstName: "Homer", lastName: "Simpson").save(flush: true) + new Pet(name: "Santa's Little Helper", owner: p).save(flush: true) + manager.session.clear() + manager.hibernateSession.clear() + + Person loaded = Person.get(p.id) + + expect: + proxyHandler.isInitialized(loaded, 'pets') == false + + when: + loaded.pets.size() + + then: + proxyHandler.isInitialized(loaded, 'pets') == true + } + + void "test isProxy"() { + given: + Location location = new Location(name: "Test").save(flush: true) + manager.session.clear() + manager.hibernateSession.clear() + + Location proxy = Location.proxy(location.id) + + expect: + proxyHandler.isProxy(proxy) == true + proxyHandler.isProxy(location) == false + proxyHandler.isProxy(null) == false + } + + void "test getIdentifier"() { + given: + Location location = new Location(name: "Test").save(flush: true) + manager.session.clear() + manager.hibernateSession.clear() + + Location proxy = Location.proxy(location.id) + + expect: + proxyHandler.getIdentifier(proxy) == location.id + proxyHandler.getIdentifier(location) == null + } + + void "test getProxiedClass"() { + given: + Location location = new Location(name: "Test").save(flush: true) + manager.session.clear() + manager.hibernateSession.clear() + + Location proxy = Location.proxy(location.id) + + expect: + proxyHandler.getProxiedClass(proxy) == Location + proxyHandler.getProxiedClass(location) == Location + } + + void "test initialize"() { + given: + Location location = new Location(name: "Test").save(flush: true) + manager.session.clear() + manager.hibernateSession.clear() + + Location proxy = Location.proxy(location.id) + + expect: + !Hibernate.isInitialized(proxy) + + when: + proxyHandler.initialize(proxy) + + then: + Hibernate.isInitialized(proxy) + } + + void "test unwrap for persistent collection"() { + given: + Person p = new Person(firstName: "Homer", lastName: "Simpson").save(flush: true) + new Pet(name: "Santa's Little Helper", owner: p).save(flush: true) + manager.session.clear() + manager.hibernateSession.clear() + + Person loaded = Person.get(p.id) + def pets = loaded.pets + + expect: + !proxyHandler.isInitialized(pets) + + when: + def unwrapped = proxyHandler.unwrap(pets) + + then: + unwrapped == pets + proxyHandler.isInitialized(pets) + } + + void "test isInitialized for association name with null object"() { + expect: + proxyHandler.isInitialized(null, 'any') == false + } + + void "test createProxy"() { + given: + Location location = new Location(name: "Test").save(flush: true) + manager.session.clear() + manager.hibernateSession.clear() + + when: + Location proxy = proxyHandler.createProxy(manager.session, Location, location.id) + + then: + proxy != null + proxy instanceof org.hibernate.proxy.HibernateProxy + proxy.id == location.id + !Hibernate.isInitialized(proxy) + } + + void "test createProxy with AssociationQueryExecutor"() { + when: + proxyHandler.createProxy(manager.session, null, null) + + then: + thrown(UnsupportedOperationException) + } + + void "test createProxy throws IllegalStateException if native interface is not GrailsHibernateTemplate"() { + given: + def mockSession = Stub(org.grails.datastore.mapping.core.Session) + mockSession.getNativeInterface() >> "not a template" + + when: + proxyHandler.createProxy(mockSession, Location, 1L) + + then: + thrown(IllegalStateException) + } + + void "test deprecated unwrapProxy and unwrapIfProxy"() { + given: + Location location = new Location(name: "Test").save(flush: true) + manager.session.clear() + manager.hibernateSession.clear() + + Location proxy = Location.proxy(location.id) + + expect: + proxyHandler.unwrapProxy(proxy) != proxy + proxyHandler.unwrapIfProxy(proxy) != proxy + proxyHandler.unwrapProxy(location) == location + proxyHandler.unwrapIfProxy(location) == location + } + + void "test getAssociationProxy"() { + given: + Person p = new Person(firstName: "Homer", lastName: "Simpson").save(flush: true) + Pet pet = new Pet(name: "Santa's Little Helper", owner: p).save(flush: true) + manager.session.clear() + manager.hibernateSession.clear() + + Pet loadedPet = Pet.get(pet.id) + + expect: + proxyHandler.getAssociationProxy(loadedPet, 'owner') instanceof org.hibernate.proxy.HibernateProxy + proxyHandler.getAssociationProxy(loadedPet, 'name') == null + } } \ No newline at end of file diff --git a/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/proxy/HibernateProxyHandler.java b/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/proxy/HibernateProxyHandler.java index 6c722b8f62..4baa06a419 100644 --- a/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/proxy/HibernateProxyHandler.java +++ b/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/proxy/HibernateProxyHandler.java @@ -1,37 +1,42 @@ /* - * Copyright 2004-2008 the original author or authors. + * 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 * - * Licensed 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 * - * http://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. + * 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.grails.datastore.mapping.core.Session; -import org.grails.datastore.mapping.engine.AssociationQueryExecutor; -import org.grails.datastore.mapping.proxy.EntityProxy; -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; - +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.Serializable; import groovy.lang.GroovyObject; import groovy.lang.MetaClass; import org.codehaus.groovy.runtime.HandleMetaClass; +import org.hibernate.Hibernate; +import org.hibernate.collection.spi.PersistentCollection; +import org.hibernate.proxy.HibernateProxy; +import org.hibernate.proxy.HibernateProxyHelper; + import org.grails.datastore.gorm.proxy.ProxyInstanceMetaClass; +import org.grails.datastore.mapping.core.Session; +import org.grails.datastore.mapping.engine.AssociationQueryExecutor; +import org.grails.datastore.mapping.proxy.EntityProxy; +import org.grails.datastore.mapping.proxy.ProxyFactory; +import org.grails.datastore.mapping.proxy.ProxyHandler; +import org.grails.datastore.mapping.reflect.ClassPropertyFetcher; import org.grails.orm.hibernate.GrailsHibernateTemplate; /** @@ -43,20 +48,43 @@ import org.grails.orm.hibernate.GrailsHibernateTemplate; */ public class HibernateProxyHandler implements ProxyHandler, ProxyFactory { + private static final Logger LOG = LoggerFactory.getLogger(HibernateProxyHandler.class); + /** * Check if the proxy or persistent collection is initialized. * {@inheritDoc} */ @Override public boolean isInitialized(Object o) { + if (o == null) { + LOG.info("isInitialized(Object) - object is null, returning false"); + return false; + } + LOG.info("isInitialized(Object) - checking object of type: {}", o.getClass().getName()); if (o instanceof EntityProxy) { - return ((EntityProxy)o).isInitialized(); + boolean initialized = ((EntityProxy) o).isInitialized(); + LOG.info("isInitialized(Object) - object is EntityProxy, isInitialized: {}", initialized); + return initialized; + } + if (o instanceof HibernateProxy) { + boolean initialized = !((HibernateProxy) o).getHibernateLazyInitializer().isUninitialized(); + LOG.info("isInitialized(Object) - object is HibernateProxy, isInitialized: {}", initialized); + return initialized; + } + if (o instanceof PersistentCollection) { + boolean initialized = ((PersistentCollection) o).wasInitialized(); + LOG.info("isInitialized(Object) - object is PersistentCollection, wasInitialized: {}", initialized); + return initialized; } ProxyInstanceMetaClass proxyMc = getProxyInstanceMetaClass(o); if (proxyMc != null) { - return proxyMc.isProxyInitiated(); + boolean initialized = proxyMc.isProxyInitiated(); + LOG.info("isInitialized(Object) - object is Groovy Proxy, isProxyInitiated: {}", initialized); + return initialized; } - return Hibernate.isInitialized(o); + boolean initialized = Hibernate.isInitialized(o); + LOG.info("isInitialized(Object) - Hibernate.isInitialized returned: {}", initialized); + return initialized; } /** @@ -65,11 +93,15 @@ public class HibernateProxyHandler implements ProxyHandler, ProxyFactory { */ @Override public boolean isInitialized(Object obj, String associationName) { + LOG.info("isInitialized(Object, String) - checking association '{}' on object of type: {}", associationName, obj != null ? obj.getClass().getName() : "null"); try { Object proxy = ClassPropertyFetcher.getInstancePropertyValue(obj, associationName); - return isInitialized(proxy); + boolean initialized = isInitialized(proxy); + LOG.info("isInitialized(Object, String) - association '{}' isInitialized: {}", associationName, initialized); + return initialized; } catch (RuntimeException e) { + LOG.info("isInitialized(Object, String) - RuntimeException occurred while checking association '{}', returning false", associationName); return false; } } @@ -168,15 +200,20 @@ public class HibernateProxyHandler implements ProxyHandler, ProxyFactory { } private ProxyInstanceMetaClass getProxyInstanceMetaClass(Object o) { + LOG.info("getProxyInstanceMetaClass() - checking if object is GroovyObject: {}", o != null ? o.getClass().getName() : "null"); if (o instanceof GroovyObject) { MetaClass mc = ((GroovyObject) o).getMetaClass(); + LOG.info("getProxyInstanceMetaClass() - metaClass type: {}", mc.getClass().getName()); if (mc instanceof HandleMetaClass) { mc = ((HandleMetaClass) mc).getAdaptee(); + LOG.info("getProxyInstanceMetaClass() - handleMetaClass adaptee type: {}", mc.getClass().getName()); } if (mc instanceof ProxyInstanceMetaClass) { + LOG.info("getProxyInstanceMetaClass() - found ProxyInstanceMetaClass"); return (ProxyInstanceMetaClass) mc; } } + LOG.info("getProxyInstanceMetaClass() - no ProxyInstanceMetaClass found"); return null; } diff --git a/grails-data-hibernate7/core/HIBERNATE7-UPGRADE-PROGRESS.md b/grails-data-hibernate7/core/HIBERNATE7-UPGRADE-PROGRESS.md index c724428e19..7033b55c71 100644 --- a/grails-data-hibernate7/core/HIBERNATE7-UPGRADE-PROGRESS.md +++ b/grails-data-hibernate7/core/HIBERNATE7-UPGRADE-PROGRESS.md @@ -5,77 +5,8 @@ This document summarizes the approaches taken, challenges encountered, and futur ## Resolved Challenges -### 1. Proxy Initialization Behavior -- **Issue:** In `Hibernate7GroovyProxySpec`, `Location.proxy(id)` returned an object that was already reported as initialized (`Hibernate.isInitialized(location) == true`) even when it was a lazy Groovy proxy. -- **Cause:** Hibernate 7's `Hibernate.isInitialized()` does not automatically recognize GORM's `EntityProxy` interface used by `GroovyProxyFactory`. -- **Solution:** Updated `org.grails.orm.hibernate.proxy.HibernateProxyHandler` to explicitly check for `EntityProxy`. This ensures that `isInitialized()`, `unwrap()`, and `getIdentifier()` work correctly for both native Hibernate proxies and GORM Groovy proxies. -- **Verified:** `Hibernate7GroovyProxySpec` now passes. - -### 2. Multi-tenancy Many-to-Many -- **Issue:** `MultiTenancyBidirectionalManyToManySpec` failed with `Found two representations of same collection: grails.gorm.specs.multitenancy.Department.users`. -- **Cause:** Complex save logic in test setup caused the same collection to be associated with the session multiple times. -- **Solution:** Simplified `createSomeUsers` in the test to use GORM's cascading saves by saving the owner entity (`Department`) once after adding all users. -- **Verified:** `MultiTenancyBidirectionalManyToManySpec` now passes. - -### 3. Embedded Component Property Access -- **Issue:** `HibernateDirtyCheckingSpec` failed with `PropertyAccessException: Could not set value of type [java.lang.String]: 'grails.gorm.specs.dirtychecking.Address.street' (setter)`. -- **Cause:** `GrailsDomainBinder.createProperty` was incorrectly using the parent entity's class name when configuring Hibernate properties for embedded components, leading to a type mismatch in Hibernate's reflection-based setters. -- **Solution:** Updated `createProperty` to use `grailsProperty.getOwner().getJavaClass().getName()`, ensuring the correct class is used for property accessors in components. -- **Verified:** The `PropertyAccessException` is resolved. (Remaining issue: `hasChanged()` method missing on non-entity embedded classes in test environment). - -### 4. Disabled Incompatible TCK Tests -- **Issue:** `NamedQuerySpec` failed with `"No signature of method: static org.grails.datastore.gorm.GormEnhancer.createNamedQuery()"`. -- **Solution:** Disabled `NamedQuerySpec` for Hibernate 7 using `@IgnoreIf({ System.getProperty("hibernate7.gorm.suite") == "true" })`. -- **Status:** Pending proper implementation of named queries in Hibernate 7 module. - ## Hibernate 7 Key Constraints & Best Practices -### Identifier Generators -- **Avoid Deprecated `configure`:** Do **not** use the three-parameter `configure(Type, Properties, ServiceRegistry)` method in `IdentifierGenerator` (or its subclasses like `SequenceStyleGenerator`). It is marked for removal in Hibernate 7. -- **Prefer modern initialization:** Use the `GeneratorCreationContext` provided by `setCustomIdGeneratorCreator` to perform initialization. - -### GrailsIncrementGenerator Status -- **Progress:** Table name resolution has been fixed by prioritizing `domainClass.getMappedForm().getTableName()`. -- **Remaining Issue:** `IncrementGenerator` in Hibernate 7 requires explicit call to `initialize(SqlStringGenerationContext)` or it throws NPE during `generate()`. Current GORM initialization flow needs adjustment to provide this context at the right time. - ## Strategy for GrailsDomainBinder Refactoring -### Goal -To decompose the monolithic `GrailsDomainBinder` (over 2000 lines) into smaller, specialized binder classes within the `org.grails.orm.hibernate.cfg.domainbinding` package. - -### Refactoring Progress -- [x] `SimpleIdBinder`: Orchestrates binding of simple identifiers. -- [x] `PropertyBinder`: Binds `PersistentProperty` to Hibernate `Property`. -- [x] `ManyToOneBinder`: Specialized binder for many-to-one associations. -- [x] `SimpleValueBinder`: Handles binding of simple values to Hibernate `SimpleValue`. - -## Future Steps - -1. **Complete Increment Generator Fix:** Implement a mechanism to call `initialize()` on `GrailsIncrementGenerator` with a valid `SqlStringGenerationContext`. -2. **Address `Session.save()` usage:** Systematically find and replace `save()` with `persist()` or `merge()` across the codebase and TCK where direct Hibernate session access is used. -3. **Resolve Dirty Checking Test:** Investigate why `@DirtyCheck` AST transformation is not providing `hasChanged()` in the `HibernateDirtyCheckingSpec` test environment. - -## Current Task: Multi-module Refactoring and Test Fixing - -The current task involves extensive modifications and additions across multiple `grails-data-hibernate` modules (`hibernate5`, `hibernate6`, and `hibernate7`). - -**Objective:** -To align the testing infrastructure and add specific proxy handler tests across different Hibernate versions. - -**Steps to be completed (blocked by agent's working directory limitations):** - -1. **Add `GrailsDataHibernate5TckManager` to `grails-data-hibernate5`:** Create a `GrailsDataHibernate5TckManager.groovy` based on `GrailsDataHibernate7TckManager` in `grails-data-hibernate5/core/src/test/groovy/org/apache/grails/data/hibernate5/core/`. -2. **Add `HibernateGormDatastoreSpec` to `grails-data-hibernate5`:** Create a `HibernateGormDatastoreSpec.groovy` based on the existing `grails.gorm.specs.HibernateGormDatastoreSpec` in `grails-data-hibernate5/core/src/test/groovy/grails/gorm/specs/`, adapted to use `GrailsDataHibernate5TckManager`. -3. **Add `HibernateProxyHandlerSpec` to `grails-data-hibernate5`, `grails-data-hibernate6`, and `grails-data-hibernate7`:** - * For each module, create `HibernateProxyHandlerSpec.groovy` in `[module]/core/src/test/groovy/org/grails/orm/hibernate/proxy/`. - * This test will extend the respective module's `HibernateGormDatastoreSpec`. - * The test content will validate `isInitialized`, `unwrap`, and `getIdentifier` methods of `HibernateProxyHandler`. -4. **Make tests pass in sequence:** - * Run and fix tests for `grails-data-hibernate5`. - * Run and fix tests for `grails-data-hibernate6`. - * Run and fix tests for `grails-data-hibernate7`. - -**Current Blocking Issue:** -The `read_file` and `write_file` tools are strictly confined to the agent's initial launch directory: `/Users/walterduquedeestrada/IdeaProjects/grails-core/grails-data-hibernate7/core`. - -To proceed with creating and modifying files in other modules (`grails-data-hibernate5`, `grails-data-hibernate6`), the agent's working directory needs to be moved to the root of the entire `grails-core` project: `/Users/walterduquedeestrada/IdeaProjects/grails-core`. \ No newline at end of file +## Future Steps \ No newline at end of file diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/proxy/HibernateProxyHandler.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/proxy/HibernateProxyHandler.java index cd3e9f11a1..4baa06a419 100644 --- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/proxy/HibernateProxyHandler.java +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/proxy/HibernateProxyHandler.java @@ -1,33 +1,42 @@ /* - * Copyright 2004-2008 the original author or authors. + * 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 * - * Licensed 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 * - * http://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. + * 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.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.io.Serializable; + +import groovy.lang.GroovyObject; +import groovy.lang.MetaClass; +import org.codehaus.groovy.runtime.HandleMetaClass; +import org.hibernate.Hibernate; +import org.hibernate.collection.spi.PersistentCollection; +import org.hibernate.proxy.HibernateProxy; +import org.hibernate.proxy.HibernateProxyHelper; + +import org.grails.datastore.gorm.proxy.ProxyInstanceMetaClass; import org.grails.datastore.mapping.core.Session; import org.grails.datastore.mapping.engine.AssociationQueryExecutor; import org.grails.datastore.mapping.proxy.EntityProxy; 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; - -import java.io.Serializable; - import org.grails.orm.hibernate.GrailsHibernateTemplate; /** @@ -39,16 +48,43 @@ import org.grails.orm.hibernate.GrailsHibernateTemplate; */ public class HibernateProxyHandler implements ProxyHandler, ProxyFactory { + private static final Logger LOG = LoggerFactory.getLogger(HibernateProxyHandler.class); + /** * Check if the proxy or persistent collection is initialized. * {@inheritDoc} */ @Override public boolean isInitialized(Object o) { + if (o == null) { + LOG.info("isInitialized(Object) - object is null, returning false"); + return false; + } + LOG.info("isInitialized(Object) - checking object of type: {}", o.getClass().getName()); if (o instanceof EntityProxy) { - return ((EntityProxy)o).isInitialized(); + boolean initialized = ((EntityProxy) o).isInitialized(); + LOG.info("isInitialized(Object) - object is EntityProxy, isInitialized: {}", initialized); + return initialized; + } + if (o instanceof HibernateProxy) { + boolean initialized = !((HibernateProxy) o).getHibernateLazyInitializer().isUninitialized(); + LOG.info("isInitialized(Object) - object is HibernateProxy, isInitialized: {}", initialized); + return initialized; } - return Hibernate.isInitialized(o); + if (o instanceof PersistentCollection) { + boolean initialized = ((PersistentCollection) o).wasInitialized(); + LOG.info("isInitialized(Object) - object is PersistentCollection, wasInitialized: {}", initialized); + return initialized; + } + ProxyInstanceMetaClass proxyMc = getProxyInstanceMetaClass(o); + if (proxyMc != null) { + boolean initialized = proxyMc.isProxyInitiated(); + LOG.info("isInitialized(Object) - object is Groovy Proxy, isProxyInitiated: {}", initialized); + return initialized; + } + boolean initialized = Hibernate.isInitialized(o); + LOG.info("isInitialized(Object) - Hibernate.isInitialized returned: {}", initialized); + return initialized; } /** @@ -57,11 +93,15 @@ public class HibernateProxyHandler implements ProxyHandler, ProxyFactory { */ @Override public boolean isInitialized(Object obj, String associationName) { + LOG.info("isInitialized(Object, String) - checking association '{}' on object of type: {}", associationName, obj != null ? obj.getClass().getName() : "null"); try { Object proxy = ClassPropertyFetcher.getInstancePropertyValue(obj, associationName); - return isInitialized(proxy); + boolean initialized = isInitialized(proxy); + LOG.info("isInitialized(Object, String) - association '{}' isInitialized: {}", associationName, initialized); + return initialized; } catch (RuntimeException e) { + LOG.info("isInitialized(Object, String) - RuntimeException occurred while checking association '{}', returning false", associationName); return false; } } @@ -77,6 +117,10 @@ public class HibernateProxyHandler implements ProxyHandler, ProxyFactory { if (object instanceof EntityProxy) { return ((EntityProxy)object).getTarget(); } + ProxyInstanceMetaClass proxyMc = getProxyInstanceMetaClass(object); + if (proxyMc != null) { + return proxyMc.getProxyTarget(); + } if (object instanceof PersistentCollection) { initialize(object); return object; @@ -93,6 +137,10 @@ public class HibernateProxyHandler implements ProxyHandler, ProxyFactory { if (o instanceof EntityProxy) { return ((EntityProxy)o).getProxyKey(); } + ProxyInstanceMetaClass proxyMc = getProxyInstanceMetaClass(o); + if (proxyMc != null) { + return proxyMc.getKey(); + } if (o instanceof HibernateProxy) { return (Serializable) ((HibernateProxy)o).getHibernateLazyInitializer().getIdentifier(); } @@ -125,6 +173,9 @@ public class HibernateProxyHandler implements ProxyHandler, ProxyFactory { */ @Override public boolean isProxy(Object o) { + if (getProxyInstanceMetaClass(o) != null) { + return true; + } return (o instanceof EntityProxy) || (o instanceof HibernateProxy) || (o instanceof PersistentCollection); } @@ -138,8 +189,32 @@ public class HibernateProxyHandler implements ProxyHandler, ProxyFactory { ((EntityProxy)o).initialize(); } else { - Hibernate.initialize(o); + ProxyInstanceMetaClass proxyMc = getProxyInstanceMetaClass(o); + if (proxyMc != null) { + proxyMc.getProxyTarget(); + } + else { + Hibernate.initialize(o); + } + } + } + + private ProxyInstanceMetaClass getProxyInstanceMetaClass(Object o) { + LOG.info("getProxyInstanceMetaClass() - checking if object is GroovyObject: {}", o != null ? o.getClass().getName() : "null"); + if (o instanceof GroovyObject) { + MetaClass mc = ((GroovyObject) o).getMetaClass(); + LOG.info("getProxyInstanceMetaClass() - metaClass type: {}", mc.getClass().getName()); + if (mc instanceof HandleMetaClass) { + mc = ((HandleMetaClass) mc).getAdaptee(); + LOG.info("getProxyInstanceMetaClass() - handleMetaClass adaptee type: {}", mc.getClass().getName()); + } + if (mc instanceof ProxyInstanceMetaClass) { + LOG.info("getProxyInstanceMetaClass() - found ProxyInstanceMetaClass"); + return (ProxyInstanceMetaClass) mc; + } } + LOG.info("getProxyInstanceMetaClass() - no ProxyInstanceMetaClass found"); + return null; } @Override @@ -183,4 +258,4 @@ public class HibernateProxyHandler implements ProxyHandler, ProxyFactory { return null; } } -} \ No newline at end of file +} diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/HibernateGormStaticApiSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/HibernateGormStaticApiSpec.groovy index b92fdd5475..acc3718ae1 100644 --- a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/HibernateGormStaticApiSpec.groovy +++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/HibernateGormStaticApiSpec.groovy @@ -13,6 +13,24 @@ class HibernateGormStaticApiSpec extends HibernateGormDatastoreSpec { manager.addAllDomainClasses([HibernateGormStaticApiEntity,Club]) } + void "proxy test"() { + given: + def entity = new Club(name: "test").save(flush: true, failOnError: true) + println "SAVED ENTITY" + manager.session.clear() + println "CLEARED SESSION" + when: + println "CALLING PROXY" + def same = Club.proxy(entity.id) + println "CALLED PROXY" + then: + same != null + println "CHECKING INITIALIZED" + !datastore.mappingContext.proxyFactory.isInitialized(same) + println "CHECKED INITIALIZED" + same.id == entity.id + } + void "Test that get returns the correct instance"() { given: def entity = new HibernateGormStaticApiEntity(name: "test").save(flush: true, failOnError: true)
