DeepCloner.java
/*
* Copyright 2021 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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
*
* 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.
*/
package org.keycloak.models.map.common;
import org.keycloak.models.map.common.delegate.DelegateProvider;
import org.keycloak.models.map.common.delegate.EntityFieldDelegate;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Stack;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import org.jboss.logging.Logger;
/**
* Helper class for deep cloning and fine-grained instantiation per interface and deep copying their properties.
* <p>
* This class is intended to be used by individual map storage implementations for copying
* over entities into their native implementations.
* <p>
* For example, a {@code MapClientEntity} interface could be implemented by {@code MapClientEntityImpl}
* (used by a file-based storage in this example) and an {@code HotRodClientEntityImpl} (for Infinispan).
* Say that the Infinispan is stacked on top of the file-based storage to provide caching layer.
* Upon first read, a {@code MapClientEntityImpl} could be obtained from file-based storage and passed
* to Infinispan layer for caching. Infinispan, regardless of the actual implementation, need to store
* the {@code MapClientEntity} data in a form that can be processed and sent over the wire in Infinispan
* (say in an {@code InfinispanClientEntityImpl}). To achieve this, the Infinispan store has to clone
* the file entity values from the {@code MapClientEntityImpl} to {@code InfinispanClientEntityImpl},
* i.e. it performs deep cloning, using this helper class.
* <p>
* <i>Broader context:</i>
* In tree store, map storages are agnostic to their neighbours. Therefore each implementation can be
* provided with a record (a {@code MapClientEntity} instance in the example above) originating from
* any other implementation. For a map storage to process the record (beyond read-only mode),
* it needs to be able to clone it into its own entity. Each of the storages thus can benefit from
* the {@code DeepCloner} capabilities.
*
* @author hmlnarik
*/
public class DeepCloner {
/**
* Marker for interfaces that could be requested for instantiation and cloning.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Root {}
/**
* Function that clones properties from {@code original} object to a {@code target} object and returns
* the cloned object (usually the same as the {@code target}).
* @param <V> Object class
*/
@FunctionalInterface
public interface Cloner<V> {
/**
* Function that clones properties from {@code original} object to a {@code target} object and returns
* the cloned object (usually the same as the {@code target}).
*/
V clone(V original, V target);
}
/**
* Function that instantiates a delegation object of type {@code V} with the given delegate provider
* @param <V> Object class
*/
@FunctionalInterface
public interface DelegateCreator<V> {
/**
* Function that instantiates a delegation object of type {@code V} with the given delegate provider.
*/
V create(DelegateProvider<V> delegateProvider);
}
/**
* Function that instantiates a delegation object of type {@code V} with the given per-field delegate provider
* @param <V> Object class
*/
@FunctionalInterface
public interface EntityFieldDelegateCreator<V> {
/**
* Function that instantiates a delegation object of type {@code V} with the given per-field delegate provider.
*/
V create(EntityFieldDelegate<V> entityDelegateProvider);
}
public static final DeepCloner DUMB_CLONER = new Builder().build();
/**
* Builder for the {@code DeepCloner} helper class.
*/
public static class Builder {
private final Map<Class<?>, Function<DeepCloner, ?>> constructors = new HashMap<>(org.keycloak.models.map.common.AutogeneratedClasses.CONSTRUCTORS_DC);
private final Map<Class<?>, Cloner<?>> clonersWithId = new HashMap<>(org.keycloak.models.map.common.AutogeneratedClasses.CLONERS_WITH_ID);
private final Map<Class<?>, Cloner<?>> clonersWithoutId = new HashMap<>(org.keycloak.models.map.common.AutogeneratedClasses.CLONERS_WITHOUT_ID);
private final Map<Class<?>, DelegateCreator<?>> delegateCreators = new HashMap<>(org.keycloak.models.map.common.AutogeneratedClasses.DELEGATE_CREATORS);
private final Map<Class<?>, EntityFieldDelegateCreator<?>> entityFieldDelegateCreators = new HashMap<>(org.keycloak.models.map.common.AutogeneratedClasses.ENTITY_FIELD_DELEGATE_CREATORS);
private Cloner<?> genericCloner = (from, to) -> { throw new IllegalStateException("Cloner not found for class " + (from == null ? "<null>" : from.getClass())); };
/**
* Returns a {@link DeepCloner} initialized with the respective constructors and cloners.
* @return
*/
public DeepCloner build() {
return new DeepCloner(constructors, delegateCreators, entityFieldDelegateCreators, clonersWithId, clonersWithoutId, genericCloner);
}
private <V> void forThisClassAndAllMarkedParentsAndInterfaces(Class<V> rootClazz, Consumer<Class<?>> action) {
action.accept(rootClazz);
Stack<Class<?>> c = new Stack<>();
c.push(rootClazz);
while (! c.isEmpty()) {
Class<?> cl = c.pop();
if (cl == null) {
continue;
}
c.push(cl.getSuperclass());
for (Class<?> iface : cl.getInterfaces()) {
c.push(iface);
}
if (cl.getAnnotation(Root.class) != null) {
action.accept(cl);
}
}
}
/**
* Adds a method, often a constructor, that instantiates a record of type {@code V}.
*
* @param <V> Class or interface that would be instantiated by the given methods
* @param clazz Class or interface that would be instantiated by the given methods
* @param constructor Function that creates a new instance of class {@code V}.
* If {@code null}, such a single-parameter constructor is not available.
* @return This builder.
*/
public <V> Builder constructor(Class<V> clazz, Function<DeepCloner, ? extends V> constructor) {
if (constructor != null) {
forThisClassAndAllMarkedParentsAndInterfaces(clazz, cl -> this.constructors.put(cl, constructor));
}
return this;
}
/**
* Adds a method that instantiates an per-field delegate of type {@code V}.
*
* @param <V> Class or interface that would be instantiated by the given methods
* @param clazz Class or interface that would be instantiated by the given methods
* @param delegateCreator Function that creates a new instance of class {@code V}.
* If {@code null}, such a single-parameter constructor is not available.
* @return This builder.
*/
public <V> Builder delegateCreator(Class<V> clazz, EntityFieldDelegateCreator<V> delegateCreator) {
if (delegateCreator != null) {
forThisClassAndAllMarkedParentsAndInterfaces(clazz, cl -> this.entityFieldDelegateCreators.put(cl, delegateCreator));
}
return this;
}
/**
* Adds a method, often a constructor, that instantiates a delegate of type {@code V}.
*
* @param <V> Class or interface that would be instantiated by the given methods
* @param clazz Class or interface that would be instantiated by the given methods
* @param delegateCreator Function that creates a new instance of class {@code V}.
* If {@code null}, such a single-parameter constructor is not available.
* @return This builder.
*/
public <V> Builder delegateCreator(Class<V> clazz, DelegateCreator<V> delegateCreator) {
if (delegateCreator != null) {
forThisClassAndAllMarkedParentsAndInterfaces(clazz, cl -> this.delegateCreators.put(cl, delegateCreator));
}
return this;
}
/**
* Adds a method that copies (as in a deep copy) an object properties from one object to another
*
* @param <V> Class or interface whose instance would be copied over to another instance by the given cloner
* @param clazz Class or interface whose instance would be copied over to another instance by the given cloner
* @param cloner A method for cloning with the following signature: {@code V deepClone(V from, V to)} which
* copies properties of an object {@code from} onto the object {@code to}. This
* function usually returns {@code to}
* @return This builder.
*/
public <V> Builder cloner(Class<? extends V> clazz, Cloner<?> cloner) {
if (cloner != null) {
forThisClassAndAllMarkedParentsAndInterfaces(clazz, cl -> this.clonersWithId.put(cl, cloner));
}
return this;
}
/**
* Adds a method that copies (as in a deep copy) an object properties from one object to another
*
* @param <V> Class or interface whose instance would be copied over to another instance by the given cloner
* @param clazz Class or interface whose instance would be copied over to another instance by the given cloner
* @param clonerWithId A method for cloning with the following signature: {@code V deepClone(V from, V to)} which
* copies properties of an object {@code from} onto the object {@code to}. This
* function usually returns {@code to}
* @return This builder.
*/
public <V> Builder cloner(Class<? extends V> clazz, Cloner<?> clonerWithId, Cloner<?> clonerWithoutId) {
if (clonerWithId != null) {
forThisClassAndAllMarkedParentsAndInterfaces(clazz, cl -> this.clonersWithId.put(cl, clonerWithId));
}
if (clonerWithoutId != null) {
forThisClassAndAllMarkedParentsAndInterfaces(clazz, cl -> this.clonersWithoutId.put(cl, clonerWithoutId));
}
return this;
}
/**
* Adds a method that copies (as in a deep copy) an object properties to another object for any class
* that is not covered by a specific cloner set via {@link #cloner(Class, BiFunction)} method.
*
* @param <V> Class or interface whose instance would be copied over to another instance by the given cloner
* @param genericCloner A method for cloning which copies properties of an object onto another object. This
* function usually returns {@code to}
* @return This builder.
*/
public <V> Builder genericCloner(Cloner<V> genericCloner) {
this.genericCloner = genericCloner;
return this;
}
}
private static final Logger LOG = Logger.getLogger(DeepCloner.class);
private final Map<Class<?>, Function<DeepCloner, ?>> constructors;
private final Map<Class<?>, Cloner<?>> clonersWithId;
private final Map<Class<?>, Cloner<?>> clonersWithoutId;
private final Map<Class<?>, DelegateCreator<?>> delegateCreators;
private final Map<Class<?>, EntityFieldDelegateCreator<?>> entityFieldDelegateCreators;
private final Cloner<?> genericCloner;
private final Map<Class<?>, Object> emptyInstances = new HashMap<>(AutogeneratedClasses.EMPTY_INSTANCES);
private DeepCloner(Map<Class<?>, Function<DeepCloner, ?>> constructors,
Map<Class<?>, DelegateCreator<?>> delegateCreators,
Map<Class<?>, EntityFieldDelegateCreator<?>> entityFieldDelegateCreators,
Map<Class<?>, Cloner<?>> clonersWithId,
Map<Class<?>, Cloner<?>> clonersWithoutId,
Cloner<?> genericCloner) {
this.constructors = constructors;
this.clonersWithId = clonersWithId;
this.clonersWithoutId = clonersWithoutId;
this.delegateCreators = delegateCreators;
this.genericCloner = genericCloner;
this.entityFieldDelegateCreators = entityFieldDelegateCreators;
}
private <V> V getFromClassRespectingHierarchy(Map<Class<?>, V> map, Class<?> clazz) {
// fast lookup
V res = map.get(clazz);
if (res != null) {
return res;
}
// BFS on implemented supertypes and interfaces. Skip clazz as it has been looked up already
LinkedList<Class<?>> ll = new LinkedList<>();
ll.push(clazz.getSuperclass());
for (Class<?> iface : clazz.getInterfaces()) {
ll.push(iface);
}
while (! ll.isEmpty()) {
Class<?> cl = ll.pollFirst();
if (cl == null) {
continue;
}
res = map.get(cl);
if (res != null) {
map.put(clazz, res); // Wire clazz with the result for fast lookup next time
return res;
}
ll.push(cl.getSuperclass());
ll.addAll(Arrays.asList(cl.getInterfaces()));
}
return null;
}
@SuppressWarnings("unchecked")
public <D, V extends D> D delegate(V delegate, DelegateProvider<D> delegateProvider) {
return delegate((Class<V>) delegate.getClass(), delegateProvider);
}
public <D, V extends D> D delegate(Class<V> delegateClass, DelegateProvider<D> delegateProvider) {
@SuppressWarnings("unchecked")
DelegateCreator<D> delegateCreator = (DelegateCreator<D>) getFromClassRespectingHierarchy(delegateCreators, delegateClass);
if (delegateCreator != null) {
return delegateCreator.create(delegateProvider);
}
throw new IllegalStateException("Cannot create delegate for " + delegateClass);
}
@SuppressWarnings("unchecked")
public <V extends AbstractEntity> V entityFieldDelegate(V delegate, EntityFieldDelegate<V> delegateProvider) {
return entityFieldDelegate((Class<V>) delegate.getClass(), delegateProvider);
}
public <V> V entityFieldDelegate(Class<V> delegateClass, EntityFieldDelegate<V> delegateProvider) {
@SuppressWarnings("unchecked")
EntityFieldDelegateCreator<V> delegateCreator = (EntityFieldDelegateCreator<V>) getFromClassRespectingHierarchy(entityFieldDelegateCreators, delegateClass);
if (delegateCreator != null) {
return delegateCreator.create(delegateProvider);
}
throw new IllegalStateException("Cannot create delegate for " + delegateClass);
}
public <V> V emptyInstance(Class<V> instanceClass) {
@SuppressWarnings("unchecked")
V emptyInstance = (V) getFromClassRespectingHierarchy(emptyInstances, instanceClass);
if (emptyInstance != null) {
return emptyInstance;
}
throw new IllegalStateException("Cannot create empty instance for " + instanceClass);
}
/**
* Creates a new instance of the given class or interface if the parameterless constructor for that type is known.
* @param <V> Type (class or a {@code @Root} interface) to create a new instance
* @param clazz Type (class or a {@code @Root} interface) to create a new instance
* @return A new instance
* @throws IllegalStateException When the constructor is not known.
*/
public <V> V newInstance(Class<V> clazz) {
if (clazz == null) {
return null;
}
V res;
@SuppressWarnings("unchecked")
Function<DeepCloner, V> c = (Function<DeepCloner, V>) getFromClassRespectingHierarchy(this.constructors, clazz);
if (c == null) {
try {
res = clazz.getDeclaredConstructor().newInstance();
} catch (ReflectiveOperationException ex) {
res = null;
}
} else {
res = c.apply(this);
}
if (res == null) {
throw new IllegalStateException("Cannot instantiate " + clazz);
}
return res;
}
/**
* Returns a class type of an instance that would be instantiated by {@link #newInstance(java.lang.Class)} method.
* @param <V> Type (class or a {@code @Root} interface) to create a new instance
* @param valueType Type (class or a {@code @Root} interface) to create a new instance
* @return See description
*/
@SuppressWarnings("unchecked")
public <V> Class<? extends V> newInstanceType(Class<V> valueType) {
if (valueType == null) {
return null;
}
try {
V v = newInstance(valueType);
return v == null ? null : (Class<? extends V>) v.getClass();
} catch (IllegalStateException ex) {
return null;
}
}
/**
* Deeply clones properties from the {@code from} instance to the {@code to} instance.
* @param <V> Type (class or a {@code @Root} interface) to clone the instance
* @param from Original instance
* @param to Instance to copy the properties onto
* @return Instance which has all the properties same as the {@code from}. Preferably, {@code to} is returned.
* However {@code from} is returned if the cloner is not known and generic cloner is not available.
*/
public <V> V deepClone(V from, V to) {
return deepClone(from, to, this.clonersWithId);
}
/**
* Deeply clones properties from the {@code from} instance to the {@code to} instance excluding the ID field.
* @param <V> Type (class or a {@code @Root} interface) to clone the instance
* @param from Original instance
* @param to Instance to copy the properties onto
* @return Instance which has all the properties same as the {@code from}. Preferably, {@code to} is returned.
* However {@code from} is returned if the cloner is not known and generic cloner is not available.
*/
public <V> V deepCloneNoId(V from, V to) {
return deepClone(from, to, this.clonersWithoutId);
}
@SuppressWarnings("unchecked")
private <V> V deepClone(V from, V to, Map<Class<?>, Cloner<?>> cloners) {
Cloner<V> cloner = (Cloner<V>) getFromClassRespectingHierarchy(cloners, from.getClass());
if (cloner != null) {
return cloner.clone(from, to);
}
if (genericCloner != null) {
LOG.debugf("Using generic cloner for %s", from.getClass());
final V res = ((Cloner<V>) genericCloner).clone(from, to);
if (res instanceof UpdatableEntity) {
((UpdatableEntity) res).clearUpdatedFlag();
}
return res;
}
return warnCloneNotSupported(from);
}
/**
* Creates a new instance of the given type and copies its properties from the {@code from} instance
* @param <V> Type (class or a {@code @Root} interface) to create a new instance and clone properties from
* @param newId ID of the new object
* @param from Original instance
* @return Newly created instance or {@code null} if {@code from} is {@code null}.
*/
@SuppressWarnings("unchecked")
public <V extends AbstractEntity> V from(String newId, V from) {
if (from == null) {
return null;
}
final V res = newInstance((Class<V>) from.getClass());
if (newId != null) {
res.setId(newId);
}
return deepCloneNoId(from, res);
}
/**
* Creates a new instance of the given type and copies its properties from the {@code from} instance
* @param <V> Type (class or a {@code @Root} interface) to create a new instance and clone properties from
* @param from Original instance
* @return Newly created instance or {@code null} if {@code from} is {@code null}.
*/
@SuppressWarnings("unchecked")
public <V> V from(V from) {
return from == null ? null : deepClone(from, newInstance((Class<V>) from.getClass()));
}
/**
* Issues warning in the logs and returns the input parameter {@code o}
* @param o
* @return The {@code o} object
*/
public static <T> T warnCloneNotSupported(T o) {
if (o != null) {
LOG.warnf("Cloning not supported for %s, returning the same instance!", o.getClass());
}
return o;
}
}