Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Eclipse Texo ModelEMFConverter and Hibernate proxies

I'm trying to integrate Eclipse Texo into my existing Hibernate project. I have modeled my domain model in ECore and generated both EMF and POJO code from there using Texo and the regular EMF code generation.

Fetching entities (POJOs) stored in the database works without problems, now I would like to use Texo's ModelEMFConverter to transform the Hibernate-mapped data model into the corresponding EMF model. However, this attempt fails due to the entities returned by Hibernate being transparently proxied. Texo's ModelResolver is unable to look up a model descriptor for these proxied entities, since it compares the class of the entity (which is the proxy class) to the mapped classes and fails with an exception in my case:

Exception in thread "main" java.lang.IllegalStateException: The class class foobar.Entity_$$_jvst4f2_5 is not managed by this ModelResolver at org.eclipse.emf.texo.utils.Check.isNotNull(Check.java:66) at org.eclipse.emf.texo.model.ModelResolver.getModelDescriptor(ModelResolver.java:366) at org.eclipse.emf.texo.model.ModelResolver.getModelObject(ModelResolver.java:298) at org.eclipse.emf.texo.resolver.DefaultObjectResolver.toUri(DefaultObjectResolver.java:188) at org.eclipse.emf.texo.resolver.DefaultObjectResolver.resolveToEObject(DefaultObjectResolver.java:98) at org.eclipse.emf.texo.converter.ModelEMFConverter.createTarget(ModelEMFConverter.java:146) at org.eclipse.emf.texo.converter.ModelEMFConverter.convertSingleEReference(ModelEMFConverter.java:265) at org.eclipse.emf.texo.converter.ModelEMFConverter.convertContent(ModelEMFConverter.java:189) at org.eclipse.emf.texo.converter.ModelEMFConverter.convert(ModelEMFConverter.java:107) [...]

The relevant code bits from ModelResolver:

  public ModelObject<?> getModelObject(final Object target) {
    /* ... snip ... */

    final ModelDescriptor modelDescriptor = getModelDescriptor(target.getClass(), true);
    return modelDescriptor.createAdapter(target);
  }

I tried manually unwrapping the proxied entities before passing them to the model converter using the following code:

    final List<Object> objects = entities
            .stream()
            .map(o ->
                o instanceof HibernateProxy ?
                    (Entity) ((HibernateProxy) o).getHibernateLazyInitializer().getImplementation() : o)
            .collect(Collectors.toList());

    final ModelEMFConverter converter = new ModelEMFConverter();
    final Collection<EObject> eObjects = converter.convert(objects);

In theory this approach seems to work (I checked by single-stepping through the conversion code), however it fails for entities referenced by associations in my data model, that are not contained in the original entities list. I would like to avoid having to traverse the entire object graph by hand in order to get rid of the proxies.

Is there a way to retrieve unproxied entities from Hibernate? Or does anyone maybe have a suggestion as to how I could approach this model transformation from a different angle?

Thanks for your help in advance!

like image 565
AdrianoKF Avatar asked Nov 09 '22 09:11

AdrianoKF


1 Answers

You can write a generic replacer which will traverse the entire graph and replace all the proxies for the given entity instance, something like this:

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;

import org.hibernate.Hibernate;
import org.hibernate.proxy.HibernateProxy;

public class HibernateProxyReplacer {

    @SuppressWarnings("unchecked")
    public <T extends Entity> T replaceProxies(T entity) {
        try {
            return (T) replaceProxies(entity, new ArrayList<Object>());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @SuppressWarnings("unchecked")
    private Object replaceProxies(Object entity, List<Object> processedObjects) throws Exception {
        entity = getImplementation(entity);
        if (isProcessed(entity, processedObjects)) {
            return entity;
        }
        processedObjects.add(entity);

        for (Field field : getDeclaredFields(entity)) {
            if (isStaticOrFinal(field)) {
                continue;
            }
            field.setAccessible(true);
            Object value = field.get(entity);
            if (value == null) {
                continue;
            }
            Hibernate.initialize(value);
            if (value instanceof Collection) {
                replaceProxiesInCollection((Collection<Object>) value, processedObjects);
            } else if (value instanceof Entity) {
                field.set(entity, replaceProxies(value, processedObjects));
            }
        }

        return entity;
    }

    private Object getImplementation(Object object) {
        return object instanceof HibernateProxy ? ((HibernateProxy) object).getHibernateLazyInitializer().getImplementation() : object;
    }

    private boolean isStaticOrFinal(Field field) {
        return ((Modifier.STATIC | Modifier.FINAL) & field.getModifiers()) != 0;
    }

    private List<Field> getDeclaredFields(Object object) {
        List<Field> result = new ArrayList<Field>(Arrays.asList(object.getClass().getDeclaredFields()));
        for (Class<?> superclass = object.getClass().getSuperclass(); superclass != null; superclass = superclass.getSuperclass()) {
            result.addAll(Arrays.asList(superclass.getDeclaredFields()));
        }
        return result;
    }

    private void replaceProxiesInCollection(Collection<Object> collection, List<Object> processedObjects) throws Exception {
        Collection<Object> deproxiedCollection = new ArrayList<Object>();
        for (Object object : collection) {
            deproxiedCollection.add(replaceProxies(object, processedObjects));
        }
        collection.clear();
        collection.addAll(deproxiedCollection);
    }

    private boolean isProcessed(Object object, List<Object> processedObjects) {
        for (Object processedObject : processedObjects) {
            // Intentional comparison by reference to avoid relying on equals/hashCode
            if (processedObject == object) {
                return true;
            }
        }
        return false;
    }
}

Don't forget to rollback the transaction in which this is done (Hibernate may think that the object is dirty because we manually changed the field values). Or make it read-only (by setting flush mode to manual). Or explicitly clear the session without flushing it, so that the deproxied graph becomes detached.

If this is an obstacle for your requirements, then you could change this approach by reading the values from the managed entity instance and setting the deproxied values to another instance. This way you can build a new separate non-managed entity instance whose entire graph is initialized without any proxy.

Or, you can just store the information about necessary changes and apply them later out of the transaction on the detached instance, for example:

commands.add(new ReplaceFieldCommand(field, entity, deproxiedObject));
commands.add(new ReplaceCollectionCommand(collection, entity, deproxiedCollection));
like image 132
Dragan Bozanovic Avatar answered Nov 14 '22 22:11

Dragan Bozanovic