All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.bonitasoft.engine.business.data.proxy.ServerProxyfier Maven / Gradle / Ivy

There is a newer version: 10.2.0
Show newest version
/**
 * Copyright (C) 2019 Bonitasoft S.A.
 * Bonitasoft, 32 rue Gustave Eiffel - 38000 Grenoble
 * This library is free software; you can redistribute it and/or modify it under the terms
 * of the GNU Lesser General Public License as published by the Free Software Foundation
 * version 2.1 of the License.
 * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Lesser General Public License for more details.
 * You should have received a copy of the GNU Lesser General Public License along with this
 * program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
 * Floor, Boston, MA 02110-1301, USA.
 **/
package org.bonitasoft.engine.business.data.proxy;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import javassist.util.proxy.MethodHandler;
import javassist.util.proxy.Proxy;
import javassist.util.proxy.ProxyFactory;

import org.bonitasoft.engine.bdm.Entity;
import org.bonitasoft.engine.bdm.lazy.LazyLoaded;
import org.springframework.beans.PropertyAccessorFactory;

/**
 * @author Colin Puy
 * @author Laurent Leseigneur
 */
public class ServerProxyfier {

    private final ServerLazyLoader lazyLoader;

    public ServerProxyfier(final ServerLazyLoader lazyLoader) {
        this.lazyLoader = lazyLoader;
    }

    public static boolean isLazyMethodProxyfied(final Entity e) {
        return ProxyFactory.isProxyClass(e.getClass())
                && ProxyFactory.getHandler((Proxy) e) instanceof LazyMethodHandler;
    }

    @SuppressWarnings("unchecked")
    public  T proxify(final T entity) {
        if (entity == null || isLazyMethodProxyfied(entity)) {
            return entity;
        }
        return (T) proxifyEntity(entity);
    }

    private Entity proxifyEntity(final Entity entity) {
        if (entity == null) {
            return null;
        }

        final ProxyFactory factory = new ProxyFactory();
        Class classForProxy = entity.getClass();

        //It's not possible to create a Proxy on a Proxy
        //Here Entity can already be an Hibernate Proxy
        if (ProxyFactory.isProxyClass(classForProxy)) {
            classForProxy = classForProxy.getSuperclass();
        }
        factory.setSuperclass(classForProxy);
        factory.setFilter((Method m) -> true);
        try {
            return (Entity) factory.create(new Class[0], new Object[0], new LazyMethodHandler(entity, lazyLoader));
        } catch (final Exception e) {
            throw new RuntimeException("Error when proxifying object", e);
        }
    }

    // This methods seems unused but is called by reflection, I think:
    @SuppressWarnings({ "unchecked" })
    public  List proxify(final List entities) {
        if (entities == null) {
            return null;
        }
        return (List) proxifyEntities((List) entities);
    }

    private List proxifyEntities(final List entities) {
        final List proxies = new ArrayList<>();
        for (final Entity entity : entities) {
            proxies.add(proxifyEntity(entity));
        }
        return proxies;
    }

    /**
     * Handler that lazy load values for lazy loading methods that hasn't been loaded
     */
    public class LazyMethodHandler implements MethodHandler {

        private final ServerLazyLoader lazyLoader;
        private final Entity entity;

        public LazyMethodHandler(final Entity entity, final ServerLazyLoader lazyLoader) {
            this.entity = entity;
            this.lazyLoader = lazyLoader;
        }

        public Entity getEntity() {
            return entity;
        }

        @Override
        public Object invoke(final Object self, final Method thisMethod, final Method proceed, final Object[] args)
                throws Throwable {
            Object invocationResult;
            if (isMethodGetterOnLazyLoadedField(thisMethod)) {
                invocationResult = lazyLoader.load(thisMethod, entity.getPersistenceId());
            } else {
                invocationResult = thisMethod.invoke(entity, args);
            }
            return proxifyIfNeeded(invocationResult);
        }

        private boolean isMethodGetterOnLazyLoadedField(final Method thisMethod) {
            return isGetter(thisMethod) && thisMethod.isAnnotationPresent(LazyLoaded.class);
        }

        @SuppressWarnings("unchecked")
        private Object proxifyIfNeeded(final Object invocationResult) {
            if (isAnEntity(invocationResult)) {
                return proxifyEntity((Entity) invocationResult);
            }

            if (isAListOfEntities(invocationResult)) {
                return proxifyEntities((List) invocationResult);
            }
            return invocationResult;
        }

        private boolean isAListOfEntities(final Object invocationResult) {
            if (invocationResult instanceof List) {
                final List list = (List) invocationResult;
                return !list.isEmpty() && list.get(0) instanceof Entity;
            }
            return false;
        }

        private boolean isAnEntity(final Object invocationResult) {
            return invocationResult instanceof Entity;
        }

        private boolean isGetter(final Method method) {
            return method.getName().startsWith("get");
        }

    }

    public static Entity unProxifyIfNeeded(final Entity entity) {
        Set visitedEntities = new HashSet<>();
        return unProxifyIfNeeded(entity, visitedEntities);
    }

    private static Entity unProxifyIfNeeded(Entity entity, Set alreadyUnproxyfiedEntities) {
        Entity detachedEntity = entity;
        if (entity != null && isLazyMethodProxyfied(entity)) {
            detachedEntity = ((LazyMethodHandler) ProxyFactory.getHandler((Proxy) entity)).getEntity();
        }
        if (detachedEntity == null) {
            return null;
        }
        boolean wasNotAlreadyUnproxified = alreadyUnproxyfiedEntities.add(detachedEntity);
        // If we just unproxified this entity, we must also unproxify its related Entity fields:
        if (wasNotAlreadyUnproxified) {
            var propertyAccessor = PropertyAccessorFactory.forBeanPropertyAccess(detachedEntity);
            for (var property : propertyAccessor.getPropertyDescriptors()) {
                var propertyType = property.getPropertyType();
                if (Entity.class.isAssignableFrom(propertyType)) {
                    propertyAccessor.setPropertyValue(property.getName(),
                            unProxifyIfNeeded((Entity) propertyAccessor.getPropertyValue(property.getName()),
                                    alreadyUnproxyfiedEntities));
                } else if (List.class.isAssignableFrom(propertyType)) {
                    final List list = (List) propertyAccessor.getPropertyValue(property.getName());
                    if (list != null && !list.isEmpty() && isThereAnyNonNullEntityInTheList(list)) {
                        final List realEntities = ((List) list).stream()
                                .map(element -> unProxifyIfNeeded(element, alreadyUnproxyfiedEntities))
                                .collect(Collectors.toList());
                        list.clear();
                        list.addAll(realEntities);
                        propertyAccessor.setPropertyValue(property.getName(), list);
                    }
                }
            }
        }
        return detachedEntity;
    }

    private static boolean isThereAnyNonNullEntityInTheList(List list) {
        return list.stream().anyMatch(e -> e != null && Entity.class.isAssignableFrom(e.getClass()));
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy