Skip to content
This repository was archived by the owner on Jan 19, 2022. It is now read-only.
This repository was archived by the owner on Jan 19, 2022. It is now read-only.

Expose Datastore LazyUtil #2418

Open
Open
@BenDol

Description

@BenDol

Expose spring datastore LazyUtil or an explaination why this shouldn't be exposed for use? Some utility methods like LazyUtil.isLazyAndNotLoaded would be very useful for me to process data for transactions, etc.

Basic example use case:

User user = userRepository.findByName("John Doe");
Set<Group> groupProxy = user.getGroups();
if(LazyUtil.isLazyAndNotLoaded(groupProxy)) {
	LazyUtil.SimpleLazyDynamicInvocationHandler ih = LazyUtil.getProxy(groupProxy);
	Class type = ih.getType();
	// override proxy set before serialization (avoid lazy loading all data upon serialization)
	user.setGroups((Set<Group>) type.newInstance());
}

This would mean exposing the invocation handler and making use the SimpleDynamicIncovationHandler stored the proxies type. From my testing I was unable to get the proxies underlying type any other way.

LazyUtil.java

/**
 * Utilities used to support lazy loaded properties.
 *
 * @author Dmitry Solomakha
 *
 * @since 1.2.2
 */
public final class LazyUtil {

	static private final ObjenesisStd objenesis = new ObjenesisStd();

	private LazyUtil() {
	}

	/**
	 * Returns a proxy that lazily loads the value provided by a supplier. The proxy also
	 * stores the key(s) that can be used in case the value was not loaded. If the type of the
	 * value is interface, {@link java.lang.reflect.Proxy} is used, otherwise cglib proxy is
	 * used (creates a sub-class of the original type; the original class can't be final and
	 * can't have final methods).
	 * @param supplierFunc a function that provides the value
	 * @param type the type of the value
	 * @param keys Datastore key(s) that can be used when the parent entity is saved
	 * @return true if the object is a proxy that was not evaluated
	 */
	static <T> T wrapSimpleLazyProxy(Supplier<T> supplierFunc, Class<T> type, Value keys) {
		if (type.isInterface()) {
			return (T) Proxy.newProxyInstance(type.getClassLoader(), new Class[] {type},
				new SimpleLazyDynamicInvocationHandler<>(type, supplierFunc, keys));
		}
		Factory factory = (Factory) objenesis.newInstance(getEnhancedTypeFor(type));
		factory.setCallbacks(new Callback[] { new SimpleLazyDynamicInvocationHandler<>(type, supplierFunc, keys) });

		return (T) factory;
	}

	private static Class<?> getEnhancedTypeFor(Class<?> type) {
		Enhancer enhancer = new Enhancer();
		enhancer.setSuperclass(type);
		enhancer.setCallbackType(org.springframework.cglib.proxy.MethodInterceptor.class);

		return enhancer.createClass();
	}

	/**
	 * Check if the object is a lazy loaded proxy that hasn't been evaluated.
	 * @param object an object
	 * @return true if the object is a proxy that was not evaluated
	 */
	public static boolean isLazyAndNotLoaded(Object object) {
		SimpleLazyDynamicInvocationHandler handler = getProxy(object);
		if (handler != null) {
			return !handler.isEvaluated() && handler.getKeys() != null;
		}
		return false;
	}

	/**
	 * Extract keys from a proxy object.
	 * @param object a proxy object
	 * @return list of keys if the object is a proxy, null otherwise
	 */
	public static Value getKeys(Object object) {
		SimpleLazyDynamicInvocationHandler handler = getProxy(object);
		if (handler != null) {
			if (!handler.isEvaluated()) {
				return handler.getKeys();
			}
		}
		return null;
	}

	public static SimpleLazyDynamicInvocationHandler getProxy(Object object) {
		if (Proxy.isProxyClass(object.getClass())
				&& (Proxy.getInvocationHandler(object) instanceof SimpleLazyDynamicInvocationHandler)) {
			return (SimpleLazyDynamicInvocationHandler) Proxy
					.getInvocationHandler(object);
		}
		else if (object instanceof Factory) {
			Callback[] callbacks = ((Factory) object).getCallbacks();
			if (callbacks != null && callbacks.length == 1
					&& callbacks[0] instanceof SimpleLazyDynamicInvocationHandler) {
				return (SimpleLazyDynamicInvocationHandler) callbacks[0];
			}
		}
		return null;
	}

	/**
	 * Proxy class used for lazy loading.
	 */
	public static final class SimpleLazyDynamicInvocationHandler<T> implements InvocationHandler, MethodInterceptor {

		private final Supplier<T> supplierFunc;

		private final Value keys;

		private boolean isEvaluated = false;

		private Class<T> type;
		private T value;

		private SimpleLazyDynamicInvocationHandler(Class<T> type, Supplier<T> supplierFunc, Value keys) {
			Assert.notNull(supplierFunc, "A non-null supplier function is required for a lazy proxy.");
			Assert.notNull(supplierFunc, "A non-null class type is required for a lazy proxy.");
			this.type = type;
			this.supplierFunc = supplierFunc;
			this.keys = keys;
		}

		private boolean isEvaluated() {
			return this.isEvaluated;
		}

		public Value getKeys() {
			return this.keys;
		}

		@Override
		public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
			if (!this.isEvaluated) {
				T value = this.supplierFunc.get();
				if (value == null) {
					throw new DatastoreDataException("Can't load referenced entity");
				}
				this.value = value;

				this.isEvaluated = true;
			}
			return method.invoke(this.value, args);
		}

		@Override
		public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
			return invoke(o, method, objects);
		}

		public Class<T> getType() {
			return type;
		}
	}
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions