parent
02b1932413
commit
3c7e5c8440
15 changed files with 1099 additions and 59 deletions
|
@ -45,13 +45,20 @@ import java.util.regex.Pattern;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import javax.annotation.processing.SupportedSourceVersion;
|
||||||
|
import javax.lang.model.SourceVersion;
|
||||||
import static org.keycloak.models.map.processor.FieldAccessorType.GETTER;
|
import static org.keycloak.models.map.processor.FieldAccessorType.GETTER;
|
||||||
import static org.keycloak.models.map.processor.Util.getGenericsDeclaration;
|
import static org.keycloak.models.map.processor.Util.getGenericsDeclaration;
|
||||||
import static org.keycloak.models.map.processor.Util.isMapType;
|
import static org.keycloak.models.map.processor.Util.isMapType;
|
||||||
|
|
||||||
|
@SupportedSourceVersion(SourceVersion.RELEASE_8)
|
||||||
public abstract class AbstractGenerateEntityImplementationsProcessor extends AbstractProcessor {
|
public abstract class AbstractGenerateEntityImplementationsProcessor extends AbstractProcessor {
|
||||||
|
|
||||||
protected static final String FQN_DEEP_CLONER = "org.keycloak.models.map.common.DeepCloner";
|
protected static final String FQN_DEEP_CLONER = "org.keycloak.models.map.common.DeepCloner";
|
||||||
|
protected static final String FQN_ENTITY_FIELD = "org.keycloak.models.map.common.EntityField";
|
||||||
|
protected static final String FQN_HAS_ENTITY_FIELD_DELEGATE = "org.keycloak.models.map.common.delegate.HasEntityFieldDelegate";
|
||||||
|
protected static final String FQN_ENTITY_FIELD_DELEGATE = "org.keycloak.models.map.common.delegate.EntityFieldDelegate";
|
||||||
|
|
||||||
protected Elements elements;
|
protected Elements elements;
|
||||||
protected Types types;
|
protected Types types;
|
||||||
|
|
||||||
|
@ -100,16 +107,19 @@ public abstract class AbstractGenerateEntityImplementationsProcessor extends Abs
|
||||||
// );
|
// );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected Stream<ExecutableElement> getAllAbstractMethods(TypeElement e) {
|
||||||
|
return elements.getAllMembers(e).stream()
|
||||||
|
.filter(el -> el.getKind() == ElementKind.METHOD)
|
||||||
|
.filter(el -> el.getModifiers().contains(Modifier.ABSTRACT))
|
||||||
|
.filter(ExecutableElement.class::isInstance)
|
||||||
|
.map(ExecutableElement.class::cast);
|
||||||
|
}
|
||||||
|
|
||||||
protected Map<String, HashSet<ExecutableElement>> methodsPerAttributeMapping(TypeElement e) {
|
protected Map<String, HashSet<ExecutableElement>> methodsPerAttributeMapping(TypeElement e) {
|
||||||
final List<? extends Element> allMembers = elements.getAllMembers(e);
|
Map<String, HashSet<ExecutableElement>> methodsPerAttribute = getAllAbstractMethods(e)
|
||||||
Map<String, HashSet<ExecutableElement>> methodsPerAttribute = allMembers.stream()
|
.filter(Util::isNotIgnored)
|
||||||
.filter(el -> el.getKind() == ElementKind.METHOD)
|
.filter(ee -> ! (ee.getReceiverType() instanceof NoType))
|
||||||
.filter(el -> el.getModifiers().contains(Modifier.ABSTRACT))
|
.collect(Collectors.toMap(this::determineAttributeFromMethodName, v -> new HashSet<>(Arrays.asList(v)), (a,b) -> { a.addAll(b); return a; }));
|
||||||
.filter(Util::isNotIgnored)
|
|
||||||
.filter(ExecutableElement.class::isInstance)
|
|
||||||
.map(ExecutableElement.class::cast)
|
|
||||||
.filter(ee -> ! (ee.getReceiverType() instanceof NoType))
|
|
||||||
.collect(Collectors.toMap(this::determineAttributeFromMethodName, v -> new HashSet(Arrays.asList(v)), (a, b) -> { a.addAll(b); return a; }));
|
|
||||||
|
|
||||||
// Merge plurals with singulars
|
// Merge plurals with singulars
|
||||||
methodsPerAttribute.keySet().stream()
|
methodsPerAttribute.keySet().stream()
|
||||||
|
@ -129,7 +139,7 @@ public abstract class AbstractGenerateEntityImplementationsProcessor extends Abs
|
||||||
FORBIDDEN_PREFIXES.put("delete", "remove");
|
FORBIDDEN_PREFIXES.put("delete", "remove");
|
||||||
}
|
}
|
||||||
|
|
||||||
private String determineAttributeFromMethodName(ExecutableElement e) {
|
protected String determineAttributeFromMethodName(ExecutableElement e) {
|
||||||
Name name = e.getSimpleName();
|
Name name = e.getSimpleName();
|
||||||
Matcher m = BEAN_NAME.matcher(name.toString());
|
Matcher m = BEAN_NAME.matcher(name.toString());
|
||||||
if (m.matches()) {
|
if (m.matches()) {
|
||||||
|
|
|
@ -17,12 +17,12 @@
|
||||||
package org.keycloak.models.map.processor;
|
package org.keycloak.models.map.processor;
|
||||||
|
|
||||||
import org.keycloak.models.map.annotations.GenerateEntityImplementations;
|
import org.keycloak.models.map.annotations.GenerateEntityImplementations;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import javax.annotation.processing.SupportedAnnotationTypes;
|
import javax.annotation.processing.SupportedAnnotationTypes;
|
||||||
import javax.annotation.processing.SupportedSourceVersion;
|
import javax.annotation.processing.SupportedSourceVersion;
|
||||||
|
@ -47,6 +47,7 @@ import java.util.Optional;
|
||||||
import java.util.TreeSet;
|
import java.util.TreeSet;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
import java.util.stream.Stream;
|
||||||
import javax.lang.model.element.VariableElement;
|
import javax.lang.model.element.VariableElement;
|
||||||
import javax.lang.model.type.TypeKind;
|
import javax.lang.model.type.TypeKind;
|
||||||
|
|
||||||
|
@ -58,30 +59,36 @@ import javax.lang.model.type.TypeKind;
|
||||||
@SupportedSourceVersion(SourceVersion.RELEASE_8)
|
@SupportedSourceVersion(SourceVersion.RELEASE_8)
|
||||||
public class GenerateEntityImplementationsProcessor extends AbstractGenerateEntityImplementationsProcessor {
|
public class GenerateEntityImplementationsProcessor extends AbstractGenerateEntityImplementationsProcessor {
|
||||||
|
|
||||||
private Collection<String> cloners = new TreeSet<>();
|
private final Collection<String> autogenerated = new TreeSet<>();
|
||||||
|
|
||||||
private final Generator[] generators = new Generator[] {
|
private final Generator[] generators = new Generator[] {
|
||||||
new ClonerGenerator(),
|
new ClonerGenerator(),
|
||||||
new DelegateGenerator(),
|
new DelegateGenerator(),
|
||||||
new FieldsGenerator(),
|
new FieldsGenerator(),
|
||||||
new ImplGenerator()
|
new FieldDelegateGenerator(),
|
||||||
|
new ImplGenerator(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void afterAnnotationProcessing() {
|
protected void afterAnnotationProcessing() {
|
||||||
if (! cloners.isEmpty()) {
|
if (! autogenerated.isEmpty()) {
|
||||||
try {
|
try {
|
||||||
JavaFileObject file = processingEnv.getFiler().createSourceFile("org.keycloak.models.map.common.AutogeneratedCloners");
|
JavaFileObject file = processingEnv.getFiler().createSourceFile("org.keycloak.models.map.common.AutogeneratedCloners");
|
||||||
try (PrintWriter pw = new PrintWriterNoJavaLang(file.openWriter())) {
|
try (PrintWriter pw = new PrintWriterNoJavaLang(file.openWriter())) {
|
||||||
pw.println("package org.keycloak.models.map.common;");
|
pw.println("package org.keycloak.models.map.common;");
|
||||||
|
|
||||||
pw.println("import org.keycloak.models.map.common.DeepCloner.Cloner;");
|
pw.println("import " + FQN_DEEP_CLONER + ".Cloner;");
|
||||||
|
pw.println("import " + FQN_DEEP_CLONER + ".DelegateCreator;");
|
||||||
|
pw.println("import " + FQN_DEEP_CLONER + ".EntityFieldDelegateCreator;");
|
||||||
pw.println("// DO NOT CHANGE THIS CLASS, IT IS GENERATED AUTOMATICALLY BY " + GenerateEntityImplementationsProcessor.class.getSimpleName());
|
pw.println("// DO NOT CHANGE THIS CLASS, IT IS GENERATED AUTOMATICALLY BY " + GenerateEntityImplementationsProcessor.class.getSimpleName());
|
||||||
pw.println("public final class AutogeneratedCloners {");
|
pw.println("public final class AutogeneratedCloners {");
|
||||||
pw.println(" public static final java.util.Map<Class<?>, Cloner<?>> CLONERS_WITH_ID = new java.util.HashMap<>();");
|
pw.println(" public static final java.util.Map<Class<?>, Cloner<?>> CLONERS_WITH_ID = new java.util.HashMap<>();");
|
||||||
pw.println(" public static final java.util.Map<Class<?>, Cloner<?>> CLONERS_WITHOUT_ID = new java.util.HashMap<>();");
|
pw.println(" public static final java.util.Map<Class<?>, Cloner<?>> CLONERS_WITHOUT_ID = new java.util.HashMap<>();");
|
||||||
|
pw.println(" public static final java.util.Map<Class<?>, DelegateCreator<?>> DELEGATE_CREATORS = new java.util.HashMap<>();");
|
||||||
|
pw.println(" public static final java.util.Map<Class<?>, EntityFieldDelegateCreator<?>> ENTITY_FIELD_DELEGATE_CREATORS = new java.util.HashMap<>();");
|
||||||
|
pw.println(" public static final java.util.Map<Class<?>, Object> EMPTY_INSTANCES = new java.util.HashMap<>();");
|
||||||
pw.println(" static {");
|
pw.println(" static {");
|
||||||
cloners.forEach(pw::println);
|
autogenerated.forEach(pw::println);
|
||||||
pw.println(" }");
|
pw.println(" }");
|
||||||
pw.println("}");
|
pw.println("}");
|
||||||
}
|
}
|
||||||
|
@ -111,6 +118,7 @@ public class GenerateEntityImplementationsProcessor extends AbstractGenerateEnti
|
||||||
}
|
}
|
||||||
|
|
||||||
private class FieldsGenerator implements Generator {
|
private class FieldsGenerator implements Generator {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void generate(TypeElement e) throws IOException {
|
public void generate(TypeElement e) throws IOException {
|
||||||
Map<String, HashSet<ExecutableElement>> methodsPerAttribute = methodsPerAttributeMapping(e);
|
Map<String, HashSet<ExecutableElement>> methodsPerAttribute = methodsPerAttributeMapping(e);
|
||||||
|
@ -131,14 +139,104 @@ public class GenerateEntityImplementationsProcessor extends AbstractGenerateEnti
|
||||||
pw.println("package " + packageName + ";");
|
pw.println("package " + packageName + ";");
|
||||||
}
|
}
|
||||||
|
|
||||||
pw.println("public enum " + mapSimpleFieldsClassName + " {");
|
pw.println("public enum " + mapSimpleFieldsClassName + " implements " + FQN_ENTITY_FIELD + "<" + className + "> {");
|
||||||
methodsPerAttribute.keySet().stream()
|
methodsPerAttribute.keySet().stream()
|
||||||
.sorted(NameFirstComparator.ID_INSTANCE)
|
.sorted(NameFirstComparator.ID_INSTANCE)
|
||||||
.map(GenerateEntityImplementationsProcessor::toEnumConstant)
|
.forEach(key -> {
|
||||||
.forEach(key -> pw.println(" " + key + ","));
|
pw.println(" " + toEnumConstant(key) + " {");
|
||||||
|
printEntityFieldMethods(pw, className, key, methodsPerAttribute.get(key));
|
||||||
|
pw.println(" },");
|
||||||
|
});
|
||||||
pw.println("}");
|
pw.println("}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void printEntityFieldMethods(PrintWriter pw, String className, String fieldName, HashSet<ExecutableElement> methods) {
|
||||||
|
TypeMirror fieldType = determineFieldType(fieldName, methods);
|
||||||
|
pw.println(" public static final String FIELD_NAME = \"" + fieldName + "\";");
|
||||||
|
pw.println(" public static final String FIELD_NAME_DASHED = \"" + fieldName.replaceAll("([^_A-Z])([A-Z])", "$1-$2").toLowerCase() + "\";");
|
||||||
|
pw.println(" @SuppressWarnings(\"unchecked\") @Override public Class<?> getFieldClass() {");
|
||||||
|
pw.println(" return " + types.erasure(fieldType) + ".class;");
|
||||||
|
pw.println(" }");
|
||||||
|
pw.println(" @Override public String getName() {");
|
||||||
|
pw.println(" return FIELD_NAME;");
|
||||||
|
pw.println(" }");
|
||||||
|
pw.println(" @Override public String getNameDashed() {");
|
||||||
|
pw.println(" return FIELD_NAME_DASHED;");
|
||||||
|
pw.println(" }");
|
||||||
|
|
||||||
|
FieldAccessorType.getMethod(FieldAccessorType.COLLECTION_ADD, methods, fieldName, types, fieldType).ifPresent(method -> {
|
||||||
|
TypeMirror firstParameterType = method.getParameters().get(0).asType();
|
||||||
|
pw.println(" @SuppressWarnings(\"unchecked\") @Override public Class<?> getCollectionElementClass() {");
|
||||||
|
pw.println(" return " + types.erasure(firstParameterType) + ".class;");
|
||||||
|
pw.println(" }");
|
||||||
|
});
|
||||||
|
|
||||||
|
FieldAccessorType.getMethod(FieldAccessorType.MAP_ADD, methods, fieldName, types, fieldType).ifPresent(method -> {
|
||||||
|
TypeMirror firstParameterType = method.getParameters().get(0).asType();
|
||||||
|
TypeMirror secondParameterType = method.getParameters().get(1).asType();
|
||||||
|
pw.println(" @SuppressWarnings(\"unchecked\") @Override public Class<?> getMapKeyClass() {");
|
||||||
|
pw.println(" return " + types.erasure(firstParameterType) + ".class;");
|
||||||
|
pw.println(" }");
|
||||||
|
pw.println(" @SuppressWarnings(\"unchecked\") @Override public Class<?> getMapValueClass() {");
|
||||||
|
pw.println(" return " + types.erasure(secondParameterType) + ".class;");
|
||||||
|
pw.println(" }");
|
||||||
|
});
|
||||||
|
|
||||||
|
for (ExecutableElement ee : methods) {
|
||||||
|
FieldAccessorType fat = FieldAccessorType.determineType(ee, fieldName, types, fieldType);
|
||||||
|
printMethodBody(pw, fat, ee, className, fieldType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void printMethodBody(PrintWriter pw, FieldAccessorType accessorType, ExecutableElement method, String className, TypeMirror fieldType) {
|
||||||
|
TypeMirror firstParameterType = method.getParameters().isEmpty()
|
||||||
|
? types.getNullType()
|
||||||
|
: method.getParameters().get(0).asType();
|
||||||
|
|
||||||
|
switch (accessorType) {
|
||||||
|
case GETTER:
|
||||||
|
pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " get(" + className + " e) {");
|
||||||
|
pw.println(" return (" + fieldType + ") e." + method.getSimpleName() + "();");
|
||||||
|
pw.println(" }");
|
||||||
|
return;
|
||||||
|
case SETTER:
|
||||||
|
pw.println(" @SuppressWarnings(\"unchecked\") @Override public <T> void set(" + className + " e, T value) {");
|
||||||
|
pw.println(" e." + method.getSimpleName() + "((" + firstParameterType + ") value);");
|
||||||
|
pw.println(" }");
|
||||||
|
return;
|
||||||
|
case COLLECTION_ADD:
|
||||||
|
pw.println(" @SuppressWarnings(\"unchecked\") @Override public <T> void collectionAdd(" + className + " e, T value) {");
|
||||||
|
pw.println(" e." + method.getSimpleName() + "((" + firstParameterType + ") value);");
|
||||||
|
pw.println(" }");
|
||||||
|
return;
|
||||||
|
case COLLECTION_DELETE:
|
||||||
|
String returnType = method.getReturnType().getKind() == TypeKind.VOID ? "Void" : method.getReturnType().toString();
|
||||||
|
TypeElement fieldTypeElement = elements.getTypeElement(types.erasure(fieldType).toString());
|
||||||
|
if (Util.isMapType(fieldTypeElement)) {
|
||||||
|
pw.println(" @SuppressWarnings(\"unchecked\") @Override public <K> " + returnType + " mapRemove(" + className + " e, K p0) {");
|
||||||
|
} else {
|
||||||
|
pw.println(" @SuppressWarnings(\"unchecked\") @Override public <T> " + returnType + " collectionRemove(" + className + " e, T p0) {");
|
||||||
|
}
|
||||||
|
if (method.getReturnType().getKind() == TypeKind.VOID) {
|
||||||
|
pw.println(" e." + method.getSimpleName() + "((" + firstParameterType + ") p0); return null;");
|
||||||
|
} else {
|
||||||
|
pw.println(" return (" + method.getReturnType() + ") e." + method.getSimpleName() + "((" + firstParameterType + ") p0);");
|
||||||
|
}
|
||||||
|
pw.println(" }");
|
||||||
|
return;
|
||||||
|
case MAP_ADD:
|
||||||
|
TypeMirror secondParameterType = method.getParameters().get(1).asType();
|
||||||
|
pw.println(" @SuppressWarnings(\"unchecked\") @Override public <K, T> void mapPut(" + className + " e, K key, T value) {");
|
||||||
|
pw.println(" e." + method.getSimpleName() + "((" + firstParameterType + ") key, (" + secondParameterType + ") value);");
|
||||||
|
pw.println(" }");
|
||||||
|
return;
|
||||||
|
case MAP_GET:
|
||||||
|
pw.println(" @SuppressWarnings(\"unchecked\") @Override public <K> " + method.getReturnType() + " mapGet(" + className + " e, K key) {");
|
||||||
|
pw.println(" return (" + method.getReturnType() + ") e." + method.getSimpleName() + "((" + firstParameterType + ") key);");
|
||||||
|
pw.println(" }");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ImplGenerator implements Generator {
|
private class ImplGenerator implements Generator {
|
||||||
|
@ -151,7 +249,7 @@ public class GenerateEntityImplementationsProcessor extends AbstractGenerateEnti
|
||||||
if (parentTypeElement == null) {
|
if (parentTypeElement == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final List<? extends Element> allMembers = elements.getAllMembers(parentTypeElement);
|
final List<? extends Element> allParentMembers = elements.getAllMembers(parentTypeElement);
|
||||||
String className = e.getQualifiedName().toString();
|
String className = e.getQualifiedName().toString();
|
||||||
String packageName = null;
|
String packageName = null;
|
||||||
int lastDot = className.lastIndexOf('.');
|
int lastDot = className.lastIndexOf('.');
|
||||||
|
@ -162,8 +260,8 @@ public class GenerateEntityImplementationsProcessor extends AbstractGenerateEnti
|
||||||
String simpleClassName = className.substring(lastDot + 1);
|
String simpleClassName = className.substring(lastDot + 1);
|
||||||
String mapImplClassName = className + "Impl";
|
String mapImplClassName = className + "Impl";
|
||||||
String mapSimpleClassName = simpleClassName + "Impl";
|
String mapSimpleClassName = simpleClassName + "Impl";
|
||||||
boolean hasId = methodsPerAttribute.containsKey("Id") || allMembers.stream().anyMatch(el -> "getId".equals(el.getSimpleName().toString()));
|
boolean hasId = methodsPerAttribute.containsKey("Id") || allParentMembers.stream().anyMatch(el -> "getId".equals(el.getSimpleName().toString()));
|
||||||
boolean hasDeepClone = allMembers.stream().filter(el -> el.getKind() == ElementKind.METHOD).anyMatch(el -> "deepClone".equals(el.getSimpleName().toString()));
|
boolean hasDeepClone = allParentMembers.stream().filter(el -> el.getKind() == ElementKind.METHOD).anyMatch(el -> "deepClone".equals(el.getSimpleName().toString()));
|
||||||
boolean needsDeepClone = fieldGetters(methodsPerAttribute)
|
boolean needsDeepClone = fieldGetters(methodsPerAttribute)
|
||||||
.map(ExecutableElement::getReturnType)
|
.map(ExecutableElement::getReturnType)
|
||||||
.anyMatch(fieldType -> ! isKnownCollectionOfImmutableFinalTypes(fieldType) && ! isImmutableFinalType(fieldType));
|
.anyMatch(fieldType -> ! isKnownCollectionOfImmutableFinalTypes(fieldType) && ! isImmutableFinalType(fieldType));
|
||||||
|
@ -180,7 +278,7 @@ public class GenerateEntityImplementationsProcessor extends AbstractGenerateEnti
|
||||||
pw.println("public class " + mapSimpleClassName + (an.inherits().isEmpty() ? "" : " extends " + an.inherits()) + " implements " + className + " {");
|
pw.println("public class " + mapSimpleClassName + (an.inherits().isEmpty() ? "" : " extends " + an.inherits()) + " implements " + className + " {");
|
||||||
|
|
||||||
// Constructors
|
// Constructors
|
||||||
allMembers.stream()
|
allParentMembers.stream()
|
||||||
.filter(ExecutableElement.class::isInstance)
|
.filter(ExecutableElement.class::isInstance)
|
||||||
.map(ExecutableElement.class::cast)
|
.map(ExecutableElement.class::cast)
|
||||||
.filter((ExecutableElement ee) -> ee.getKind() == ElementKind.CONSTRUCTOR)
|
.filter((ExecutableElement ee) -> ee.getKind() == ElementKind.CONSTRUCTOR)
|
||||||
|
@ -222,7 +320,7 @@ public class GenerateEntityImplementationsProcessor extends AbstractGenerateEnti
|
||||||
+ ";");
|
+ ";");
|
||||||
pw.println(" }");
|
pw.println(" }");
|
||||||
pw.println(" @Override public int hashCode() {");
|
pw.println(" @Override public int hashCode() {");
|
||||||
pw.println(" return "
|
pw.println(" return "
|
||||||
+ (hasId
|
+ (hasId
|
||||||
? "(getId() == null ? super.hashCode() : getId().hashCode())"
|
? "(getId() == null ? super.hashCode() : getId().hashCode())"
|
||||||
: "Objects.hash("
|
: "Objects.hash("
|
||||||
|
@ -261,12 +359,7 @@ public class GenerateEntityImplementationsProcessor extends AbstractGenerateEnti
|
||||||
|
|
||||||
for (ExecutableElement method : methods) {
|
for (ExecutableElement method : methods) {
|
||||||
FieldAccessorType fat = FieldAccessorType.determineType(method, me.getKey(), types, fieldType);
|
FieldAccessorType fat = FieldAccessorType.determineType(method, me.getKey(), types, fieldType);
|
||||||
Optional<ExecutableElement> parentMethod = allMembers.stream()
|
Optional<ExecutableElement> parentMethod = Util.findParentMethodImplementation(allParentMembers, method);
|
||||||
.filter(ExecutableElement.class::isInstance)
|
|
||||||
.map(ExecutableElement.class::cast)
|
|
||||||
.filter(ee -> Objects.equals(ee.toString(), method.toString()))
|
|
||||||
.filter((ExecutableElement ee) -> ! ee.getModifiers().contains(Modifier.ABSTRACT))
|
|
||||||
.findAny();
|
|
||||||
|
|
||||||
if (parentMethod.isPresent()) {
|
if (parentMethod.isPresent()) {
|
||||||
processingEnv.getMessager().printMessage(Kind.OTHER, "Method " + method + " is declared in a parent class.", method);
|
processingEnv.getMessager().printMessage(Kind.OTHER, "Method " + method + " is declared in a parent class.", method);
|
||||||
|
@ -275,6 +368,44 @@ public class GenerateEntityImplementationsProcessor extends AbstractGenerateEnti
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Read-only class overrides setters to be no-op
|
||||||
|
pw.println(" public static class Empty " + (an.inherits().isEmpty() ? "" : " extends " + an.inherits()) + " implements " + className + " {");
|
||||||
|
pw.println(" public static final Empty INSTANCE = new Empty();");
|
||||||
|
methodsPerAttribute.entrySet().stream().sorted(Comparator.comparing(Map.Entry::getKey, NameFirstComparator.ID_INSTANCE))
|
||||||
|
.map(Map.Entry::getValue)
|
||||||
|
.flatMap(Collection::stream)
|
||||||
|
.forEach(ee -> {
|
||||||
|
pw.println(" @Override "
|
||||||
|
+ ee.getModifiers().stream().filter(m -> m != Modifier.ABSTRACT).map(Object::toString).collect(Collectors.joining(" "))
|
||||||
|
+ " " + ee.getReturnType()
|
||||||
|
+ " " + ee.getSimpleName()
|
||||||
|
+ "(" + methodParameters(ee.getParameters()) + ") {");
|
||||||
|
if (ee.getReturnType().getKind() == TypeKind.VOID) {
|
||||||
|
pw.println(" }");
|
||||||
|
} else {
|
||||||
|
pw.println(" return null;");
|
||||||
|
pw.println(" }");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
elements.getAllMembers(e).stream()
|
||||||
|
.filter(ee -> ee.getSimpleName().contentEquals("isUpdated"))
|
||||||
|
.filter(ExecutableElement.class::isInstance)
|
||||||
|
.map(ExecutableElement.class::cast)
|
||||||
|
.filter(ee -> ee.getReturnType().getKind() == TypeKind.BOOLEAN)
|
||||||
|
.forEach(ee -> {
|
||||||
|
pw.println(" @Override "
|
||||||
|
+ ee.getModifiers().stream().filter(m -> m != Modifier.ABSTRACT).map(Object::toString).collect(Collectors.joining(" "))
|
||||||
|
+ " " + ee.getReturnType()
|
||||||
|
+ " " + ee.getSimpleName()
|
||||||
|
+ "(" + methodParameters(ee.getParameters()) + ") {");
|
||||||
|
pw.println(" return false;");
|
||||||
|
pw.println(" }");
|
||||||
|
});
|
||||||
|
pw.println(" }");
|
||||||
|
|
||||||
|
autogenerated.add(" EMPTY_INSTANCES.put(" + className + ".class, " + mapImplClassName + ".Empty.INSTANCE);");
|
||||||
|
|
||||||
pw.println("}");
|
pw.println("}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -343,6 +474,122 @@ public class GenerateEntityImplementationsProcessor extends AbstractGenerateEnti
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class FieldDelegateGenerator implements Generator {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void generate(TypeElement e) throws IOException {
|
||||||
|
Map<String, HashSet<ExecutableElement>> methodsPerAttribute = methodsPerAttributeMapping(e);
|
||||||
|
String className = e.getQualifiedName().toString();
|
||||||
|
String packageName = null;
|
||||||
|
int lastDot = className.lastIndexOf('.');
|
||||||
|
if (lastDot > 0) {
|
||||||
|
packageName = className.substring(0, lastDot);
|
||||||
|
}
|
||||||
|
|
||||||
|
String simpleClassName = className.substring(lastDot + 1);
|
||||||
|
String mapClassName = className + "FieldDelegate";
|
||||||
|
String mapSimpleClassName = simpleClassName + "FieldDelegate";
|
||||||
|
String fieldsClassName = className + "Fields";
|
||||||
|
|
||||||
|
GenerateEntityImplementations an = e.getAnnotation(GenerateEntityImplementations.class);
|
||||||
|
TypeElement parentTypeElement = elements.getTypeElement((an.inherits() == null || an.inherits().isEmpty()) ? "void" : an.inherits());
|
||||||
|
if (parentTypeElement == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
JavaFileObject file = processingEnv.getFiler().createSourceFile(mapClassName);
|
||||||
|
IdentityHashMap<ExecutableElement, String> m2field = new IdentityHashMap<>();
|
||||||
|
methodsPerAttribute.forEach((f, s) -> s.forEach(m -> m2field.put(m, f))); // Create reverse map
|
||||||
|
try (PrintWriter pw = new PrintWriterNoJavaLang(file.openWriter())) {
|
||||||
|
if (packageName != null) {
|
||||||
|
pw.println("package " + packageName + ";");
|
||||||
|
}
|
||||||
|
|
||||||
|
pw.println("public class " + mapSimpleClassName + (an.inherits().isEmpty() ? "" : " extends " + an.inherits()) + " implements " + className + ", " + FQN_HAS_ENTITY_FIELD_DELEGATE + "<" + className + ">" + " {");
|
||||||
|
pw.println(" private final " + FQN_ENTITY_FIELD_DELEGATE + "<" + className + "> entityFieldDelegate;");
|
||||||
|
pw.println(" public " + mapSimpleClassName + "(" + FQN_ENTITY_FIELD_DELEGATE + "<" + className + "> entityFieldDelegate) {");
|
||||||
|
pw.println(" this.entityFieldDelegate = entityFieldDelegate;");
|
||||||
|
pw.println(" }");
|
||||||
|
pw.println(" public " + FQN_ENTITY_FIELD_DELEGATE + "<" + className + "> getEntityFieldDelegate() {");
|
||||||
|
pw.println(" return this.entityFieldDelegate;");
|
||||||
|
pw.println(" }");
|
||||||
|
|
||||||
|
getAllAbstractMethods(e)
|
||||||
|
.forEach(ee -> {
|
||||||
|
String originalField = m2field.get(ee);
|
||||||
|
if (originalField == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
TypeMirror fieldType = determineFieldType(originalField, methodsPerAttribute.get(originalField));
|
||||||
|
String field = fieldsClassName + "." + toEnumConstant(originalField);
|
||||||
|
|
||||||
|
if (ee.getReturnType().getKind() == TypeKind.BOOLEAN && "isUpdated".equals(ee.getSimpleName().toString())) {
|
||||||
|
pw.println(" return entityFieldDelegate.isUpdated();");
|
||||||
|
pw.println(" }");
|
||||||
|
} else if (ee.getReturnType().getKind() == TypeKind.VOID && "clearUpdatedFlag".equals(ee.getSimpleName().toString())) {
|
||||||
|
pw.println(" return entityFieldDelegate.clearUpdatedFlag();");
|
||||||
|
pw.println(" }");
|
||||||
|
} else {
|
||||||
|
FieldAccessorType fat = FieldAccessorType.determineType(ee, originalField, types, fieldType);
|
||||||
|
printMethodBody(pw, fat, ee, field, fieldType);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
autogenerated.add(" ENTITY_FIELD_DELEGATE_CREATORS.put(" + className + ".class, (EntityFieldDelegateCreator<" + className + ">) " + mapClassName + "::new);");
|
||||||
|
|
||||||
|
pw.println("}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean printMethodBody(PrintWriter pw, FieldAccessorType accessorType, ExecutableElement method, String fieldName, TypeMirror fieldType) {
|
||||||
|
TypeMirror firstParameterType = method.getParameters().isEmpty()
|
||||||
|
? types.getNullType()
|
||||||
|
: method.getParameters().get(0).asType();
|
||||||
|
|
||||||
|
switch (accessorType) {
|
||||||
|
case GETTER:
|
||||||
|
pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method + " {");
|
||||||
|
pw.println(" return (" + fieldType + ") entityFieldDelegate.get(" + fieldName + ");");
|
||||||
|
pw.println(" }");
|
||||||
|
return true;
|
||||||
|
case SETTER:
|
||||||
|
pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(" + firstParameterType + " p0) {");
|
||||||
|
pw.println(" entityFieldDelegate.set(" + fieldName + ", p0);");
|
||||||
|
pw.println(" }");
|
||||||
|
return true;
|
||||||
|
case COLLECTION_ADD:
|
||||||
|
pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(" + firstParameterType + " p0) {");
|
||||||
|
pw.println(" entityFieldDelegate.collectionAdd(" + fieldName + ", p0);");
|
||||||
|
pw.println(" }");
|
||||||
|
return true;
|
||||||
|
case COLLECTION_DELETE:
|
||||||
|
pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(" + firstParameterType + " p0) {");
|
||||||
|
TypeElement fieldTypeElement = elements.getTypeElement(types.erasure(fieldType).toString());
|
||||||
|
String removeMethod = Util.isMapType(fieldTypeElement) ? "mapRemove" : "collectionRemove";
|
||||||
|
if (method.getReturnType().getKind() == TypeKind.VOID) {
|
||||||
|
pw.println(" entityFieldDelegate." + removeMethod + "(" + fieldName + ", p0);");
|
||||||
|
} else {
|
||||||
|
pw.println(" return (" + method.getReturnType() + ") entityFieldDelegate." + removeMethod + "(" + fieldName + ", p0);");
|
||||||
|
}
|
||||||
|
pw.println(" }");
|
||||||
|
return true;
|
||||||
|
case MAP_ADD:
|
||||||
|
TypeMirror secondParameterType = method.getParameters().get(1).asType();
|
||||||
|
pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(" + firstParameterType + " p0, " + secondParameterType + " p1) {");
|
||||||
|
pw.println(" entityFieldDelegate.mapPut(" + fieldName + ", p0, p1);");
|
||||||
|
pw.println(" }");
|
||||||
|
return true;
|
||||||
|
case MAP_GET:
|
||||||
|
pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(" + firstParameterType + " p0) {");
|
||||||
|
pw.println(" return (" + method.getReturnType() + ") entityFieldDelegate.mapGet(" + fieldName + ", p0);");
|
||||||
|
pw.println(" }");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private class DelegateGenerator implements Generator {
|
private class DelegateGenerator implements Generator {
|
||||||
@Override
|
@Override
|
||||||
public void generate(TypeElement e) throws IOException {
|
public void generate(TypeElement e) throws IOException {
|
||||||
|
@ -364,7 +611,6 @@ public class GenerateEntityImplementationsProcessor extends AbstractGenerateEnti
|
||||||
if (parentTypeElement == null) {
|
if (parentTypeElement == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final List<? extends Element> allMembers = elements.getAllMembers(e);
|
|
||||||
|
|
||||||
JavaFileObject file = processingEnv.getFiler().createSourceFile(mapClassName);
|
JavaFileObject file = processingEnv.getFiler().createSourceFile(mapClassName);
|
||||||
IdentityHashMap<ExecutableElement, String> m2field = new IdentityHashMap<>();
|
IdentityHashMap<ExecutableElement, String> m2field = new IdentityHashMap<>();
|
||||||
|
@ -380,11 +626,7 @@ public class GenerateEntityImplementationsProcessor extends AbstractGenerateEnti
|
||||||
pw.println(" this.delegateProvider = delegateProvider;");
|
pw.println(" this.delegateProvider = delegateProvider;");
|
||||||
pw.println(" }");
|
pw.println(" }");
|
||||||
|
|
||||||
allMembers.stream()
|
getAllAbstractMethods(e)
|
||||||
.filter(m -> m.getKind() == ElementKind.METHOD)
|
|
||||||
.filter(ExecutableElement.class::isInstance)
|
|
||||||
.map(ExecutableElement.class::cast)
|
|
||||||
.filter(ee -> ee.getModifiers().contains(Modifier.ABSTRACT))
|
|
||||||
.forEach(ee -> {
|
.forEach(ee -> {
|
||||||
pw.println(" @Override "
|
pw.println(" @Override "
|
||||||
+ ee.getModifiers().stream().filter(m -> m != Modifier.ABSTRACT).map(Object::toString).collect(Collectors.joining(" "))
|
+ ee.getModifiers().stream().filter(m -> m != Modifier.ABSTRACT).map(Object::toString).collect(Collectors.joining(" "))
|
||||||
|
@ -396,11 +638,15 @@ public class GenerateEntityImplementationsProcessor extends AbstractGenerateEnti
|
||||||
if (ee.getReturnType().getKind() == TypeKind.BOOLEAN && "isUpdated".equals(ee.getSimpleName().toString())) {
|
if (ee.getReturnType().getKind() == TypeKind.BOOLEAN && "isUpdated".equals(ee.getSimpleName().toString())) {
|
||||||
pw.println(" return delegateProvider.isUpdated();");
|
pw.println(" return delegateProvider.isUpdated();");
|
||||||
} else if (ee.getReturnType().getKind() == TypeKind.VOID) { // write operation
|
} else if (ee.getReturnType().getKind() == TypeKind.VOID) { // write operation
|
||||||
pw.println(" delegateProvider.getDelegate(false, " + field + ")." + ee.getSimpleName() + "("
|
pw.println(" delegateProvider.getDelegate(false, "
|
||||||
|
+ Stream.concat(Stream.of(field), ee.getParameters().stream().map(VariableElement::getSimpleName)).collect(Collectors.joining(", "))
|
||||||
|
+ ")." + ee.getSimpleName() + "("
|
||||||
+ ee.getParameters().stream().map(VariableElement::getSimpleName).collect(Collectors.joining(", "))
|
+ ee.getParameters().stream().map(VariableElement::getSimpleName).collect(Collectors.joining(", "))
|
||||||
+ ");");
|
+ ");");
|
||||||
} else {
|
} else {
|
||||||
pw.println(" return delegateProvider.getDelegate(true, " + field + ")." + ee.getSimpleName() + "("
|
pw.println(" return delegateProvider.getDelegate(true, "
|
||||||
|
+ Stream.concat(Stream.of(field), ee.getParameters().stream().map(VariableElement::getSimpleName)).collect(Collectors.joining(", "))
|
||||||
|
+ ")." + ee.getSimpleName() + "("
|
||||||
+ ee.getParameters().stream().map(VariableElement::getSimpleName).collect(Collectors.joining(", "))
|
+ ee.getParameters().stream().map(VariableElement::getSimpleName).collect(Collectors.joining(", "))
|
||||||
+ ");");
|
+ ");");
|
||||||
}
|
}
|
||||||
|
@ -408,6 +654,8 @@ public class GenerateEntityImplementationsProcessor extends AbstractGenerateEnti
|
||||||
});
|
});
|
||||||
|
|
||||||
pw.println("}");
|
pw.println("}");
|
||||||
|
|
||||||
|
autogenerated.add(" DELEGATE_CREATORS.put(" + className + ".class, (DelegateCreator<" + className + ">) " + mapClassName + "::new);");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -457,7 +705,7 @@ public class GenerateEntityImplementationsProcessor extends AbstractGenerateEnti
|
||||||
pw.println(" return target;");
|
pw.println(" return target;");
|
||||||
pw.println(" }");
|
pw.println(" }");
|
||||||
|
|
||||||
cloners.add(" CLONERS_WITH_ID.put(" + className + ".class, (Cloner<" + className + ">) " + clonerImplClassName + "::deepClone);");
|
autogenerated.add(" CLONERS_WITH_ID.put(" + className + ".class, (Cloner<" + className + ">) " + clonerImplClassName + "::deepClone);");
|
||||||
|
|
||||||
if (methodsPerAttribute.containsKey("Id")) {
|
if (methodsPerAttribute.containsKey("Id")) {
|
||||||
pw.println(" public static " + className + " deepCloneNoId(" + className + " original, " + className + " target) {");
|
pw.println(" public static " + className + " deepCloneNoId(" + className + " original, " + className + " target) {");
|
||||||
|
@ -476,7 +724,7 @@ public class GenerateEntityImplementationsProcessor extends AbstractGenerateEnti
|
||||||
pw.println(" return target;");
|
pw.println(" return target;");
|
||||||
pw.println(" }");
|
pw.println(" }");
|
||||||
|
|
||||||
cloners.add(" CLONERS_WITHOUT_ID.put(" + className + ".class, (Cloner<" + className + ">) " + clonerImplClassName + "::deepCloneNoId);");
|
autogenerated.add(" CLONERS_WITHOUT_ID.put(" + className + ".class, (Cloner<" + className + ">) " + clonerImplClassName + "::deepCloneNoId);");
|
||||||
}
|
}
|
||||||
pw.println("}");
|
pw.println("}");
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,10 +24,14 @@ import java.util.LinkedHashSet;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.TreeSet;
|
import java.util.TreeSet;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import javax.lang.model.element.Element;
|
import javax.lang.model.element.Element;
|
||||||
|
import javax.lang.model.element.ExecutableElement;
|
||||||
|
import javax.lang.model.element.Modifier;
|
||||||
import javax.lang.model.element.Name;
|
import javax.lang.model.element.Name;
|
||||||
import javax.lang.model.element.TypeElement;
|
import javax.lang.model.element.TypeElement;
|
||||||
import javax.lang.model.element.VariableElement;
|
import javax.lang.model.element.VariableElement;
|
||||||
|
@ -86,4 +90,13 @@ public class Util {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected static Optional<ExecutableElement> findParentMethodImplementation(List<? extends Element> allParentMembers, ExecutableElement method) {
|
||||||
|
return allParentMembers.stream()
|
||||||
|
.filter(ExecutableElement.class::isInstance)
|
||||||
|
.map(ExecutableElement.class::cast)
|
||||||
|
.filter(ee -> Objects.equals(ee.toString(), method.toString()))
|
||||||
|
.filter((ExecutableElement ee) -> ! ee.getModifiers().contains(Modifier.ABSTRACT))
|
||||||
|
.findAny();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2021 Red Hat, Inc. and/or its affiliates
|
* Copyright 2021 Red Hat, Inc. and/or its affiliates
|
||||||
* and other contributors as indicated by the @author tags.
|
* and other contributors as indicated by the @author tags.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://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
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@ -16,6 +16,8 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.models.map.common;
|
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.ElementType;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
@ -79,6 +81,30 @@ public class DeepCloner {
|
||||||
V clone(V original, V 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();
|
public static final DeepCloner DUMB_CLONER = new Builder().build();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -87,8 +113,10 @@ public class DeepCloner {
|
||||||
public static class Builder {
|
public static class Builder {
|
||||||
private final Map<Class<?>, Supplier<?>> parameterlessConstructors = new HashMap<>();
|
private final Map<Class<?>, Supplier<?>> parameterlessConstructors = new HashMap<>();
|
||||||
private final Map<Class<?>, Function<DeepCloner, ?>> constructors = new HashMap<>();
|
private final Map<Class<?>, Function<DeepCloner, ?>> constructors = new HashMap<>();
|
||||||
private final Map<Class<?>, Cloner> clonersWithId = new HashMap<>(org.keycloak.models.map.common.AutogeneratedCloners.CLONERS_WITH_ID);
|
private final Map<Class<?>, Cloner<?>> clonersWithId = new HashMap<>(org.keycloak.models.map.common.AutogeneratedCloners.CLONERS_WITH_ID);
|
||||||
private final Map<Class<?>, Cloner> clonersWithoutId = new HashMap<>(org.keycloak.models.map.common.AutogeneratedCloners.CLONERS_WITHOUT_ID);
|
private final Map<Class<?>, Cloner<?>> clonersWithoutId = new HashMap<>(org.keycloak.models.map.common.AutogeneratedCloners.CLONERS_WITHOUT_ID);
|
||||||
|
private final Map<Class<?>, DelegateCreator<?>> delegateCreators = new HashMap<>(org.keycloak.models.map.common.AutogeneratedCloners.DELEGATE_CREATORS);
|
||||||
|
private final Map<Class<?>, EntityFieldDelegateCreator<?>> entityFieldDelegateCreators = new HashMap<>(org.keycloak.models.map.common.AutogeneratedCloners.ENTITY_FIELD_DELEGATE_CREATORS);
|
||||||
private Cloner<?> genericCloner = (from, to) -> { throw new IllegalStateException("Cloner not found for class " + (from == null ? "<null>" : from.getClass())); };
|
private Cloner<?> genericCloner = (from, to) -> { throw new IllegalStateException("Cloner not found for class " + (from == null ? "<null>" : from.getClass())); };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -96,7 +124,7 @@ public class DeepCloner {
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public DeepCloner build() {
|
public DeepCloner build() {
|
||||||
return new DeepCloner(parameterlessConstructors, constructors, clonersWithId, clonersWithoutId, genericCloner);
|
return new DeepCloner(parameterlessConstructors, constructors, delegateCreators, entityFieldDelegateCreators, clonersWithId, clonersWithoutId, genericCloner);
|
||||||
}
|
}
|
||||||
|
|
||||||
private <V> void forThisClassAndAllMarkedParentsAndInterfaces(Class<V> rootClazz, Consumer<Class<?>> action) {
|
private <V> void forThisClassAndAllMarkedParentsAndInterfaces(Class<V> rootClazz, Consumer<Class<?>> action) {
|
||||||
|
@ -123,7 +151,7 @@ public class DeepCloner {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a method, often a constructor, that instantiates a record of type {@code V}.
|
* 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 <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 clazz Class or interface that would be instantiated by the given methods
|
||||||
* @param constructorNoParameters Parameterless function that creates a new instance of class {@code V}.
|
* @param constructorNoParameters Parameterless function that creates a new instance of class {@code V}.
|
||||||
|
@ -153,6 +181,38 @@ public class DeepCloner {
|
||||||
return this;
|
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 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 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 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 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
|
* Adds a method that copies (as in a deep copy) an object properties from one object to another
|
||||||
*
|
*
|
||||||
|
@ -163,7 +223,7 @@ public class DeepCloner {
|
||||||
* function usually returns {@code to}
|
* function usually returns {@code to}
|
||||||
* @return This builder.
|
* @return This builder.
|
||||||
*/
|
*/
|
||||||
public <V> Builder cloner(Class<? extends V> clazz, Cloner cloner) {
|
public <V> Builder cloner(Class<? extends V> clazz, Cloner<?> cloner) {
|
||||||
if (cloner != null) {
|
if (cloner != null) {
|
||||||
forThisClassAndAllMarkedParentsAndInterfaces(clazz, cl -> this.clonersWithId.put(cl, cloner));
|
forThisClassAndAllMarkedParentsAndInterfaces(clazz, cl -> this.clonersWithId.put(cl, cloner));
|
||||||
}
|
}
|
||||||
|
@ -180,7 +240,7 @@ public class DeepCloner {
|
||||||
* function usually returns {@code to}
|
* function usually returns {@code to}
|
||||||
* @return This builder.
|
* @return This builder.
|
||||||
*/
|
*/
|
||||||
public <V> Builder cloner(Class<? extends V> clazz, Cloner clonerWithId, Cloner clonerWithoutId) {
|
public <V> Builder cloner(Class<? extends V> clazz, Cloner<?> clonerWithId, Cloner<?> clonerWithoutId) {
|
||||||
if (clonerWithId != null) {
|
if (clonerWithId != null) {
|
||||||
forThisClassAndAllMarkedParentsAndInterfaces(clazz, cl -> this.clonersWithId.put(cl, clonerWithId));
|
forThisClassAndAllMarkedParentsAndInterfaces(clazz, cl -> this.clonersWithId.put(cl, clonerWithId));
|
||||||
}
|
}
|
||||||
|
@ -193,7 +253,7 @@ public class DeepCloner {
|
||||||
/**
|
/**
|
||||||
* Adds a method that copies (as in a deep copy) an object properties to another object for any class
|
* 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.
|
* 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 <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
|
* @param genericCloner A method for cloning which copies properties of an object onto another object. This
|
||||||
* function usually returns {@code to}
|
* function usually returns {@code to}
|
||||||
|
@ -209,20 +269,27 @@ public class DeepCloner {
|
||||||
|
|
||||||
private final Map<Class<?>, Supplier<?>> parameterlessConstructors;
|
private final Map<Class<?>, Supplier<?>> parameterlessConstructors;
|
||||||
private final Map<Class<?>, Function<DeepCloner, ?>> constructors;
|
private final Map<Class<?>, Function<DeepCloner, ?>> constructors;
|
||||||
private final Map<Class<?>, Cloner> clonersWithId;
|
private final Map<Class<?>, Cloner<?>> clonersWithId;
|
||||||
private final Map<Class<?>, Cloner> clonersWithoutId;
|
private final Map<Class<?>, Cloner<?>> clonersWithoutId;
|
||||||
|
private final Map<Class<?>, DelegateCreator<?>> delegateCreators;
|
||||||
|
private final Map<Class<?>, EntityFieldDelegateCreator<?>> entityFieldDelegateCreators;
|
||||||
private final Cloner<?> genericCloner;
|
private final Cloner<?> genericCloner;
|
||||||
|
private final Map<Class<?>, Object> emptyInstances = new HashMap<>(AutogeneratedCloners.EMPTY_INSTANCES);
|
||||||
|
|
||||||
private DeepCloner(Map<Class<?>, Supplier<?>> parameterlessConstructors,
|
private DeepCloner(Map<Class<?>, Supplier<?>> parameterlessConstructors,
|
||||||
Map<Class<?>, Function<DeepCloner, ?>> constructors,
|
Map<Class<?>, Function<DeepCloner, ?>> constructors,
|
||||||
Map<Class<?>, Cloner> clonersWithId,
|
Map<Class<?>, DelegateCreator<?>> delegateCreators,
|
||||||
Map<Class<?>, Cloner> clonersWithoutId,
|
Map<Class<?>, EntityFieldDelegateCreator<?>> entityFieldDelegateCreators,
|
||||||
|
Map<Class<?>, Cloner<?>> clonersWithId,
|
||||||
|
Map<Class<?>, Cloner<?>> clonersWithoutId,
|
||||||
Cloner<?> genericCloner) {
|
Cloner<?> genericCloner) {
|
||||||
this.parameterlessConstructors = parameterlessConstructors;
|
this.parameterlessConstructors = parameterlessConstructors;
|
||||||
this.constructors = constructors;
|
this.constructors = constructors;
|
||||||
this.clonersWithId = clonersWithId;
|
this.clonersWithId = clonersWithId;
|
||||||
this.clonersWithoutId = clonersWithoutId;
|
this.clonersWithoutId = clonersWithoutId;
|
||||||
|
this.delegateCreators = delegateCreators;
|
||||||
this.genericCloner = genericCloner;
|
this.genericCloner = genericCloner;
|
||||||
|
this.entityFieldDelegateCreators = entityFieldDelegateCreators;
|
||||||
}
|
}
|
||||||
|
|
||||||
private <V> V getFromClassRespectingHierarchy(Map<Class<?>, V> map, Class<?> clazz) {
|
private <V> V getFromClassRespectingHierarchy(Map<Class<?>, V> map, Class<?> clazz) {
|
||||||
|
@ -257,6 +324,43 @@ public class DeepCloner {
|
||||||
return null;
|
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> 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.
|
* 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 <V> Type (class or a {@code @Root} interface) to create a new instance
|
||||||
|
@ -301,6 +405,7 @@ public class DeepCloner {
|
||||||
* @param clazz 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 See description
|
* @return See description
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
public <V> Class<? extends V> newInstanceType(Class<V> valueType) {
|
public <V> Class<? extends V> newInstanceType(Class<V> valueType) {
|
||||||
if (valueType == null) {
|
if (valueType == null) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -338,7 +443,7 @@ public class DeepCloner {
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private <V> V deepClone(V from, V to, Map<Class<?>, Cloner> cloners) {
|
private <V> V deepClone(V from, V to, Map<Class<?>, Cloner<?>> cloners) {
|
||||||
Cloner<V> cloner = (Cloner<V>) getFromClassRespectingHierarchy(cloners, from.getClass());
|
Cloner<V> cloner = (Cloner<V>) getFromClassRespectingHierarchy(cloners, from.getClass());
|
||||||
if (cloner != null) {
|
if (cloner != null) {
|
||||||
return cloner.clone(from, to);
|
return cloner.clone(from, to);
|
||||||
|
@ -383,6 +488,7 @@ public class DeepCloner {
|
||||||
* @param from Original instance
|
* @param from Original instance
|
||||||
* @return Newly created instance or {@code null} if {@code from} is {@code null}.
|
* @return Newly created instance or {@code null} if {@code from} is {@code null}.
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
public <V> V from(V from) {
|
public <V> V from(V from) {
|
||||||
return from == null ? null : deepClone(from, newInstance((Class<V>) from.getClass()));
|
return from == null ? null : deepClone(from, newInstance((Class<V>) from.getClass()));
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,127 @@
|
||||||
|
package org.keycloak.models.map.common;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a field in an entity with appropriate accessors.
|
||||||
|
*
|
||||||
|
* @author hmlnarik
|
||||||
|
* @param <E>
|
||||||
|
*/
|
||||||
|
public interface EntityField<E> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns name of this field with no spaces where each word starts with a capital letter.
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
String getName();
|
||||||
|
/**
|
||||||
|
* Returns name of this field in lowercase with words separated by a dash ({@code -}).
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
String getNameDashed();
|
||||||
|
/**
|
||||||
|
* Returns the value of this field.
|
||||||
|
*
|
||||||
|
* @param e Entity
|
||||||
|
* @return Value of the field
|
||||||
|
*/
|
||||||
|
Object get(E e);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the value of this field. Does nothing by default. If you want to have a field set, override this method.
|
||||||
|
* @param <T>
|
||||||
|
* @param e Entity
|
||||||
|
* @param value Value of the field
|
||||||
|
*/
|
||||||
|
default <T> void set(E e, T value) {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an element to the collection stored in this field.
|
||||||
|
* @param e Entity
|
||||||
|
* @param value Value to be added to the collection
|
||||||
|
* @throws ClassCastException If this field is not a collection.
|
||||||
|
*/
|
||||||
|
default <T> void collectionAdd(E e, T value) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Collection<T> c = (Collection<T>) get(e);
|
||||||
|
if (c != null) {
|
||||||
|
c.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Removes an element from the collection stored in this field.
|
||||||
|
* @param e Entity
|
||||||
|
* @param value Value to be added to the collection
|
||||||
|
* @return Defined by the underlying field. Preferrably it should return deleted object, but it can return
|
||||||
|
* {@code true / false} indication of removal, or just {@code null}.
|
||||||
|
* @throws ClassCastException If this field is not a collection.
|
||||||
|
*/
|
||||||
|
default <T> Object collectionRemove(E e, T value) {
|
||||||
|
Collection<?> c = (Collection<?>) get(e);
|
||||||
|
return c == null ? null : c.remove(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a value from the map stored in this field.
|
||||||
|
* @param e Entity
|
||||||
|
* @param key Requested key
|
||||||
|
* @return Object mapped to this key
|
||||||
|
* @throws ClassCastException If this field is not a map.
|
||||||
|
*/
|
||||||
|
default <K> Object mapGet(E e, K key) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Map<K, ?> m = (Map<K, ?>) get(e);
|
||||||
|
return m == null ? null : m.get(key);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Adds a mapping to the map stored in this field.
|
||||||
|
* @param e Entity
|
||||||
|
* @param key Key to map
|
||||||
|
* @param value Mapped value
|
||||||
|
* @throws ClassCastException If this field is not a map.
|
||||||
|
*/
|
||||||
|
default <K, T> void mapPut(E e, K key, T value) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Map<K, T> m = (Map<K, T>) get(e);
|
||||||
|
if (m != null) {
|
||||||
|
m.put(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Removes a mapping from the map stored in this field.
|
||||||
|
* @param e Entity
|
||||||
|
* @param key Key to remove
|
||||||
|
* @return Object mapped to this key
|
||||||
|
* @throws ClassCastException If this field is not a map.
|
||||||
|
*/
|
||||||
|
default <K> Object mapRemove(E e, K key) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Map<K, ?> m = (Map<K, ?>) get(e);
|
||||||
|
if (m != null) {
|
||||||
|
return m.remove(key);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Returns the most specific type of this field.
|
||||||
|
*/
|
||||||
|
default Class<?> getFieldClass() { return Object.class; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return If this field is a collection, returns type of its elements; otherwise returns {@code Void} class.
|
||||||
|
*/
|
||||||
|
default Class<?> getCollectionElementClass() { return Void.class; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return If this field is a map, returns type of its keys; otherwise returns {@code Void} class.
|
||||||
|
*/
|
||||||
|
default Class<?> getMapKeyClass() { return Void.class; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return If this field is a map, returns type of its values; otherwise returns {@code Void} class.
|
||||||
|
*/
|
||||||
|
default Class<?> getMapValueClass() { return Void.class; }
|
||||||
|
}
|
|
@ -17,7 +17,8 @@
|
||||||
package org.keycloak.models.map.common.delegate;
|
package org.keycloak.models.map.common.delegate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Interface for a provider of a delegate of type {@code T}, optionally
|
||||||
|
* providing the flag on the object been updated.
|
||||||
* @author hmlnarik
|
* @author hmlnarik
|
||||||
*/
|
*/
|
||||||
public interface DelegateProvider<T> {
|
public interface DelegateProvider<T> {
|
||||||
|
@ -27,7 +28,7 @@ public interface DelegateProvider<T> {
|
||||||
* @param field Identification of the field this delegates operates on.
|
* @param field Identification of the field this delegates operates on.
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
T getDelegate(boolean isRead, Object field);
|
T getDelegate(boolean isRead, Object field, Object... parameters);
|
||||||
|
|
||||||
default boolean isUpdated() { return false; }
|
default boolean isUpdated() { return false; }
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
package org.keycloak.models.map.common.delegate;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.keycloak.models.map.common.EntityField;
|
||||||
|
import org.keycloak.models.map.common.UpdatableEntity;
|
||||||
|
|
||||||
|
public interface EntityFieldDelegate<E> extends UpdatableEntity {
|
||||||
|
// Non-collection values
|
||||||
|
Object get(EntityField<E> field);
|
||||||
|
default <T> void set(EntityField<E> field, T value) {}
|
||||||
|
|
||||||
|
default <T> void collectionAdd(EntityField<E> field, T value) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Collection<T> c = (Collection<T>) get(field);
|
||||||
|
if (c != null) {
|
||||||
|
c.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default <T> Object collectionRemove(EntityField<E> field, T value) {
|
||||||
|
Collection<?> c = (Collection<?>) get(field);
|
||||||
|
return c == null ? null : c.remove(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param <K> Key type
|
||||||
|
* @param <T> Value type
|
||||||
|
* @param field Field identifier. Should be one of the generated {@code *Fields} enum constants.
|
||||||
|
* @param key Key
|
||||||
|
* @param valueClass class of the value
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
default <K> Object mapGet(EntityField<E> field, K key) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Map<K, ?> m = (Map<K, ?>) get(field);
|
||||||
|
return m == null ? null : m.get(key);
|
||||||
|
}
|
||||||
|
default <K, T> void mapPut(EntityField<E> field, K key, T value) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Map<K, T> m = (Map<K, T>) get(field);
|
||||||
|
if (m != null) {
|
||||||
|
m.put(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default <K> Object mapRemove(EntityField<E> field, K key) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Map<K, ?> m = (Map<K, ?>) get(field);
|
||||||
|
if (m != null) {
|
||||||
|
return m.remove(key);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
package org.keycloak.models.map.common.delegate;
|
||||||
|
|
||||||
|
public interface HasEntityFieldDelegate<E> {
|
||||||
|
EntityFieldDelegate<E> getEntityFieldDelegate();
|
||||||
|
}
|
|
@ -35,7 +35,7 @@ public class LazyDelegateProvider<T extends UpdatableEntity> implements Delegate
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public T getDelegate(boolean isRead, Object field) {
|
public T getDelegate(boolean isRead, Object field, Object... parameters) {
|
||||||
if (! isDelegateInitialized()) {
|
if (! isDelegateInitialized()) {
|
||||||
delegate.compareAndSet(null, delegateSupplier == null ? null : delegateSupplier.get(), false, true);
|
delegate.compareAndSet(null, delegateSupplier == null ? null : delegateSupplier.get(), false, true);
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ public class SimpleDelegateProvider<T extends UpdatableEntity> implements Delega
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public T getDelegate(boolean isRead, Object field) {
|
public T getDelegate(boolean isRead, Object field, Object... parameters) {
|
||||||
return this.delegate;
|
return this.delegate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,100 @@
|
||||||
|
/*
|
||||||
|
* 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.storage.tree;
|
||||||
|
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.map.common.AbstractEntity;
|
||||||
|
import org.keycloak.models.map.storage.MapKeycloakTransaction;
|
||||||
|
import org.keycloak.models.map.storage.MapStorage;
|
||||||
|
import org.keycloak.models.map.storage.QueryParameters;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author hmlnarik
|
||||||
|
*/
|
||||||
|
public class EmptyMapStorage<V extends AbstractEntity, M> implements MapStorage<V, M> {
|
||||||
|
|
||||||
|
private static final EmptyMapStorage<?, ?> INSTANCE = new EmptyMapStorage<>();
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public static <V extends AbstractEntity, M> EmptyMapStorage<V, M> getInstance() {
|
||||||
|
return (EmptyMapStorage<V, M>) INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MapKeycloakTransaction<V, M> createTransaction(KeycloakSession session) {
|
||||||
|
return new MapKeycloakTransaction<V, M>() {
|
||||||
|
@Override
|
||||||
|
public V create(V value) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public V read(String key) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<V> read(QueryParameters<M> queryParameters) {
|
||||||
|
return Stream.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getCount(QueryParameters<M> queryParameters) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean delete(String key) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long delete(QueryParameters<M> queryParameters) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void begin() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void commit() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void rollback() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRollbackOnly() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getRollbackOnly() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isActive() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -29,6 +29,7 @@ import static org.hamcrest.Matchers.is;
|
||||||
import static org.hamcrest.Matchers.not;
|
import static org.hamcrest.Matchers.not;
|
||||||
import static org.hamcrest.Matchers.nullValue;
|
import static org.hamcrest.Matchers.nullValue;
|
||||||
import static org.hamcrest.Matchers.sameInstance;
|
import static org.hamcrest.Matchers.sameInstance;
|
||||||
|
import static org.keycloak.models.map.common.DeepCloner.DUMB_CLONER;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
@ -112,4 +113,40 @@ public class MapClientEntityClonerTest {
|
||||||
assertThat(clonedInstance.getAuthenticationFlowBindingOverrides(), nullValue());
|
assertThat(clonedInstance.getAuthenticationFlowBindingOverrides(), nullValue());
|
||||||
assertThat(clonedInstance.getRegistrationToken(), nullValue());
|
assertThat(clonedInstance.getRegistrationToken(), nullValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCloneToExistingInstanceDumb() {
|
||||||
|
MapClientEntity newInstance = new MapClientEntityImpl();
|
||||||
|
newInstance.setId("my-id");
|
||||||
|
newInstance.setClientId("a-client-id");
|
||||||
|
newInstance.setAttribute("attr", Arrays.asList("aa", "bb", "cc"));
|
||||||
|
MapProtocolMapperEntity pmm = new MapProtocolMapperEntityImpl();
|
||||||
|
pmm.setId("pmm-id");
|
||||||
|
pmm.setConfig(new HashMap<>());
|
||||||
|
pmm.getConfig().put("key1", "value1");
|
||||||
|
pmm.getConfig().put("key2", "value2");
|
||||||
|
newInstance.setProtocolMapper("pmm-id", pmm);
|
||||||
|
newInstance.setAttribute("attr", Arrays.asList("aa", "bb", "cc"));
|
||||||
|
|
||||||
|
MapClientEntity clonedInstance = CLONER.newInstance(MapClientEntity.class);
|
||||||
|
assertThat(CLONER.deepCloneNoId(newInstance, clonedInstance), sameInstance(clonedInstance));
|
||||||
|
assertThat(clonedInstance, instanceOf(MapClientEntityImpl.class));
|
||||||
|
clonedInstance.setId("my-id2");
|
||||||
|
assertThat(clonedInstance.getId(), is("my-id2"));
|
||||||
|
assertThat(clonedInstance.getClientId(), is("a-client-id"));
|
||||||
|
|
||||||
|
assertThat(clonedInstance.getAttributes(), not(sameInstance(newInstance.getAttributes())));
|
||||||
|
assertThat(clonedInstance.getAttributes().keySet(), containsInAnyOrder("attr"));
|
||||||
|
assertThat(clonedInstance.getAttributes().get("attr"), contains("aa", "bb", "cc"));
|
||||||
|
assertThat(clonedInstance.getAttributes().get("attr"), not(sameInstance(newInstance.getAttributes().get("attr"))));
|
||||||
|
|
||||||
|
assertThat(clonedInstance.getProtocolMappers(), not(sameInstance(newInstance.getProtocolMappers())));
|
||||||
|
assertThat(clonedInstance.getProtocolMapper("pmm-id"), not(sameInstance(newInstance.getProtocolMapper("pmm-id"))));
|
||||||
|
assertThat(clonedInstance.getProtocolMapper("pmm-id"), equalTo(newInstance.getProtocolMapper("pmm-id")));
|
||||||
|
assertThat(clonedInstance.getProtocolMapper("pmm-id").getConfig(), not(sameInstance(newInstance.getProtocolMapper("pmm-id").getConfig())));
|
||||||
|
assertThat(clonedInstance.getProtocolMapper("pmm-id").getConfig(), equalTo(newInstance.getProtocolMapper("pmm-id").getConfig()));
|
||||||
|
|
||||||
|
assertThat(clonedInstance.getAuthenticationFlowBindingOverrides(), nullValue());
|
||||||
|
assertThat(clonedInstance.getRegistrationToken(), nullValue());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,157 @@
|
||||||
|
/*
|
||||||
|
* 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.testsuite.model.storage.tree.sample;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.keycloak.models.map.client.MapClientEntity;
|
||||||
|
import org.keycloak.models.map.client.MapClientEntityFieldDelegate;
|
||||||
|
import org.keycloak.models.map.client.MapClientEntityFields;
|
||||||
|
import org.keycloak.models.map.common.DeepCloner;
|
||||||
|
import org.keycloak.models.map.common.EntityField;
|
||||||
|
import org.keycloak.models.map.common.UpdatableEntity;
|
||||||
|
import org.keycloak.models.map.common.delegate.EntityFieldDelegate;
|
||||||
|
import org.keycloak.models.map.common.delegate.HasEntityFieldDelegate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author hmlnarik
|
||||||
|
*/
|
||||||
|
public class Dict<E> extends UpdatableEntity.Impl implements EntityFieldDelegate<E> {
|
||||||
|
|
||||||
|
public static final String CLIENT_FIELD_LOGO = "LOGO";
|
||||||
|
public static final String CLIENT_FIELD_ENABLED = "ENABLED";
|
||||||
|
public static final String CLIENT_FIELD_NAME = "NAME";
|
||||||
|
|
||||||
|
private static final Set<String> CLIENT_ALLOWED_KEYS = new HashSet<>(Arrays.asList(CLIENT_FIELD_NAME, CLIENT_FIELD_ENABLED, CLIENT_FIELD_LOGO));
|
||||||
|
|
||||||
|
public static MapClientEntity clientDelegate() {
|
||||||
|
// To be replaced by dynamic mapper config
|
||||||
|
Map<String, String> fieldName2key = new HashMap<>();
|
||||||
|
fieldName2key.put(MapClientEntityFields.ID.getName(), CLIENT_FIELD_NAME);
|
||||||
|
fieldName2key.put(MapClientEntityFields.CLIENT_ID.getName(), CLIENT_FIELD_NAME);
|
||||||
|
fieldName2key.put(MapClientEntityFields.ENABLED.getName(), CLIENT_FIELD_ENABLED);
|
||||||
|
|
||||||
|
Map<String, String> attributeName2key = new HashMap<>();
|
||||||
|
attributeName2key.put("logo", CLIENT_FIELD_LOGO);
|
||||||
|
|
||||||
|
Dict<MapClientEntity> dict = new Dict<>(CLIENT_ALLOWED_KEYS, fieldName2key, attributeName2key);
|
||||||
|
return DeepCloner.DUMB_CLONER.entityFieldDelegate(MapClientEntity.class, dict);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public static <E> Dict<E> asDict(E entity) {
|
||||||
|
return (entity instanceof HasEntityFieldDelegate && ((HasEntityFieldDelegate<?>) entity).getEntityFieldDelegate() instanceof Dict)
|
||||||
|
? (Dict<E>) ((HasEntityFieldDelegate<E>) entity).getEntityFieldDelegate()
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Set<String> allowedKeys;
|
||||||
|
private final Map<String, Object> contents = new HashMap<>();
|
||||||
|
private final Map<String, String> fieldName2key;
|
||||||
|
private final Map<String, String> attributeName2key;
|
||||||
|
|
||||||
|
public Dict(Set<String> allowedKeys, Map<String, String> fieldName2key, Map<String, String> attributeName2key) {
|
||||||
|
this.allowedKeys = allowedKeys;
|
||||||
|
this.fieldName2key = fieldName2key;
|
||||||
|
this.attributeName2key = attributeName2key;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object get(EntityField<E> field) {
|
||||||
|
if ("Attributes".equals(field.getName())) {
|
||||||
|
return attributeName2key.entrySet().stream()
|
||||||
|
.filter(me -> get(me.getValue()) != null)
|
||||||
|
.collect(Collectors.toMap(me -> me.getKey(), me -> Collections.singletonList(get(me.getValue()))));
|
||||||
|
}
|
||||||
|
String key = fieldName2key.get(field.getName());
|
||||||
|
if (key != null) {
|
||||||
|
return get(key);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> void set(EntityField<E> field, T value) {
|
||||||
|
String key = fieldName2key.get(field.getName());
|
||||||
|
if (key != null) {
|
||||||
|
put(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <K> Object mapGet(EntityField<E> field, K key) {
|
||||||
|
if ("Attributes".equals(field.getName()) && attributeName2key.containsKey(key)) {
|
||||||
|
Object v = get(attributeName2key.get(key));
|
||||||
|
return v == null ? null : Collections.singletonList(get(attributeName2key.get(key)));
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <K, T> void mapPut(EntityField<E> field, K key, T value) {
|
||||||
|
if ("Attributes".equals(field.getName()) && attributeName2key.containsKey(key) && (value instanceof List)) {
|
||||||
|
List<?> l = (List<?>) value;
|
||||||
|
if (l.isEmpty()) {
|
||||||
|
remove(attributeName2key.get(key));
|
||||||
|
} else {
|
||||||
|
put(attributeName2key.get(key), l.get(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <K> Object mapRemove(EntityField<E> field, K key) {
|
||||||
|
if ("Attributes".equals(field.getName()) && attributeName2key.containsKey(key)) {
|
||||||
|
Object o = remove(attributeName2key.get(key));
|
||||||
|
return o == null ? null : Collections.singletonList(o);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean isKeyAllowed(String key) {
|
||||||
|
return allowedKeys.contains(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object get(String key) {
|
||||||
|
return isKeyAllowed(key) ? contents.get(key) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void put(String key, Object value) {
|
||||||
|
if (isKeyAllowed(key)) {
|
||||||
|
updated |= ! Objects.equals(contents.put(key, value), value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object remove(String key) {
|
||||||
|
key = key == null ? null : key.toUpperCase();
|
||||||
|
if (isKeyAllowed(key)) {
|
||||||
|
Object res = contents.remove(key);
|
||||||
|
updated |= res != null;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,120 @@
|
||||||
|
/*
|
||||||
|
* 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.testsuite.model.storage.tree.sample;
|
||||||
|
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.map.common.AbstractEntity;
|
||||||
|
import org.keycloak.models.map.common.DeepCloner;
|
||||||
|
import org.keycloak.models.map.storage.MapKeycloakTransaction;
|
||||||
|
import org.keycloak.models.map.storage.MapStorage;
|
||||||
|
import org.keycloak.models.map.storage.QueryParameters;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author hmlnarik
|
||||||
|
*/
|
||||||
|
public class DictStorage<V extends AbstractEntity, M> implements MapStorage<V, M> {
|
||||||
|
|
||||||
|
private final DeepCloner cloner;
|
||||||
|
|
||||||
|
private final List<V> store;
|
||||||
|
|
||||||
|
public DictStorage(DeepCloner cloner, List<V> store) {
|
||||||
|
this.cloner = cloner;
|
||||||
|
this.store = store;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<V> getStore() {
|
||||||
|
return store;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class Transaction implements MapKeycloakTransaction<V, M> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public V create(V value) {
|
||||||
|
V res = cloner.from(value);
|
||||||
|
store.add(res);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public V read(String key) {
|
||||||
|
return store.stream()
|
||||||
|
.filter(e -> Objects.equals(e.getId(), key))
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<V> read(QueryParameters<M> queryParameters) {
|
||||||
|
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getCount(QueryParameters<M> queryParameters) {
|
||||||
|
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean delete(String key) {
|
||||||
|
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long delete(QueryParameters<M> queryParameters) {
|
||||||
|
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void begin() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void commit() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void rollback() {
|
||||||
|
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRollbackOnly() {
|
||||||
|
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getRollbackOnly() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isActive() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MapKeycloakTransaction<V, M> createTransaction(KeycloakSession session) {
|
||||||
|
return new Transaction();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
package org.keycloak.testsuite.model.storage.tree.sample;
|
||||||
|
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
import static org.hamcrest.Matchers.empty;
|
||||||
|
import static org.hamcrest.Matchers.hasItems;
|
||||||
|
import static org.hamcrest.Matchers.is;
|
||||||
|
import static org.hamcrest.Matchers.nullValue;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.keycloak.models.map.client.MapClientEntity;
|
||||||
|
|
||||||
|
public class DictTest {
|
||||||
|
@Test
|
||||||
|
public void testDictClientFromMap() {
|
||||||
|
MapClientEntity mce = Dict.clientDelegate();
|
||||||
|
assertThat(mce.getClientId(), nullValue());
|
||||||
|
assertThat(mce.isEnabled(), nullValue());
|
||||||
|
assertThat(mce.getAttribute("logo"), nullValue());
|
||||||
|
assertThat(mce.getAttributes().keySet(), is(empty()));
|
||||||
|
|
||||||
|
Dict.asDict(mce).put(Dict.CLIENT_FIELD_NAME, "name");
|
||||||
|
Dict.asDict(mce).put(Dict.CLIENT_FIELD_ENABLED, false);
|
||||||
|
Dict.asDict(mce).put(Dict.CLIENT_FIELD_LOGO, "thisShouldBeBase64Logo");
|
||||||
|
Dict.asDict(mce).put("nonexistent", "nonexistent");
|
||||||
|
|
||||||
|
assertThat(mce.getId(), is("name"));
|
||||||
|
assertThat(mce.getClientId(), is("name"));
|
||||||
|
assertThat(mce.isEnabled(), is(false));
|
||||||
|
assertThat(mce.getAttribute("logo"), hasItems("thisShouldBeBase64Logo"));
|
||||||
|
assertThat(mce.getAttributes().keySet(), hasItems("logo"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDictClientFromEntity() {
|
||||||
|
MapClientEntity mce = Dict.clientDelegate();
|
||||||
|
|
||||||
|
assertThat(Dict.asDict(mce).get(Dict.CLIENT_FIELD_NAME), nullValue());
|
||||||
|
assertThat(Dict.asDict(mce).get(Dict.CLIENT_FIELD_ENABLED), nullValue());
|
||||||
|
assertThat(Dict.asDict(mce).get(Dict.CLIENT_FIELD_LOGO), nullValue());
|
||||||
|
|
||||||
|
mce.setClientId("name");
|
||||||
|
mce.setEnabled(false);
|
||||||
|
mce.setAttribute("logo", Arrays.asList("thisShouldBeBase64Logo"));
|
||||||
|
mce.setAttribute("blah", Arrays.asList("thisShouldBeBase64Logofdas"));
|
||||||
|
|
||||||
|
assertThat(mce.getAttributes().keySet(), hasItems("logo"));
|
||||||
|
|
||||||
|
assertThat(Dict.asDict(mce).get(Dict.CLIENT_FIELD_NAME), is("name"));
|
||||||
|
assertThat(Dict.asDict(mce).get(Dict.CLIENT_FIELD_ENABLED), is(false));
|
||||||
|
assertThat(Dict.asDict(mce).get(Dict.CLIENT_FIELD_LOGO), is("thisShouldBeBase64Logo"));
|
||||||
|
|
||||||
|
mce.setAttribute("logo", Arrays.asList("thisShouldBeAnotherBase64Logo"));
|
||||||
|
assertThat(Dict.asDict(mce).get(Dict.CLIENT_FIELD_LOGO), is("thisShouldBeAnotherBase64Logo"));
|
||||||
|
|
||||||
|
mce.removeAttribute("logo");
|
||||||
|
assertThat(Dict.asDict(mce).get(Dict.CLIENT_FIELD_LOGO), nullValue());
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue