KEYCLOAK-19570 Add annotation processing for HotRod clients
This commit is contained in:
parent
95614e8b40
commit
7aaa33739b
24 changed files with 1259 additions and 929 deletions
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* 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.annotations;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(ElementType.TYPE)
|
||||
public @interface GenerateHotRodEntityImplementation {
|
||||
String implementInterface();
|
||||
String inherits() default "org.keycloak.models.map.common.UpdatableEntity.Impl";
|
||||
}
|
|
@ -25,7 +25,7 @@ import java.lang.annotation.Target;
|
|||
*
|
||||
* @author hmlnarik
|
||||
*/
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Retention(RetentionPolicy.CLASS)
|
||||
@Target({ElementType.TYPE, ElementType.METHOD})
|
||||
public @interface IgnoreForEntityImplementationGenerator {
|
||||
}
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* 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.exceptions;
|
||||
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class CannotMigrateTypeException extends RuntimeException {
|
||||
private final TypeMirror toType;
|
||||
private final TypeMirror[] fromType;
|
||||
|
||||
public CannotMigrateTypeException(TypeMirror toType, TypeMirror[] fromType) {
|
||||
this.toType = toType;
|
||||
this.fromType = fromType;
|
||||
}
|
||||
|
||||
public String getFormattedMessage() {
|
||||
return "Cannot migrate [" + Arrays.stream(fromType).map(TypeMirror::toString).collect(Collectors.joining(", ")) + "] to " + toType.toString();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,253 @@
|
|||
/*
|
||||
* 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 javax.annotation.processing.AbstractProcessor;
|
||||
import javax.annotation.processing.RoundEnvironment;
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.ElementKind;
|
||||
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.type.NoType;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
import javax.lang.model.util.Elements;
|
||||
import javax.lang.model.util.Types;
|
||||
import javax.tools.Diagnostic;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.keycloak.models.map.processor.FieldAccessorType.GETTER;
|
||||
import static org.keycloak.models.map.processor.Util.getGenericsDeclaration;
|
||||
|
||||
public abstract class AbstractGenerateEntityImplementationsProcessor extends AbstractProcessor {
|
||||
|
||||
protected static final String FQN_DEEP_CLONER = "org.keycloak.models.map.common.DeepCloner";
|
||||
protected Elements elements;
|
||||
protected Types types;
|
||||
|
||||
protected static interface Generator {
|
||||
void generate(TypeElement e) throws IOException;
|
||||
}
|
||||
|
||||
@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()
|
||||
.map(TypeElement.class::cast)
|
||||
.filter(this::testAnnotationElement)
|
||||
.forEach(this::processTypeElement);
|
||||
}
|
||||
|
||||
if (!annotations.isEmpty()) {
|
||||
afterAnnotationProcessing();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean testAnnotationElement(TypeElement kind) { return true; }
|
||||
protected void afterAnnotationProcessing() {}
|
||||
protected abstract Generator[] getGenerators();
|
||||
|
||||
private void processTypeElement(TypeElement e) {
|
||||
for (GenerateEntityImplementationsProcessor.Generator generator : getGenerators()) {
|
||||
try {
|
||||
generator.generate(e);
|
||||
} catch (Exception ex) {
|
||||
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Could not generate implementation for class: " + ex, e);
|
||||
}
|
||||
}
|
||||
|
||||
// methodsPerAttribute.entrySet().stream()
|
||||
// .sorted(Comparator.comparing(Map.Entry::getKey))
|
||||
// .forEach(me -> processingEnv.getMessager().printMessage(
|
||||
// Diagnostic.Kind.NOTE,
|
||||
// "** " + me.getKey() + ": " + me.getValue().stream().map(ExecutableElement::getSimpleName).sorted(Comparator.comparing(Object::toString)).collect(Collectors.joining(", ")))
|
||||
// );
|
||||
}
|
||||
|
||||
protected Map<String, HashSet<ExecutableElement>> methodsPerAttributeMapping(TypeElement e) {
|
||||
final List<? extends Element> allMembers = elements.getAllMembers(e);
|
||||
Map<String, HashSet<ExecutableElement>> methodsPerAttribute = allMembers.stream()
|
||||
.filter(el -> el.getKind() == ElementKind.METHOD)
|
||||
.filter(el -> el.getModifiers().contains(Modifier.ABSTRACT))
|
||||
.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
|
||||
methodsPerAttribute.keySet().stream()
|
||||
.filter(key -> methodsPerAttribute.containsKey(key + "s"))
|
||||
.collect(Collectors.toSet())
|
||||
.forEach(key -> {
|
||||
HashSet<ExecutableElement> removed = methodsPerAttribute.remove(key);
|
||||
methodsPerAttribute.get(key + "s").addAll(removed);
|
||||
});
|
||||
|
||||
return methodsPerAttribute;
|
||||
}
|
||||
|
||||
private static final Pattern BEAN_NAME = Pattern.compile("(get|set|is|delete|remove|add|update)([A-Z]\\S+)");
|
||||
private static final Map<String, String> FORBIDDEN_PREFIXES = new HashMap<>();
|
||||
static {
|
||||
FORBIDDEN_PREFIXES.put("delete", "remove");
|
||||
}
|
||||
|
||||
private String determineAttributeFromMethodName(ExecutableElement e) {
|
||||
Name name = e.getSimpleName();
|
||||
Matcher m = BEAN_NAME.matcher(name.toString());
|
||||
if (m.matches()) {
|
||||
String prefix = m.group(1);
|
||||
if (FORBIDDEN_PREFIXES.containsKey(prefix)) {
|
||||
processingEnv.getMessager().printMessage(
|
||||
Diagnostic.Kind.ERROR,
|
||||
"Forbidden prefix " + prefix + "... detected, use " + FORBIDDEN_PREFIXES.get(prefix) + "... instead", e
|
||||
);
|
||||
}
|
||||
return m.group(2);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected Stream<ExecutableElement> fieldGetters(Map<String, HashSet<ExecutableElement>> methodsPerAttribute) {
|
||||
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);
|
||||
}
|
||||
|
||||
protected boolean isImmutableFinalType(TypeMirror fieldType) {
|
||||
return isPrimitiveType(fieldType) || isBoxedPrimitiveType(fieldType) || Objects.equals("java.lang.String", fieldType.toString());
|
||||
}
|
||||
|
||||
protected boolean isKnownCollectionOfImmutableFinalTypes(TypeMirror fieldType) {
|
||||
List<TypeMirror> res = getGenericsDeclaration(fieldType);
|
||||
return isCollection(fieldType) && res.stream().allMatch(tm -> isImmutableFinalType(tm) || isKnownCollectionOfImmutableFinalTypes(tm));
|
||||
}
|
||||
|
||||
protected boolean isCollection(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":
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected 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 + ")";
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean isPrimitiveType(TypeMirror fieldType) {
|
||||
try {
|
||||
types.getPrimitiveType(fieldType.getKind());
|
||||
return true;
|
||||
} catch (IllegalArgumentException ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean isBoxedPrimitiveType(TypeMirror fieldType) {
|
||||
try {
|
||||
types.unboxedType(fieldType);
|
||||
return true;
|
||||
} catch (IllegalArgumentException ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected 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(Diagnostic.Kind.ERROR, "Could not determine implementation for type " + typeElement, typeElement);
|
||||
return "TODO()";
|
||||
}
|
||||
}
|
||||
|
||||
protected TypeMirror determineFieldType(String fieldName, HashSet<ExecutableElement> methods) {
|
||||
Pattern getter = Pattern.compile("(get|is)" + Pattern.quote(fieldName));
|
||||
TypeMirror res = null;
|
||||
for (ExecutableElement method : methods) {
|
||||
if (getter.matcher(method.getSimpleName()).matches() && method.getParameters().isEmpty()) {
|
||||
return method.getReturnType();
|
||||
}
|
||||
}
|
||||
if (res == null) {
|
||||
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Could not determine return type for the field " + fieldName, methods.iterator().next());
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
protected static class NameFirstComparator implements Comparator<String> {
|
||||
protected static final Comparator<String> ID_INSTANCE = new NameFirstComparator("id").thenComparing(Comparator.naturalOrder());
|
||||
protected static final Comparator<String> GET_ID_INSTANCE = new NameFirstComparator("getId").thenComparing(Comparator.naturalOrder());
|
||||
private final String name;
|
||||
public NameFirstComparator(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
@Override
|
||||
public int compare(String o1, String o2) {
|
||||
return Objects.equals(o1, o2)
|
||||
? 0
|
||||
: name.equalsIgnoreCase(o1)
|
||||
? -1
|
||||
: name.equalsIgnoreCase(o2)
|
||||
? 1
|
||||
: 0;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -19,18 +19,11 @@ package org.keycloak.models.map.processor;
|
|||
import org.keycloak.models.map.annotations.GenerateEntityImplementations;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Arrays;
|
||||
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.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.annotation.processing.AbstractProcessor;
|
||||
import javax.annotation.processing.RoundEnvironment;
|
||||
import javax.annotation.processing.SupportedAnnotationTypes;
|
||||
import javax.annotation.processing.SupportedSourceVersion;
|
||||
import javax.lang.model.SourceVersion;
|
||||
|
@ -40,14 +33,11 @@ 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.type.NoType;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
import javax.lang.model.util.Elements;
|
||||
import javax.lang.model.util.Types;
|
||||
import javax.tools.Diagnostic;
|
||||
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.Collection;
|
||||
|
@ -57,7 +47,6 @@ import java.util.Optional;
|
|||
import java.util.TreeSet;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.stream.Stream;
|
||||
import javax.lang.model.element.VariableElement;
|
||||
import javax.lang.model.type.TypeKind;
|
||||
|
||||
|
@ -67,14 +56,8 @@ import javax.lang.model.type.TypeKind;
|
|||
*/
|
||||
@SupportedAnnotationTypes("org.keycloak.models.map.annotations.GenerateEntityImplementations")
|
||||
@SupportedSourceVersion(SourceVersion.RELEASE_8)
|
||||
public class GenerateEntityImplementationsProcessor extends AbstractProcessor {
|
||||
public class GenerateEntityImplementationsProcessor extends AbstractGenerateEntityImplementationsProcessor {
|
||||
|
||||
private static interface Generator {
|
||||
void generate(TypeElement e, Map<String, HashSet<ExecutableElement>> methodsPerAttribute) throws IOException;
|
||||
}
|
||||
|
||||
private Elements elements;
|
||||
private Types types;
|
||||
private Collection<String> cloners = new TreeSet<>();
|
||||
|
||||
private final Generator[] generators = new Generator[] {
|
||||
|
@ -85,23 +68,13 @@ public class GenerateEntityImplementationsProcessor extends AbstractProcessor {
|
|||
};
|
||||
|
||||
@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()
|
||||
.map(TypeElement.class::cast)
|
||||
.forEach(this::processTypeElement);
|
||||
}
|
||||
|
||||
if (! cloners.isEmpty() && ! annotations.isEmpty()) {
|
||||
protected void afterAnnotationProcessing() {
|
||||
if (! cloners.isEmpty()) {
|
||||
try {
|
||||
JavaFileObject file = processingEnv.getFiler().createSourceFile("org.keycloak.models.map.common.AutogeneratedCloners");
|
||||
try (PrintWriter pw = new PrintWriterNoJavaLang(file.openWriter())) {
|
||||
pw.println("package org.keycloak.models.map.common;");
|
||||
|
||||
|
||||
pw.println("import org.keycloak.models.map.common.DeepCloner.Cloner;");
|
||||
pw.println("// DO NOT CHANGE THIS CLASS, IT IS GENERATED AUTOMATICALLY BY " + GenerateEntityImplementationsProcessor.class.getSimpleName());
|
||||
pw.println("public final class AutogeneratedCloners {");
|
||||
|
@ -116,178 +89,31 @@ public class GenerateEntityImplementationsProcessor extends AbstractProcessor {
|
|||
Logger.getLogger(GenerateEntityImplementationsProcessor.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Generator[] getGenerators() {
|
||||
return this.generators;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean testAnnotationElement(TypeElement e) {
|
||||
if (e.getKind() != ElementKind.INTERFACE) {
|
||||
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Annotation @GenerateEntityImplementations is only applicable to an interface", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
private static final String FQN_DEEP_CLONER = "org.keycloak.models.map.common.DeepCloner";
|
||||
|
||||
private void processTypeElement(TypeElement e) {
|
||||
if (e.getKind() != ElementKind.INTERFACE) {
|
||||
processingEnv.getMessager().printMessage(Kind.ERROR, "Annotation @GenerateEntityImplementations is only applicable to an interface", e);
|
||||
return;
|
||||
}
|
||||
|
||||
// Find all properties
|
||||
final List<? extends Element> allMembers = elements.getAllMembers(e);
|
||||
Map<String, HashSet<ExecutableElement>> methodsPerAttribute = allMembers.stream()
|
||||
.filter(el -> el.getKind() == ElementKind.METHOD)
|
||||
.filter(el -> el.getModifiers().contains(Modifier.ABSTRACT))
|
||||
.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
|
||||
methodsPerAttribute.keySet().stream()
|
||||
.filter(key -> methodsPerAttribute.containsKey(key + "s"))
|
||||
.collect(Collectors.toSet())
|
||||
.forEach(key -> {
|
||||
HashSet<ExecutableElement> removed = methodsPerAttribute.remove(key);
|
||||
methodsPerAttribute.get(key + "s").addAll(removed);
|
||||
});
|
||||
|
||||
for (Generator generator : this.generators) {
|
||||
try {
|
||||
generator.generate(e, methodsPerAttribute);
|
||||
} catch (Exception ex) {
|
||||
processingEnv.getMessager().printMessage(Kind.ERROR, "Could not generate implementation for class: " + ex, e);
|
||||
}
|
||||
}
|
||||
|
||||
// methodsPerAttribute.entrySet().stream()
|
||||
// .sorted(Comparator.comparing(Map.Entry::getKey))
|
||||
// .forEach(me -> processingEnv.getMessager().printMessage(
|
||||
// Diagnostic.Kind.NOTE,
|
||||
// "** " + me.getKey() + ": " + me.getValue().stream().map(ExecutableElement::getSimpleName).sorted(Comparator.comparing(Object::toString)).collect(Collectors.joining(", ")))
|
||||
// );
|
||||
}
|
||||
|
||||
private static final Pattern BEAN_NAME = Pattern.compile("(get|set|is|delete|remove|add|update)([A-Z]\\S+)");
|
||||
private static final Map<String, String> FORBIDDEN_PREFIXES = new HashMap<>();
|
||||
static {
|
||||
FORBIDDEN_PREFIXES.put("delete", "remove");
|
||||
}
|
||||
|
||||
private String determineAttributeFromMethodName(ExecutableElement e) {
|
||||
Name name = e.getSimpleName();
|
||||
Matcher m = BEAN_NAME.matcher(name.toString());
|
||||
if (m.matches()) {
|
||||
String prefix = m.group(1);
|
||||
if (FORBIDDEN_PREFIXES.containsKey(prefix)) {
|
||||
processingEnv.getMessager().printMessage(
|
||||
Kind.ERROR,
|
||||
"Forbidden prefix " + prefix + "... detected, use " + FORBIDDEN_PREFIXES.get(prefix) + "... instead", e
|
||||
);
|
||||
}
|
||||
return m.group(2);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected static String toEnumConstant(String key) {
|
||||
return key.replaceAll("([a-z])([A-Z])", "$1_$2").toUpperCase();
|
||||
}
|
||||
|
||||
private TypeMirror determineFieldType(String fieldName, HashSet<ExecutableElement> methods) {
|
||||
Pattern getter = Pattern.compile("(get|is)" + Pattern.quote(fieldName));
|
||||
TypeMirror res = null;
|
||||
for (ExecutableElement method : methods) {
|
||||
if (getter.matcher(method.getSimpleName()).matches() && method.getParameters().isEmpty()) {
|
||||
return method.getReturnType();
|
||||
}
|
||||
}
|
||||
if (res == null) {
|
||||
processingEnv.getMessager().printMessage(Kind.ERROR, "Could not determine return type for the field " + fieldName, methods.iterator().next());
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
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 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 + ")";
|
||||
}
|
||||
}
|
||||
|
||||
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 static class NameFirstComparator implements Comparator<String> {
|
||||
private static final Comparator<String> ID_INSTANCE = new NameFirstComparator("id").thenComparing(Comparator.naturalOrder());
|
||||
private static final Comparator<String> GET_ID_INSTANCE = new NameFirstComparator("getId").thenComparing(Comparator.naturalOrder());
|
||||
private final String name;
|
||||
public NameFirstComparator(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
@Override
|
||||
public int compare(String o1, String o2) {
|
||||
return Objects.equals(o1, o2)
|
||||
? 0
|
||||
: name.equalsIgnoreCase(o1)
|
||||
? -1
|
||||
: name.equalsIgnoreCase(o2)
|
||||
? 1
|
||||
: 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class FieldsGenerator implements Generator {
|
||||
@Override
|
||||
public void generate(TypeElement e, Map<String, HashSet<ExecutableElement>> methodsPerAttribute) throws IOException {
|
||||
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('.');
|
||||
|
@ -318,7 +144,8 @@ public class GenerateEntityImplementationsProcessor extends AbstractProcessor {
|
|||
private class ImplGenerator implements Generator {
|
||||
|
||||
@Override
|
||||
public void generate(TypeElement e, Map<String, HashSet<ExecutableElement>> methodsPerAttribute) throws IOException {
|
||||
public void generate(TypeElement e) throws IOException {
|
||||
Map<String, HashSet<ExecutableElement>> methodsPerAttribute = methodsPerAttributeMapping(e);
|
||||
GenerateEntityImplementations an = e.getAnnotation(GenerateEntityImplementations.class);
|
||||
TypeElement parentTypeElement = elements.getTypeElement((an.inherits() == null || an.inherits().isEmpty()) ? "void" : an.inherits());
|
||||
if (parentTypeElement == null) {
|
||||
|
@ -452,13 +279,6 @@ public class GenerateEntityImplementationsProcessor extends AbstractProcessor {
|
|||
}
|
||||
}
|
||||
|
||||
private Stream<ExecutableElement> fieldGetters(Map<String, HashSet<ExecutableElement>> methodsPerAttribute) {
|
||||
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);
|
||||
}
|
||||
|
||||
private boolean printMethodBody(PrintWriter pw, FieldAccessorType accessorType, ExecutableElement method, String fieldName, TypeMirror fieldType) {
|
||||
TypeMirror firstParameterType = method.getParameters().isEmpty()
|
||||
? types.getNullType()
|
||||
|
@ -525,7 +345,8 @@ public class GenerateEntityImplementationsProcessor extends AbstractProcessor {
|
|||
|
||||
private class DelegateGenerator implements Generator {
|
||||
@Override
|
||||
public void generate(TypeElement e, Map<String, HashSet<ExecutableElement>> methodsPerAttribute) throws IOException {
|
||||
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('.');
|
||||
|
@ -594,7 +415,8 @@ public class GenerateEntityImplementationsProcessor extends AbstractProcessor {
|
|||
private class ClonerGenerator implements Generator {
|
||||
|
||||
@Override
|
||||
public void generate(TypeElement e, Map<String, HashSet<ExecutableElement>> methodsPerAttribute) throws IOException {
|
||||
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('.');
|
||||
|
|
|
@ -0,0 +1,508 @@
|
|||
/*
|
||||
* 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 org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation;
|
||||
import org.keycloak.models.map.exceptions.CannotMigrateTypeException;
|
||||
|
||||
import javax.annotation.processing.SupportedAnnotationTypes;
|
||||
import javax.annotation.processing.SupportedSourceVersion;
|
||||
import javax.lang.model.SourceVersion;
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.ElementKind;
|
||||
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.TypeMirror;
|
||||
import javax.tools.Diagnostic;
|
||||
import javax.tools.JavaFileObject;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.OptionalInt;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
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.isSetType;
|
||||
import static org.keycloak.models.map.processor.Util.methodParameters;
|
||||
|
||||
@SupportedAnnotationTypes("org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation")
|
||||
@SupportedSourceVersion(SourceVersion.RELEASE_8)
|
||||
public class GenerateHotRodEntityImplementationsProcessor extends AbstractGenerateEntityImplementationsProcessor {
|
||||
|
||||
|
||||
@Override
|
||||
protected Generator[] getGenerators() {
|
||||
return new Generator[] { new HotRodGettersAndSettersDelegateGenerator() };
|
||||
}
|
||||
|
||||
|
||||
private class HotRodGettersAndSettersDelegateGenerator implements Generator {
|
||||
|
||||
private static final String ENTITY_VARIABLE = "hotRodEntity";
|
||||
private String hotRodSimpleClassName;
|
||||
private TypeElement generalHotRodDelegate;
|
||||
private TypeElement abstractEntity;
|
||||
private TypeElement abstractHotRodEntity;
|
||||
private TypeElement hotRodUtils;
|
||||
|
||||
@Override
|
||||
public void generate(TypeElement e) throws IOException {
|
||||
GenerateHotRodEntityImplementation hotRodAnnotation = e.getAnnotation(GenerateHotRodEntityImplementation.class);
|
||||
String interfaceClass = hotRodAnnotation.implementInterface();
|
||||
if (interfaceClass == null || interfaceClass.isEmpty()) return;
|
||||
TypeElement parentClassElement = elements.getTypeElement(hotRodAnnotation.inherits());
|
||||
if (parentClassElement == null) return;
|
||||
|
||||
TypeElement parentInterfaceElement = elements.getTypeElement(interfaceClass);
|
||||
if (parentInterfaceElement == null) return;
|
||||
Map<String, HashSet<ExecutableElement>> methodsPerAttribute = methodsPerAttributeMapping(parentInterfaceElement);
|
||||
|
||||
|
||||
final List<? extends Element> allMembers = elements.getAllMembers(parentClassElement);
|
||||
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 hotRodImplClassName = className + "Delegate";
|
||||
hotRodSimpleClassName = simpleClassName + "Delegate";
|
||||
generalHotRodDelegate = elements.getTypeElement("org.keycloak.models.map.storage.hotRod.common.HotRodEntityDelegate");
|
||||
abstractEntity = elements.getTypeElement("org.keycloak.models.map.common.AbstractEntity");
|
||||
abstractHotRodEntity = elements.getTypeElement("org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity");
|
||||
hotRodUtils = elements.getTypeElement("org.keycloak.models.map.storage.hotRod.common.HotRodTypesUtils");
|
||||
|
||||
boolean hasDeepClone = allMembers.stream().filter(el -> el.getKind() == ElementKind.METHOD).anyMatch(el -> "deepClone".equals(el.getSimpleName().toString()));
|
||||
boolean needsDeepClone = fieldGetters(methodsPerAttribute)
|
||||
.map(ExecutableElement::getReturnType)
|
||||
.anyMatch(fieldType -> ! isKnownCollectionOfImmutableFinalTypes(fieldType) && ! isImmutableFinalType(fieldType));
|
||||
boolean hasId = methodsPerAttribute.containsKey("Id") || allMembers.stream().anyMatch(el -> "getId".equals(el.getSimpleName().toString()));
|
||||
|
||||
JavaFileObject file = processingEnv.getFiler().createSourceFile(hotRodImplClassName);
|
||||
try (PrintWriter pw = new PrintWriterNoJavaLang(file.openWriter())) {
|
||||
if (packageName != null) {
|
||||
pw.println("package " + packageName + ";");
|
||||
}
|
||||
|
||||
pw.println("import java.util.Objects;");
|
||||
pw.println("import " + FQN_DEEP_CLONER + ";");
|
||||
pw.println("import java.util.Optional;");
|
||||
pw.println("import java.util.stream.Collectors;");
|
||||
pw.println();
|
||||
pw.println("// DO NOT CHANGE THIS CLASS, IT IS GENERATED AUTOMATICALLY BY " + GenerateHotRodEntityImplementationsProcessor.class.getSimpleName());
|
||||
pw.println("public class " + hotRodSimpleClassName + " extends " + parentClassElement.getQualifiedName().toString() + " implements "
|
||||
+ parentInterfaceElement.getQualifiedName().toString()
|
||||
+ ", " + generalHotRodDelegate.getQualifiedName().toString() + "<" + e.getQualifiedName().toString() + ">"
|
||||
+ " {");
|
||||
pw.println();
|
||||
pw.println(" private final " + className + " " + ENTITY_VARIABLE + ";");
|
||||
pw.println();
|
||||
|
||||
// Constructors
|
||||
allMembers.stream()
|
||||
.filter(ExecutableElement.class::isInstance)
|
||||
.map(ExecutableElement.class::cast)
|
||||
.filter((ExecutableElement ee) -> ee.getKind() == ElementKind.CONSTRUCTOR)
|
||||
.forEach((ExecutableElement ee) -> {
|
||||
if (hasDeepClone || ! needsDeepClone) {
|
||||
pw.println(" "
|
||||
+ ee.getModifiers().stream().map(Object::toString).collect(Collectors.joining(" "))
|
||||
+ " " + hotRodSimpleClassName + "(" + methodParameters(ee.getParameters()) + ") {"
|
||||
);
|
||||
pw.println(" super(" + ee.getParameters() + ");");
|
||||
pw.println(" this." + ENTITY_VARIABLE + " = new " + className + "();");
|
||||
pw.println(" }");
|
||||
} else if (needsDeepClone) {
|
||||
pw.println(" /**");
|
||||
pw.println(" * @deprecated This constructor uses a {@link DeepCloner#DUMB_CLONER} that does not clone anything. Use {@link #" + hotRodSimpleClassName + "(DeepCloner)} variant instead");
|
||||
pw.println(" */");
|
||||
pw.println(" "
|
||||
+ ee.getModifiers().stream().map(Object::toString).collect(Collectors.joining(" "))
|
||||
+ " "
|
||||
+ hotRodSimpleClassName + "(" + methodParameters(ee.getParameters()) + ") { this(DeepCloner.DUMB_CLONER" + (ee.getParameters().isEmpty() ? "" : ", ") + ee.getParameters() + "); }"
|
||||
);
|
||||
pw.println(" "
|
||||
+ ee.getModifiers().stream().map(Object::toString).collect(Collectors.joining(" "))
|
||||
+ " "
|
||||
+ hotRodSimpleClassName + "(DeepCloner cloner" + (ee.getParameters().isEmpty() ? "" : ", ") + methodParameters(ee.getParameters()) + ") {"
|
||||
);
|
||||
pw.println(" super(" + ee.getParameters() + ");");
|
||||
pw.println(" this.cloner = cloner;");
|
||||
pw.println(" this." + ENTITY_VARIABLE + " = new " + className + "();");
|
||||
pw.println(" }");
|
||||
}
|
||||
});
|
||||
|
||||
// Add constructor for setting HotRodEntity
|
||||
pw.println(" " +
|
||||
"public " + hotRodSimpleClassName + "(" + className + " " + ENTITY_VARIABLE + ") {"
|
||||
);
|
||||
pw.println(" this." + ENTITY_VARIABLE + " = " + ENTITY_VARIABLE + ";");
|
||||
if (! hasDeepClone && needsDeepClone) {
|
||||
pw.println(" this.cloner = DeepCloner.DUMB_CLONER;");
|
||||
}
|
||||
pw.println(" }");
|
||||
|
||||
// equals, hashCode, toString
|
||||
pw.println(" @Override public boolean equals(Object o) {");
|
||||
pw.println(" if (o == this) return true; ");
|
||||
pw.println(" if (! (o instanceof " + hotRodSimpleClassName + ")) return false; ");
|
||||
pw.println(" " + hotRodSimpleClassName + " other = (" + hotRodSimpleClassName + ") o; ");
|
||||
pw.println(" return "
|
||||
+ fieldGetters(methodsPerAttribute)
|
||||
.map(ExecutableElement::getSimpleName)
|
||||
.map(Name::toString)
|
||||
.sorted(NameFirstComparator.GET_ID_INSTANCE)
|
||||
.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("
|
||||
+ fieldGetters(methodsPerAttribute)
|
||||
.filter(ee -> isImmutableFinalType(ee.getReturnType()))
|
||||
.map(ExecutableElement::getSimpleName)
|
||||
.map(Name::toString)
|
||||
.sorted(GenerateEntityImplementationsProcessor.NameFirstComparator.GET_ID_INSTANCE)
|
||||
.map(v -> v + "()")
|
||||
.collect(Collectors.joining(",\n "))
|
||||
+ ")")
|
||||
+ ";");
|
||||
pw.println(" }");
|
||||
pw.println(" @Override public String toString() {");
|
||||
pw.println(" return String.format(\"%s@%08x\", " + (hasId ? "getId()" : "\"" + hotRodSimpleClassName + "\"" ) + ", System.identityHashCode(this));");
|
||||
pw.println(" }");
|
||||
|
||||
pw.println(" public static boolean entityEquals(Object o1, Object o2) {");
|
||||
pw.println(" if (!(o1 instanceof " + className + ")) return false;");
|
||||
pw.println(" if (!(o2 instanceof " + className + ")) return false;");
|
||||
|
||||
pw.println(" if (o1 == o2) return true;");
|
||||
|
||||
pw.println(" " + className + " e1 = (" + className + ") o1;");
|
||||
pw.println(" " + className + " e2 = (" + className + ") o2;");
|
||||
|
||||
pw.print(" return ");
|
||||
pw.println(elements.getAllMembers(e).stream()
|
||||
.filter(VariableElement.class::isInstance)
|
||||
.map(VariableElement.class::cast)
|
||||
.map(var -> "Objects.equals(e1." + var.getSimpleName().toString() + ", e2." + var.getSimpleName().toString() + ")")
|
||||
.collect(Collectors.joining("\n && ")));
|
||||
pw.println(" ;");
|
||||
pw.println(" }");
|
||||
|
||||
pw.println(" public static int entityHashCode(" + className + " e) {");
|
||||
pw.println(" return "
|
||||
+ (hasId
|
||||
? "(e.id == null ? Objects.hash(e) : e.id.hashCode())"
|
||||
: "Objects.hash("
|
||||
+ elements.getAllMembers(e).stream()
|
||||
.filter(VariableElement.class::isInstance)
|
||||
.map(VariableElement.class::cast)
|
||||
.map(var -> var.getSimpleName().toString())
|
||||
.collect(Collectors.joining(",\n "))
|
||||
+ ")")
|
||||
+ ";"
|
||||
);
|
||||
pw.println(" }");
|
||||
|
||||
// deepClone
|
||||
if (! hasDeepClone && needsDeepClone) {
|
||||
pw.println(" private final DeepCloner cloner;");
|
||||
pw.println(" public <V> V deepClone(V obj) {");
|
||||
pw.println(" return cloner.from(obj);");
|
||||
pw.println(" }");
|
||||
}
|
||||
|
||||
// getters, setters
|
||||
methodsPerAttribute.entrySet().stream().sorted(Comparator.comparing(Map.Entry::getKey, GenerateEntityImplementationsProcessor.NameFirstComparator.ID_INSTANCE)).forEach(me -> {
|
||||
HashSet<ExecutableElement> methods = me.getValue();
|
||||
TypeMirror fieldType = determineFieldType(me.getKey(), methods);
|
||||
if (fieldType == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine HotRod entity field name by changing case of first letter
|
||||
char[] c = me.getKey().toCharArray();
|
||||
c[0] = Character.toLowerCase(c[0]);
|
||||
String hotRodEntityFieldName = new String(c);
|
||||
|
||||
// Find corresponding variable in HotRod*Entity
|
||||
Optional<VariableElement> hotRodVariable = elements.getAllMembers(e).stream()
|
||||
.filter(VariableElement.class::isInstance)
|
||||
.map(VariableElement.class::cast)
|
||||
.filter(variableElement -> variableElement.getSimpleName().toString().equals(hotRodEntityFieldName))
|
||||
.findFirst();
|
||||
|
||||
if (!hotRodVariable.isPresent()) {
|
||||
// throw an error when no variable found
|
||||
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Cannot find " + e.getSimpleName().toString() + " field for methods: [" + me.getValue().stream().map(ee -> ee.getSimpleName().toString()).collect(Collectors.joining(", ")) + "]", parentInterfaceElement);
|
||||
return;
|
||||
}
|
||||
|
||||
// Implement each method
|
||||
for (ExecutableElement method : methods) {
|
||||
FieldAccessorType fat = FieldAccessorType.determineType(method, me.getKey(), types, fieldType);
|
||||
|
||||
// Check if the parent class implements the method already
|
||||
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();
|
||||
|
||||
try {
|
||||
if (parentMethod.isPresent()) {
|
||||
// Do not implement the method if it is already implemented by the parent class
|
||||
processingEnv.getMessager().printMessage(Diagnostic.Kind.OTHER, "Method " + method + " is declared in a parent class.", method);
|
||||
} else if (fat != FieldAccessorType.UNKNOWN && !printMethodBody(pw, fat, method, hotRodEntityFieldName, fieldType, hotRodVariable.get().asType())) {
|
||||
processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING, "Could not determine desired semantics of method from its signature", method);
|
||||
}
|
||||
} catch (CannotMigrateTypeException ex) {
|
||||
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, ex.getFormattedMessage(), method);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Implement HotRodDelegate interface
|
||||
pw.println(" public " + className + " getHotRodEntity() {");
|
||||
pw.println(" return this." + ENTITY_VARIABLE + ";");
|
||||
pw.println(" }");
|
||||
pw.println("}");
|
||||
}
|
||||
}
|
||||
|
||||
private String hotRodEntityField(String fieldName) {
|
||||
return "this." + ENTITY_VARIABLE + "." + fieldName;
|
||||
}
|
||||
|
||||
private boolean printMethodBody(PrintWriter pw, FieldAccessorType accessorType, ExecutableElement method, String fieldName, TypeMirror fieldType, TypeMirror hotRodFieldType) {
|
||||
TypeMirror firstParameterType = method.getParameters().isEmpty()
|
||||
? types.getNullType()
|
||||
: method.getParameters().get(0).asType();
|
||||
TypeElement typeElement = elements.getTypeElement(types.erasure(fieldType).toString());
|
||||
|
||||
switch (accessorType) {
|
||||
case GETTER:
|
||||
pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method + " {");
|
||||
pw.println(" return " + migrateToType(method.getReturnType(), hotRodFieldType, hotRodEntityField(fieldName)) + ";");
|
||||
pw.println(" }");
|
||||
return true;
|
||||
case SETTER:
|
||||
pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(" + firstParameterType + " p0) {");
|
||||
if (! isImmutableFinalType(firstParameterType)) {
|
||||
pw.println(" p0 = " + deepClone(fieldType, "p0") + ";");
|
||||
}
|
||||
pw.println(" " + hotRodFieldType.toString() + " migrated = " + migrateToType(hotRodFieldType, firstParameterType, "p0") + ";");
|
||||
pw.println(" updated |= ! Objects.equals(" + hotRodEntityField(fieldName) + ", migrated);");
|
||||
pw.println(" " + hotRodEntityField(fieldName) + " = migrated;");
|
||||
pw.println(" }");
|
||||
return true;
|
||||
case COLLECTION_ADD:
|
||||
TypeMirror collectionItemType = getGenericsDeclaration(hotRodFieldType).get(0);
|
||||
pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(" + firstParameterType + " p0) {");
|
||||
pw.println(" if (" + hotRodEntityField(fieldName) + " == null) { " + hotRodEntityField(fieldName) + " = " + interfaceToImplementation(typeElement, "") + "; }");
|
||||
if (! isImmutableFinalType(firstParameterType)) {
|
||||
pw.println(" p0 = " + deepClone(fieldType, "p0") + ";");
|
||||
}
|
||||
pw.println(" " + collectionItemType.toString() + " migrated = " + migrateToType(collectionItemType, firstParameterType, "p0") + ";");
|
||||
if (isSetType(typeElement)) {
|
||||
pw.println(" updated |= " + hotRodEntityField(fieldName) + ".add(migrated);");
|
||||
} else {
|
||||
pw.println(" " + hotRodEntityField(fieldName) + ".add(migrated);");
|
||||
pw.println(" updated = true;");
|
||||
}
|
||||
pw.println(" }");
|
||||
return true;
|
||||
case COLLECTION_DELETE:
|
||||
collectionItemType = getGenericsDeclaration(hotRodFieldType).get(0);
|
||||
pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(" + firstParameterType + " p0) {");
|
||||
if (isMapType(typeElement)) {
|
||||
// Maps are stored as sets
|
||||
pw.println(" this.updated |= " + hotRodUtils.getQualifiedName().toString() + ".removeFromSetByMapKey("
|
||||
+ hotRodEntityField(fieldName) + ", "
|
||||
+ "p0, "
|
||||
+ keyGetterReference(collectionItemType) + ");"
|
||||
);
|
||||
} else {
|
||||
pw.println(" if (" + hotRodEntityField(fieldName) + " == null) { return; }");
|
||||
pw.println(" boolean removed = " + hotRodEntityField(fieldName) + ".remove(p0);");
|
||||
pw.println(" updated |= removed;");
|
||||
}
|
||||
pw.println(" }");
|
||||
return true;
|
||||
case MAP_ADD:
|
||||
collectionItemType = getGenericsDeclaration(hotRodFieldType).get(0);
|
||||
TypeMirror secondParameterType = method.getParameters().get(1).asType();
|
||||
pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(" + firstParameterType + " p0, " + secondParameterType + " p1) {");
|
||||
pw.println(" if (" + hotRodEntityField(fieldName) + " == null) { " + hotRodEntityField(fieldName) + " = " + interfaceToImplementation((TypeElement) types.asElement(types.erasure(hotRodFieldType)), "") + "; }");
|
||||
pw.println(" boolean valueUndefined = p1 == null" + (isCollection(secondParameterType) ? " || p1.isEmpty()" : "") + ";");
|
||||
if (! isImmutableFinalType(secondParameterType)) {
|
||||
pw.println(" p1 = " + deepClone(secondParameterType, "p1") + ";");
|
||||
}
|
||||
pw.println(" this.updated |= " + hotRodUtils.getQualifiedName().toString() + ".removeFromSetByMapKey("
|
||||
+ hotRodEntityField(fieldName) + ", "
|
||||
+ "p0, "
|
||||
+ keyGetterReference(collectionItemType) + ");"
|
||||
);
|
||||
pw.println(" this.updated |= !valueUndefined && " + hotRodEntityField(fieldName)
|
||||
+ ".add(" + migrateToType(collectionItemType, new TypeMirror[]{firstParameterType, secondParameterType}, new String[]{"p0", "p1"}) + ");");
|
||||
pw.println(" }");
|
||||
return true;
|
||||
case MAP_GET:
|
||||
pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(" + firstParameterType + " p0) {");
|
||||
collectionItemType = getGenericsDeclaration(hotRodFieldType).get(0);
|
||||
pw.println(" return " + hotRodUtils.getQualifiedName().toString() + ".getMapValueFromSet("
|
||||
+ hotRodEntityField(fieldName) + ", "
|
||||
+ "p0, "
|
||||
+ keyGetterReference(collectionItemType) + ", "
|
||||
+ valueGetterReference(collectionItemType) + ");"
|
||||
);
|
||||
pw.println(" }");
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private String migrateToType(TypeMirror toType, TypeMirror fromType, String fieldName) {
|
||||
return migrateToType(toType, new TypeMirror[] {fromType}, new String[]{fieldName});
|
||||
}
|
||||
|
||||
private String toSimpleName(TypeMirror typeMirror) {
|
||||
TypeElement e = elements.getTypeElement(types.erasure(typeMirror).toString());
|
||||
return e.getSimpleName().toString();
|
||||
}
|
||||
|
||||
private String keyGetterReference(TypeMirror type) {
|
||||
if (types.isAssignable(type, abstractHotRodEntity.asType())) {
|
||||
return "e -> e.id";
|
||||
}
|
||||
return hotRodUtils.getQualifiedName().toString() + "::getKey";
|
||||
}
|
||||
|
||||
private String valueGetterReference(TypeMirror type) {
|
||||
if (types.isAssignable(type, abstractHotRodEntity.asType())) {
|
||||
return toSimpleName(type) + "Delegate::new";
|
||||
}
|
||||
return hotRodUtils.getQualifiedName().toString() + "::getValue";
|
||||
}
|
||||
|
||||
private String migrateToType(TypeMirror toType, TypeMirror[] fromType, String[] fieldNames) {
|
||||
// No migration needed, fromType is assignable to toType directly
|
||||
if (fromType.length == 1 && types.isAssignable(types.erasure(fromType[0]), types.erasure(toType))) {
|
||||
return fieldNames[0];
|
||||
}
|
||||
|
||||
// HotRod entities are not allowed to use Maps, therefore we often need to migrate from Map to Set and the other way around
|
||||
if (fromType.length == 1) {
|
||||
if (isSetType((TypeElement) types.asElement(types.erasure(toType)))
|
||||
&& isMapType((TypeElement) types.asElement(types.erasure(fromType[0])))) {
|
||||
TypeMirror setType = getGenericsDeclaration(toType).get(0);
|
||||
|
||||
return hotRodUtils.getQualifiedName().toString() + ".migrateMapToSet("
|
||||
+ fieldNames[0] + ", "
|
||||
+ hotRodUtils.getQualifiedName().toString() + "::create" + toSimpleName(setType) + "FromMapEntry)";
|
||||
} else if (isMapType((TypeElement) types.asElement(types.erasure(toType)))
|
||||
&& isSetType((TypeElement) types.asElement(types.erasure(fromType[0])))) {
|
||||
TypeMirror setType = getGenericsDeclaration(fromType[0]).get(0);
|
||||
|
||||
return hotRodUtils.getQualifiedName().toString() + ".migrateSetToMap("
|
||||
+ fieldNames[0] + ", "
|
||||
+ keyGetterReference(setType) + ", "
|
||||
+ valueGetterReference(setType)
|
||||
+ ")";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Try to find constructor that can do the migration
|
||||
if (findSuitableConstructor(toType, fromType).isPresent()) {
|
||||
return "new " + toType.toString() + "(" + String.join(", ", fieldNames) + ")";
|
||||
}
|
||||
|
||||
// Check if any of parameters is another Map*Entity
|
||||
OptionalInt anotherMapEntityIndex = IntStream.range(0, fromType.length)
|
||||
.filter(i -> types.isAssignable(fromType[i], abstractEntity.asType()))
|
||||
.findFirst();
|
||||
|
||||
if (anotherMapEntityIndex.isPresent()) {
|
||||
// If yes, we can be sure that it implements HotRodEntityDelegate (this is achieved by HotRod cloner settings) so we can just call getHotRodEntity method
|
||||
return "((" + generalHotRodDelegate.getQualifiedName().toString() + "<" + toType.toString() + ">) " + fieldNames[anotherMapEntityIndex.getAsInt()] + ").getHotRodEntity()";
|
||||
}
|
||||
|
||||
// Check if any of parameters is another HotRod*Entity
|
||||
OptionalInt anotherHotRodEntityIndex = IntStream.range(0, fromType.length)
|
||||
.filter(i -> types.isAssignable(fromType[i], abstractHotRodEntity.asType()))
|
||||
.findFirst();
|
||||
|
||||
if (anotherHotRodEntityIndex.isPresent()) {
|
||||
// If yes, we can be sure that it implements HotRodEntityDelegate (this is achieved by HotRod cloner settings) so we can just call getHotRodEntity method
|
||||
return "new " + fromType[anotherHotRodEntityIndex.getAsInt()] + "Delegate(" + String.join(", ", fieldNames) + ")";
|
||||
}
|
||||
|
||||
throw new CannotMigrateTypeException(toType, fromType);
|
||||
}
|
||||
|
||||
private Optional<ExecutableElement> findSuitableConstructor(TypeMirror desiredType, TypeMirror[] parameters) {
|
||||
// Try to find constructor that can do the migration
|
||||
TypeElement type = (TypeElement) types.asElement(desiredType);
|
||||
return elements.getAllMembers(type)
|
||||
.stream()
|
||||
.filter(ExecutableElement.class::isInstance)
|
||||
.map(ExecutableElement.class::cast)
|
||||
.filter(ee -> ee.getKind() == ElementKind.CONSTRUCTOR)
|
||||
.filter(ee -> ee.getParameters().size() == parameters.length)
|
||||
.filter(method -> IntStream.range(0, parameters.length).allMatch(i -> deepCompareTypes(parameters[i], method.getParameters().get(i).asType())))
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
|
||||
private boolean deepCompareTypes(TypeMirror fromType, TypeMirror toType) {
|
||||
return types.isAssignable(types.erasure(fromType), types.erasure(toType))
|
||||
&& deepCompareTypes(getGenericsDeclaration(fromType), getGenericsDeclaration(toType));
|
||||
}
|
||||
|
||||
private boolean deepCompareTypes(List<TypeMirror> fromTypes, List<TypeMirror> toTypes) {
|
||||
if (fromTypes.size() == 0 && toTypes.size() == 0) return true;
|
||||
if (fromTypes.size() != toTypes.size()) return false;
|
||||
|
||||
for (int i = 0; i < fromTypes.size(); i++) {
|
||||
if (!deepCompareTypes(fromTypes.get(i), toTypes.get(i))) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -18,10 +18,12 @@ package org.keycloak.models.map.processor;
|
|||
|
||||
import org.keycloak.models.map.annotations.IgnoreForEntityImplementationGenerator;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.stream.Collectors;
|
||||
|
@ -40,6 +42,7 @@ import javax.lang.model.util.SimpleTypeVisitor8;
|
|||
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()));
|
||||
private static final HashSet<String> MAP_TYPES = new HashSet<>(Arrays.asList(Map.class.getCanonicalName(), HashMap.class.getCanonicalName()));
|
||||
|
||||
public static List<TypeMirror> getGenericsDeclaration(TypeMirror fieldType) {
|
||||
List<TypeMirror> res = new LinkedList<>();
|
||||
|
@ -67,6 +70,11 @@ public class Util {
|
|||
return SET_TYPES.contains(name.toString());
|
||||
}
|
||||
|
||||
public static boolean isMapType(TypeElement typeElement) {
|
||||
Name name = typeElement.getQualifiedName();
|
||||
return MAP_TYPES.contains(name.toString());
|
||||
}
|
||||
|
||||
public static boolean isNotIgnored(Element el) {
|
||||
do {
|
||||
IgnoreForEntityImplementationGenerator a = el.getAnnotation(IgnoreForEntityImplementationGenerator.class);
|
||||
|
|
|
@ -58,6 +58,44 @@
|
|||
<artifactId>hamcrest</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-model-build-processor</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<annotationProcessorPaths>
|
||||
<path>
|
||||
<groupId>org.infinispan.protostream</groupId>
|
||||
<artifactId>protostream-processor</artifactId>
|
||||
<version>${infinispan.protostream.processor.version}</version>
|
||||
</path>
|
||||
<path>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-model-build-processor</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</path>
|
||||
<path>
|
||||
<groupId>javax.annotation</groupId>
|
||||
<artifactId>javax.annotation-api</artifactId>
|
||||
<version>${javax.annotation-api.version}</version>
|
||||
</path>
|
||||
</annotationProcessorPaths>
|
||||
<annotationProcessors>
|
||||
<annotationProcessor>org.infinispan.protostream.annotations.impl.processor.AutoProtoSchemaBuilderAnnotationProcessor</annotationProcessor>
|
||||
<annotationProcessor>org.keycloak.models.map.processor.GenerateHotRodEntityImplementationsProcessor</annotationProcessor>
|
||||
</annotationProcessors>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
|
|
|
@ -26,9 +26,10 @@ import org.jboss.logging.Logger;
|
|||
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.hotRod.common.AbstractHotRodEntity;
|
||||
import org.keycloak.models.map.storage.hotRod.common.HotRodEntityDelegate;
|
||||
import org.keycloak.models.map.storage.hotRod.common.HotRodEntityDescriptor;
|
||||
import org.keycloak.models.map.common.StringKeyConvertor;
|
||||
import org.keycloak.models.map.common.UpdatableEntity;
|
||||
import org.keycloak.models.map.storage.MapKeycloakTransaction;
|
||||
import org.keycloak.models.map.storage.MapStorage;
|
||||
import org.keycloak.models.map.storage.QueryParameters;
|
||||
|
@ -42,6 +43,7 @@ import java.util.Map;
|
|||
import java.util.Objects;
|
||||
import java.util.Spliterators;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
@ -49,20 +51,22 @@ import java.util.stream.StreamSupport;
|
|||
import static org.keycloak.models.map.storage.hotRod.common.HotRodUtils.paginateQuery;
|
||||
import static org.keycloak.utils.StreamsUtil.closing;
|
||||
|
||||
public class HotRodMapStorage<K, V extends AbstractEntity & UpdatableEntity, M> implements MapStorage<V, M>, ConcurrentHashMapCrudOperations<V, M> {
|
||||
public class HotRodMapStorage<K, E extends AbstractHotRodEntity, V extends HotRodEntityDelegate<E>, M> implements MapStorage<V, M>, ConcurrentHashMapCrudOperations<V, M> {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(HotRodMapStorage.class);
|
||||
|
||||
private final RemoteCache<K, V> remoteCache;
|
||||
private final RemoteCache<K, E> remoteCache;
|
||||
private final StringKeyConvertor<K> keyConvertor;
|
||||
private final HotRodEntityDescriptor<V> storedEntityDescriptor;
|
||||
private final HotRodEntityDescriptor<E, V> storedEntityDescriptor;
|
||||
private final Function<E, V> delegateProducer;
|
||||
private final DeepCloner cloner;
|
||||
|
||||
public HotRodMapStorage(RemoteCache<K, V> remoteCache, StringKeyConvertor<K> keyConvertor, HotRodEntityDescriptor<V> storedEntityDescriptor, DeepCloner cloner) {
|
||||
public HotRodMapStorage(RemoteCache<K, E> remoteCache, StringKeyConvertor<K> keyConvertor, HotRodEntityDescriptor<E, V> storedEntityDescriptor, DeepCloner cloner) {
|
||||
this.remoteCache = remoteCache;
|
||||
this.keyConvertor = keyConvertor;
|
||||
this.storedEntityDescriptor = storedEntityDescriptor;
|
||||
this.cloner = cloner;
|
||||
this.delegateProducer = storedEntityDescriptor.getHotRodDelegateProvider();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -73,7 +77,7 @@ public class HotRodMapStorage<K, V extends AbstractEntity & UpdatableEntity, M>
|
|||
value = cloner.from(keyConvertor.keyToString(key), value);
|
||||
}
|
||||
|
||||
remoteCache.putIfAbsent(key, value);
|
||||
remoteCache.putIfAbsent(key, value.getHotRodEntity());
|
||||
|
||||
return value;
|
||||
}
|
||||
|
@ -82,13 +86,13 @@ public class HotRodMapStorage<K, V extends AbstractEntity & UpdatableEntity, M>
|
|||
public V read(String key) {
|
||||
Objects.requireNonNull(key, "Key must be non-null");
|
||||
K k = keyConvertor.fromStringSafe(key);
|
||||
return remoteCache.get(k);
|
||||
return delegateProducer.apply(remoteCache.get(k));
|
||||
}
|
||||
|
||||
@Override
|
||||
public V update(V value) {
|
||||
K key = keyConvertor.fromStringSafe(value.getId());
|
||||
return remoteCache.replace(key, value);
|
||||
return delegateProducer.apply(remoteCache.replace(key, value.getHotRodEntity()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -120,14 +124,15 @@ public class HotRodMapStorage<K, V extends AbstractEntity & UpdatableEntity, M>
|
|||
|
||||
QueryFactory queryFactory = Search.getQueryFactory(remoteCache);
|
||||
|
||||
Query<V> query = paginateQuery(queryFactory.create(queryString), queryParameters.getOffset(),
|
||||
Query<E> query = paginateQuery(queryFactory.create(queryString), queryParameters.getOffset(),
|
||||
queryParameters.getLimit());
|
||||
|
||||
query.setParameters(iqmcb.getParameters());
|
||||
|
||||
CloseableIterator<V> iterator = query.iterator();
|
||||
CloseableIterator<E> iterator = query.iterator();
|
||||
return closing(StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, 0), false))
|
||||
.onClose(iterator::close);
|
||||
.onClose(iterator::close)
|
||||
.map(this.delegateProducer);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -19,9 +19,10 @@ package org.keycloak.models.map.storage.hotRod;
|
|||
|
||||
import org.keycloak.models.map.common.AbstractEntity;
|
||||
import org.keycloak.models.map.common.DeepCloner;
|
||||
import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity;
|
||||
import org.keycloak.models.map.storage.hotRod.common.HotRodEntityDelegate;
|
||||
import org.keycloak.models.map.storage.hotRod.common.HotRodEntityDescriptor;
|
||||
import org.keycloak.models.map.common.StringKeyConvertor;
|
||||
import org.keycloak.models.map.common.UpdatableEntity;
|
||||
import org.keycloak.models.map.storage.hotRod.connections.HotRodConnectionProvider;
|
||||
import org.keycloak.models.map.storage.MapStorage;
|
||||
import org.keycloak.models.map.storage.MapStorageProvider;
|
||||
|
@ -46,8 +47,8 @@ public class HotRodMapStorageProvider implements MapStorageProvider {
|
|||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <V extends AbstractEntity & UpdatableEntity, M> HotRodMapStorage<String, V, M> getHotRodStorage(Class<M> modelType, MapStorageProviderFactory.Flag... flags) {
|
||||
HotRodEntityDescriptor<V> entityDescriptor = (HotRodEntityDescriptor<V>) factory.getEntityDescriptor(modelType);
|
||||
public <E extends AbstractHotRodEntity, V extends HotRodEntityDelegate<E>, M> HotRodMapStorage<String, E, V, M> getHotRodStorage(Class<M> modelType, MapStorageProviderFactory.Flag... flags) {
|
||||
HotRodEntityDescriptor<E, V> entityDescriptor = (HotRodEntityDescriptor<E, V>) factory.getEntityDescriptor(modelType);
|
||||
return new HotRodMapStorage<>(connectionProvider.getRemoteCache(entityDescriptor.getCacheName()), StringKeyConvertor.StringKey.INSTANCE, entityDescriptor, cloner);
|
||||
}
|
||||
|
||||
|
|
|
@ -24,10 +24,9 @@ import org.keycloak.component.AmphibianProviderFactory;
|
|||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.map.storage.hotRod.client.HotRodAttributeEntity;
|
||||
import org.keycloak.models.map.storage.hotRod.client.HotRodClientEntity;
|
||||
import org.keycloak.models.map.storage.hotRod.client.HotRodProtocolMapperEntity;
|
||||
import org.keycloak.models.map.storage.hotRod.common.HotRodPair;
|
||||
import org.keycloak.models.map.storage.hotRod.client.HotRodClientEntityDelegate;
|
||||
import org.keycloak.models.map.storage.hotRod.client.HotRodProtocolMapperEntityDelegate;
|
||||
import org.keycloak.models.map.client.MapClientEntity;
|
||||
import org.keycloak.models.map.client.MapProtocolMapperEntity;
|
||||
import org.keycloak.models.map.common.DeepCloner;
|
||||
|
@ -37,7 +36,6 @@ import org.keycloak.models.map.storage.MapStorageProvider;
|
|||
import org.keycloak.models.map.storage.MapStorageProviderFactory;
|
||||
import org.keycloak.provider.EnvironmentDependentProviderFactory;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
|
@ -47,18 +45,17 @@ public class HotRodMapStorageProviderFactory implements AmphibianProviderFactory
|
|||
private static final Logger LOG = Logger.getLogger(HotRodMapStorageProviderFactory.class);
|
||||
|
||||
private final static DeepCloner CLONER = new DeepCloner.Builder()
|
||||
.constructorDC(MapClientEntity.class, HotRodClientEntity::new)
|
||||
.constructor(MapProtocolMapperEntity.class, HotRodProtocolMapperEntity::new)
|
||||
.constructorDC(MapClientEntity.class, HotRodClientEntityDelegate::new)
|
||||
.constructor(MapProtocolMapperEntity.class, HotRodProtocolMapperEntityDelegate::new)
|
||||
.build();
|
||||
|
||||
public static final Map<Class<?>, HotRodEntityDescriptor<?>> ENTITY_DESCRIPTOR_MAP = new HashMap<>();
|
||||
public static final Map<Class<?>, HotRodEntityDescriptor<?, ?>> ENTITY_DESCRIPTOR_MAP = new HashMap<>();
|
||||
static {
|
||||
// Clients descriptor
|
||||
ENTITY_DESCRIPTOR_MAP.put(ClientModel.class,
|
||||
new HotRodEntityDescriptor<>(ClientModel.class,
|
||||
MapClientEntity.class,
|
||||
Arrays.asList(HotRodClientEntity.class, HotRodAttributeEntity.class, HotRodProtocolMapperEntity.class, HotRodPair.class),
|
||||
"clients"));
|
||||
HotRodClientEntity.class,
|
||||
HotRodClientEntityDelegate::new));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -72,7 +69,7 @@ public class HotRodMapStorageProviderFactory implements AmphibianProviderFactory
|
|||
return new HotRodMapStorageProvider(this, cacheProvider, CLONER);
|
||||
}
|
||||
|
||||
public HotRodEntityDescriptor<?> getEntityDescriptor(Class<?> c) {
|
||||
public HotRodEntityDescriptor<?, ?> getEntityDescriptor(Class<?> c) {
|
||||
return ENTITY_DESCRIPTOR_MAP.get(c);
|
||||
}
|
||||
|
||||
|
|
|
@ -18,25 +18,26 @@
|
|||
package org.keycloak.models.map.storage.hotRod.client;
|
||||
|
||||
import org.infinispan.protostream.annotations.ProtoField;
|
||||
import org.keycloak.models.map.client.MapClientEntity;
|
||||
import org.keycloak.models.map.client.MapProtocolMapperEntity;
|
||||
import org.keycloak.models.map.common.DeepCloner;
|
||||
import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation;
|
||||
import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity;
|
||||
import org.keycloak.models.map.storage.hotRod.common.HotRodEntityDelegate;
|
||||
import org.keycloak.models.map.storage.hotRod.common.HotRodPair;
|
||||
import org.keycloak.models.map.storage.hotRod.common.Versioned;
|
||||
import org.keycloak.models.map.client.MapClientEntity;
|
||||
import org.keycloak.models.map.common.UpdatableEntity;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class HotRodClientEntity implements MapClientEntity, Versioned {
|
||||
|
||||
@GenerateHotRodEntityImplementation(
|
||||
implementInterface = "org.keycloak.models.map.client.MapClientEntity",
|
||||
inherits = "org.keycloak.models.map.storage.hotRod.client.HotRodClientEntity.AbstractHotRodClientEntityDelegate"
|
||||
)
|
||||
public class HotRodClientEntity implements AbstractHotRodEntity {
|
||||
|
||||
@ProtoField(number = 1, required = true)
|
||||
public int entityVersion = 1;
|
||||
|
@ -57,7 +58,7 @@ public class HotRodClientEntity implements MapClientEntity, Versioned {
|
|||
public String description;
|
||||
|
||||
@ProtoField(number = 7)
|
||||
public Set<String> redirectUris = new HashSet<>();
|
||||
public Set<String> redirectUris;
|
||||
|
||||
@ProtoField(number = 8)
|
||||
public Boolean enabled;
|
||||
|
@ -78,10 +79,10 @@ public class HotRodClientEntity implements MapClientEntity, Versioned {
|
|||
public String protocol;
|
||||
|
||||
@ProtoField(number = 14)
|
||||
public Set<HotRodAttributeEntity> attributes = new HashSet<>();
|
||||
public Set<HotRodAttributeEntity> attributes;
|
||||
|
||||
@ProtoField(number = 15)
|
||||
public Set<HotRodPair<String, String>> authFlowBindings = new HashSet<>();
|
||||
public Set<HotRodPair<String, String>> authenticationFlowBindingOverrides;
|
||||
|
||||
@ProtoField(number = 16)
|
||||
public Boolean publicClient;
|
||||
|
@ -96,19 +97,19 @@ public class HotRodClientEntity implements MapClientEntity, Versioned {
|
|||
public Integer notBefore;
|
||||
|
||||
@ProtoField(number = 20)
|
||||
public Set<String> scope = new HashSet<>();
|
||||
public Set<String> scope;
|
||||
|
||||
@ProtoField(number = 21)
|
||||
public Set<String> webOrigins = new HashSet<>();
|
||||
public Set<String> webOrigins;
|
||||
|
||||
@ProtoField(number = 22)
|
||||
public Set<HotRodProtocolMapperEntity> protocolMappers = new HashSet<>();
|
||||
public Set<HotRodProtocolMapperEntity> protocolMappers;
|
||||
|
||||
@ProtoField(number = 23)
|
||||
public Set<HotRodPair<String, Boolean>> clientScopes = new HashSet<>();
|
||||
public Set<HotRodPair<String, Boolean>> clientScopes;
|
||||
|
||||
@ProtoField(number = 24)
|
||||
public Set<String> scopeMappings = new HashSet<>();
|
||||
@ProtoField(number = 24, collectionImplementation = LinkedList.class)
|
||||
public Collection<String> scopeMappings;
|
||||
|
||||
@ProtoField(number = 25)
|
||||
public Boolean surrogateAuthRequired;
|
||||
|
@ -143,572 +144,37 @@ public class HotRodClientEntity implements MapClientEntity, Versioned {
|
|||
@ProtoField(number = 35)
|
||||
public Integer nodeReRegistrationTimeout;
|
||||
|
||||
private boolean updated = false;
|
||||
public static abstract class AbstractHotRodClientEntityDelegate extends UpdatableEntity.Impl implements HotRodEntityDelegate<HotRodClientEntity>, MapClientEntity {
|
||||
|
||||
private final DeepCloner cloner;
|
||||
|
||||
public HotRodClientEntity() {
|
||||
this(DeepCloner.DUMB_CLONER);
|
||||
}
|
||||
|
||||
public HotRodClientEntity(DeepCloner cloner) {
|
||||
this.cloner = cloner;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getEntityVersion() {
|
||||
return entityVersion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getAttribute(String name) {
|
||||
return attributes.stream()
|
||||
.filter(attributeEntity -> Objects.equals(attributeEntity.getName(), name))
|
||||
.findFirst()
|
||||
.map(HotRodAttributeEntity::getValues)
|
||||
.orElse(Collections.emptyList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, List<String>> getAttributes() {
|
||||
return attributes.stream().collect(Collectors.toMap(HotRodAttributeEntity::getName, HotRodAttributeEntity::getValues));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttribute(String name, List<String> values) {
|
||||
boolean valueUndefined = values == null || values.isEmpty();
|
||||
|
||||
Optional<HotRodAttributeEntity> first = attributes.stream()
|
||||
.filter(attributeEntity -> Objects.equals(attributeEntity.getName(), name))
|
||||
.findFirst();
|
||||
|
||||
if (first.isPresent()) {
|
||||
HotRodAttributeEntity attributeEntity = first.get();
|
||||
if (valueUndefined) {
|
||||
this.updated = true;
|
||||
removeAttribute(name);
|
||||
} else {
|
||||
this.updated |= !Objects.equals(attributeEntity.getValues(), values);
|
||||
attributeEntity.setValues(values);
|
||||
}
|
||||
|
||||
return;
|
||||
@Override
|
||||
public String getId() {
|
||||
return getHotRodEntity().id;
|
||||
}
|
||||
|
||||
// do not create attributes if empty or null
|
||||
if (valueUndefined) {
|
||||
return;
|
||||
@Override
|
||||
public void setId(String id) {
|
||||
HotRodClientEntity entity = getHotRodEntity();
|
||||
if (entity.id != null) throw new IllegalStateException("Id cannot be changed");
|
||||
entity.id = id;
|
||||
this.updated |= id != null;
|
||||
}
|
||||
|
||||
HotRodAttributeEntity newAttributeEntity = new HotRodAttributeEntity(name, values);
|
||||
updated |= attributes.add(newAttributeEntity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAttribute(String name) {
|
||||
attributes.stream()
|
||||
.filter(attributeEntity -> Objects.equals(attributeEntity.getName(), name))
|
||||
.findFirst()
|
||||
.ifPresent(attr -> {
|
||||
this.updated |= attributes.remove(attr);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClientId() {
|
||||
return clientId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClientId(String clientId) {
|
||||
this.updated |= ! Objects.equals(this.clientId, clientId);
|
||||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setName(String name) {
|
||||
this.updated |= ! Objects.equals(this.name, name);
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDescription(String description) {
|
||||
this.updated |= ! Objects.equals(this.description, description);
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getRedirectUris() {
|
||||
return redirectUris;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRedirectUris(Set<String> redirectUris) {
|
||||
if (redirectUris == null || redirectUris.isEmpty()) {
|
||||
this.updated |= !this.redirectUris.isEmpty();
|
||||
this.redirectUris.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
this.updated |= ! Objects.equals(this.redirectUris, redirectUris);
|
||||
this.redirectUris.clear();
|
||||
this.redirectUris.addAll(redirectUris);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnabled(Boolean enabled) {
|
||||
this.updated |= ! Objects.equals(this.enabled, enabled);
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean isAlwaysDisplayInConsole() {
|
||||
return alwaysDisplayInConsole;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlwaysDisplayInConsole(Boolean alwaysDisplayInConsole) {
|
||||
this.updated |= ! Objects.equals(this.alwaysDisplayInConsole, alwaysDisplayInConsole);
|
||||
this.alwaysDisplayInConsole = alwaysDisplayInConsole;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClientAuthenticatorType() {
|
||||
return clientAuthenticatorType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClientAuthenticatorType(String clientAuthenticatorType) {
|
||||
this.updated |= ! Objects.equals(this.clientAuthenticatorType, clientAuthenticatorType);
|
||||
this.clientAuthenticatorType = clientAuthenticatorType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSecret() {
|
||||
return secret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSecret(String secret) {
|
||||
this.updated |= ! Objects.equals(this.secret, secret);
|
||||
this.secret = secret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRegistrationToken() {
|
||||
return registrationToken;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRegistrationToken(String registrationToken) {
|
||||
this.updated |= ! Objects.equals(this.registrationToken, registrationToken);
|
||||
this.registrationToken = registrationToken;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProtocol() {
|
||||
return protocol;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProtocol(String protocol) {
|
||||
this.updated |= ! Objects.equals(this.protocol, protocol);
|
||||
this.protocol = protocol;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getAuthFlowBindings() {
|
||||
return authFlowBindings.stream().collect(Collectors.toMap(HotRodPair::getFirst, HotRodPair::getSecond));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAuthFlowBindings(Map<String, String> authFlowBindings) {
|
||||
if (authFlowBindings == null || authFlowBindings.isEmpty()) {
|
||||
this.updated |= !this.authFlowBindings.isEmpty();
|
||||
this.authFlowBindings.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
this.updated = true;
|
||||
this.authFlowBindings.clear();
|
||||
this.authFlowBindings.addAll(authFlowBindings.entrySet().stream().map(e -> new HotRodPair<>(e.getKey(), e.getValue())).collect(Collectors.toSet()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean isPublicClient() {
|
||||
return publicClient;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPublicClient(Boolean publicClient) {
|
||||
this.updated |= ! Objects.equals(this.publicClient, publicClient);
|
||||
this.publicClient = publicClient;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRealmId(String realmId) {
|
||||
this.realmId = realmId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean isFullScopeAllowed() {
|
||||
return fullScopeAllowed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFullScopeAllowed(Boolean fullScopeAllowed) {
|
||||
this.updated |= ! Objects.equals(this.fullScopeAllowed, fullScopeAllowed);
|
||||
this.fullScopeAllowed = fullScopeAllowed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean isFrontchannelLogout() {
|
||||
return frontchannelLogout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFrontchannelLogout(Boolean frontchannelLogout) {
|
||||
this.updated |= ! Objects.equals(this.frontchannelLogout, frontchannelLogout);
|
||||
this.frontchannelLogout = frontchannelLogout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getNotBefore() {
|
||||
return notBefore;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNotBefore(Integer notBefore) {
|
||||
this.updated |= ! Objects.equals(this.notBefore, notBefore);
|
||||
this.notBefore = notBefore;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getScope() {
|
||||
return scope;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScope(Set<String> scope) {
|
||||
if (scope == null || scope.isEmpty()) {
|
||||
this.updated |= !this.scope.isEmpty();
|
||||
this.scope.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
this.updated |= ! Objects.equals(this.scope, scope);
|
||||
this.scope.clear();
|
||||
this.scope.addAll(scope);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getWebOrigins() {
|
||||
return webOrigins;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setWebOrigins(Set<String> webOrigins) {
|
||||
if (webOrigins == null || webOrigins.isEmpty()) {
|
||||
this.updated |= !this.webOrigins.isEmpty();
|
||||
this.webOrigins.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
this.updated |= ! Objects.equals(this.webOrigins, webOrigins);
|
||||
this.webOrigins.clear();
|
||||
this.webOrigins.addAll(webOrigins);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, MapProtocolMapperEntity> getProtocolMappers() {
|
||||
return protocolMappers.stream().collect(Collectors.toMap(HotRodProtocolMapperEntity::getId, Function.identity()));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public MapProtocolMapperEntity getProtocolMapper(String id) {
|
||||
return protocolMappers.stream().filter(hotRodMapper -> Objects.equals(hotRodMapper.getId(), id)).findFirst().orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProtocolMapper(String id, MapProtocolMapperEntity mapping) {
|
||||
removeProtocolMapper(id);
|
||||
|
||||
protocolMappers.add((HotRodProtocolMapperEntity) cloner.from(mapping)); // Workaround, will be replaced by cloners
|
||||
this.updated = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeProtocolMapper(String id) {
|
||||
protocolMappers.stream().filter(entity -> Objects.equals(id, entity.id))
|
||||
.findFirst()
|
||||
.ifPresent(entity -> {
|
||||
protocolMappers.remove(entity);
|
||||
updated = true;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean isSurrogateAuthRequired() {
|
||||
return surrogateAuthRequired;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSurrogateAuthRequired(Boolean surrogateAuthRequired) {
|
||||
this.updated |= ! Objects.equals(this.surrogateAuthRequired, surrogateAuthRequired);
|
||||
this.surrogateAuthRequired = surrogateAuthRequired;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getManagementUrl() {
|
||||
return managementUrl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setManagementUrl(String managementUrl) {
|
||||
this.updated |= ! Objects.equals(this.managementUrl, managementUrl);
|
||||
this.managementUrl = managementUrl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRootUrl() {
|
||||
return rootUrl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRootUrl(String rootUrl) {
|
||||
this.updated |= ! Objects.equals(this.rootUrl, rootUrl);
|
||||
this.rootUrl = rootUrl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBaseUrl() {
|
||||
return baseUrl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBaseUrl(String baseUrl) {
|
||||
this.updated |= ! Objects.equals(this.baseUrl, baseUrl);
|
||||
this.baseUrl = baseUrl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean isBearerOnly() {
|
||||
return bearerOnly;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBearerOnly(Boolean bearerOnly) {
|
||||
this.updated |= ! Objects.equals(this.bearerOnly, bearerOnly);
|
||||
this.bearerOnly = bearerOnly;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean isConsentRequired() {
|
||||
return consentRequired;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setConsentRequired(Boolean consentRequired) {
|
||||
this.updated |= ! Objects.equals(this.consentRequired, consentRequired);
|
||||
this.consentRequired = consentRequired;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean isStandardFlowEnabled() {
|
||||
return standardFlowEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStandardFlowEnabled(Boolean standardFlowEnabled) {
|
||||
this.updated |= ! Objects.equals(this.standardFlowEnabled, standardFlowEnabled);
|
||||
this.standardFlowEnabled = standardFlowEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean isImplicitFlowEnabled() {
|
||||
return implicitFlowEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setImplicitFlowEnabled(Boolean implicitFlowEnabled) {
|
||||
this.updated |= ! Objects.equals(this.implicitFlowEnabled, implicitFlowEnabled);
|
||||
this.implicitFlowEnabled = implicitFlowEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean isDirectAccessGrantsEnabled() {
|
||||
return directAccessGrantsEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDirectAccessGrantsEnabled(Boolean directAccessGrantsEnabled) {
|
||||
this.updated |= ! Objects.equals(this.directAccessGrantsEnabled, directAccessGrantsEnabled);
|
||||
this.directAccessGrantsEnabled = directAccessGrantsEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean isServiceAccountsEnabled() {
|
||||
return serviceAccountsEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setServiceAccountsEnabled(Boolean serviceAccountsEnabled) {
|
||||
this.updated |= ! Objects.equals(this.serviceAccountsEnabled, serviceAccountsEnabled);
|
||||
this.serviceAccountsEnabled = serviceAccountsEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getNodeReRegistrationTimeout() {
|
||||
return nodeReRegistrationTimeout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNodeReRegistrationTimeout(Integer nodeReRegistrationTimeout) {
|
||||
this.updated |= ! Objects.equals(this.nodeReRegistrationTimeout, nodeReRegistrationTimeout);
|
||||
this.nodeReRegistrationTimeout = nodeReRegistrationTimeout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addWebOrigin(String webOrigin) {
|
||||
updated = true;
|
||||
this.webOrigins.add(webOrigin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeWebOrigin(String webOrigin) {
|
||||
updated |= this.webOrigins.remove(webOrigin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addRedirectUri(String redirectUri) {
|
||||
this.updated |= ! this.redirectUris.contains(redirectUri);
|
||||
this.redirectUris.add(redirectUri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeRedirectUri(String redirectUri) {
|
||||
updated |= this.redirectUris.remove(redirectUri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAuthenticationFlowBindingOverride(String binding) {
|
||||
return authFlowBindings.stream().filter(pair -> Objects.equals(pair.getFirst(), binding)).findFirst()
|
||||
.map(HotRodPair::getSecond)
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getAuthenticationFlowBindingOverrides() {
|
||||
return this.authFlowBindings.stream().collect(Collectors.toMap(HotRodPair::getFirst, HotRodPair::getSecond));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAuthenticationFlowBindingOverride(String binding) {
|
||||
this.authFlowBindings.stream().filter(pair -> Objects.equals(pair.getFirst(), binding)).findFirst()
|
||||
.ifPresent(pair -> {
|
||||
updated = true;
|
||||
authFlowBindings.remove(pair);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAuthenticationFlowBindingOverride(String binding, String flowId) {
|
||||
this.updated = true;
|
||||
|
||||
removeAuthenticationFlowBindingOverride(binding);
|
||||
|
||||
this.authFlowBindings.add(new HotRodPair<>(binding, flowId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getScopeMappings() {
|
||||
return scopeMappings;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addScopeMapping(String id) {
|
||||
if (id != null) {
|
||||
updated = true;
|
||||
scopeMappings.add(id);
|
||||
@Override
|
||||
public Stream<String> getClientScopes(boolean defaultScope) {
|
||||
final Map<String, Boolean> clientScopes = getClientScopes();
|
||||
return clientScopes == null ? Stream.empty() : clientScopes.entrySet().stream()
|
||||
.filter(me -> Objects.equals(me.getValue(), defaultScope))
|
||||
.map(Map.Entry::getKey);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeScopeMapping(String id) {
|
||||
updated |= scopeMappings.remove(id);
|
||||
public boolean equals(Object o) {
|
||||
return HotRodClientEntityDelegate.entityEquals(this, o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Boolean> getClientScopes() {
|
||||
return this.clientScopes.stream().collect(Collectors.toMap(HotRodPair::getFirst, HotRodPair::getSecond));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClientScope(String id, Boolean defaultScope) {
|
||||
if (id != null) {
|
||||
updated = true;
|
||||
removeClientScope(id);
|
||||
|
||||
this.clientScopes.add(new HotRodPair<>(id, defaultScope));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeClientScope(String id) {
|
||||
this.clientScopes.stream().filter(pair -> Objects.equals(pair.getFirst(), id)).findFirst()
|
||||
.ifPresent(pair -> {
|
||||
updated = true;
|
||||
clientScopes.remove(pair);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<String> getClientScopes(boolean defaultScope) {
|
||||
return this.clientScopes.stream()
|
||||
.filter(pair -> Objects.equals(pair.getSecond(), defaultScope))
|
||||
.map(HotRodPair::getFirst);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRealmId() {
|
||||
return this.realmId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setId(String id) {
|
||||
if (this.id != null) throw new IllegalStateException("Id cannot be changed");
|
||||
this.id = id;
|
||||
this.updated |= id != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearUpdatedFlag() {
|
||||
this.updated = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUpdated() {
|
||||
return updated;
|
||||
public int hashCode() {
|
||||
return HotRodClientEntityDelegate.entityHashCode(this);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,16 +18,15 @@
|
|||
package org.keycloak.models.map.storage.hotRod.client;
|
||||
|
||||
import org.infinispan.protostream.annotations.ProtoField;
|
||||
import org.keycloak.models.map.client.MapProtocolMapperEntity;
|
||||
import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation;
|
||||
import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity;
|
||||
import org.keycloak.models.map.storage.hotRod.common.HotRodPair;
|
||||
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class HotRodProtocolMapperEntity implements MapProtocolMapperEntity {
|
||||
@GenerateHotRodEntityImplementation(implementInterface = "org.keycloak.models.map.client.MapProtocolMapperEntity")
|
||||
public class HotRodProtocolMapperEntity implements AbstractHotRodEntity {
|
||||
@ProtoField(number = 1)
|
||||
public String id;
|
||||
@ProtoField(number = 2)
|
||||
|
@ -41,73 +40,15 @@ public class HotRodProtocolMapperEntity implements MapProtocolMapperEntity {
|
|||
// @ProtoField(number = 5)
|
||||
// public String consentText;
|
||||
@ProtoField(number = 5)
|
||||
public Set<HotRodPair<String, String>> config = new LinkedHashSet<>();
|
||||
|
||||
private boolean updated;
|
||||
public Set<HotRodPair<String, String>> config;
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
HotRodProtocolMapperEntity entity = (HotRodProtocolMapperEntity) o;
|
||||
|
||||
return id.equals(entity.id);
|
||||
return HotRodProtocolMapperEntityDelegate.entityEquals(this, o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return id.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setId(String id) {
|
||||
updated |= !Objects.equals(this.id, id);
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setName(String name) {
|
||||
updated |= !Objects.equals(this.name, name);
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProtocolMapper() {
|
||||
return protocolMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProtocolMapper(String protocolMapper) {
|
||||
updated |= !Objects.equals(this.protocolMapper, protocolMapper);
|
||||
this.protocolMapper = protocolMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getConfig() {
|
||||
return config.stream().collect(Collectors.toMap(HotRodPair::getFirst, HotRodPair::getSecond));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setConfig(Map<String, String> config) {
|
||||
updated |= !Objects.equals(this.config, config);
|
||||
this.config.clear();
|
||||
|
||||
config.entrySet().stream().map(entry -> new HotRodPair<>(entry.getKey(), entry.getValue())).forEach(this.config::add);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUpdated() {
|
||||
return updated;
|
||||
return HotRodProtocolMapperEntityDelegate.entityHashCode(this);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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.hotRod.common;
|
||||
|
||||
public interface AbstractHotRodEntity {
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* 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.hotRod.common;
|
||||
|
||||
import org.keycloak.models.map.common.AbstractEntity;
|
||||
import org.keycloak.models.map.common.UpdatableEntity;
|
||||
|
||||
public interface HotRodEntityDelegate<E> extends AbstractEntity, UpdatableEntity {
|
||||
E getHotRodEntity();
|
||||
}
|
|
@ -17,35 +17,34 @@
|
|||
|
||||
package org.keycloak.models.map.storage.hotRod.common;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
import org.keycloak.models.map.storage.ModelEntityUtil;
|
||||
|
||||
public class HotRodEntityDescriptor<EntityType> {
|
||||
import java.util.function.Function;
|
||||
|
||||
public class HotRodEntityDescriptor<E, D extends HotRodEntityDelegate<E>> {
|
||||
private final Class<?> modelTypeClass;
|
||||
private final Class<EntityType> entityTypeClass;
|
||||
private final List<Class<?>> hotRodClasses;
|
||||
private final String cacheName;
|
||||
private final Class<E> entityTypeClass;
|
||||
private final Function<E, D> hotRodDelegateProvider;
|
||||
|
||||
public HotRodEntityDescriptor(Class<?> modelTypeClass, Class<EntityType> entityTypeClass, List<Class<?>> hotRodClasses, String cacheName) {
|
||||
public HotRodEntityDescriptor(Class<?> modelTypeClass, Class<E> entityTypeClass, Function<E, D> hotRodDelegateProvider) {
|
||||
this.modelTypeClass = modelTypeClass;
|
||||
this.entityTypeClass = entityTypeClass;
|
||||
this.hotRodClasses = hotRodClasses;
|
||||
this.cacheName = cacheName;
|
||||
this.hotRodDelegateProvider = hotRodDelegateProvider;
|
||||
}
|
||||
|
||||
public Class<?> getModelTypeClass() {
|
||||
return modelTypeClass;
|
||||
}
|
||||
|
||||
public Class<EntityType> getEntityTypeClass() {
|
||||
public Class<E> getEntityTypeClass() {
|
||||
return entityTypeClass;
|
||||
}
|
||||
|
||||
public Stream<Class<?>> getHotRodClasses() {
|
||||
return hotRodClasses.stream();
|
||||
public String getCacheName() {
|
||||
return ModelEntityUtil.getModelName(modelTypeClass);
|
||||
}
|
||||
|
||||
public String getCacheName() {
|
||||
return cacheName;
|
||||
public Function<E, D> getHotRodDelegateProvider() {
|
||||
return hotRodDelegateProvider;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,33 +20,48 @@ package org.keycloak.models.map.storage.hotRod.common;
|
|||
import org.infinispan.protostream.WrappedMessage;
|
||||
import org.infinispan.protostream.annotations.ProtoField;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class HotRodPair<T, V> {
|
||||
|
||||
@ProtoField(number = 1)
|
||||
public WrappedMessage firstWrapped;
|
||||
public WrappedMessage key;
|
||||
@ProtoField(number = 2)
|
||||
public WrappedMessage secondWrapped;
|
||||
public WrappedMessage value;
|
||||
|
||||
public HotRodPair() {}
|
||||
|
||||
public HotRodPair(T first, V second) {
|
||||
this.firstWrapped = new WrappedMessage(first);
|
||||
this.secondWrapped = new WrappedMessage(second);
|
||||
this.key = new WrappedMessage(first);
|
||||
this.value = new WrappedMessage(second);
|
||||
}
|
||||
|
||||
public T getFirst() {
|
||||
return firstWrapped == null ? null : (T) firstWrapped.getValue();
|
||||
public T getKey() {
|
||||
return key == null ? null : (T) key.getValue();
|
||||
}
|
||||
|
||||
public V getSecond() {
|
||||
return secondWrapped == null ? null : (V) secondWrapped.getValue();
|
||||
public V getValue() {
|
||||
return value == null ? null : (V) value.getValue();
|
||||
}
|
||||
|
||||
public void setFirst(T first) {
|
||||
this.firstWrapped = new WrappedMessage(first);
|
||||
public void setKey(T first) {
|
||||
this.key = new WrappedMessage(first);
|
||||
}
|
||||
|
||||
public void setSecond(V second) {
|
||||
this.secondWrapped = new WrappedMessage(second);
|
||||
public void setValue(V second) {
|
||||
this.value = new WrappedMessage(second);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
HotRodPair<?, ?> that = (HotRodPair<?, ?>) o;
|
||||
return Objects.equals(key, that.key) && Objects.equals(value, that.value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(key, value);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* 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.hotRod.common;
|
||||
|
||||
import org.keycloak.models.map.common.AbstractEntity;
|
||||
import org.keycloak.models.map.storage.hotRod.client.HotRodAttributeEntity;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class HotRodTypesUtils {
|
||||
|
||||
public static <MapKey, MapValue, SetValue> Set<SetValue> migrateMapToSet(Map<MapKey, MapValue> map, Function<Map.Entry<MapKey, MapValue>, SetValue> creator) {
|
||||
return map == null ? null : map.entrySet()
|
||||
.stream()
|
||||
.map(creator)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
public static <MapKey, MapValue, SetValue> Map<MapKey, MapValue> migrateSetToMap(Set<SetValue> set, Function<SetValue, MapKey> keyProducer, Function<SetValue, MapValue> valueProducer) {
|
||||
return set == null ? null : set.stream().collect(Collectors.toMap(keyProducer, valueProducer));
|
||||
}
|
||||
|
||||
public static <T, V> HotRodPair<T, V> createHotRodPairFromMapEntry(Map.Entry<T, V> entry) {
|
||||
return new HotRodPair<>(entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
public static <SetType, KeyType> boolean removeFromSetByMapKey(Set<SetType> set, KeyType key, Function<SetType, KeyType> keyGetter) {
|
||||
if (set == null || set.isEmpty()) { return false; }
|
||||
return set.stream()
|
||||
.filter(entry -> Objects.equals(keyGetter.apply(entry), key))
|
||||
.findFirst()
|
||||
.map(set::remove)
|
||||
.orElse(false);
|
||||
}
|
||||
|
||||
public static <SetType, MapKey, MapValue> MapValue getMapValueFromSet(Set<SetType> set, MapKey key, Function<SetType, MapKey> keyGetter, Function<SetType, MapValue> valueGetter) {
|
||||
return set == null ? null : set.stream().filter(entry -> Objects.equals(keyGetter.apply(entry), key)).findFirst().map(valueGetter).orElse(null);
|
||||
}
|
||||
|
||||
public static <K, V> K getKey(HotRodPair<K, V> hotRodPair) {
|
||||
return hotRodPair.getKey();
|
||||
}
|
||||
|
||||
public static <K, V> V getValue(HotRodPair<K, V> hotRodPair) {
|
||||
return hotRodPair.getValue();
|
||||
}
|
||||
|
||||
public static String getKey(HotRodAttributeEntity attributeEntity) {
|
||||
return attributeEntity.name;
|
||||
}
|
||||
|
||||
public static List<String> getValue(HotRodAttributeEntity attributeEntity) {
|
||||
return attributeEntity.values;
|
||||
}
|
||||
|
||||
public static String getKey(AbstractEntity entity) {
|
||||
return entity.getId();
|
||||
}
|
||||
}
|
|
@ -1,22 +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.storage.hotRod.common;
|
||||
|
||||
public interface Versioned {
|
||||
int getEntityVersion();
|
||||
}
|
|
@ -20,6 +20,7 @@ package org.keycloak.models.map.storage.hotRod;
|
|||
import org.junit.Test;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.map.storage.hotRod.client.HotRodClientEntity;
|
||||
import org.keycloak.models.map.storage.hotRod.client.HotRodClientEntityDelegate;
|
||||
import org.keycloak.models.map.storage.ModelCriteriaBuilder;
|
||||
import org.keycloak.models.map.storage.criteria.DefaultModelCriteria;
|
||||
|
||||
|
@ -36,8 +37,8 @@ import static org.keycloak.models.map.storage.criteria.DefaultModelCriteria.crit
|
|||
public class IckleQueryMapModelCriteriaBuilderTest {
|
||||
@Test
|
||||
public void testSimpleIckleQuery() {
|
||||
IckleQueryMapModelCriteriaBuilder<String, HotRodClientEntity, ClientModel> v = new IckleQueryMapModelCriteriaBuilder<>();
|
||||
IckleQueryMapModelCriteriaBuilder<String, HotRodClientEntity, ClientModel> mcb = v.compare(CLIENT_ID, ModelCriteriaBuilder.Operator.EQ, 3);
|
||||
IckleQueryMapModelCriteriaBuilder<String, HotRodClientEntityDelegate, ClientModel> v = new IckleQueryMapModelCriteriaBuilder<>();
|
||||
IckleQueryMapModelCriteriaBuilder<String, HotRodClientEntityDelegate, ClientModel> mcb = v.compare(CLIENT_ID, ModelCriteriaBuilder.Operator.EQ, 3);
|
||||
assertThat(mcb.getIckleQuery(), is(equalTo("FROM org.keycloak.models.map.storage.hotrod.HotRodClientEntity c WHERE (c.clientId = :clientId0)")));
|
||||
assertThat(mcb.getParameters().entrySet(), hasSize(1));
|
||||
assertThat(mcb.getParameters(), hasEntry("clientId0", 3));
|
||||
|
@ -55,7 +56,7 @@ public class IckleQueryMapModelCriteriaBuilderTest {
|
|||
@Test
|
||||
public void testSimpleIckleQueryFlashedFromDefault() {
|
||||
DefaultModelCriteria<ClientModel> v = criteria();
|
||||
IckleQueryMapModelCriteriaBuilder<String, HotRodClientEntity, ClientModel> mcb = v.compare(CLIENT_ID, ModelCriteriaBuilder.Operator.EQ, 3).flashToModelCriteriaBuilder(new IckleQueryMapModelCriteriaBuilder<>());
|
||||
IckleQueryMapModelCriteriaBuilder<String, HotRodClientEntityDelegate, ClientModel> mcb = v.compare(CLIENT_ID, ModelCriteriaBuilder.Operator.EQ, 3).flashToModelCriteriaBuilder(new IckleQueryMapModelCriteriaBuilder<>());
|
||||
assertThat(mcb.getIckleQuery(), is(equalTo("FROM org.keycloak.models.map.storage.hotrod.HotRodClientEntity c WHERE (c.clientId = :clientId0)")));
|
||||
assertThat(mcb.getParameters().entrySet(), hasSize(1));
|
||||
assertThat(mcb.getParameters(), hasEntry("clientId0", 3));
|
||||
|
|
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
* 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.hotRod.common;
|
||||
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.CoreMatchers.nullValue;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||
import static org.hamcrest.Matchers.hasEntry;
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.keycloak.models.map.storage.hotRod.common.HotRodTypesUtils.getMapValueFromSet;
|
||||
import static org.keycloak.models.map.storage.hotRod.common.HotRodTypesUtils.migrateMapToSet;
|
||||
import static org.keycloak.models.map.storage.hotRod.common.HotRodTypesUtils.migrateSetToMap;
|
||||
import static org.keycloak.models.map.storage.hotRod.common.HotRodTypesUtils.removeFromSetByMapKey;
|
||||
|
||||
public class HotRodTypesUtilsTest {
|
||||
|
||||
@Test
|
||||
public void testMigrateMapToSet() {
|
||||
// Test null map
|
||||
assertThat(migrateMapToSet((Map<String, String>) null, Map.Entry::getKey), nullValue());
|
||||
|
||||
Map<String, String> m = new HashMap<>();
|
||||
m.put("key", "value");
|
||||
|
||||
assertThat(migrateMapToSet(m, e -> e.getKey() + "#" + e.getValue()),
|
||||
contains("key#value"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMigrateSetToMap() {
|
||||
// Test null map
|
||||
assertThat(migrateSetToMap((Set<String>) null, Function.identity(), Function.identity()), nullValue());
|
||||
|
||||
Set<String> s = new HashSet<>();
|
||||
s.add("key#value");
|
||||
|
||||
Map<String, String> result = HotRodTypesUtils.migrateSetToMap(s, e -> e.split("#")[0], e -> e.split("#")[1]);
|
||||
|
||||
assertThat(result.keySet(), hasSize(1));
|
||||
assertThat(result, hasEntry("key", "value"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveFromSetByMapKey() {
|
||||
assertThat(removeFromSetByMapKey((Set<String>) null, null, Function.identity()), is(false));
|
||||
assertThat(removeFromSetByMapKey(Collections.emptySet(), null, Function.identity()), is(false));
|
||||
|
||||
Set<String> s = new HashSet<>();
|
||||
s.add("key#value");
|
||||
s.add("key1#value1");
|
||||
s.add("key2#value2");
|
||||
|
||||
// Remove existing
|
||||
Set<String> testSet = new HashSet<>(s);
|
||||
assertThat(removeFromSetByMapKey(testSet, "key", e -> e.split("#")[0]), is(true));
|
||||
assertThat(testSet, hasSize(2));
|
||||
assertThat(testSet, containsInAnyOrder("key1#value1", "key2#value2"));
|
||||
|
||||
// Remove not existing
|
||||
testSet = new HashSet<>(s);
|
||||
assertThat(removeFromSetByMapKey(testSet, "key3", e -> e.split("#")[0]), is(false));
|
||||
assertThat(testSet, hasSize(3));
|
||||
assertThat(testSet, containsInAnyOrder("key#value", "key1#value1", "key2#value2"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetMapValueFromSet() {
|
||||
assertThat(getMapValueFromSet((Set<String>) null, null, Function.identity(), Function.identity()), nullValue());
|
||||
assertThat(getMapValueFromSet(Collections.emptySet(), "key", Function.identity(), Function.identity()), nullValue());
|
||||
|
||||
Set<String> s = new HashSet<>();
|
||||
s.add("key#value");
|
||||
s.add("key1#value1");
|
||||
s.add("key2#value2");
|
||||
|
||||
// search existing
|
||||
assertThat(getMapValueFromSet(s, "key", e -> e.split("#")[0], e -> e.split("#")[1]), is("value"));
|
||||
assertThat(getMapValueFromSet(s, "key1", e -> e.split("#")[0], e -> e.split("#")[1]), is("value1"));
|
||||
|
||||
// Search not existing
|
||||
assertThat(getMapValueFromSet(s, "key3", e -> e.split("#")[0], e -> e.split("#")[1]), nullValue());
|
||||
}
|
||||
}
|
|
@ -92,9 +92,6 @@ public interface MapClientEntity extends AbstractEntity, UpdatableEntity {
|
|||
void removeAttribute(String name);
|
||||
void setAttribute(String name, List<String> values);
|
||||
|
||||
Map<String, String> getAuthFlowBindings();
|
||||
void setAuthFlowBindings(Map<String, String> authFlowBindings);
|
||||
|
||||
String getAuthenticationFlowBindingOverride(String binding);
|
||||
Map<String, String> getAuthenticationFlowBindingOverrides();
|
||||
void removeAuthenticationFlowBindingOverride(String binding);
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
package org.keycloak.models.map.client;
|
||||
|
||||
import org.keycloak.models.map.annotations.GenerateEntityImplementations;
|
||||
import org.keycloak.models.map.common.AbstractEntity;
|
||||
import org.keycloak.models.map.common.DeepCloner;
|
||||
import org.keycloak.models.map.common.UpdatableEntity;
|
||||
import java.util.Map;
|
||||
|
@ -27,10 +28,7 @@ import java.util.Map;
|
|||
*/
|
||||
@GenerateEntityImplementations
|
||||
@DeepCloner.Root
|
||||
public interface MapProtocolMapperEntity extends UpdatableEntity {
|
||||
|
||||
String getId();
|
||||
void setId(String id);
|
||||
public interface MapProtocolMapperEntity extends UpdatableEntity, AbstractEntity {
|
||||
|
||||
String getName();
|
||||
void setName(String name);
|
||||
|
|
1
pom.xml
1
pom.xml
|
@ -79,6 +79,7 @@
|
|||
<hibernate.c3p0.version>5.3.20.Final</hibernate.c3p0.version>
|
||||
<infinispan.version>11.0.9.Final</infinispan.version>
|
||||
<infinispan.protostream.processor.version>4.3.4.Final</infinispan.protostream.processor.version>
|
||||
<javax.annotation-api.version>1.3.2</javax.annotation-api.version>
|
||||
<jackson.version>2.12.1</jackson.version>
|
||||
<jackson.databind.version>${jackson.version}</jackson.databind.version>
|
||||
<jackson.annotations.version>${jackson.databind.version}</jackson.annotations.version>
|
||||
|
|
Loading…
Reference in a new issue