KEYCLOAK-19505 Generate map entity delegates
This commit is contained in:
parent
5b0986e490
commit
675e1b0941
16 changed files with 717 additions and 681 deletions
|
@ -28,5 +28,5 @@ import java.lang.annotation.Target;
|
|||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(ElementType.TYPE)
|
||||
public @interface GenerateEntityImplementations {
|
||||
String inherits() default "";
|
||||
String inherits() default "org.keycloak.models.map.common.UpdatableEntity.Impl";
|
||||
}
|
||||
|
|
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
* 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.processor;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.regex.Pattern;
|
||||
import javax.lang.model.element.ExecutableElement;
|
||||
import javax.lang.model.element.Name;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
import javax.lang.model.util.Types;
|
||||
import static org.keycloak.models.map.processor.Util.getGenericsDeclaration;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author hmlnarik
|
||||
*/
|
||||
enum FieldAccessorType {
|
||||
GETTER {
|
||||
@Override
|
||||
public boolean is(ExecutableElement method, String fieldName, Types types, TypeMirror fieldType) {
|
||||
Pattern getter = Pattern.compile("(get|is)" + Pattern.quote(fieldName));
|
||||
Name methodName = method.getSimpleName();
|
||||
return getter.matcher(methodName).matches() && method.getParameters().isEmpty() && types.isSameType(fieldType, method.getReturnType());
|
||||
}
|
||||
},
|
||||
SETTER {
|
||||
@Override
|
||||
public boolean is(ExecutableElement method, String fieldName, Types types, TypeMirror fieldType) {
|
||||
String methodName = "set" + fieldName;
|
||||
return Objects.equals(methodName, method.getSimpleName().toString())
|
||||
&& method.getParameters().size() == 1
|
||||
&& types.isSameType(fieldType, method.getParameters().get(0).asType());
|
||||
}
|
||||
},
|
||||
COLLECTION_ADD {
|
||||
@Override
|
||||
public boolean is(ExecutableElement method, String fieldName, Types types, TypeMirror fieldType) {
|
||||
String fieldNameSingular = fieldName.endsWith("s") ? fieldName.substring(0, fieldName.length() - 1) : fieldName;
|
||||
String methodName = "add" + fieldNameSingular;
|
||||
List<TypeMirror> res = getGenericsDeclaration(fieldType);
|
||||
return Objects.equals(methodName, method.getSimpleName().toString())
|
||||
&& res.size() == 1
|
||||
&& types.isSameType(res.get(0), method.getParameters().get(0).asType());
|
||||
}
|
||||
},
|
||||
COLLECTION_DELETE {
|
||||
@Override
|
||||
public boolean is(ExecutableElement method, String fieldName, Types types, TypeMirror fieldType) {
|
||||
String fieldNameSingular = fieldName.endsWith("s") ? fieldName.substring(0, fieldName.length() - 1) : fieldName;
|
||||
String removeFromCollection = "remove" + fieldNameSingular;
|
||||
List<TypeMirror> res = getGenericsDeclaration(fieldType);
|
||||
return Objects.equals(removeFromCollection, method.getSimpleName().toString())
|
||||
&& method.getParameters().size() == 1
|
||||
&& types.isSameType(res.get(0), method.getParameters().get(0).asType());
|
||||
}
|
||||
},
|
||||
MAP_ADD {
|
||||
@Override
|
||||
public boolean is(ExecutableElement method, String fieldName, Types types, TypeMirror fieldType) {
|
||||
String fieldNameSingular = fieldName.endsWith("s") ? fieldName.substring(0, fieldName.length() - 1) : fieldName;
|
||||
String methodName = "set" + fieldNameSingular;
|
||||
List<TypeMirror> res = getGenericsDeclaration(fieldType);
|
||||
return Objects.equals(methodName, method.getSimpleName().toString())
|
||||
&& res.size() == 2
|
||||
&& types.isSameType(res.get(0), method.getParameters().get(0).asType())
|
||||
&& types.isSameType(res.get(1), method.getParameters().get(1).asType());
|
||||
}
|
||||
},
|
||||
MAP_GET {
|
||||
@Override
|
||||
public boolean is(ExecutableElement method, String fieldName, Types types, TypeMirror fieldType) {
|
||||
String fieldNameSingular = fieldName.endsWith("s") ? fieldName.substring(0, fieldName.length() - 1) : fieldName;
|
||||
String methodName = "get" + fieldNameSingular;
|
||||
List<TypeMirror> res = getGenericsDeclaration(fieldType);
|
||||
return Objects.equals(methodName, method.getSimpleName().toString())
|
||||
&& res.size() == 2
|
||||
&& types.isSameType(res.get(0), method.getParameters().get(0).asType());
|
||||
}
|
||||
},
|
||||
UNKNOWN /* Must be the last */ {
|
||||
@Override
|
||||
public boolean is(ExecutableElement method, String fieldName, Types types, TypeMirror fieldType) {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
;
|
||||
|
||||
public abstract boolean is(ExecutableElement method, String fieldName, Types types, TypeMirror fieldType);
|
||||
|
||||
public static Optional<ExecutableElement> getMethod(FieldAccessorType type,
|
||||
HashSet<ExecutableElement> methods, String fieldName, Types types, TypeMirror fieldType) {
|
||||
return methods.stream().filter(ee -> type.is(ee, fieldName, types, fieldType)).findAny();
|
||||
}
|
||||
|
||||
public static FieldAccessorType determineType(ExecutableElement method, String fieldName, Types types, TypeMirror fieldType) {
|
||||
for (FieldAccessorType fat : values()) {
|
||||
if (fat.is(method, fieldName, types, fieldType)) {
|
||||
return fat;
|
||||
}
|
||||
}
|
||||
return UNKNOWN;
|
||||
}
|
||||
}
|
|
@ -17,19 +17,15 @@
|
|||
package org.keycloak.models.map.processor;
|
||||
|
||||
import org.keycloak.models.map.annotations.GenerateEntityImplementations;
|
||||
import org.keycloak.models.map.annotations.GenerateEnumMapFieldType;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
@ -44,13 +40,21 @@ import javax.lang.model.element.ExecutableElement;
|
|||
import javax.lang.model.element.Modifier;
|
||||
import javax.lang.model.element.Name;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.element.VariableElement;
|
||||
import javax.lang.model.type.NoType;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
import javax.lang.model.util.Elements;
|
||||
import javax.lang.model.util.Types;
|
||||
import javax.tools.Diagnostic.Kind;
|
||||
import javax.tools.JavaFileObject;
|
||||
import static org.keycloak.models.map.processor.FieldAccessorType.*;
|
||||
import static org.keycloak.models.map.processor.Util.getGenericsDeclaration;
|
||||
import static org.keycloak.models.map.processor.Util.isSetType;
|
||||
import static org.keycloak.models.map.processor.Util.methodParameters;
|
||||
import java.util.Comparator;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.Optional;
|
||||
import javax.lang.model.element.VariableElement;
|
||||
import javax.lang.model.type.TypeKind;
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -60,8 +64,24 @@ import javax.tools.JavaFileObject;
|
|||
@SupportedSourceVersion(SourceVersion.RELEASE_8)
|
||||
public class GenerateEntityImplementationsProcessor extends AbstractProcessor {
|
||||
|
||||
private static interface Generator {
|
||||
void generate(TypeElement e, Map<String, HashSet<ExecutableElement>> methodsPerAttribute) throws IOException;
|
||||
}
|
||||
|
||||
private Elements elements;
|
||||
private Types types;
|
||||
|
||||
private final Generator[] generators = new Generator[] {
|
||||
new DelegateGenerator(),
|
||||
new FieldsGenerator(),
|
||||
new ImplGenerator()
|
||||
};
|
||||
|
||||
@Override
|
||||
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
|
||||
elements = processingEnv.getElementUtils();
|
||||
types = processingEnv.getTypeUtils();
|
||||
|
||||
for (TypeElement annotation : annotations) {
|
||||
Set<? extends Element> annotatedElements = roundEnv.getElementsAnnotatedWith(annotation);
|
||||
annotatedElements.stream()
|
||||
|
@ -74,7 +94,7 @@ public class GenerateEntityImplementationsProcessor extends AbstractProcessor {
|
|||
|
||||
private void processTypeElement(TypeElement e) {
|
||||
if (e.getKind() != ElementKind.INTERFACE) {
|
||||
processingEnv.getMessager().printMessage(Kind.ERROR, "Annotation @GenerateEntityImplementations is only applicable to interface", e);
|
||||
processingEnv.getMessager().printMessage(Kind.ERROR, "Annotation @GenerateEntityImplementations is only applicable to an interface", e);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -94,10 +114,12 @@ public class GenerateEntityImplementationsProcessor extends AbstractProcessor {
|
|||
methodsPerAttribute.get(key + "s").addAll(removed);
|
||||
});
|
||||
|
||||
for (Generator generator : this.generators) {
|
||||
try {
|
||||
generateImpl(e, methodsPerAttribute);
|
||||
} catch (IOException ex) {
|
||||
processingEnv.getMessager().printMessage(Kind.ERROR, "Could not generate implementation for class", e);
|
||||
generator.generate(e, methodsPerAttribute);
|
||||
} catch (Exception ex) {
|
||||
processingEnv.getMessager().printMessage(Kind.ERROR, "Could not generate implementation for class: " + ex, e);
|
||||
}
|
||||
}
|
||||
|
||||
// methodsPerAttribute.entrySet().stream()
|
||||
|
@ -130,83 +152,6 @@ public class GenerateEntityImplementationsProcessor extends AbstractProcessor {
|
|||
return null;
|
||||
}
|
||||
|
||||
private void generateImpl(TypeElement e, Map<String, HashSet<ExecutableElement>> methodsPerAttribute) throws IOException {
|
||||
GenerateEntityImplementations an = e.getAnnotation(GenerateEntityImplementations.class);
|
||||
Elements elements = processingEnv.getElementUtils();
|
||||
TypeElement parentTypeElement = elements.getTypeElement(an.inherits().isEmpty() ? "void" : an.inherits());
|
||||
final List<? extends Element> allMembers = elements.getAllMembers(parentTypeElement);
|
||||
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 mapImplClassName = className + "Impl";
|
||||
String mapSimpleClassName = simpleClassName + "Impl";
|
||||
|
||||
JavaFileObject enumFile = processingEnv.getFiler().createSourceFile(mapImplClassName);
|
||||
try (PrintWriter pw = new PrintWriter(enumFile.openWriter()) {
|
||||
@Override
|
||||
public void println(String x) {
|
||||
super.println(x == null ? x : x.replaceAll("java.lang.", ""));
|
||||
}
|
||||
}) {
|
||||
if (packageName != null) {
|
||||
pw.println("package " + packageName + ";");
|
||||
}
|
||||
|
||||
pw.println("import java.util.EnumMap;");
|
||||
pw.println("import java.util.Objects;");
|
||||
pw.println("// DO NOT CHANGE THIS CLASS, IT IS GENERATED AUTOMATICALLY BY " + GenerateEntityImplementationsProcessor.class.getSimpleName());
|
||||
pw.println("public class " + mapSimpleClassName + (an.inherits().isEmpty() ? "" : " extends " + an.inherits()) + " implements " + className + " {");
|
||||
pw.println(" public enum Field {");
|
||||
methodsPerAttribute.keySet().stream()
|
||||
.sorted()
|
||||
.map(GenerateEntityImplementationsProcessor::toEnumConstant)
|
||||
.forEach(key -> pw.println(" " + key + ","));
|
||||
pw.println(" }");
|
||||
pw.println(" private final EnumMap<Field, Object> values = new EnumMap<>(Field.class);");
|
||||
pw.println(" protected Object get(Field field) { return values.get(field); }");
|
||||
pw.println(" protected Object set(Field field, Object p0) { return values.put(field, p0); }");
|
||||
|
||||
// Constructors
|
||||
allMembers.stream()
|
||||
.filter(ExecutableElement.class::isInstance)
|
||||
.map(ExecutableElement.class::cast)
|
||||
.filter((ExecutableElement ee) -> ee.getKind() == ElementKind.CONSTRUCTOR)
|
||||
.forEach((ExecutableElement ee) -> pw.println(" public " + mapSimpleClassName + "(" + methodParameters(ee.getParameters()) + ") { super(" + ee.getParameters() + "); }"));
|
||||
|
||||
for (Entry<String, HashSet<ExecutableElement>> me : methodsPerAttribute.entrySet()) {
|
||||
String enumConstant = toEnumConstant(me.getKey());
|
||||
HashSet<ExecutableElement> methods = me.getValue();
|
||||
TypeMirror fieldType = determineFieldType(me.getKey(), methods);
|
||||
if (fieldType == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (ExecutableElement method : methods) {
|
||||
if (! printMethodBody(pw, method, me.getKey(), enumConstant, fieldType)) {
|
||||
List<ExecutableElement> parentMethods = allMembers.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))
|
||||
.collect(Collectors.toList());
|
||||
if (! parentMethods.isEmpty()) {
|
||||
processingEnv.getMessager().printMessage(Kind.OTHER, "Method " + method + " is declared in a parent class.");
|
||||
} else {
|
||||
processingEnv.getMessager().printMessage(Kind.WARNING, "Could not determine desired semantics of method from its signature", method);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pw.println("}");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
protected static String toEnumConstant(String key) {
|
||||
return key.replaceAll("([a-z])([A-Z])", "$1_$2").toUpperCase();
|
||||
}
|
||||
|
@ -220,70 +165,261 @@ public class GenerateEntityImplementationsProcessor extends AbstractProcessor {
|
|||
}
|
||||
}
|
||||
if (res == null) {
|
||||
processingEnv.getMessager().printMessage(Kind.ERROR, "Could not determine return type for field " + fieldName, methods.iterator().next());
|
||||
processingEnv.getMessager().printMessage(Kind.ERROR, "Could not determine return type for the field " + fieldName, methods.iterator().next());
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
private boolean printMethodBody(PrintWriter pw, ExecutableElement method, String fieldName, String enumConstant, TypeMirror fieldType) {
|
||||
Pattern getter = Pattern.compile("(get|is)" + Pattern.quote(fieldName));
|
||||
Types types = processingEnv.getTypeUtils();
|
||||
final String methodName = method.getSimpleName().toString();
|
||||
String setter = "set" + fieldName;
|
||||
private boolean isImmutableFinalType(TypeMirror fieldType) {
|
||||
return isPrimitiveType(fieldType) || isBoxedPrimitiveType(fieldType) || Objects.equals("java.lang.String", fieldType.toString());
|
||||
}
|
||||
|
||||
private boolean isKnownCollectionOfImmutableFinalTypes(TypeMirror fieldType) {
|
||||
TypeElement typeElement = elements.getTypeElement(types.erasure(fieldType).toString());
|
||||
switch (typeElement.getQualifiedName().toString()) {
|
||||
case "java.util.List":
|
||||
case "java.util.Map":
|
||||
case "java.util.Set":
|
||||
case "java.util.Collection":
|
||||
case "org.keycloak.common.util.MultivaluedHashMap":
|
||||
List<TypeMirror> res = getGenericsDeclaration(fieldType);
|
||||
return res.stream().allMatch(tm -> isImmutableFinalType(tm) || isKnownCollectionOfImmutableFinalTypes(tm));
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isPrimitiveType(TypeMirror fieldType) {
|
||||
try {
|
||||
types.getPrimitiveType(fieldType.getKind());
|
||||
return true;
|
||||
} catch (IllegalArgumentException ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isBoxedPrimitiveType(TypeMirror fieldType) {
|
||||
try {
|
||||
types.unboxedType(fieldType);
|
||||
return true;
|
||||
} catch (IllegalArgumentException ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private String interfaceToImplementation(TypeElement typeElement, String parameter) {
|
||||
Name parameterTypeQN = typeElement.getQualifiedName();
|
||||
switch (parameterTypeQN.toString()) {
|
||||
case "java.util.List":
|
||||
case "java.util.Collection":
|
||||
return "new java.util.LinkedList<>(" + parameter + ")";
|
||||
case "java.util.Map":
|
||||
return "new java.util.HashMap<>(" + parameter + ")";
|
||||
case "java.util.Set":
|
||||
return "new java.util.HashSet<>(" + parameter + ")";
|
||||
default:
|
||||
processingEnv.getMessager().printMessage(Kind.ERROR, "Could not determine implementation for type " + typeElement, typeElement);
|
||||
return "TODO()";
|
||||
}
|
||||
}
|
||||
|
||||
private class FieldsGenerator implements Generator {
|
||||
@Override
|
||||
public void generate(TypeElement e, Map<String, HashSet<ExecutableElement>> methodsPerAttribute) throws IOException {
|
||||
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 mapFieldsClassName = className + "Fields";
|
||||
String mapSimpleFieldsClassName = simpleClassName + "Fields";
|
||||
|
||||
JavaFileObject file = processingEnv.getFiler().createSourceFile(mapFieldsClassName);
|
||||
try (PrintWriter pw = new PrintWriterNoJavaLang(file.openWriter())) {
|
||||
if (packageName != null) {
|
||||
pw.println("package " + packageName + ";");
|
||||
}
|
||||
|
||||
pw.println("public enum " + mapSimpleFieldsClassName + " {");
|
||||
methodsPerAttribute.keySet().stream()
|
||||
.sorted()
|
||||
.map(GenerateEntityImplementationsProcessor::toEnumConstant)
|
||||
.forEach(key -> pw.println(" " + key + ","));
|
||||
pw.println("}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ImplGenerator implements Generator {
|
||||
|
||||
@Override
|
||||
public void generate(TypeElement e, Map<String, HashSet<ExecutableElement>> methodsPerAttribute) throws IOException {
|
||||
GenerateEntityImplementations an = e.getAnnotation(GenerateEntityImplementations.class);
|
||||
TypeElement parentTypeElement = elements.getTypeElement((an.inherits() == null || an.inherits().isEmpty()) ? "void" : an.inherits());
|
||||
if (parentTypeElement == null) {
|
||||
return;
|
||||
}
|
||||
final List<? extends Element> allMembers = elements.getAllMembers(parentTypeElement);
|
||||
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 mapImplClassName = className + "Impl";
|
||||
String mapSimpleClassName = simpleClassName + "Impl";
|
||||
boolean hasId = methodsPerAttribute.containsKey("Id") || allMembers.stream().anyMatch(el -> "getId".equals(el.getSimpleName().toString()));
|
||||
|
||||
JavaFileObject file = processingEnv.getFiler().createSourceFile(mapImplClassName);
|
||||
try (PrintWriter pw = new PrintWriterNoJavaLang(file.openWriter())) {
|
||||
if (packageName != null) {
|
||||
pw.println("package " + packageName + ";");
|
||||
}
|
||||
|
||||
pw.println("import java.util.Objects;");
|
||||
pw.println("// DO NOT CHANGE THIS CLASS, IT IS GENERATED AUTOMATICALLY BY " + GenerateEntityImplementationsProcessor.class.getSimpleName());
|
||||
pw.println("public class " + mapSimpleClassName + (an.inherits().isEmpty() ? "" : " extends " + an.inherits()) + " implements " + className + " {");
|
||||
// pw.println(" private final EnumMap<Field, Object> values = new EnumMap<>(Field.class);");
|
||||
// pw.println(" protected Object get(Field field) { return values.get(field); }");
|
||||
// pw.println(" protected Object set(Field field, Object p0) { return values.put(field, p0); }");
|
||||
pw.println(" @Override public boolean equals(Object o) {");
|
||||
pw.println(" if (o == this) return true; ");
|
||||
pw.println(" if (! (o instanceof " + mapSimpleClassName + ")) return false; ");
|
||||
pw.println(" " + mapSimpleClassName + " other = (" + mapSimpleClassName + ") o; ");
|
||||
pw.println(" return "
|
||||
+ methodsPerAttribute.entrySet().stream()
|
||||
.map(me -> FieldAccessorType.getMethod(GETTER, me.getValue(), me.getKey(), types, determineFieldType(me.getKey(), me.getValue())))
|
||||
.filter(Optional::isPresent)
|
||||
.map(Optional::get)
|
||||
.map(ExecutableElement::getSimpleName)
|
||||
.map(Name::toString)
|
||||
.sorted()
|
||||
.map(v -> "Objects.equals(" + v + "(), other." + v + "())")
|
||||
.collect(Collectors.joining("\n && "))
|
||||
+ ";");
|
||||
pw.println(" }");
|
||||
pw.println(" @Override public int hashCode() {");
|
||||
pw.println(" return "
|
||||
+ (hasId
|
||||
? "(getId() == null ? super.hashCode() : getId().hashCode())"
|
||||
: "Objects.hash("
|
||||
+ methodsPerAttribute.entrySet().stream() // generate hashcode from simple-typed properties (no collections etc.)
|
||||
.map(me -> FieldAccessorType.getMethod(GETTER, me.getValue(), me.getKey(), types, determineFieldType(me.getKey(), me.getValue())))
|
||||
.filter(Optional::isPresent)
|
||||
.map(Optional::get)
|
||||
.filter(ee -> isImmutableFinalType(ee.getReturnType()))
|
||||
.map(ExecutableElement::getSimpleName)
|
||||
.map(Name::toString)
|
||||
.sorted()
|
||||
.map(v -> v + "()")
|
||||
.collect(Collectors.joining(",\n "))
|
||||
+ ")")
|
||||
+ ";");
|
||||
pw.println(" }");
|
||||
pw.println(" @Override public String toString() {");
|
||||
pw.println(" return String.format(\"%s@%08x\", " + (hasId ? "getId()" : "\"" + mapSimpleClassName + "\"" ) + ", System.identityHashCode(this));");
|
||||
pw.println(" }");
|
||||
|
||||
// Constructors
|
||||
allMembers.stream()
|
||||
.filter(ExecutableElement.class::isInstance)
|
||||
.map(ExecutableElement.class::cast)
|
||||
.filter((ExecutableElement ee) -> ee.getKind() == ElementKind.CONSTRUCTOR)
|
||||
.forEach((ExecutableElement ee) -> pw.println(" "
|
||||
+ ee.getModifiers().stream().map(Object::toString).collect(Collectors.joining(" "))
|
||||
+ " " + mapSimpleClassName + "(" + methodParameters(ee.getParameters()) + ") { super(" + ee.getParameters() + "); }"));
|
||||
|
||||
methodsPerAttribute.entrySet().stream().sorted(Comparator.comparing(Map.Entry::getKey)).forEach(me -> {
|
||||
HashSet<ExecutableElement> methods = me.getValue();
|
||||
TypeMirror fieldType = determineFieldType(me.getKey(), methods);
|
||||
if (fieldType == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
pw.println("");
|
||||
pw.println(" private " + fieldType + " f" + me.getKey() + ";");
|
||||
|
||||
for (ExecutableElement method : methods) {
|
||||
FieldAccessorType fat = FieldAccessorType.determineType(method, me.getKey(), types, fieldType);
|
||||
Optional<ExecutableElement> parentMethod = allMembers.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();
|
||||
|
||||
if (parentMethod.isPresent()) {
|
||||
processingEnv.getMessager().printMessage(Kind.OTHER, "Method " + method + " is declared in a parent class.", method);
|
||||
} else if (fat != FieldAccessorType.UNKNOWN && ! printMethodBody(pw, fat, method, "f" + me.getKey(), fieldType)) {
|
||||
processingEnv.getMessager().printMessage(Kind.WARNING, "Could not determine desired semantics of method from its signature", method);
|
||||
}
|
||||
}
|
||||
});
|
||||
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();
|
||||
String fieldNameSingular = fieldName.endsWith("s") ? fieldName.substring(0, fieldName.length() - 1) : fieldName;
|
||||
String getFromMap = "get" + fieldNameSingular;
|
||||
String addToCollection = "add" + fieldNameSingular;
|
||||
String updateMap = "set" + fieldNameSingular;
|
||||
String removeFromCollection = "remove" + fieldNameSingular;
|
||||
Elements elements = processingEnv.getElementUtils();
|
||||
TypeElement typeElement = elements.getTypeElement(types.erasure(fieldType).toString());
|
||||
|
||||
if (getter.matcher(methodName).matches() && method.getParameters().isEmpty() && types.isSameType(fieldType, method.getReturnType())) {
|
||||
pw.println(" @Override public " + method.getReturnType() + " " + method + " {");
|
||||
pw.println(" return (" + fieldType + ") get(Field." + enumConstant + ");");
|
||||
switch (accessorType) {
|
||||
case GETTER:
|
||||
pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method + " {");
|
||||
pw.println(" return " + fieldName + ";");
|
||||
pw.println(" }");
|
||||
return true;
|
||||
} else if (setter.equals(methodName) && types.isSameType(firstParameterType, fieldType)) {
|
||||
pw.println(" @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(" + firstParameterType + " p0) {");
|
||||
pw.println(" Object o = set(Field." + enumConstant + ", p0);");
|
||||
pw.println(" updated |= ! Objects.equals(o, p0);");
|
||||
case SETTER:
|
||||
pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(" + firstParameterType + " p0) {");
|
||||
if (! isImmutableFinalType(fieldType)) {
|
||||
pw.println(" p0 = " + deepClone(fieldType, "p0") + ";");
|
||||
}
|
||||
pw.println(" updated |= ! Objects.equals(" + fieldName + ", p0);");
|
||||
pw.println(" " + fieldName + " = p0;");
|
||||
pw.println(" }");
|
||||
return true;
|
||||
} else if (addToCollection.equals(methodName) && method.getParameters().size() == 1) {
|
||||
pw.println(" @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(" + firstParameterType + " p0) {");
|
||||
pw.println(" " + fieldType + " o = (" + fieldType + ") get(Field." + enumConstant + ");");
|
||||
pw.println(" if (o == null) { o = " + interfaceToImplementation(typeElement) + "; set(Field." + enumConstant + ", o); }");
|
||||
case COLLECTION_ADD:
|
||||
pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(" + firstParameterType + " p0) {");
|
||||
pw.println(" if (" + fieldName + " == null) { " + fieldName + " = " + interfaceToImplementation(typeElement, "") + "; }");
|
||||
if (! isImmutableFinalType(firstParameterType)) {
|
||||
pw.println(" p0 = " + deepClone(fieldType, "p0") + ";");
|
||||
}
|
||||
if (isSetType(typeElement)) {
|
||||
pw.println(" updated |= o.add(p0);");
|
||||
pw.println(" updated |= " + fieldName + ".add(p0);");
|
||||
} else {
|
||||
pw.println(" o.add(p0);");
|
||||
pw.println(" " + fieldName + ".add(p0);");
|
||||
pw.println(" updated = true;");
|
||||
}
|
||||
pw.println(" }");
|
||||
return true;
|
||||
} else if (removeFromCollection.equals(methodName) && method.getParameters().size() == 1) {
|
||||
pw.println(" @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(" + firstParameterType + " p0) {");
|
||||
pw.println(" " + fieldType + " o = (" + fieldType + ") get(Field." + enumConstant + ");");
|
||||
pw.println(" if (o == null) { return; }");
|
||||
pw.println(" boolean removed = o.remove(p0)" + ("java.util.Map".equals(typeElement.getQualifiedName().toString()) ? " != null" : "") + ";");
|
||||
case COLLECTION_DELETE:
|
||||
pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(" + firstParameterType + " p0) {");
|
||||
pw.println(" if (" + fieldName + " == null) { return; }");
|
||||
pw.println(" boolean removed = " + fieldName + ".remove(p0)" + ("java.util.Map".equals(typeElement.getQualifiedName().toString()) ? " != null" : "") + ";");
|
||||
pw.println(" updated |= removed;");
|
||||
pw.println(" }");
|
||||
return true;
|
||||
} else if (updateMap.equals(methodName) && method.getParameters().size() == 2) {
|
||||
pw.println(" @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(" + firstParameterType + " p0, " + method.getParameters().get(1).asType() + " p1) {");
|
||||
pw.println(" " + fieldType + " o = (" + fieldType + ") get(Field." + enumConstant + ");");
|
||||
pw.println(" if (o == null) { o = " + interfaceToImplementation(typeElement) + "; set(Field." + enumConstant + ", o); }");
|
||||
pw.println(" Object v = o.put(p0, p1);");
|
||||
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(" if (" + fieldName + " == null) { " + fieldName + " = " + interfaceToImplementation(typeElement, "") + "; }");
|
||||
if (! isImmutableFinalType(secondParameterType)) {
|
||||
pw.println(" p1 = " + deepClone(secondParameterType, "p1") + ";");
|
||||
}
|
||||
pw.println(" Object v = " + fieldName + ".put(p0, p1);");
|
||||
pw.println(" updated |= ! Objects.equals(v, p1);");
|
||||
pw.println(" }");
|
||||
return true;
|
||||
} else if (getFromMap.equals(methodName) && method.getParameters().size() == 1) {
|
||||
pw.println(" @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(" + firstParameterType + " p0) {");
|
||||
pw.println(" " + fieldType + " o = (" + fieldType + ") get(Field." + enumConstant + ");");
|
||||
pw.println(" return o == null ? null : o.get(p0);");
|
||||
case MAP_GET:
|
||||
pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(" + firstParameterType + " p0) {");
|
||||
pw.println(" return " + fieldName + " == null ? null : " + fieldName + ".get(p0);");
|
||||
pw.println(" }");
|
||||
return true;
|
||||
}
|
||||
|
@ -291,38 +427,81 @@ public class GenerateEntityImplementationsProcessor extends AbstractProcessor {
|
|||
return false;
|
||||
}
|
||||
|
||||
private String interfaceToImplementation(TypeElement typeElement) {
|
||||
GenerateEnumMapFieldType an = typeElement.getAnnotation(GenerateEnumMapFieldType.class);
|
||||
if (an != null) {
|
||||
return "new " + an.value().getCanonicalName() + "<>()";
|
||||
private String deepClone(TypeMirror fieldType, String parameterName) {
|
||||
if (isKnownCollectionOfImmutableFinalTypes(fieldType)) {
|
||||
TypeElement typeElement = elements.getTypeElement(types.erasure(fieldType).toString());
|
||||
return parameterName + " == null ? null : " + interfaceToImplementation(typeElement, parameterName);
|
||||
} else {
|
||||
return "deepClone(" + parameterName + ")";
|
||||
}
|
||||
|
||||
Name parameterTypeQN = typeElement.getQualifiedName();
|
||||
switch (parameterTypeQN.toString()) {
|
||||
case "java.util.List":
|
||||
return "new java.util.LinkedList<>()";
|
||||
case "java.util.Map":
|
||||
return "new java.util.HashMap<>()";
|
||||
case "java.util.Set":
|
||||
return "new java.util.HashSet<>()";
|
||||
case "java.util.Collection":
|
||||
return "new java.util.LinkedList<>()";
|
||||
default:
|
||||
processingEnv.getMessager().printMessage(Kind.ERROR, "Could not determine implementation for type " + typeElement, typeElement);
|
||||
return "TODO()";
|
||||
}
|
||||
}
|
||||
|
||||
private String methodParameters(List<? extends VariableElement> parameters) {
|
||||
return parameters.stream()
|
||||
.map(p -> p.asType() + " " + p.getSimpleName())
|
||||
.collect(Collectors.joining(", "));
|
||||
private class DelegateGenerator implements Generator {
|
||||
@Override
|
||||
public void generate(TypeElement e, Map<String, HashSet<ExecutableElement>> methodsPerAttribute) throws IOException {
|
||||
String className = e.getQualifiedName().toString();
|
||||
String packageName = null;
|
||||
int lastDot = className.lastIndexOf('.');
|
||||
if (lastDot > 0) {
|
||||
packageName = className.substring(0, lastDot);
|
||||
}
|
||||
|
||||
private static final HashSet<String> SET_TYPES = new HashSet<>(Arrays.asList(Set.class.getCanonicalName(), TreeSet.class.getCanonicalName(), HashSet.class.getCanonicalName(), LinkedHashSet.class.getCanonicalName()));
|
||||
String simpleClassName = className.substring(lastDot + 1);
|
||||
String mapClassName = className + "Delegate";
|
||||
String mapSimpleClassName = simpleClassName + "Delegate";
|
||||
String fieldsClassName = className + "Fields";
|
||||
|
||||
private boolean isSetType(TypeElement typeElement) {
|
||||
Name name = typeElement.getQualifiedName();
|
||||
return SET_TYPES.contains(name.toString());
|
||||
GenerateEntityImplementations an = e.getAnnotation(GenerateEntityImplementations.class);
|
||||
TypeElement parentTypeElement = elements.getTypeElement((an.inherits() == null || an.inherits().isEmpty()) ? "void" : an.inherits());
|
||||
if (parentTypeElement == null) {
|
||||
return;
|
||||
}
|
||||
final List<? extends Element> allMembers = elements.getAllMembers(e);
|
||||
|
||||
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 + " implements " + className + " {");
|
||||
pw.println(" private final org.keycloak.models.map.common.delegate.DelegateProvider<" + mapSimpleClassName + "> delegateProvider;");
|
||||
pw.println(" public " + mapSimpleClassName + "(org.keycloak.models.map.common.delegate.DelegateProvider<" + mapSimpleClassName + "> delegateProvider) {");
|
||||
pw.println(" this.delegateProvider = delegateProvider;");
|
||||
pw.println(" }");
|
||||
|
||||
allMembers.stream()
|
||||
.filter(m -> m.getKind() == ElementKind.METHOD)
|
||||
.filter(ExecutableElement.class::isInstance)
|
||||
.map(ExecutableElement.class::cast)
|
||||
.filter(ee -> ee.getModifiers().contains(Modifier.ABSTRACT))
|
||||
.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()) + ") {");
|
||||
String field = m2field.get(ee);
|
||||
field = field == null ? "null" : fieldsClassName + "." + toEnumConstant(field);
|
||||
if (ee.getReturnType().getKind() == TypeKind.BOOLEAN && "isUpdated".equals(ee.getSimpleName().toString())) {
|
||||
pw.println(" return delegateProvider.isUpdated();");
|
||||
} else if (ee.getReturnType().getKind() == TypeKind.VOID) { // write operation
|
||||
pw.println(" delegateProvider.getDelegate(false, " + field + ")." + ee.getSimpleName() + "("
|
||||
+ ee.getParameters().stream().map(VariableElement::getSimpleName).collect(Collectors.joining(", "))
|
||||
+ ");");
|
||||
} else {
|
||||
pw.println(" return delegateProvider.getDelegate(true, " + field + ")." + ee.getSimpleName() + "("
|
||||
+ ee.getParameters().stream().map(VariableElement::getSimpleName).collect(Collectors.joining(", "))
|
||||
+ ");");
|
||||
}
|
||||
pw.println(" }");
|
||||
});
|
||||
|
||||
pw.println("}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,23 +14,24 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.models.map.client;
|
||||
package org.keycloak.models.map.processor;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.Writer;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author hmlnarik
|
||||
*/
|
||||
public class MapClientEntityDelegate extends MapClientEntityLazyDelegate {
|
||||
public class PrintWriterNoJavaLang extends PrintWriter {
|
||||
|
||||
private final MapClientEntity delegate;
|
||||
|
||||
public MapClientEntityDelegate(MapClientEntity delegate) {
|
||||
super(null);
|
||||
this.delegate = delegate;
|
||||
public PrintWriterNoJavaLang(Writer out) {
|
||||
super(out);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MapClientEntity getWriteDelegate() {
|
||||
return delegate;
|
||||
public void println(String x) {
|
||||
super.println(x == null ? x : x.replaceAll("java.lang.", ""));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* 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.processor;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.lang.model.element.Name;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.element.VariableElement;
|
||||
import javax.lang.model.type.DeclaredType;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
import javax.lang.model.util.SimpleTypeVisitor8;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author hmlnarik
|
||||
*/
|
||||
public class Util {
|
||||
|
||||
private static final HashSet<String> SET_TYPES = new HashSet<>(Arrays.asList(Set.class.getCanonicalName(), TreeSet.class.getCanonicalName(), HashSet.class.getCanonicalName(), LinkedHashSet.class.getCanonicalName()));
|
||||
|
||||
public static List<TypeMirror> getGenericsDeclaration(TypeMirror fieldType) {
|
||||
List<TypeMirror> res = new LinkedList<>();
|
||||
|
||||
fieldType.accept(new SimpleTypeVisitor8<Void, List<TypeMirror>>() {
|
||||
@Override
|
||||
public Void visitDeclared(DeclaredType t, List<TypeMirror> p) {
|
||||
List<? extends TypeMirror> typeArguments = t.getTypeArguments();
|
||||
res.addAll(typeArguments);
|
||||
return null;
|
||||
}
|
||||
}, res);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
public static String methodParameters(List<? extends VariableElement> parameters) {
|
||||
return parameters.stream()
|
||||
.map(p -> p.asType() + " " + p.getSimpleName())
|
||||
.collect(Collectors.joining(", "));
|
||||
}
|
||||
|
||||
public static boolean isSetType(TypeElement typeElement) {
|
||||
Name name = typeElement.getQualifiedName();
|
||||
return SET_TYPES.contains(name.toString());
|
||||
}
|
||||
|
||||
}
|
|
@ -26,6 +26,7 @@ import java.util.Objects;
|
|||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
import org.keycloak.models.map.annotations.GenerateEntityImplementations;
|
||||
import org.keycloak.models.map.common.Serialization;
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -34,11 +35,10 @@ import org.keycloak.models.map.annotations.GenerateEntityImplementations;
|
|||
@GenerateEntityImplementations(inherits="org.keycloak.models.map.client.MapClientEntity.AbstractClientEntity")
|
||||
public interface MapClientEntity extends AbstractEntity, UpdatableEntity {
|
||||
|
||||
public abstract class AbstractClientEntity implements MapClientEntity {
|
||||
public abstract class AbstractClientEntity extends UpdatableEntity.Impl implements MapClientEntity {
|
||||
/**
|
||||
* Flag signalizing that any of the setters has been meaningfully used.
|
||||
*/
|
||||
protected boolean updated;
|
||||
private String id;
|
||||
|
||||
protected AbstractClientEntity() {}
|
||||
|
@ -59,9 +59,8 @@ public interface MapClientEntity extends AbstractEntity, UpdatableEntity {
|
|||
this.updated |= id != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUpdated() {
|
||||
return this.updated;
|
||||
public <V> V deepClone(V obj) {
|
||||
return Serialization.from(obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -97,7 +96,7 @@ public interface MapClientEntity extends AbstractEntity, UpdatableEntity {
|
|||
void removeWebOrigin(String webOrigin);
|
||||
void setWebOrigins(Set<String> webOrigins);
|
||||
|
||||
default List<String> getAttribute(String name) { return getAttributes().get(name); }
|
||||
default List<String> getAttribute(String name) { return getAttributes() == null ? null : getAttributes().get(name); }
|
||||
Map<String, List<String>> getAttributes();
|
||||
void removeAttribute(String name);
|
||||
void setAttribute(String name, List<String> values);
|
||||
|
|
|
@ -1,480 +0,0 @@
|
|||
/*
|
||||
* 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.client;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicMarkableReference;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author hmlnarik
|
||||
*/
|
||||
public class MapClientEntityLazyDelegate implements MapClientEntity {
|
||||
|
||||
private final Supplier<MapClientEntity> delegateSupplier;
|
||||
|
||||
private final AtomicMarkableReference<MapClientEntity> delegate = new AtomicMarkableReference<>(null, false);
|
||||
|
||||
public MapClientEntityLazyDelegate(Supplier<MapClientEntity> delegateSupplier) {
|
||||
this.delegateSupplier = delegateSupplier;
|
||||
}
|
||||
|
||||
protected MapClientEntity getWriteDelegate() {
|
||||
if (! isWriteDelegateInitialized()) {
|
||||
delegate.compareAndSet(null, delegateSupplier == null ? null : delegateSupplier.get(), false, true);
|
||||
}
|
||||
MapClientEntity ref = delegate.getReference();
|
||||
if (ref == null) {
|
||||
throw new IllegalStateException("Invalid delegate obtained");
|
||||
}
|
||||
return ref;
|
||||
}
|
||||
|
||||
protected boolean isWriteDelegateInitialized() {
|
||||
return delegate.isMarked();
|
||||
}
|
||||
|
||||
protected MapClientEntity getReadDelegate() {
|
||||
return getWriteDelegate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClientScope(String id, Boolean defaultScope) {
|
||||
getWriteDelegate().setClientScope(id, defaultScope);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addRedirectUri(String redirectUri) {
|
||||
getWriteDelegate().addRedirectUri(redirectUri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addScopeMapping(String id) {
|
||||
getWriteDelegate().addScopeMapping(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addWebOrigin(String webOrigin) {
|
||||
getWriteDelegate().addWebOrigin(webOrigin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeScopeMapping(String id) {
|
||||
getWriteDelegate().removeScopeMapping(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getAttribute(String name) {
|
||||
return getReadDelegate().getAttribute(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, List<String>> getAttributes() {
|
||||
return getReadDelegate().getAttributes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getAuthFlowBindings() {
|
||||
return getReadDelegate().getAuthFlowBindings();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAuthenticationFlowBindingOverride(String binding) {
|
||||
return getReadDelegate().getAuthenticationFlowBindingOverride(binding);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getAuthenticationFlowBindingOverrides() {
|
||||
return getReadDelegate().getAuthenticationFlowBindingOverrides();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBaseUrl() {
|
||||
return getReadDelegate().getBaseUrl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClientAuthenticatorType() {
|
||||
return getReadDelegate().getClientAuthenticatorType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClientId() {
|
||||
return getReadDelegate().getClientId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<String> getClientScopes(boolean defaultScope) {
|
||||
return getReadDelegate().getClientScopes(defaultScope);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Boolean> getClientScopes() {
|
||||
return getReadDelegate().getClientScopes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return getReadDelegate().getDescription();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getManagementUrl() {
|
||||
return getReadDelegate().getManagementUrl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return getReadDelegate().getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getNodeReRegistrationTimeout() {
|
||||
return getReadDelegate().getNodeReRegistrationTimeout();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getNotBefore() {
|
||||
return getReadDelegate().getNotBefore();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProtocol() {
|
||||
return getReadDelegate().getProtocol();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MapProtocolMapperEntity getProtocolMapper(String id) {
|
||||
return getReadDelegate().getProtocolMapper(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String,MapProtocolMapperEntity> getProtocolMappers() {
|
||||
return getReadDelegate().getProtocolMappers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRealmId() {
|
||||
return getReadDelegate().getRealmId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRealmId(String realmId) {
|
||||
getWriteDelegate().setRealmId(realmId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getRedirectUris() {
|
||||
return getReadDelegate().getRedirectUris();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRegistrationToken() {
|
||||
return getReadDelegate().getRegistrationToken();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRootUrl() {
|
||||
return getReadDelegate().getRootUrl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getScope() {
|
||||
return getReadDelegate().getScope();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getScopeMappings() {
|
||||
return getReadDelegate().getScopeMappings();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSecret() {
|
||||
return getReadDelegate().getSecret();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getWebOrigins() {
|
||||
return getReadDelegate().getWebOrigins();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean isAlwaysDisplayInConsole() {
|
||||
return getWriteDelegate().isAlwaysDisplayInConsole();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean isBearerOnly() {
|
||||
return getWriteDelegate().isBearerOnly();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean isConsentRequired() {
|
||||
return getWriteDelegate().isConsentRequired();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean isDirectAccessGrantsEnabled() {
|
||||
return getWriteDelegate().isDirectAccessGrantsEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean isEnabled() {
|
||||
return getWriteDelegate().isEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean isFrontchannelLogout() {
|
||||
return getWriteDelegate().isFrontchannelLogout();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean isFullScopeAllowed() {
|
||||
return getWriteDelegate().isFullScopeAllowed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean isImplicitFlowEnabled() {
|
||||
return getWriteDelegate().isImplicitFlowEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean isPublicClient() {
|
||||
return getWriteDelegate().isPublicClient();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean isServiceAccountsEnabled() {
|
||||
return getWriteDelegate().isServiceAccountsEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean isStandardFlowEnabled() {
|
||||
return getWriteDelegate().isStandardFlowEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean isSurrogateAuthRequired() {
|
||||
return getWriteDelegate().isSurrogateAuthRequired();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAttribute(String name) {
|
||||
getWriteDelegate().removeAttribute(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAuthenticationFlowBindingOverride(String binding) {
|
||||
getWriteDelegate().removeAuthenticationFlowBindingOverride(binding);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeClientScope(String id) {
|
||||
getWriteDelegate().removeClientScope(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeProtocolMapper(String id) {
|
||||
getWriteDelegate().removeProtocolMapper(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeRedirectUri(String redirectUri) {
|
||||
getWriteDelegate().removeRedirectUri(redirectUri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeWebOrigin(String webOrigin) {
|
||||
getWriteDelegate().removeWebOrigin(webOrigin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlwaysDisplayInConsole(Boolean alwaysDisplayInConsole) {
|
||||
getWriteDelegate().setAlwaysDisplayInConsole(alwaysDisplayInConsole);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttribute(String name, List<String> values) {
|
||||
getWriteDelegate().setAttribute(name, values);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAuthFlowBindings(Map<String, String> authFlowBindings) {
|
||||
getWriteDelegate().setAuthFlowBindings(authFlowBindings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAuthenticationFlowBindingOverride(String binding, String flowId) {
|
||||
getWriteDelegate().setAuthenticationFlowBindingOverride(binding, flowId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBaseUrl(String baseUrl) {
|
||||
getWriteDelegate().setBaseUrl(baseUrl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBearerOnly(Boolean bearerOnly) {
|
||||
getWriteDelegate().setBearerOnly(bearerOnly);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClientAuthenticatorType(String clientAuthenticatorType) {
|
||||
getWriteDelegate().setClientAuthenticatorType(clientAuthenticatorType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClientId(String clientId) {
|
||||
getWriteDelegate().setClientId(clientId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setConsentRequired(Boolean consentRequired) {
|
||||
getWriteDelegate().setConsentRequired(consentRequired);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDescription(String description) {
|
||||
getWriteDelegate().setDescription(description);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDirectAccessGrantsEnabled(Boolean directAccessGrantsEnabled) {
|
||||
getWriteDelegate().setDirectAccessGrantsEnabled(directAccessGrantsEnabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnabled(Boolean enabled) {
|
||||
getWriteDelegate().setEnabled(enabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFrontchannelLogout(Boolean frontchannelLogout) {
|
||||
getWriteDelegate().setFrontchannelLogout(frontchannelLogout);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFullScopeAllowed(Boolean fullScopeAllowed) {
|
||||
getWriteDelegate().setFullScopeAllowed(fullScopeAllowed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setImplicitFlowEnabled(Boolean implicitFlowEnabled) {
|
||||
getWriteDelegate().setImplicitFlowEnabled(implicitFlowEnabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setManagementUrl(String managementUrl) {
|
||||
getWriteDelegate().setManagementUrl(managementUrl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setName(String name) {
|
||||
getWriteDelegate().setName(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNodeReRegistrationTimeout(Integer nodeReRegistrationTimeout) {
|
||||
getWriteDelegate().setNodeReRegistrationTimeout(nodeReRegistrationTimeout);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNotBefore(Integer notBefore) {
|
||||
getWriteDelegate().setNotBefore(notBefore);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProtocol(String protocol) {
|
||||
getWriteDelegate().setProtocol(protocol);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPublicClient(Boolean publicClient) {
|
||||
getWriteDelegate().setPublicClient(publicClient);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRedirectUris(Set<String> redirectUris) {
|
||||
getWriteDelegate().setRedirectUris(redirectUris);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRegistrationToken(String registrationToken) {
|
||||
getWriteDelegate().setRegistrationToken(registrationToken);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRootUrl(String rootUrl) {
|
||||
getWriteDelegate().setRootUrl(rootUrl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScope(Set<String> scope) {
|
||||
getWriteDelegate().setScope(scope);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSecret(String secret) {
|
||||
getWriteDelegate().setSecret(secret);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setServiceAccountsEnabled(Boolean serviceAccountsEnabled) {
|
||||
getWriteDelegate().setServiceAccountsEnabled(serviceAccountsEnabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStandardFlowEnabled(Boolean standardFlowEnabled) {
|
||||
getWriteDelegate().setStandardFlowEnabled(standardFlowEnabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSurrogateAuthRequired(Boolean surrogateAuthRequired) {
|
||||
getWriteDelegate().setSurrogateAuthRequired(surrogateAuthRequired);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setWebOrigins(Set<String> webOrigins) {
|
||||
getWriteDelegate().setWebOrigins(webOrigins);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProtocolMapper(String id, MapProtocolMapperEntity mapping) {
|
||||
getWriteDelegate().setProtocolMapper(id, mapping);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return getReadDelegate().getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setId(String id) {
|
||||
getWriteDelegate().setId(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUpdated() {
|
||||
return isWriteDelegateInitialized() && getWriteDelegate().isUpdated();
|
||||
}
|
||||
|
||||
}
|
|
@ -24,20 +24,9 @@ import java.util.Map;
|
|||
*
|
||||
* @author hmlnarik
|
||||
*/
|
||||
@GenerateEntityImplementations(
|
||||
inherits = "org.keycloak.models.map.client.MapProtocolMapperEntity.AbstractProtocolMapperEntity"
|
||||
)
|
||||
@GenerateEntityImplementations
|
||||
public interface MapProtocolMapperEntity extends UpdatableEntity {
|
||||
|
||||
public abstract class AbstractProtocolMapperEntity implements MapProtocolMapperEntity {
|
||||
protected boolean updated;
|
||||
|
||||
@Override
|
||||
public boolean isUpdated() {
|
||||
return this.updated;
|
||||
}
|
||||
}
|
||||
|
||||
String getId();
|
||||
void setId(String id);
|
||||
|
||||
|
|
|
@ -52,7 +52,7 @@ public class Serialization {
|
|||
.setSerializationInclusion(JsonInclude.Include.NON_NULL)
|
||||
.setVisibility(PropertyAccessor.ALL, Visibility.NONE)
|
||||
.setVisibility(PropertyAccessor.FIELD, Visibility.ANY)
|
||||
.activateDefaultTyping(new LaissezFaireSubTypeValidator() /* TODO - see javadoc */, ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT, JsonTypeInfo.As.PROPERTY)
|
||||
.activateDefaultTyping(new LaissezFaireSubTypeValidator() /* TODO - see javadoc */, ObjectMapper.DefaultTyping.NON_CONCRETE_AND_ARRAYS, JsonTypeInfo.As.PROPERTY)
|
||||
.addMixIn(UpdatableEntity.class, IgnoreUpdatedMixIn.class)
|
||||
;
|
||||
|
||||
|
@ -71,7 +71,7 @@ public class Serialization {
|
|||
}
|
||||
|
||||
|
||||
public static <T extends AbstractEntity> T from(T orig) {
|
||||
public static <T> T from(T orig) {
|
||||
if (orig == null) {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -18,9 +18,29 @@ package org.keycloak.models.map.common;
|
|||
|
||||
public interface UpdatableEntity {
|
||||
|
||||
public static class Impl implements UpdatableEntity {
|
||||
protected boolean updated;
|
||||
|
||||
@Override
|
||||
public boolean isUpdated() {
|
||||
return this.updated;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearUpdatedFlag() {
|
||||
this.updated = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Flag signalizing that any of the setters has been meaningfully used.
|
||||
* @return
|
||||
*/
|
||||
boolean isUpdated();
|
||||
|
||||
/**
|
||||
* An optional operation clearing the updated flag. Right after using this method, the
|
||||
* {@link #isUpdated()} would return {@code false}.
|
||||
*/
|
||||
default void clearUpdatedFlag() { }
|
||||
}
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* 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.delegate;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author hmlnarik
|
||||
*/
|
||||
public interface DelegateProvider<T> {
|
||||
/**
|
||||
* Returns a delegate for and entity for an operation on a field.
|
||||
* @param isRead {@code true} when the delegate requested for a read operation, false otherwise
|
||||
* @param field Identification of the field this delegates operates on.
|
||||
* @return
|
||||
*/
|
||||
T getDelegate(boolean isRead, Object field);
|
||||
|
||||
default boolean isUpdated() { return false; }
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* 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.delegate;
|
||||
|
||||
import org.keycloak.models.map.common.UpdatableEntity;
|
||||
import java.util.concurrent.atomic.AtomicMarkableReference;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author hmlnarik
|
||||
*/
|
||||
public class LazyDelegateProvider<T extends UpdatableEntity> implements DelegateProvider {
|
||||
|
||||
private final Supplier<T> delegateSupplier;
|
||||
|
||||
private final AtomicMarkableReference<T> delegate = new AtomicMarkableReference<>(null, false);
|
||||
|
||||
public LazyDelegateProvider(Supplier<T> delegateSupplier) {
|
||||
this.delegateSupplier = delegateSupplier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T getDelegate(boolean isRead, Object field) {
|
||||
if (! isDelegateInitialized()) {
|
||||
delegate.compareAndSet(null, delegateSupplier == null ? null : delegateSupplier.get(), false, true);
|
||||
}
|
||||
T ref = delegate.getReference();
|
||||
if (ref == null) {
|
||||
throw new IllegalStateException("Invalid delegate obtained");
|
||||
}
|
||||
return ref;
|
||||
}
|
||||
|
||||
protected boolean isDelegateInitialized() {
|
||||
return delegate.isMarked();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUpdated() {
|
||||
if (isDelegateInitialized()) {
|
||||
T d = getDelegate(true, this);
|
||||
return d.isUpdated();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* 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.delegate;
|
||||
|
||||
import org.keycloak.models.map.common.UpdatableEntity;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author hmlnarik
|
||||
*/
|
||||
public class SimpleDelegateProvider<T extends UpdatableEntity> implements DelegateProvider {
|
||||
|
||||
private final T delegate;
|
||||
|
||||
public SimpleDelegateProvider(T delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T getDelegate(boolean isRead, Object field) {
|
||||
return this.delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUpdated() {
|
||||
return this.delegate.isUpdated();
|
||||
}
|
||||
}
|
|
@ -212,6 +212,7 @@ public class ConcurrentHashMapKeycloakTransaction<K, V extends AbstractEntity &
|
|||
public V create(V value) {
|
||||
String key = value.getId();
|
||||
if (key == null) {
|
||||
value = Serialization.from(value);
|
||||
K newKey = keyConvertor.yieldNewUniqueKey();
|
||||
key = keyConvertor.keyToString(newKey);
|
||||
value.setId(key);
|
||||
|
|
|
@ -61,6 +61,7 @@ public class ConcurrentHashMapStorage<K, V extends AbstractEntity & UpdatableEnt
|
|||
public V create(V value) {
|
||||
K key = keyConvertor.fromStringSafe(value.getId());
|
||||
if (key == null) {
|
||||
value = Serialization.from(value);
|
||||
key = keyConvertor.yieldNewUniqueKey();
|
||||
value.setId(keyConvertor.keyToString(key));
|
||||
}
|
||||
|
|
|
@ -71,6 +71,7 @@ import org.keycloak.provider.ProviderConfigProperty;
|
|||
import org.keycloak.sessions.RootAuthenticationSessionModel;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.keycloak.models.map.storage.QueryParameters.withCriteria;
|
||||
|
@ -272,7 +273,7 @@ public class ConcurrentHashMapStorageProviderFactory implements AmphibianProvide
|
|||
try {
|
||||
LOG.debugf("Restoring contents from %s", f.getCanonicalPath());
|
||||
Class<?> valueImplType = INTERFACE_TO_IMPL.getOrDefault(valueType, valueType);
|
||||
JavaType type = Serialization.MAPPER.getTypeFactory().constructCollectionType(List.class, valueImplType);
|
||||
JavaType type = Serialization.MAPPER.getTypeFactory().constructCollectionType(LinkedList.class, valueImplType);
|
||||
|
||||
List<V> values = Serialization.MAPPER.readValue(f, type);
|
||||
values.forEach((V mce) -> store.create(mce));
|
||||
|
|
Loading…
Reference in a new issue