Remove map related modules

Signed-off-by: vramik <vramik@redhat.com>

Closes #24100
This commit is contained in:
vramik 2023-11-03 10:44:56 +01:00 committed by Alexander Schwartz
parent 8acb6c1845
commit 926be135e8
605 changed files with 44 additions and 69299 deletions

View file

@ -50,11 +50,6 @@
</dependency>
<!-- Include all classes that are marked "provided" and therefore not included in the dependencies above.
This avoids warnings when generating the JavaDoc -->
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-model-build-processor</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging-annotations</artifactId>

View file

@ -1,20 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>keycloak-model-pom</artifactId>
<groupId>org.keycloak</groupId>
<version>999.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>keycloak-model-build-processor</artifactId>
<name>Keycloak Model Java Annotations and Processor</name>
<description/>
<properties>
<maven.compiler.release>11</maven.compiler.release>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
</project>

View file

@ -1,41 +0,0 @@
/*
* Copyright 2023 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;
/**
* Determines getter of a field which is unique across a set of the same entities within the same context.
* This field can be used as unique key in map-like access to a collection. For example, in the set of
* user consents, this can be client ID.
*
* @author hmlnarik
*/
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.METHOD)
public @interface CollectionKey {
/**
* Priority of this annotation: The higher the value, the more appropriate the annotation is.
* @return
*/
public int priority() default 0;
}

View file

@ -1,32 +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.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
*
* @author hmlnarik
*/
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface GenerateEntityImplementations {
String inherits() default "org.keycloak.models.map.common.UpdatableEntity.Impl";
}

View file

@ -1,35 +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.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.storage.hotRod.common.UpdatableHotRodEntityDelegateImpl";
boolean topLevelEntity() default false;
String modelClass() default "";
String cacheName() default "";
}

View file

@ -1,43 +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.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Specifies the default implementation with a no-args constructor for
* a container property (e.g. a {@code} List} or a {@code Map}).
* <p>
* Applicable to a setter of a single key from the map (e.g. {@code setAttribute}) or an adder to
* a collection (e.g. {@code addWebOrigin}). This is used to override default type generated by the
* generator in case the entry does not exist yet and a new container needs to be instantiated.
*
* Example:
* <pre>
* @GeneratedFieldType(HashSet) void addWebOrigin() { ... }
* </pre>
*
* @author hmlnarik
*/
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.METHOD)
public @interface GeneratedFieldType {
Class<?> value() default Void.class;
}

View file

@ -1,31 +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.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
*
* @author hmlnarik
*/
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
public @interface IgnoreForEntityImplementationGenerator {
}

View file

@ -1,37 +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.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();
}
}

View file

@ -1,340 +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.processor;
import org.keycloak.models.map.annotations.CollectionKey;
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.TypeKind;
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.io.PrintWriter;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
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 javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import static org.keycloak.models.map.processor.FieldAccessorType.GETTER;
import static org.keycloak.models.map.processor.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.singularToPlural;
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public abstract class AbstractGenerateEntityImplementationsProcessor extends AbstractProcessor {
protected static final String FQN_DEEP_CLONER = "org.keycloak.models.map.common.DeepCloner";
protected static final String FQN_ENTITY_FIELD = "org.keycloak.models.map.common.EntityField";
protected static final String FQN_HAS_ENTITY_FIELD_DELEGATE = "org.keycloak.models.map.common.delegate.HasEntityFieldDelegate";
protected static final String FQN_ENTITY_FIELD_DELEGATE = "org.keycloak.models.map.common.delegate.EntityFieldDelegate";
protected Elements elements;
protected 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;
}
public ExecutableElement getCollectionKey(TypeMirror fieldType, ExecutableElement callingMethod) {
if (! Util.isCollectionType(elements.getTypeElement(types.erasure(fieldType).toString()))) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Invalid collection type: " + fieldType, callingMethod);
return null;
}
TypeMirror collectionType = getGenericsDeclaration(fieldType).get(0);
TypeElement collectionTypeEl = elements.getTypeElement(types.erasure(collectionType).toString());
Iterator<ExecutableElement> it = elements.getAllMembers(collectionTypeEl).stream()
.filter(el -> el.getKind() == ElementKind.METHOD)
.filter(el -> el.getAnnotation(CollectionKey.class) != null)
.sorted(Comparator.comparing((Element el) -> el.getAnnotation(CollectionKey.class).priority()).reversed())
.filter(ExecutableElement.class::isInstance)
.map(ExecutableElement.class::cast)
.iterator();
ExecutableElement res = null;
if (it.hasNext()) {
res = it.next();
if (! res.getParameters().isEmpty() || ! "java.lang.String".equals(res.getReturnType().toString())) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Invalid getter annotated with @CollectionKey in " + res, callingMethod);
}
if (it.hasNext() && it.next().getAnnotation(CollectionKey.class).priority() == res.getAnnotation(CollectionKey.class).priority()) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Multiple getters annotated with @CollectionKey found: " + res + ", " + it.next(), callingMethod);
}
} else {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "No getters annotated with @CollectionKey in " + collectionType, callingMethod);
}
return res;
}
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 Stream<ExecutableElement> getAllAbstractMethods(TypeElement e) {
return elements.getAllMembers(e).stream()
.filter(el -> el.getKind() == ElementKind.METHOD)
.filter(el -> el.getModifiers().contains(Modifier.ABSTRACT))
.filter(ExecutableElement.class::isInstance)
.map(ExecutableElement.class::cast);
}
protected Map<String, HashSet<ExecutableElement>> methodsPerAttributeMapping(TypeElement e) {
Map<String, HashSet<ExecutableElement>> methodsPerAttribute = getAllAbstractMethods(e)
.filter(Util::isNotIgnored)
.filter(ee -> !(ee.getReceiverType() instanceof NoType && ee.getReceiverType().getKind() != TypeKind.NONE))
.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(singularToPlural(key)))
.collect(Collectors.toSet())
.forEach(key -> {
HashSet<ExecutableElement> removed = methodsPerAttribute.remove(key);
methodsPerAttribute.get(singularToPlural(key)).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");
}
protected 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)
|| isEnumType(fieldType)
|| Objects.equals("java.lang.String", fieldType.toString());
}
protected boolean isKnownCollectionOfImmutableFinalTypes(TypeMirror fieldType) {
List<TypeMirror> res = getGenericsDeclaration(fieldType);
return isCollection(fieldType) && res.stream().allMatch(this::isImmutableFinalType);
}
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) {
TypeElement typeElement = elements.getTypeElement(types.erasure(fieldType).toString());
if (isKnownCollectionOfImmutableFinalTypes(fieldType)) {
return parameterName + " == null ? null : " + interfaceToImplementation(typeElement, parameterName);
} else if (isMapType(typeElement)) {
List<TypeMirror> mapTypes = getGenericsDeclaration(fieldType);
boolean isKeyImmutable = isImmutableFinalType(mapTypes.get(0));
boolean isValueImmutable = isImmutableFinalType(mapTypes.get(1));
return parameterName + " == null ? null : " + parameterName + ".entrySet().stream().collect(" +
"java.util.stream.Collectors.toMap(" +
(isKeyImmutable ? "java.util.Map.Entry::getKey" : "entry -> " + deepClone(mapTypes.get(0), "entry.getKey()")) +
", " +
(isValueImmutable ? "java.util.Map.Entry::getValue" : "entry -> " + deepClone(mapTypes.get(1), "entry.getValue()")) +
", (o1, o2) -> o1" +
", java.util.HashMap::new" +
"))";
} else if (isCollection(typeElement.asType())) {
TypeMirror collectionType = getGenericsDeclaration(fieldType).get(0);
return parameterName + " == null ? null : " + parameterName + ".stream().map(entry -> " + deepClone(collectionType, "entry") + ").collect(java.util.stream.Collectors.toCollection(" + (isSetType(typeElement) ? "java.util.HashSet::new" : "java.util.LinkedList::new") + "))";
}
return "deepClone(" + parameterName + ")";
}
protected String removeUndefined(TypeMirror fieldType, String parameterName) {
TypeElement typeElement = elements.getTypeElement(types.erasure(fieldType).toString());
boolean isMapType = isMapType(typeElement);
return parameterName + (isMapType ? ".values()" : "") + ".removeIf(org.keycloak.models.map.common.UndefinedValuesUtils::isUndefined)";
}
protected String isUndefined(String parameterName) {
return "org.keycloak.models.map.common.UndefinedValuesUtils.isUndefined(" + parameterName + ")";
}
protected boolean isEnumType(TypeMirror fieldType) {
return types.asElement(fieldType).getKind() == ElementKind.ENUM;
}
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 void generatedAnnotation(final PrintWriter pw) {
pw.println("@javax.annotation.processing.Generated(\"" + getClass().getName() + "\")");
}
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;
}
}
}

View file

@ -1,147 +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.processor;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.regex.Pattern;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Name;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Types;
import static org.keycloak.models.map.processor.Util.getGenericsDeclaration;
import static org.keycloak.models.map.processor.Util.pluralToSingular;
/**
*
* @author hmlnarik
*/
enum FieldAccessorType {
// Order does matter, see {@link #getMethod}
GETTER {
@Override
public boolean is(ExecutableElement method, String fieldName, Types types, TypeMirror fieldType) {
Pattern getter = Pattern.compile("(get|is)" + Pattern.quote(fieldName));
Name methodName = method.getSimpleName();
return getter.matcher(methodName).matches() && method.getParameters().isEmpty() && types.isSameType(fieldType, method.getReturnType());
}
},
SETTER {
@Override
public boolean is(ExecutableElement method, String fieldName, Types types, TypeMirror fieldType) {
String methodName = "set" + fieldName;
return Objects.equals(methodName, method.getSimpleName().toString())
&& method.getParameters().size() == 1
&& types.isSameType(fieldType, method.getParameters().get(0).asType());
}
},
COLLECTION_ADD {
@Override
public boolean is(ExecutableElement method, String fieldName, Types types, TypeMirror fieldType) {
String fieldNameSingular = pluralToSingular(fieldName);
String methodName = "add" + fieldNameSingular;
List<TypeMirror> res = getGenericsDeclaration(fieldType);
return Objects.equals(methodName, method.getSimpleName().toString())
&& res.size() == 1
&& types.isSameType(res.get(0), method.getParameters().get(0).asType());
}
},
COLLECTION_DELETE {
@Override
public boolean is(ExecutableElement method, String fieldName, Types types, TypeMirror fieldType) {
String fieldNameSingular = pluralToSingular(fieldName);
String removeFromCollection = "remove" + fieldNameSingular;
List<TypeMirror> res = getGenericsDeclaration(fieldType);
return Objects.equals(removeFromCollection, method.getSimpleName().toString())
&& method.getParameters().size() == 1
&& types.isSameType(res.get(0), method.getParameters().get(0).asType());
}
},
MAP_ADD {
@Override
public boolean is(ExecutableElement method, String fieldName, Types types, TypeMirror fieldType) {
String fieldNameSingular = pluralToSingular(fieldName);
String methodName = "set" + fieldNameSingular;
List<TypeMirror> res = getGenericsDeclaration(fieldType);
return Objects.equals(methodName, method.getSimpleName().toString())
&& res.size() == 2
&& types.isSameType(res.get(0), method.getParameters().get(0).asType())
&& types.isSameType(res.get(1), method.getParameters().get(1).asType());
}
},
MAP_GET {
@Override
public boolean is(ExecutableElement method, String fieldName, Types types, TypeMirror fieldType) {
String fieldNameSingular = pluralToSingular(fieldName);
String methodName = "get" + fieldNameSingular;
List<TypeMirror> res = getGenericsDeclaration(fieldType);
return Objects.equals(methodName, method.getSimpleName().toString())
&& res.size() == 2
&& types.isSameType(res.get(0), method.getParameters().get(0).asType());
}
},
COLLECTION_GET_BY_ID {
@Override
public boolean is(ExecutableElement method, String fieldName, Types types, TypeMirror fieldType) {
String fieldNameSingular = pluralToSingular(fieldName);
String getFromCollection = "get" + fieldNameSingular;
List<TypeMirror> res = getGenericsDeclaration(fieldType);
return Objects.equals(getFromCollection, method.getSimpleName().toString())
&& method.getParameters().size() == 1
&& res.size() == 1
&& Objects.equals("java.lang.String", method.getParameters().get(0).asType().toString());
}
},
COLLECTION_DELETE_BY_ID {
@Override
public boolean is(ExecutableElement method, String fieldName, Types types, TypeMirror fieldType) {
String fieldNameSingular = pluralToSingular(fieldName);
String removeFromCollection = "remove" + fieldNameSingular;
return Objects.equals(removeFromCollection, method.getSimpleName().toString())
&& method.getParameters().size() == 1
&& Objects.equals("java.lang.String", method.getParameters().get(0).asType().toString());
}
},
UNKNOWN /* Must be the last */ {
@Override
public boolean is(ExecutableElement method, String fieldName, Types types, TypeMirror fieldType) {
return true;
}
}
;
public abstract boolean is(ExecutableElement method, String fieldName, Types types, TypeMirror fieldType);
public static Optional<ExecutableElement> getMethod(FieldAccessorType type,
HashSet<ExecutableElement> methods, String fieldName, Types types, TypeMirror fieldType) {
return methods.stream().filter(ee -> type.is(ee, fieldName, types, fieldType)).findAny();
}
public static FieldAccessorType determineType(ExecutableElement method, String fieldName, Types types, TypeMirror fieldType) {
for (FieldAccessorType fat : values()) {
if (fat.is(method, fieldName, types, fieldType)) {
return fat;
}
}
return UNKNOWN;
}
}

View file

@ -1,867 +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.processor;
import org.keycloak.models.map.annotations.GenerateEntityImplementations;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
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.type.TypeMirror;
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.isSetType;
import static org.keycloak.models.map.processor.Util.methodParameters;
import java.util.Collection;
import java.util.Comparator;
import java.util.IdentityHashMap;
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;
/**
*
* @author hmlnarik
*/
@SupportedAnnotationTypes("org.keycloak.models.map.annotations.GenerateEntityImplementations")
@SupportedSourceVersion(SourceVersion.RELEASE_11)
public class GenerateEntityImplementationsProcessor extends AbstractGenerateEntityImplementationsProcessor {
private static final Collection<String> autogenerated = new TreeSet<>();
private static final String ID_FIELD_NAME = "Id";
private final Generator[] generators = new Generator[] {
new ClonerGenerator(),
new DelegateGenerator(),
new FieldsGenerator(),
new FieldDelegateGenerator(),
new ImplGenerator(),
};
@Override
protected void afterAnnotationProcessing() {
if (! autogenerated.isEmpty()) {
try {
JavaFileObject file = processingEnv.getFiler().createSourceFile("org.keycloak.models.map.common.AutogeneratedClasses");
try (PrintWriter pw = new PrintWriterNoJavaLang(file.openWriter())) {
pw.println("package org.keycloak.models.map.common;");
pw.println("import " + FQN_DEEP_CLONER + ".Cloner;");
pw.println("import " + FQN_DEEP_CLONER + ".DelegateCreator;");
pw.println("import java.util.function.Function;");
pw.println("import " + FQN_DEEP_CLONER + ".EntityFieldDelegateCreator;");
pw.println("// DO NOT CHANGE THIS CLASS, IT IS GENERATED AUTOMATICALLY BY " + GenerateEntityImplementationsProcessor.class.getSimpleName());
generatedAnnotation(pw);
pw.println("public final class AutogeneratedClasses {");
pw.println(" public static final java.util.Map<Class<?>, Cloner<?>> CLONERS_WITH_ID = new java.util.HashMap<>();");
pw.println(" public static final java.util.Map<Class<?>, Cloner<?>> CLONERS_WITHOUT_ID = new java.util.HashMap<>();");
pw.println(" public static final java.util.Map<Class<?>, DelegateCreator<?>> DELEGATE_CREATORS = new java.util.HashMap<>();");
pw.println(" public static final java.util.Map<Class<?>, EntityFieldDelegateCreator<?>> ENTITY_FIELD_DELEGATE_CREATORS = new java.util.HashMap<>();");
pw.println(" public static final java.util.Map<Class<?>, Object> EMPTY_INSTANCES = new java.util.HashMap<>();");
pw.println(" public static final java.util.Map<Class<?>, EntityField<?>[]> ENTITY_FIELDS = new java.util.HashMap<>();");
pw.println(" public static final java.util.Map<Class<?>, Function<DeepCloner, ?>> CONSTRUCTORS_DC = new java.util.HashMap<>();");
pw.println(" static {");
autogenerated.forEach(pw::println);
pw.println(" }");
pw.println("}");
}
} catch (IOException ex) {
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;
}
protected static String toEnumConstant(String key) {
return key.replaceAll("([a-z])([A-Z])", "$1_$2").toUpperCase();
}
private class FieldsGenerator implements Generator {
@Override
public void generate(TypeElement e) throws IOException {
Map<String, HashSet<ExecutableElement>> methodsPerAttribute = methodsPerAttributeMapping(e);
String className = e.getQualifiedName().toString();
String packageName = null;
int lastDot = className.lastIndexOf('.');
if (lastDot > 0) {
packageName = className.substring(0, lastDot);
}
String simpleClassName = className.substring(lastDot + 1);
String mapFieldsClassName = className + "Fields";
String mapSimpleFieldsClassName = simpleClassName + "Fields";
JavaFileObject file = processingEnv.getFiler().createSourceFile(mapFieldsClassName);
try (PrintWriter pw = new PrintWriterNoJavaLang(file.openWriter())) {
if (packageName != null) {
pw.println("package " + packageName + ";");
}
generatedAnnotation(pw);
pw.println("public enum " + mapSimpleFieldsClassName + " implements " + FQN_ENTITY_FIELD + "<" + className + "> {");
methodsPerAttribute.keySet().stream()
.sorted(NameFirstComparator.ID_INSTANCE)
.forEach(key -> {
pw.println(" " + toEnumConstant(key) + " {");
printEntityFieldMethods(pw, className, key, methodsPerAttribute.get(key));
pw.println(" },");
});
pw.println("}");
autogenerated.add(" ENTITY_FIELDS.put(" + className + ".class, " + mapFieldsClassName + ".values());");
}
}
private void printEntityFieldMethods(PrintWriter pw, String className, String fieldName, HashSet<ExecutableElement> methods) {
TypeMirror fieldType = determineFieldType(fieldName, methods);
pw.println(" public static final String FIELD_NAME = \"" + fieldName + "\";");
pw.println(" public static final String FIELD_NAME_DASHED = \"" + fieldName.replaceAll("([^_A-Z])([A-Z])", "$1-$2").toLowerCase() + "\";");
pw.println(" public static final String FIELD_NAME_CAMEL_CASE = \"" + fieldName.substring(0, 1).toLowerCase() + fieldName.substring(1) + "\";");
pw.println(" @SuppressWarnings(\"unchecked\") @Override public Class<?> getFieldClass() {");
pw.println(" return " + types.erasure(fieldType) + ".class;");
pw.println(" }");
pw.println(" @Override public String getName() {");
pw.println(" return FIELD_NAME;");
pw.println(" }");
pw.println(" @Override public String getNameDashed() {");
pw.println(" return FIELD_NAME_DASHED;");
pw.println(" }");
pw.println(" @Override public String getNameCamelCase() {");
pw.println(" return FIELD_NAME_CAMEL_CASE;");
pw.println(" }");
FieldAccessorType.getMethod(FieldAccessorType.GETTER, methods, fieldName, types, fieldType).ifPresent(method -> {
if (Util.isCollectionType((TypeElement) types.asElement(types.erasure(fieldType)))) {
TypeMirror firstParameterType = Util.getGenericsDeclaration(method.getReturnType()).get(0);
pw.println(" @SuppressWarnings(\"unchecked\") @Override public Class<?> getCollectionElementClass() {");
pw.println(" return " + types.erasure(firstParameterType) + ".class;");
pw.println(" }");
} else if (Util.isMapType((TypeElement) types.asElement(types.erasure(fieldType)))) {
TypeMirror firstParameterType = Util.getGenericsDeclaration(method.getReturnType()).get(0);
TypeMirror secondParameterType = Util.getGenericsDeclaration(method.getReturnType()).get(1);
pw.println(" @SuppressWarnings(\"unchecked\") @Override public Class<?> getMapKeyClass() {");
pw.println(" return " + types.erasure(firstParameterType) + ".class;");
pw.println(" }");
pw.println(" @SuppressWarnings(\"unchecked\") @Override public Class<?> getMapValueClass() {");
pw.println(" return " + types.erasure(secondParameterType) + ".class;");
pw.println(" }");
}
});
for (ExecutableElement ee : methods) {
FieldAccessorType fat = FieldAccessorType.determineType(ee, fieldName, types, fieldType);
printMethodBody(pw, fat, ee, className, fieldType);
}
}
private void printMethodBody(PrintWriter pw, FieldAccessorType accessorType, ExecutableElement method, String className, TypeMirror fieldType) {
TypeMirror firstParameterType = method.getParameters().isEmpty()
? types.getNullType()
: method.getParameters().get(0).asType();
switch (accessorType) {
case GETTER:
pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " get(" + className + " e) {");
pw.println(" return (" + fieldType + ") e." + method.getSimpleName() + "();");
pw.println(" }");
return;
case SETTER:
pw.println(" @SuppressWarnings(\"unchecked\") @Override public <T> void set(" + className + " e, T value) {");
pw.println(" e." + method.getSimpleName() + "((" + firstParameterType + ") value);");
pw.println(" }");
return;
case COLLECTION_ADD:
pw.println(" @SuppressWarnings(\"unchecked\") @Override public <T> void collectionAdd(" + className + " e, T value) {");
pw.println(" e." + method.getSimpleName() + "((" + firstParameterType + ") value);");
pw.println(" }");
return;
case COLLECTION_DELETE:
{
String returnType = method.getReturnType().getKind() == TypeKind.VOID ? "Void" : method.getReturnType().toString();
TypeElement fieldTypeElement = elements.getTypeElement(types.erasure(fieldType).toString());
if (Util.isMapType(fieldTypeElement)) {
pw.println(" @SuppressWarnings(\"unchecked\") @Override public <K> " + returnType + " mapRemove(" + className + " e, K p0) {");
} else {
pw.println(" @SuppressWarnings(\"unchecked\") @Override public <T> " + returnType + " collectionRemove(" + className + " e, T p0) {");
}
if (method.getReturnType().getKind() == TypeKind.VOID) {
pw.println(" e." + method.getSimpleName() + "((" + firstParameterType + ") p0); return null;");
} else {
pw.println(" return e." + method.getSimpleName() + "((" + firstParameterType + ") p0);");
}
pw.println(" }");
return;
}
case COLLECTION_DELETE_BY_ID:
{
String returnType = method.getReturnType().getKind() == TypeKind.VOID ? "Void" : method.getReturnType().toString();
pw.println(" @SuppressWarnings(\"unchecked\") @Override public <K> " + returnType + " mapRemove(" + className + " e, K p0) {");
if (method.getReturnType().getKind() == TypeKind.VOID) {
pw.println(" e." + method.getSimpleName() + "((String) p0); return null;");
} else {
pw.println(" return e." + method.getSimpleName() + "((String) p0);");
}
pw.println(" }");
return;
}
case COLLECTION_GET_BY_ID:
pw.println(" @SuppressWarnings(\"unchecked\") @Override public <K> " + method.getReturnType() + " mapGet(" + className + " e, K key) {");
pw.println(" return e." + method.getSimpleName() + "((" + firstParameterType + ") key);");
pw.println(" }");
return;
case MAP_ADD:
TypeMirror secondParameterType = method.getParameters().get(1).asType();
pw.println(" @SuppressWarnings(\"unchecked\") @Override public <K, T> void mapPut(" + className + " e, K key, T value) {");
pw.println(" e." + method.getSimpleName() + "((" + firstParameterType + ") key, (" + secondParameterType + ") value);");
pw.println(" }");
return;
case MAP_GET:
pw.println(" @SuppressWarnings(\"unchecked\") @Override public <K> " + method.getReturnType() + " mapGet(" + className + " e, K key) {");
pw.println(" return (" + method.getReturnType() + ") e." + method.getSimpleName() + "((" + firstParameterType + ") key);");
pw.println(" }");
}
}
}
private class ImplGenerator implements Generator {
@Override
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) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Unable to find type " + an.inherits() + " for inherits parameter for annotation " + GenerateEntityImplementations.class.getTypeName(), e);
}
final List<? extends Element> allParentMembers = elements.getAllMembers(parentTypeElement);
String className = e.getQualifiedName().toString();
String packageName = null;
int lastDot = className.lastIndexOf('.');
if (lastDot > 0) {
packageName = className.substring(0, lastDot);
}
String simpleClassName = className.substring(lastDot + 1);
String mapImplClassName = className + "Impl";
String mapSimpleClassName = simpleClassName + "Impl";
boolean hasId = methodsPerAttribute.containsKey(ID_FIELD_NAME) || allParentMembers.stream().anyMatch(el -> "getId".equals(el.getSimpleName().toString()));
boolean hasDeepClone = allParentMembers.stream().filter(el -> el.getKind() == ElementKind.METHOD).anyMatch(el -> "deepClone".equals(el.getSimpleName().toString()));
boolean needsDeepClone = fieldGetters(methodsPerAttribute)
.map(ExecutableElement::getReturnType)
.anyMatch(fieldType -> ! isKnownCollectionOfImmutableFinalTypes(fieldType) && ! isImmutableFinalType(fieldType));
boolean usingGeneratedCloner = ! hasDeepClone && needsDeepClone;
JavaFileObject file = processingEnv.getFiler().createSourceFile(mapImplClassName);
try (PrintWriter pw = new PrintWriterNoJavaLang(file.openWriter())) {
if (packageName != null) {
pw.println("package " + packageName + ";");
}
pw.println("import java.util.Objects;");
pw.println("import java.util.Optional;");
pw.println("import " + FQN_DEEP_CLONER + ";");
pw.println("// DO NOT CHANGE THIS CLASS, IT IS GENERATED AUTOMATICALLY BY " + GenerateEntityImplementationsProcessor.class.getSimpleName());
generatedAnnotation(pw);
pw.println("public class " + mapSimpleClassName + (an.inherits().isEmpty() ? "" : " extends " + an.inherits()) + " implements " + className + " {");
pw.println(" "
+ "private "
+ mapSimpleClassName + "() { this(DeepCloner.DUMB_CLONER); } // Nullary constructor only for Jackson deserialization"
);
pw.println(" "
+ "public "
+ mapSimpleClassName + "(DeepCloner cloner) { super(); " + (!usingGeneratedCloner ? "" : "this.cloner = cloner;") + "}"
);
// equals, hashCode, toString
pw.println(" @Override public boolean equals(Object o) {");
pw.println(" if (o == this) return true; ");
pw.println(" if (! (o instanceof " + mapSimpleClassName + ")) return false; ");
pw.println(" " + mapSimpleClassName + " other = (" + mapSimpleClassName + ") o; ");
pw.println(" return "
+ 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(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()" : "\"" + mapSimpleClassName + "\"" ) + ", System.identityHashCode(this));");
pw.println(" }");
// deepClone
if (usingGeneratedCloner) {
pw.println(" private final DeepCloner cloner;");
pw.println(" public <V> V deepClone(V obj) {");
pw.println(" return cloner.from(obj);");
pw.println(" }");
}
// fields, getters, setters
methodsPerAttribute.entrySet().stream().sorted(Comparator.comparing(Map.Entry::getKey, NameFirstComparator.ID_INSTANCE)).forEach(me -> {
HashSet<ExecutableElement> methods = me.getValue();
TypeMirror fieldType = determineFieldType(me.getKey(), methods);
if (fieldType == null) {
return;
}
pw.println("");
pw.println(" private " + fieldType + " f" + me.getKey() + ";");
for (ExecutableElement method : methods) {
FieldAccessorType fat = FieldAccessorType.determineType(method, me.getKey(), types, fieldType);
Optional<ExecutableElement> parentMethod = Util.findParentMethodImplementation(allParentMembers, method);
if (parentMethod.isPresent()) {
processingEnv.getMessager().printMessage(Kind.OTHER, "Method " + method + " is declared in a parent class.", method);
} else if (fat == FieldAccessorType.UNKNOWN || ! printMethodBody(pw, fat, method, "f" + me.getKey(), fieldType)) {
processingEnv.getMessager().printMessage(Kind.WARNING, "Could not determine desired semantics of method from its signature", method);
}
}
});
// Read-only class overrides setters to be no-op
pw.println(" public static class Empty " + (an.inherits().isEmpty() ? "" : " extends " + an.inherits()) + " implements " + className + " {");
pw.println(" public static final Empty INSTANCE = new Empty();");
methodsPerAttribute.entrySet().stream().sorted(Comparator.comparing(Map.Entry::getKey, NameFirstComparator.ID_INSTANCE))
.map(Map.Entry::getValue)
.flatMap(Collection::stream)
.forEach(ee -> {
pw.println(" @Override "
+ ee.getModifiers().stream().filter(m -> m != Modifier.ABSTRACT).map(Object::toString).collect(Collectors.joining(" "))
+ " " + ee.getReturnType()
+ " " + ee.getSimpleName()
+ "(" + methodParameters(ee.getParameters()) + ") {");
if (ee.getReturnType().getKind() == TypeKind.VOID) {
pw.println(" }");
} else {
pw.println(" return null;");
pw.println(" }");
}
});
elements.getAllMembers(e).stream()
.filter(ee -> ee.getSimpleName().contentEquals("isUpdated"))
.filter(ExecutableElement.class::isInstance)
.map(ExecutableElement.class::cast)
.filter(ee -> ee.getReturnType().getKind() == TypeKind.BOOLEAN)
.forEach(ee -> {
pw.println(" @Override "
+ ee.getModifiers().stream().filter(m -> m != Modifier.ABSTRACT).map(Object::toString).collect(Collectors.joining(" "))
+ " " + ee.getReturnType()
+ " " + ee.getSimpleName()
+ "(" + methodParameters(ee.getParameters()) + ") {");
pw.println(" return false;");
pw.println(" }");
});
pw.println(" }");
autogenerated.add(" EMPTY_INSTANCES.put(" + className + ".class, " + mapImplClassName + ".Empty.INSTANCE);");
autogenerated.add(" CONSTRUCTORS_DC.put(" + className + ".class, " + mapImplClassName + "::new);");
pw.println("}");
}
}
private boolean printMethodBody(PrintWriter pw, FieldAccessorType accessorType, ExecutableElement method, String fieldName, TypeMirror fieldType) {
TypeMirror firstParameterType = method.getParameters().isEmpty()
? types.getNullType()
: method.getParameters().get(0).asType();
TypeElement typeElement = elements.getTypeElement(types.erasure(fieldType).toString());
switch (accessorType) {
case GETTER:
pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method + " {");
pw.println(" return " + fieldName + ";");
pw.println(" }");
return true;
case SETTER:
pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(" + firstParameterType + " p0) {");
if (! isImmutableFinalType(fieldType)) {
pw.println(" p0 = " + deepClone(firstParameterType, "p0") + ";");
}
if (isCollection(firstParameterType)) {
pw.println(" if (p0 != null) {");
pw.println(" " + removeUndefined(firstParameterType, "p0") + ";");
pw.println(" if (" + isUndefined("p0") + ") p0 = null;");
pw.println(" }");
}
pw.println(" updated |= ! Objects.equals(" + fieldName + ", p0);");
pw.println(" " + fieldName + " = p0;");
pw.println(" }");
return true;
case COLLECTION_ADD:
pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(" + firstParameterType + " p0) {");
if (! isImmutableFinalType(firstParameterType)) {
pw.println(" p0 = " + deepClone(firstParameterType, "p0") + ";");
}
if (isCollection(firstParameterType)) {
pw.println(" if (p0 != null) " + removeUndefined(firstParameterType, "p0") + ";");
}
pw.println(" if (" + isUndefined("p0") + ") return;");
pw.println(" if (" + fieldName + " == null) { " + fieldName + " = " + interfaceToImplementation(typeElement, "") + "; }");
if (isSetType(typeElement)) {
pw.println(" updated |= " + fieldName + ".add(p0);");
} else {
pw.println(" " + fieldName + ".add(p0);");
pw.println(" updated = true;");
}
pw.println(" }");
return true;
case COLLECTION_DELETE:
{
boolean needsReturn = method.getReturnType().getKind() != TypeKind.VOID;
pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(" + firstParameterType + " p0) {");
pw.println(" if (" + fieldName + " == null) { return" + (needsReturn ? " false" : "") + "; }");
pw.println(" boolean removed = " + fieldName + ".remove(p0)" + ("java.util.Map".equals(typeElement.getQualifiedName().toString()) ? " != null" : "") + ";");
pw.println(" updated |= removed;");
if (needsReturn) pw.println(" return removed;");
pw.println(" }");
return true;
}
case COLLECTION_DELETE_BY_ID:
{
boolean needsReturn = method.getReturnType().getKind() != TypeKind.VOID;
pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(String p0) {");
pw.println(" boolean removed = " + fieldName + " != null && " + fieldName + ".removeIf(o -> Objects.equals(o." + getCollectionKey(fieldType, method) + ", p0));");
pw.println(" updated |= removed;");
if (needsReturn) pw.println(" return removed;");
pw.println(" }");
return true;
}
case COLLECTION_GET_BY_ID:
{
pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(String p0) {");
pw.println(" if (" + fieldName + " == null || " + fieldName + ".isEmpty()) return Optional.empty();");
pw.println(" return " + fieldName + ".stream().filter(o -> Objects.equals(o." + getCollectionKey(fieldType, method) + ", p0)).findFirst();");
pw.println(" }");
return true;
}
case MAP_ADD:
TypeMirror secondParameterType = method.getParameters().get(1).asType();
pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(" + firstParameterType + " p0, " + secondParameterType + " p1) {");
if (! isImmutableFinalType(secondParameterType)) {
pw.println(" p1 = " + deepClone(secondParameterType, "p1") + ";");
}
if (isCollection(secondParameterType)) {
pw.println(" if (p1 != null) " + removeUndefined(secondParameterType, "p1") + ";");
}
pw.println(" boolean valueUndefined = " + isUndefined("p1") + ";");
pw.println(" if (valueUndefined) { if (" + fieldName + " != null) { updated |= " + fieldName + ".remove(p0) != null; } return; }");
pw.println(" if (" + fieldName + " == null) { " + fieldName + " = " + interfaceToImplementation(typeElement, "") + "; }");
pw.println(" Object v = " + fieldName + ".put(p0, p1);");
pw.println(" updated |= ! Objects.equals(v, p1);");
pw.println(" }");
return true;
case MAP_GET:
pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(" + firstParameterType + " p0) {");
pw.println(" return " + fieldName + " == null ? null : " + fieldName + ".get(p0);");
pw.println(" }");
return true;
}
return false;
}
}
private class FieldDelegateGenerator implements Generator {
@Override
public void generate(TypeElement e) throws IOException {
Map<String, HashSet<ExecutableElement>> methodsPerAttribute = methodsPerAttributeMapping(e);
String className = e.getQualifiedName().toString();
String packageName = null;
int lastDot = className.lastIndexOf('.');
if (lastDot > 0) {
packageName = className.substring(0, lastDot);
}
String simpleClassName = className.substring(lastDot + 1);
String mapClassName = className + "FieldDelegate";
String mapSimpleClassName = simpleClassName + "FieldDelegate";
String fieldsClassName = className + "Fields";
GenerateEntityImplementations an = e.getAnnotation(GenerateEntityImplementations.class);
TypeElement parentTypeElement = elements.getTypeElement((an.inherits() == null || an.inherits().isEmpty()) ? "void" : an.inherits());
if (parentTypeElement == null) {
return;
}
JavaFileObject file = processingEnv.getFiler().createSourceFile(mapClassName);
IdentityHashMap<ExecutableElement, String> m2field = new IdentityHashMap<>();
methodsPerAttribute.forEach((f, s) -> s.forEach(m -> m2field.put(m, f))); // Create reverse map
try (PrintWriter pw = new PrintWriterNoJavaLang(file.openWriter())) {
if (packageName != null) {
pw.println("package " + packageName + ";");
}
generatedAnnotation(pw);
pw.println("public class " + mapSimpleClassName + (an.inherits().isEmpty() ? "" : " extends " + an.inherits()) + " implements " + className + ", " + FQN_HAS_ENTITY_FIELD_DELEGATE + "<" + className + ">" + " {");
pw.println(" private final " + FQN_ENTITY_FIELD_DELEGATE + "<" + className + "> entityFieldDelegate;");
pw.println(" public " + mapSimpleClassName + "(" + FQN_ENTITY_FIELD_DELEGATE + "<" + className + "> entityFieldDelegate) {");
pw.println(" this.entityFieldDelegate = entityFieldDelegate;");
pw.println(" }");
pw.println(" public " + FQN_ENTITY_FIELD_DELEGATE + "<" + className + "> getEntityFieldDelegate() {");
pw.println(" return this.entityFieldDelegate;");
pw.println(" }");
pw.println(" @Override public boolean isUpdated() {");
pw.println(" return entityFieldDelegate.isUpdated();");
pw.println(" }");
pw.println(" @Override public void markUpdatedFlag() {");
pw.println(" entityFieldDelegate.markUpdatedFlag();");
pw.println(" }");
pw.println(" @Override public void clearUpdatedFlag() {");
pw.println(" entityFieldDelegate.clearUpdatedFlag();");
pw.println(" }");
pw.println(" @Override public String toString() {");
pw.println(" return \"%\" + String.valueOf(entityFieldDelegate);");
pw.println(" }");
getAllAbstractMethods(e)
.forEach(ee -> {
String originalField = m2field.get(ee);
if (originalField == null) {
return;
}
TypeMirror fieldType = determineFieldType(originalField, methodsPerAttribute.get(originalField));
String field = fieldsClassName + "." + toEnumConstant(originalField);
FieldAccessorType fat = FieldAccessorType.determineType(ee, originalField, types, fieldType);
printMethodBody(pw, fat, ee, field, fieldType);
});
autogenerated.add(" ENTITY_FIELD_DELEGATE_CREATORS.put(" + className + ".class, (EntityFieldDelegateCreator<" + className + ">) " + mapClassName + "::new);");
pw.println("}");
}
}
private boolean printMethodBody(PrintWriter pw, FieldAccessorType accessorType, ExecutableElement method, String fieldName, TypeMirror fieldType) {
TypeMirror firstParameterType = method.getParameters().isEmpty()
? types.getNullType()
: method.getParameters().get(0).asType();
switch (accessorType) {
case GETTER:
pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method + " {");
pw.println(" return (" + fieldType + ") entityFieldDelegate.get(" + fieldName + ");");
pw.println(" }");
return true;
case SETTER:
pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(" + firstParameterType + " p0) {");
pw.println(" entityFieldDelegate.set(" + fieldName + ", p0);");
pw.println(" }");
return true;
case COLLECTION_ADD:
pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(" + firstParameterType + " p0) {");
pw.println(" entityFieldDelegate.collectionAdd(" + fieldName + ", p0);");
pw.println(" }");
return true;
case COLLECTION_DELETE:
{
pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(" + firstParameterType + " p0) {");
TypeElement fieldTypeElement = elements.getTypeElement(types.erasure(fieldType).toString());
String removeMethod = Util.isMapType(fieldTypeElement) ? "mapRemove" : "collectionRemove";
if (method.getReturnType().getKind() == TypeKind.VOID) {
pw.println(" entityFieldDelegate." + removeMethod + "(" + fieldName + ", p0);");
} else {
pw.println(" return (" + method.getReturnType() + ") entityFieldDelegate." + removeMethod + "(" + fieldName + ", p0);");
}
pw.println(" }");
return true;
}
case COLLECTION_DELETE_BY_ID:
{
pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(String p0) {");
if (method.getReturnType().getKind() == TypeKind.VOID) {
pw.println(" entityFieldDelegate.mapRemove(" + fieldName + ", p0);");
} else {
pw.println(" return (" + method.getReturnType() + ") entityFieldDelegate.mapRemove(" + fieldName + ", p0);");
}
pw.println(" }");
return true;
}
case MAP_ADD:
TypeMirror secondParameterType = method.getParameters().get(1).asType();
pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(" + firstParameterType + " p0, " + secondParameterType + " p1) {");
pw.println(" entityFieldDelegate.mapPut(" + fieldName + ", p0, p1);");
pw.println(" }");
return true;
case COLLECTION_GET_BY_ID:
case MAP_GET:
pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(" + firstParameterType + " p0) {");
pw.println(" return (" + method.getReturnType() + ") entityFieldDelegate.mapGet(" + fieldName + ", p0);");
pw.println(" }");
return true;
}
return false;
}
}
private class DelegateGenerator implements Generator {
@Override
public void generate(TypeElement e) throws IOException {
Map<String, HashSet<ExecutableElement>> methodsPerAttribute = methodsPerAttributeMapping(e);
String className = e.getQualifiedName().toString();
String packageName = null;
int lastDot = className.lastIndexOf('.');
if (lastDot > 0) {
packageName = className.substring(0, lastDot);
}
String simpleClassName = className.substring(lastDot + 1);
String mapClassName = className + "Delegate";
String mapSimpleClassName = simpleClassName + "Delegate";
String fieldsClassName = className + "Fields";
GenerateEntityImplementations an = e.getAnnotation(GenerateEntityImplementations.class);
TypeElement parentTypeElement = elements.getTypeElement((an.inherits() == null || an.inherits().isEmpty()) ? "void" : an.inherits());
if (parentTypeElement == null) {
return;
}
JavaFileObject file = processingEnv.getFiler().createSourceFile(mapClassName);
IdentityHashMap<ExecutableElement, String> m2field = new IdentityHashMap<>();
methodsPerAttribute.forEach((f, s) -> s.forEach(m -> m2field.put(m, f))); // Create reverse map
try (PrintWriter pw = new PrintWriterNoJavaLang(file.openWriter())) {
if (packageName != null) {
pw.println("package " + packageName + ";");
}
generatedAnnotation(pw);
pw.println("public class " + mapSimpleClassName + " implements " + className + ", org.keycloak.models.map.common.delegate.HasDelegateProvider<" + className + "> {");
pw.println(" private final org.keycloak.models.map.common.delegate.DelegateProvider<" + className + "> delegateProvider;");
pw.println(" public " + mapSimpleClassName + "(org.keycloak.models.map.common.delegate.DelegateProvider<" + className + "> delegateProvider) {");
pw.println(" this.delegateProvider = delegateProvider;");
pw.println(" }");
pw.println(" public org.keycloak.models.map.common.delegate.DelegateProvider<" + className + "> getDelegateProvider() {");
pw.println(" return this.delegateProvider;");
pw.println(" }");
pw.println(" @Override public String toString() {");
pw.println(" return \"/\" + String.valueOf(this.delegateProvider);");
pw.println(" }");
getAllAbstractMethods(e)
.forEach(ee -> {
printMethodHeader(pw, ee);
String field = m2field.get(ee);
field = field == null ? "null" : fieldsClassName + "." + toEnumConstant(field);
if (ee.getReturnType().getKind() == TypeKind.BOOLEAN && "isUpdated".equals(ee.getSimpleName().toString())) {
pw.println(" return delegateProvider.isUpdated();");
} else if (ee.getReturnType().getKind() == TypeKind.VOID) { // write operation
pw.println(" delegateProvider.getDelegate(false, "
+ Stream.concat(Stream.of(field), ee.getParameters().stream().map(VariableElement::getSimpleName)).collect(Collectors.joining(", "))
+ ")." + ee.getSimpleName() + "("
+ ee.getParameters().stream().map(VariableElement::getSimpleName).collect(Collectors.joining(", "))
+ ");");
} else {
pw.println(" return delegateProvider.getDelegate(true, "
+ Stream.concat(Stream.of(field), ee.getParameters().stream().map(VariableElement::getSimpleName)).collect(Collectors.joining(", "))
+ ")." + ee.getSimpleName() + "("
+ ee.getParameters().stream().map(VariableElement::getSimpleName).collect(Collectors.joining(", "))
+ ");");
}
pw.println(" }");
});
pw.println("}");
autogenerated.add(" DELEGATE_CREATORS.put(" + className + ".class, (DelegateCreator<" + className + ">) " + mapClassName + "::new);");
}
}
}
protected void printMethodHeader(final PrintWriter pw, ExecutableElement ee) {
pw.println(" @Override "
+ ee.getModifiers().stream().filter(m -> m != Modifier.ABSTRACT).map(Object::toString).collect(Collectors.joining(" "))
+ " " + ee.getReturnType()
+ " " + ee.getSimpleName()
+ "(" + methodParameters(ee.getParameters()) + ") {");
}
private class ClonerGenerator implements Generator {
@Override
public void generate(TypeElement e) throws IOException {
Map<String, HashSet<ExecutableElement>> methodsPerAttribute = methodsPerAttributeMapping(e);
String className = e.getQualifiedName().toString();
String packageName = null;
int lastDot = className.lastIndexOf('.');
if (lastDot > 0) {
packageName = className.substring(0, lastDot);
}
String simpleClassName = className.substring(lastDot + 1);
String clonerImplClassName = className + "Cloner";
String clonerSimpleClassName = simpleClassName + "Cloner";
JavaFileObject enumFile = processingEnv.getFiler().createSourceFile(clonerImplClassName);
try (PrintWriter pw = new PrintWriter(enumFile.openWriter()) {
@Override
public void println(String x) {
super.println(x == null ? x : x.replaceAll("java.lang.", ""));
}
}) {
if (packageName != null) {
pw.println("package " + packageName + ";");
}
pw.println("import " + FQN_DEEP_CLONER + ";");
pw.println("// DO NOT CHANGE THIS CLASS, IT IS GENERATED AUTOMATICALLY BY " + GenerateEntityImplementationsProcessor.class.getSimpleName());
generatedAnnotation(pw);
pw.println("public class " + clonerSimpleClassName + " {");
if (methodsPerAttribute.containsKey(ID_FIELD_NAME)) {
pw.println(" public static " + className + " deepClone(" + className + " original, " + className + " target) {");
// If the entity has an ID, set the ID first and then set all other attributes.
// This was important when working with Jpa storage as the ID is the one field needed to persist an entity.
HashSet<ExecutableElement> idMethods = methodsPerAttribute.get(ID_FIELD_NAME);
TypeMirror idFieldType = determineFieldType(ID_FIELD_NAME, idMethods);
cloneField(e, ID_FIELD_NAME, idMethods, idFieldType, pw);
pw.println(" return deepCloneNoId(original, target);");
pw.println(" }");
autogenerated.add(" CLONERS_WITH_ID.put(" + className + ".class, (Cloner<" + className + ">) " + clonerImplClassName + "::deepClone);");
pw.println(" public static " + className + " deepCloneNoId(" + className + " original, " + className + " target) {");
methodsPerAttribute.entrySet().stream().sorted(Comparator.comparing(Map.Entry::getKey)).forEach(me -> {
final String fieldName = me.getKey();
HashSet<ExecutableElement> methods = me.getValue();
TypeMirror fieldType = determineFieldType(fieldName, methods);
if (fieldType == null || ID_FIELD_NAME.equals(fieldName)) {
return;
}
cloneField(e, fieldName, methods, fieldType, pw);
});
pw.println(" target.clearUpdatedFlag();");
pw.println(" return target;");
pw.println(" }");
autogenerated.add(" CLONERS_WITHOUT_ID.put(" + className + ".class, (Cloner<" + className + ">) " + clonerImplClassName + "::deepCloneNoId);");
} else {
pw.println(" public static " + className + " deepClone(" + className + " original, " + className + " target) {");
methodsPerAttribute.entrySet().stream().sorted(Comparator.comparing(Map.Entry::getKey)).forEach(me -> {
final String fieldName = me.getKey();
HashSet<ExecutableElement> methods = me.getValue();
TypeMirror fieldType = determineFieldType(fieldName, methods);
if (fieldType == null) {
return;
}
cloneField(e, fieldName, methods, fieldType, pw);
});
pw.println(" target.clearUpdatedFlag();");
pw.println(" return target;");
pw.println(" }");
autogenerated.add(" CLONERS_WITH_ID.put(" + className + ".class, (Cloner<" + className + ">) " + clonerImplClassName + "::deepClone);");
}
pw.println("}");
}
}
private void cloneField(TypeElement e, final String fieldName, HashSet<ExecutableElement> methods, TypeMirror fieldType, final PrintWriter pw) {
ExecutableElement getter = FieldAccessorType.getMethod(GETTER, methods, fieldName, types, fieldType).orElse(null);
if (getter == null) {
processingEnv.getMessager().printMessage(Kind.WARNING, "Could not determine getter for " + fieldName + " property");
return;
}
Optional<ExecutableElement> setter = FieldAccessorType.getMethod(SETTER, methods, fieldName, types, fieldType);
Optional<ExecutableElement> addToCollection = FieldAccessorType.getMethod(COLLECTION_ADD, methods, fieldName, types, fieldType);
Optional<ExecutableElement> updateMap = FieldAccessorType.getMethod(MAP_ADD, methods, fieldName, types, fieldType);
if (setter.isPresent()) {
final Name setterName = setter.get().getSimpleName();
// Setter always deep-clones whatever comes from the original, so we don't clone the value here.
pw.println(" target." + setterName + "(original." + getter.getSimpleName() + "());");
} else if (addToCollection.isPresent()) {
pw.println(" if (original." + getter.getSimpleName() + "() != null) {");
pw.println(" original." + getter.getSimpleName() + "().forEach(target::" + addToCollection.get().getSimpleName() + ");");
pw.println(" }");
} else if (updateMap.isPresent()) {
pw.println(" if (original." + getter.getSimpleName() + "() != null) {");
pw.println(" original." + getter.getSimpleName() + "().forEach(target::" + updateMap.get().getSimpleName() + ");");
pw.println(" }");
} else {
processingEnv.getMessager().printMessage(Kind.ERROR, "Could not determine way to clone " + fieldName + " property", e);
}
}
}
}

View file

@ -1,711 +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.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.TypeKind;
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.Arrays;
import java.util.Collection;
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.TreeSet;
import java.util.logging.Level;
import java.util.logging.Logger;
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 {
private static Collection<String> autogenerated = new TreeSet<>();
@Override
protected Generator[] getGenerators() {
return new Generator[] { new HotRodGettersAndSettersDelegateGenerator(), new HotRodEntityDescriptorGenerator() };
}
@Override
protected void afterAnnotationProcessing() {
if (! autogenerated.isEmpty()) {
try {
JavaFileObject file = processingEnv.getFiler().createSourceFile("org.keycloak.models.map.storage.hotRod.common.AutogeneratedHotRodDescriptors");
try (PrintWriter pw = new PrintWriterNoJavaLang(file.openWriter())) {
pw.println("package org.keycloak.models.map.storage.hotRod.common;");
pw.println("// DO NOT CHANGE THIS CLASS, IT IS GENERATED AUTOMATICALLY BY " + GenerateHotRodEntityImplementationsProcessor.class.getSimpleName());
generatedAnnotation(pw);
pw.println("public final class AutogeneratedHotRodDescriptors {");
pw.println(" public static final java.util.Map<Class<?>, HotRodEntityDescriptor<?,?>> ENTITY_DESCRIPTOR_MAP = new java.util.HashMap<>();");
pw.println(" static {");
autogenerated.forEach(pw::println);
pw.println(" }");
pw.println("}");
}
} catch (IOException ex) {
Logger.getLogger(GenerateEntityImplementationsProcessor.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
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 enumWithStableId;
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;
boolean parentClassHasGeneric = !getGenericsDeclaration(parentClassElement.asType()).isEmpty();
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");
enumWithStableId = elements.getTypeElement("org.keycloak.util.EnumWithStableIndex");
hotRodUtils = elements.getTypeElement("org.keycloak.models.map.storage.hotRod.common.HotRodTypesUtils");
boolean hasDeepClone = allMembers.stream()
.filter(el -> el.getKind() == ElementKind.METHOD && !el.getModifiers().contains(Modifier.ABSTRACT)).anyMatch(el -> "deepClone".equals(el.getSimpleName().toString()));
boolean needsDeepClone = fieldGetters(methodsPerAttribute)
.map(ExecutableElement::getReturnType)
.anyMatch(fieldType -> ! isKnownCollectionOfImmutableFinalTypes(fieldType) && ! isImmutableFinalType(fieldType));
boolean usingGeneratedCloner = ! hasDeepClone && needsDeepClone;
boolean hasId = methodsPerAttribute.containsKey("Id") || allMembers.stream().anyMatch(el -> "getId".equals(el.getSimpleName().toString()));
boolean hasFieldId = elements.getAllMembers(e).stream()
.filter(VariableElement.class::isInstance)
.map(VariableElement.class::cast)
.anyMatch(variableElement -> variableElement.getSimpleName().toString().equals("id"));
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());
generatedAnnotation(pw);
pw.println("public class " + hotRodSimpleClassName
+ " extends "
+ parentClassElement.getQualifiedName().toString() + (parentClassHasGeneric ? "<" + e.getQualifiedName().toString() + ">" : "")
+ " implements "
+ parentInterfaceElement.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) -> {
// Create constructor and initialize cloner to DUMB_CLONER if necessary
if (usingGeneratedCloner) {
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()) + ") {"
);
pw.println(" super(" + ee.getParameters() + ");");
if (usingGeneratedCloner) pw.println(" this.cloner = DeepCloner.DUMB_CLONER;");
pw.println(" this." + ENTITY_VARIABLE + " = new " + className + "();");
pw.println(" }");
});
// Add constructor for setting HotRodEntity
if (usingGeneratedCloner) {
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(" " +
"public " + hotRodSimpleClassName + "(" + className + " " + ENTITY_VARIABLE + ") {"
);
pw.println(" java.util.Objects.requireNonNull(" + ENTITY_VARIABLE + ");");
pw.println(" this." + ENTITY_VARIABLE + " = " + ENTITY_VARIABLE + ";");
if (usingGeneratedCloner) {
pw.println(" this.cloner = DeepCloner.DUMB_CLONER;");
}
pw.println(" }");
pw.println(" public " + hotRodSimpleClassName + "(DeepCloner cloner) {");
pw.println(" super();");
pw.println(" this." + ENTITY_VARIABLE + " = new " + className + "();");
if (usingGeneratedCloner) pw.println(" this.cloner = 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(Util::isNotIgnored)
.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 "
+ (hasFieldId
? "(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 -> "e." + 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();
// 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 String getFieldNameForCollectionKey(TypeMirror fieldType, ExecutableElement callingMethod) {
ExecutableElement collectionKey = getCollectionKey(fieldType, callingMethod);
char[] c = determineAttributeFromMethodName(collectionKey).toCharArray();
c[0] = Character.toLowerCase(c[0]);
return new String(c);
}
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 " + hotRodEntityField(fieldName) + " == null ? null : " + 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(firstParameterType, "p0") + ";");
}
if (isCollection(firstParameterType)) {
pw.println(" if (p0 != null) {");
pw.println(" " + removeUndefined(firstParameterType, "p0") + ";");
pw.println(" if (" + isUndefined("p0") + ") p0 = null;");
pw.println(" }");
}
pw.println(" " + hotRodFieldType.toString() + " migrated = p0 == null ? null : " + migrateToType(hotRodFieldType, firstParameterType, "p0") + ";");
pw.println(" " + hotRodEntityField("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) {");
if (! isImmutableFinalType(firstParameterType)) {
pw.println(" p0 = " + deepClone(firstParameterType, "p0") + ";");
}
if (isCollection(firstParameterType)) {
pw.println(" if (p0 != null) " + removeUndefined(firstParameterType, "p0") + ";");
}
pw.println(" if (" + isUndefined("p0") + ") return;");
pw.println(" if (" + hotRodEntityField(fieldName) + " == null) { " + hotRodEntityField(fieldName) + " = " + interfaceToImplementation(typeElement, "") + "; }");
pw.println(" " + collectionItemType.toString() + " migrated = " + migrateToType(collectionItemType, firstParameterType, "p0") + ";");
if (isSetType(typeElement)) {
pw.println(" " + hotRodEntityField("updated") + " |= " + hotRodEntityField(fieldName) + ".add(migrated);");
} else {
pw.println(" " + hotRodEntityField(fieldName) + ".add(migrated);");
pw.println(" " + hotRodEntityField("updated") + " = true;");
}
pw.println(" }");
return true;
case COLLECTION_DELETE:
{
collectionItemType = getGenericsDeclaration(hotRodFieldType).get(0);
boolean needsReturn = method.getReturnType().getKind() != TypeKind.VOID;
pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(" + firstParameterType + " p0) {");
if (isMapType(typeElement)) {
// Maps are stored as sets
pw.println(" boolean removed = " + hotRodUtils.getQualifiedName().toString() + ".removeFromSetByMapKey("
+ hotRodEntityField(fieldName) + ", "
+ "p0, "
+ keyGetterReference(collectionItemType) + ");"
);
pw.println(" " + hotRodEntityField("updated") + " |= removed;");
} else {
pw.println(" if (" + hotRodEntityField(fieldName) + " == null) { return" + (needsReturn ? " false" : "") + "; }");
pw.println(" boolean removed = " + hotRodEntityField(fieldName) + ".remove(p0);");
pw.println(" " + hotRodEntityField("updated") + " |= removed;");
}
if (needsReturn) pw.println(" return removed;");
pw.println(" }");
return true;
}
case COLLECTION_DELETE_BY_ID:
{
boolean needsReturn = method.getReturnType().getKind() != TypeKind.VOID;
pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(String p0) {");
pw.println(" boolean removed = " + hotRodEntityField(fieldName) + " != null && " + hotRodEntityField(fieldName) + ".removeIf(o -> Objects.equals(o." + getFieldNameForCollectionKey(fieldType, method) + ", p0));");
pw.println(" " + hotRodEntityField("updated") + " |= removed;");
if (needsReturn) pw.println(" return removed;");
pw.println(" }");
return true;
}
case COLLECTION_GET_BY_ID:
{
collectionItemType = getGenericsDeclaration(hotRodFieldType).get(0);
TypeMirror returnTypeGeneric = getGenericsDeclaration(method.getReturnType()).get(0);
pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(String p0) {");
pw.println(" if (" + hotRodEntityField(fieldName) + " == null || " + hotRodEntityField(fieldName) + ".isEmpty()) return Optional.empty();");
pw.println(" return " + hotRodEntityField(fieldName) + ".stream().filter(o -> Objects.equals(o." + getFieldNameForCollectionKey(fieldType, method) + ", p0)).findFirst().map(e -> " + migrateToType(returnTypeGeneric, collectionItemType, "e") + ");");
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) {");
if (! isImmutableFinalType(secondParameterType)) {
pw.println(" p1 = " + deepClone(secondParameterType, "p1") + ";");
}
if (isCollection(secondParameterType)) {
pw.println(" if (p1 != null) " + removeUndefined(secondParameterType, "p1") + ";");
}
pw.println(" boolean valueUndefined = " + isUndefined("p1") + ";");
pw.println(" if (" + hotRodEntityField(fieldName) + " == null && !valueUndefined) { " + hotRodEntityField(fieldName) + " = " + interfaceToImplementation((TypeElement) types.asElement(types.erasure(hotRodFieldType)), "") + "; }");
pw.println(" " + hotRodEntityField("updated") + " |= " + hotRodUtils.getQualifiedName().toString() + ".removeFromSetByMapKey("
+ hotRodEntityField(fieldName) + ", "
+ "p0, "
+ keyGetterReference(collectionItemType) + ");"
);
pw.println(" " + hotRodEntityField("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 boolean hasField(TypeElement type, String fieldName) {
Optional<VariableElement> hotRodVariable = elements.getAllMembers(type).stream()
.filter(VariableElement.class::isInstance)
.map(VariableElement.class::cast)
.filter(variableElement -> variableElement.getSimpleName().toString().equals(fieldName))
.findFirst();
return hotRodVariable.isPresent();
}
private String keyGetterReference(TypeMirror type) {
TypeElement typeElement = elements.getTypeElement(types.erasure(type).toString());
if (hasField(typeElement, "id")) {
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 boolean isAssignable(TypeMirror fromType, TypeMirror toType) {
return types.isAssignable(types.erasure(fromType), types.erasure(toType));
}
private String migrateToType(TypeMirror toType, TypeMirror[] fromType, String[] fieldNames) {
// No migration needed, fromType is assignable to toType directly
if (fromType.length == 1 && isAssignable(fromType[0], toType) && !isCollection(fromType[0])) {
return fieldNames[0];
}
// Solve migration of data within collections
if (fromType.length == 1) {
if (isAssignable(fromType[0], toType)) { // First case, the collection is the same
TypeMirror fromGeneric = getGenericsDeclaration(fromType[0]).get(0);
TypeMirror toGeneric = getGenericsDeclaration(toType).get(0);
// Generics are assignable too, so we can just assign the same value
if (isAssignable(fromGeneric, toGeneric)) return fieldNames[0];
return hotRodUtils.getQualifiedName().toString() + ".migrate" + toSimpleName(fromType[0]) + "("
+ fieldNames[0] + ", "
+ "collectionItem -> " + migrateToType(toGeneric, fromGeneric, "collectionItem") + ")";
} else 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)
+ ")";
}
}
if (isAssignable(fromType[0], enumWithStableId.asType())) {
return fieldNames[0] + ".getStableIndex()";
}
if (isAssignable(toType, enumWithStableId.asType())) {
return toType.toString() + ".valueOfInteger(" + fieldNames[0] + ")";
}
// Try to find constructor that can do the migration
if (findSuitableConstructor(toType, fromType).isPresent()) {
return "new " + toType.toString() + "(" + String.join(", ", fieldNames) + ")";
}
if (isAssignable(toType, abstractHotRodEntity.asType())) {
// Check if any of parameters is another Map*Entity
OptionalInt anotherMapEntityIndex = IntStream.range(0, fromType.length)
.filter(i -> isAssignable(fromType[i], abstractEntity.asType()))
.findFirst();
// 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.orElse(0)] + ").getHotRodEntity()";
}
// Check if any of parameters is another HotRod*Entity
OptionalInt anotherHotRodEntityIndex = IntStream.range(0, fromType.length)
.filter(i -> 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) + ")";
}
return hotRodUtils.getQualifiedName().toString() + ".migrate" + Arrays.stream(fromType).map(this::toSimpleName).collect(Collectors.joining("")) + "To" + toSimpleName(toType) + "(" + String.join(", ", fieldNames) + ")";
}
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;
}
}
private class HotRodEntityDescriptorGenerator implements Generator {
@Override
public void generate(TypeElement e) throws IOException {
GenerateHotRodEntityImplementation hotRodAnnotation = e.getAnnotation(GenerateHotRodEntityImplementation.class);
if (!hotRodAnnotation.topLevelEntity()) {
return;
}
if (hotRodAnnotation.modelClass().isEmpty()) {
Logger.getLogger(GenerateEntityImplementationsProcessor.class.getName()).log(Level.SEVERE, "HotRod top-level class needs to have model-class defined");
}
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 descriptorClassName = className + "Descriptor";
String descriptorSimpleClassName = simpleClassName + "Descriptor";
JavaFileObject file = processingEnv.getFiler().createSourceFile(descriptorClassName);
try (PrintWriter pw = new PrintWriterNoJavaLang(file.openWriter())) {
if (packageName != null) {
pw.println("package " + packageName + ";");
}
pw.println("import org.infinispan.protostream.GeneratedSchema;");
pw.println("import java.util.function.Function;");
generatedAnnotation(pw);
pw.println("public class " + descriptorSimpleClassName
+ " implements "
+ "org.keycloak.models.map.storage.hotRod.common.HotRodEntityDescriptor<" + className + ", " + className + "Delegate" + ">"
+ " {");
// model class
pw.println(" @Override\n" +
" public Class<?> getModelTypeClass() {\n" +
" return " + hotRodAnnotation.modelClass() + ".class;\n" +
" }");
// entity class
pw.println(" @Override\n" +
" public Class<" + className + "> getEntityTypeClass() {\n" +
" return " + className + ".class;\n" +
" }");
// cache name
boolean isMethodCall = hotRodAnnotation.cacheName().contains("(");
String quotes = isMethodCall ? "" : "\"";
pw.println(" @Override\n" +
" public String getCacheName() {\n" +
(hotRodAnnotation.cacheName().isEmpty() ?
" return org.keycloak.models.map.storage.ModelEntityUtil.getModelName(" + hotRodAnnotation.modelClass() + ".class);\n"
: " return " + quotes + hotRodAnnotation.cacheName() + quotes + ";\n") +
" }");
// delegate provider
pw.println(" @Override\n" +
" public Function<" + className + ", " + className + "Delegate> getHotRodDelegateProvider() {\n" +
" return " + className + "Delegate::new;\n" +
" }");
// Current version
pw.println(" @Override\n" +
" public Integer getCurrentVersion() {\n" +
" return " + className + ".VERSION;\n" +
" }");
// Current schema
pw.println(" @Override\n" +
" public GeneratedSchema getProtoSchema() {\n" +
" return " + className + "." + simpleClassName + "Schema.INSTANCE" + ";\n" +
" }");
pw.println("}");
}
autogenerated.add(" ENTITY_DESCRIPTOR_MAP.put(" + hotRodAnnotation.modelClass() + ".class, new " + descriptorClassName + "());" );
}
}
}

View file

@ -1,37 +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.processor;
import java.io.PrintWriter;
import java.io.Writer;
/**
*
* @author hmlnarik
*/
public class PrintWriterNoJavaLang extends PrintWriter {
public PrintWriterNoJavaLang(Writer out) {
super(out);
}
@Override
public void println(String x) {
super.println(x == null ? x : x.replaceAll("java.lang.", ""));
}
}

View file

@ -1,132 +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.processor;
import org.keycloak.models.map.annotations.IgnoreForEntityImplementationGenerator;
import java.util.ArrayList;
import java.util.Collection;
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.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.SimpleTypeVisitor8;
/**
*
* @author hmlnarik
*/
public class Util {
private static final Set<String> LIST_TYPES = Set.of(List.class.getCanonicalName(), ArrayList.class.getCanonicalName(), LinkedList.class.getCanonicalName());
private static final Set<String> SET_TYPES = Set.of(Set.class.getCanonicalName(), TreeSet.class.getCanonicalName(), HashSet.class.getCanonicalName(), LinkedHashSet.class.getCanonicalName(), Collection.class.getCanonicalName());
private static final Set<String> MAP_TYPES = Set.of(Map.class.getCanonicalName(), HashMap.class.getCanonicalName());
public static List<TypeMirror> getGenericsDeclaration(TypeMirror fieldType) {
List<TypeMirror> res = new LinkedList<>();
fieldType.accept(new SimpleTypeVisitor8<Void, List<TypeMirror>>() {
@Override
public Void visitDeclared(DeclaredType t, List<TypeMirror> p) {
List<? extends TypeMirror> typeArguments = t.getTypeArguments();
res.addAll(typeArguments);
return null;
}
}, res);
return res;
}
public static String methodParameters(List<? extends VariableElement> parameters) {
return parameters.stream()
.map(p -> p.asType() + " " + p.getSimpleName())
.collect(Collectors.joining(", "));
}
public static boolean isCollectionType(TypeElement typeElement) {
return isListType(typeElement) || isSetType(typeElement);
}
public static boolean isListType(TypeElement typeElement) {
Name name = typeElement.getQualifiedName();
return LIST_TYPES.contains(name.toString());
}
public static boolean isSetType(TypeElement typeElement) {
Name name = typeElement.getQualifiedName();
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);
if (a != null) {
return false;
}
el = el.getEnclosingElement();
} while (el != null);
return true;
}
protected static Optional<ExecutableElement> findParentMethodImplementation(List<? extends Element> allParentMembers, ExecutableElement method) {
return allParentMembers.stream()
.filter(ExecutableElement.class::isInstance)
.map(ExecutableElement.class::cast)
.filter(ee -> Objects.equals(ee.toString(), method.toString()))
.filter((ExecutableElement ee) -> ! ee.getModifiers().contains(Modifier.ABSTRACT))
.findAny();
}
public static String singularToPlural(String word) {
if (word.endsWith("y")) {
return word.substring(0, word.length() -1) + "ies";
} else if (word.endsWith("s")) {
return word + "es";
} else {
return word + "s";
}
}
public static String pluralToSingular(String word) {
if (word.endsWith("ies")) {
return word.substring(0, word.length() - 3) + "y";
} else if (word.endsWith("ses")) {
return word.substring(0, word.length() - 2);
} else {
return word.endsWith("s") ? word.substring(0, word.length() - 1) : word;
}
}
}

View file

@ -1,50 +0,0 @@
<?xml version="1.0"?>
<!--
~ Copyright 2022 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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>keycloak-model-pom</artifactId>
<groupId>org.keycloak</groupId>
<version>999.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>keycloak-model-map-file</artifactId>
<name>Keycloak Model Map File</name>
<properties>
<maven.compiler.release>11</maven.compiler.release>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-model-map</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.snakeyaml</groupId>
<artifactId>snakeyaml-engine</artifactId>
</dependency>
</dependencies>
</project>

View file

@ -1,44 +0,0 @@
/*
* Copyright 2023 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.file;
import org.keycloak.models.map.storage.criteria.DescriptiveModelCriteria;
import org.keycloak.models.map.storage.criteria.ModelCriteriaNode;
/**
*
* @author hmlnarik
*/
public class FileCriteriaBuilder<M> extends DescriptiveModelCriteria<M, FileCriteriaBuilder<M>> {
private static final FileCriteriaBuilder<?> INSTANCE = new FileCriteriaBuilder<>(null);
private FileCriteriaBuilder(ModelCriteriaNode<M> node) {
super(node);
}
@SuppressWarnings("unchecked")
public static <M> FileCriteriaBuilder<M> criteria() {
return (FileCriteriaBuilder<M>) INSTANCE;
}
@Override
protected FileCriteriaBuilder<M> instantiateForNode(ModelCriteriaNode<M> targetNode) {
return new FileCriteriaBuilder<>(targetNode);
}
}

View file

@ -1,435 +0,0 @@
/*
* Copyright 2023 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.file;
import org.jboss.logging.Logger;
import org.keycloak.common.util.StackUtil;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ModelDuplicateException;
import org.keycloak.models.map.common.AbstractEntity;
import org.keycloak.models.map.common.ExpirationUtils;
import org.keycloak.models.map.common.HasRealmId;
import org.keycloak.models.map.common.StringKeyConverter;
import org.keycloak.models.map.common.StringKeyConverter.StringKey;
import org.keycloak.models.map.common.UpdatableEntity;
import org.keycloak.models.map.realm.MapRealmEntity;
import org.keycloak.models.map.storage.ModelEntityUtil;
import org.keycloak.models.map.storage.QueryParameters;
import org.keycloak.models.map.storage.CrudOperations;
import org.keycloak.models.map.storage.chm.MapFieldPredicates;
import org.keycloak.models.map.storage.chm.MapModelCriteriaBuilder;
import org.keycloak.models.map.storage.file.common.MapEntityContext;
import org.keycloak.models.map.storage.file.yaml.PathWriter;
import org.keycloak.models.map.storage.file.yaml.YamlParser;
import org.keycloak.models.map.storage.file.yaml.YamlWritingMechanism;
import org.keycloak.storage.SearchableModelField;
import org.snakeyaml.engine.v2.emitter.Emitter;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.FileTime;
import java.util.Arrays;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.snakeyaml.engine.v2.api.DumpSettings;
import static org.keycloak.utils.StreamsUtil.paginatedStream;
public abstract class FileCrudOperations<V extends AbstractEntity & UpdatableEntity, M> implements CrudOperations<V, M>, HasRealmId {
private static final Logger LOG = Logger.getLogger(FileCrudOperations.class);
private String defaultRealmId;
private final Class<V> entityClass;
private final Function<String, Path> dataDirectoryFunc;
private final Function<V, String[]> suggestedPath;
private final boolean isExpirableEntity;
private final Map<SearchableModelField<? super M>, MapModelCriteriaBuilder.UpdatePredicatesFunc<String, V, M>> fieldPredicates;
private static final Map<Class<?>, Map<SearchableModelField<?>, MapModelCriteriaBuilder.UpdatePredicatesFunc<?, ?, ?>>> ENTITY_FIELD_PREDICATES = new HashMap<>();
public static final String SEARCHABLE_FIELD_REALM_ID_FIELD_NAME = ClientModel.SearchableFields.REALM_ID.getName();
public static final String FILE_SUFFIX = ".yaml";
public static final DumpSettings DUMP_SETTINGS = DumpSettings.builder()
.setIndent(4)
.setIndicatorIndent(2)
.setIndentWithIndicator(false)
.build();
public FileCrudOperations(Class<V> entityClass,
Function<String, Path> dataDirectoryFunc,
Function<V, String[]> suggestedPath,
boolean isExpirableEntity) {
this.entityClass = entityClass;
this.dataDirectoryFunc = dataDirectoryFunc;
this.suggestedPath = suggestedPath;
this.isExpirableEntity = isExpirableEntity;
this.fieldPredicates = new IdentityHashMap<>(getPredicates(entityClass));
this.fieldPredicates.keySet().stream() // Ignore realmId since this is treated in reading differently
.filter(f -> Objects.equals(SEARCHABLE_FIELD_REALM_ID_FIELD_NAME, f.getName()))
.findAny()
.ifPresent(key -> this.fieldPredicates.replace(key, (builder, op, params) -> builder));
}
@SuppressWarnings("unchecked")
public static <V extends AbstractEntity & UpdatableEntity, M> Map<SearchableModelField<? super M>, MapModelCriteriaBuilder.UpdatePredicatesFunc<String, V, M>> getPredicates(Class<V> entityClass) {
return (Map) ENTITY_FIELD_PREDICATES.computeIfAbsent(entityClass, n -> {
Map<SearchableModelField<? super M>, MapModelCriteriaBuilder.UpdatePredicatesFunc<String, V, M>> fieldPredicates = new IdentityHashMap<>(MapFieldPredicates.getPredicates(ModelEntityUtil.getModelType(entityClass)));
fieldPredicates.keySet().stream() // Ignore realmId since this is treated in reading differently
.filter(f -> Objects.equals(SEARCHABLE_FIELD_REALM_ID_FIELD_NAME, f.getName()))
.findAny()
.ifPresent(key -> fieldPredicates.replace(key, (builder, op, params) -> builder));
return (Map) fieldPredicates;
});
}
protected Path getPathForEscapedId(String[] escapedIdPathArray) {
Path parentDirectory = getDataDirectory();
Path targetPath = parentDirectory;
for (String path : escapedIdPathArray) {
targetPath = targetPath.resolve(path).normalize();
if (!targetPath.getParent().equals(parentDirectory)) {
LOG.warnf("Path traversal detected: %s", Arrays.toString(escapedIdPathArray));
return null;
}
parentDirectory = targetPath;
}
return targetPath.resolveSibling(targetPath.getFileName() + FILE_SUFFIX);
}
protected Path getPathForEscapedId(String escapedId) {
if (escapedId == null) {
throw new IllegalStateException("Invalid ID to escape");
}
String[] escapedIdArray = ID_COMPONENT_SEPARATOR_PATTERN.split(escapedId);
return getPathForEscapedId(escapedIdArray);
}
// Percent sign + Unix (/) and https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file reserved characters
private static final Pattern RESERVED_CHARACTERS = Pattern.compile("[%<:>\"/\\\\|?*=]");
public static final String ID_COMPONENT_SEPARATOR = ":";
private static final String ESCAPING_CHARACTER = "=";
private static final Pattern ID_COMPONENT_SEPARATOR_PATTERN = Pattern.compile(Pattern.quote(ID_COMPONENT_SEPARATOR) + "+");
private static String[] escapeId(String[] idArray) {
if (idArray == null || idArray.length == 0 || idArray.length == 1 && idArray[0] == null) {
return null;
}
return Stream.of(idArray)
.map(FileCrudOperations::escapeId)
.toArray(String[]::new);
}
private static String escapeId(String id) {
Objects.requireNonNull(id, "ID must be non-null");
StringBuilder idEscaped = new StringBuilder();
Matcher m = RESERVED_CHARACTERS.matcher(id);
while (m.find()) {
m.appendReplacement(idEscaped, String.format(ESCAPING_CHARACTER + "%02x", (int) m.group().charAt(0)));
}
m.appendTail(idEscaped);
final Path pId = Path.of(idEscaped.toString());
return pId.toString();
}
public static boolean canParseFile(Path p) {
final String fn = p.getFileName().toString();
try {
return Files.isRegularFile(p)
&& Files.size(p) > 0L
&& ! fn.startsWith(".")
&& fn.endsWith(FILE_SUFFIX)
&& Files.isReadable(p);
} catch (IOException ex) {
return false;
}
}
protected V parse(Path fileName) {
getLastModifiedTime(fileName);
final V parsedObject = YamlParser.parse(fileName, new MapEntityContext<>(entityClass));
if (parsedObject == null) {
LOG.debugf("Could not parse %s%s", fileName, StackUtil.getShortStackTrace());
return null;
}
final String fileNameStr = fileName.getFileName().toString();
final String idFromFilename = fileNameStr.substring(0, fileNameStr.length() - FILE_SUFFIX.length());
String escapedId = determineKeyFromValue(parsedObject, idFromFilename);
if (escapedId == null) {
LOG.tracef("Determined ID from filename: %s%s", idFromFilename);
escapedId = idFromFilename;
} else if (!escapedId.endsWith(idFromFilename)) {
LOG.warnf("Id \"%s\" does not conform with filename \"%s\", expected: %s", escapedId, fileNameStr, escapeId(escapedId));
}
parsedObject.setId(escapedId);
parsedObject.clearUpdatedFlag();
return parsedObject;
}
@Override
public V create(V value) {
// TODO: Lock realm directory for changes (e.g. on realm deletion)
String escapedId = value.getId();
writeYamlContents(getPathForEscapedId(escapedId), value);
return value;
}
public String determineKeyFromValue(V value, String lastIdComponentIfUnset) {
String[] proposedId = suggestedPath.apply(value);
if (proposedId == null || proposedId.length == 0) {
return lastIdComponentIfUnset;
} else if (proposedId[proposedId.length - 1] == null) {
proposedId[proposedId.length - 1] = lastIdComponentIfUnset;
}
String[] escapedProposedId = escapeId(proposedId);
final String res = String.join(ID_COMPONENT_SEPARATOR, escapedProposedId);
if (LOG.isTraceEnabled()) {
LOG.tracef("determineKeyFromValue: got %s (%s) for %s", res, res == null ? null : String.join(" [/] ", proposedId), value);
}
return res;
}
/**
* Returns escaped ID - relative file name in the file system with path separator {@link #ID_COMPONENT_SEPARATOR}.
*
* @param value Object
* @param forCreate Whether this is for create operation ({@code true}) or
* @return
*/
@Override
public String determineKeyFromValue(V value) {
final boolean randomId;
String[] proposedId = suggestedPath.apply(value);
if (proposedId == null || proposedId.length == 0) {
randomId = value.getId() == null;
proposedId = new String[] { value.getId() == null ? StringKey.INSTANCE.yieldNewUniqueKey() : value.getId() };
} else if (proposedId[proposedId.length - 1] == null) {
randomId = true;
proposedId[proposedId.length - 1] = StringKey.INSTANCE.yieldNewUniqueKey();
} else {
randomId = false;
}
String[] escapedProposedId = escapeId(proposedId);
Path sp = getPathForEscapedId(escapedProposedId); // sp will never be null
final Path parentDir = sp.getParent();
if (! Files.isDirectory(parentDir)) {
try {
Files.createDirectories(parentDir);
} catch (IOException ex) {
throw new IllegalStateException("Directory does not exist and cannot be created: " + parentDir, ex);
}
}
for (int counter = 0; counter < 100; counter++) {
LOG.tracef("Attempting to create file %s", sp, StackUtil.getShortStackTrace());
try {
final String res = String.join(ID_COMPONENT_SEPARATOR, escapedProposedId);
touch(res, sp);
LOG.tracef("determineKeyFromValue: got %s for created %s", res, value);
return res;
} catch (FileAlreadyExistsException ex) {
if (! randomId) {
throw new ModelDuplicateException("File " + sp + " already exists!");
}
final String lastComponent = StringKey.INSTANCE.yieldNewUniqueKey();
escapedProposedId[escapedProposedId.length - 1] = lastComponent;
sp = getPathForEscapedId(escapedProposedId);
} catch (IOException ex) {
throw new IllegalStateException("Could not create file " + sp, ex);
}
}
return null;
}
@Override
public V read(String key) {
return Optional.ofNullable(key)
.map(this::getPathForEscapedId)
.filter(Files::isReadable)
.map(this::parse)
.orElse(null);
}
public MapModelCriteriaBuilder<String, V, M> createCriteriaBuilder() {
return new MapModelCriteriaBuilder<>(StringKeyConverter.StringKey.INSTANCE, fieldPredicates);
}
@Override
public Stream<V> read(QueryParameters<M> queryParameters) {
final List<Path> paths;
FileCriteriaBuilder cb = queryParameters.getModelCriteriaBuilder().flashToModelCriteriaBuilder(FileCriteriaBuilder.criteria());
String realmId = (String) cb.getSingleRestrictionArgument(SEARCHABLE_FIELD_REALM_ID_FIELD_NAME);
setRealmId(realmId);
final Path dataDirectory = getDataDirectory();
if (!Files.isDirectory(dataDirectory)) {
return Stream.empty();
}
// We cannot use Files.find since it throws an UncheckedIOException if it lists a file which is removed concurrently
// before its BasicAttributes can be retrieved for its BiPredicate parameter
try (Stream<Path> dirStream = Files.walk(dataDirectory, entityClass == MapRealmEntity.class ? 1 : 3)) {
// The paths list has to be materialized first, otherwise "dirStream" would be closed
// before the resulting stream would be read and would return empty result
paths = dirStream.collect(Collectors.toList());
} catch (IOException | UncheckedIOException ex) {
LOG.warnf(ex, "Error listing %s", dataDirectory);
return Stream.empty();
}
Stream<V> res = paths.stream()
.filter(FileCrudOperations::canParseFile)
.map(this::parse)
.filter(Objects::nonNull);
MapModelCriteriaBuilder<String, V, M> mcb = queryParameters.getModelCriteriaBuilder().flashToModelCriteriaBuilder(createCriteriaBuilder());
Predicate<? super String> keyFilter = mcb.getKeyFilter();
Predicate<? super V> entityFilter;
if (isExpirableEntity) {
entityFilter = mcb.getEntityFilter().and(ExpirationUtils::isNotExpired);
} else {
entityFilter = mcb.getEntityFilter();
}
res = res.filter(e -> keyFilter.test(e.getId()) && entityFilter.test(e));
if (!queryParameters.getOrderBy().isEmpty()) {
res = res.sorted(MapFieldPredicates.getComparator(queryParameters.getOrderBy().stream()));
}
return paginatedStream(res, queryParameters.getOffset(), queryParameters.getLimit());
}
@Override
public V update(V value) {
String escapedId = value.getId();
Path sp = getPathForEscapedId(escapedId);
if (sp == null) {
throw new IllegalArgumentException("Invalid path: " + escapedId);
}
checkIsSafeToModify(sp);
// TODO: improve locking
synchronized (FileMapStorageProviderFactory.class) {
writeYamlContents(sp, value);
}
return value;
}
@Override
public boolean delete(String key) {
return Optional.ofNullable(key)
.map(this::getPathForEscapedId)
.map(this::removeIfExists)
.orElse(false);
}
@Override
public long delete(QueryParameters<M> queryParameters) {
return read(queryParameters).map(AbstractEntity::getId).map(this::delete).filter(a -> a).count();
}
@Override
public long getCount(QueryParameters<M> queryParameters) {
return read(queryParameters).count();
}
@Override
public String getRealmId() {
return defaultRealmId;
}
@Override
public void setRealmId(String realmId) {
this.defaultRealmId = realmId;
}
private Path getDataDirectory() {
return dataDirectoryFunc.apply(defaultRealmId == null ? null : escapeId(defaultRealmId));
}
private void writeYamlContents(Path sp, V value) {
Path tempSp = sp.resolveSibling("." + getTxId() + "-" + sp.getFileName());
try (PathWriter w = new PathWriter(tempSp)) {
final Emitter emitter = new Emitter(DUMP_SETTINGS, w);
try (YamlWritingMechanism mech = new YamlWritingMechanism(emitter::emit)) {
new MapEntityContext<>(entityClass).writeValue(value, mech);
}
registerRenameOnCommit(tempSp, sp);
} catch (IOException ex) {
throw new IllegalStateException("Cannot write " + sp, ex);
}
}
protected abstract void touch(String proposedId, Path sp) throws IOException;
protected abstract boolean removeIfExists(Path sp);
protected abstract void registerRenameOnCommit(Path tempSp, Path sp);
protected abstract String getTxId();
/**
* Hook to obtain the last modified time of the file identified by the supplied {@link Path}.
*
* @param path the {@link Path} to the file whose last modified time it to be obtained.
* @return the {@link FileTime} corresponding to the file's last modified time.
*/
protected abstract FileTime getLastModifiedTime(final Path path);
/**
* Hook to validate that it is safe to modify the file identified by the supplied {@link Path}. The primary
* goal is to identify if other transactions have modified the file after it was read by the current transaction,
* preventing updates to a stale entity.
*
* @param path the {@link Path} to the file that is to be modified.
*/
protected abstract void checkIsSafeToModify(final Path path);
}

View file

@ -1,311 +0,0 @@
/*
* Copyright 2023 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.file;
import org.jboss.logging.Logger;
import org.keycloak.models.map.common.AbstractEntity;
import org.keycloak.models.map.common.DeepCloner;
import org.keycloak.models.map.common.StringKeyConverter;
import org.keycloak.models.map.common.StringKeyConverter.StringKey;
import org.keycloak.models.map.common.UpdatableEntity;
import org.keycloak.models.map.common.delegate.EntityFieldDelegate;
import org.keycloak.models.map.storage.MapStorage;
import org.keycloak.models.map.storage.ModelEntityUtil;
import org.keycloak.models.map.storage.chm.ConcurrentHashMapStorage;
import org.keycloak.models.map.storage.chm.MapFieldPredicates;
import org.keycloak.storage.ReadOnlyException;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import static org.keycloak.models.map.storage.file.FileCrudOperations.ID_COMPONENT_SEPARATOR;
/**
* {@link MapStorage} implementation used with the file map storage.
*
* @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a>
*/
public class FileMapStorage<V extends AbstractEntity & UpdatableEntity, M>
extends ConcurrentHashMapStorage<String, V, M, FileCrudOperations<V, M>> {
private static final Logger LOG = Logger.getLogger(FileMapStorage.class);
private final List<Path> createdPaths = new LinkedList<>();
private final List<Path> pathsToDelete = new LinkedList<>();
private final Map<Path, Path> renameOnCommit = new LinkedHashMap<>();
private final Map<Path, FileTime> lastModified = new HashMap<>();
private final String txId = StringKey.INSTANCE.yieldNewUniqueKey();
public static <V extends AbstractEntity & UpdatableEntity, M> FileMapStorage<V, M> newInstance(Class<V> entityClass,
Function<String, Path> dataDirectoryFunc, Function<V, String[]> suggestedPath,
boolean isExpirableEntity) {
Crud<V, M> crud = new Crud<>(entityClass, dataDirectoryFunc, suggestedPath, isExpirableEntity);
FileMapStorage<V, M> store = new FileMapStorage<>(entityClass, crud);
crud.store = store;
return store;
}
private FileMapStorage(Class<V> entityClass, Crud<V, M> crud) {
super(
crud,
StringKeyConverter.StringKey.INSTANCE,
DeepCloner.DUMB_CLONER,
MapFieldPredicates.getPredicates(ModelEntityUtil.getModelType(entityClass)),
ModelEntityUtil.getRealmIdField(entityClass)
);
}
@Override
public void rollback() {
// remove all temporary and empty files that were created.
this.renameOnCommit.keySet().forEach(FileMapStorage::silentDelete);
this.createdPaths.forEach(FileMapStorage::silentDelete);
super.rollback();
}
@Override
public void commit() {
super.commit();
// check it is still safe to update/delete before moving the temp files into the actual files or deleting them.
Set<Path> allChangedPaths = new HashSet<>();
allChangedPaths.addAll(this.renameOnCommit.values());
allChangedPaths.addAll(this.pathsToDelete);
allChangedPaths.forEach(this::checkIsSafeToModify);
try {
this.renameOnCommit.forEach(FileMapStorage::move);
this.pathsToDelete.forEach(FileMapStorage::silentDelete);
// TODO: catch exception thrown by move and try to restore any previously completed moves.
} finally {
// ensure all temp files are removed.
this.renameOnCommit.keySet().forEach(FileMapStorage::silentDelete);
// remove any created files that may have been left empty.
this.createdPaths.forEach(path -> silenteDelete(path, true));
}
}
private static void move(Path from, Path to) {
try {
Files.move(from, to, StandardCopyOption.REPLACE_EXISTING);
} catch (IOException ex) {
throw new UncheckedIOException(ex);
}
}
private static void silentDelete(Path p) {
silenteDelete(p, false);
}
private static void silenteDelete(final Path path, final boolean checkEmpty) {
try {
if (Files.exists(path)) {
if (!checkEmpty || Files.size(path) == 0) {
Files.delete(path);
}
}
} catch(IOException e) {
// swallow the exception.
}
}
public void touch(String proposedId, Path path) throws IOException {
if (Optional.ofNullable(tasks.get(proposedId)).map(MapTaskWithValue::getOperation).orElse(null) == MapOperation.DELETE) {
// If deleted in the current transaction before this operation, then do not touch
return;
}
Files.createFile(path);
createdPaths.add(path);
}
public boolean removeIfExists(Path path) {
final boolean res = ! pathsToDelete.contains(path) && Files.exists(path);
pathsToDelete.add(path);
return res;
}
void registerRenameOnCommit(Path from, Path to) {
pathsToDelete.remove(to);
this.renameOnCommit.put(from, to);
}
/**
* Obtains and stores the last modified time of the file identified by the supplied {@link Path}. This value is used
* to determine if the file was changed by another transaction after it was read by this transaction.
*
* @param path the {@link Path} to the file.
*/
FileTime getLastModifiedTime(final Path path) {
try {
BasicFileAttributes attr = Files.readAttributes(path, BasicFileAttributes.class);
FileTime lastModifiedTime = attr.lastModifiedTime();
this.lastModified.put(path, lastModifiedTime);
return lastModifiedTime;
} catch (IOException ex) {
throw new IllegalStateException("Could not read file attributes " + path, ex);
}
}
/**
* Checks if it is safe to modify the file identified by the supplied {@link Path}. In particular, this method
* verifies if the file was changed (removed, updated) after it was read by this transaction. Being it the case, this
* transaction should refrain from performing further updates as it must assume its data has become stale.
*
* @param path the {@link Path} to the file that will be updated.
* @throws IllegalStateException if the file was altered by another transaction.
*/
void checkIsSafeToModify(final Path path) {
try {
// path wasn't previously loaded - log a message and return.
if (this.lastModified.get(path) == null) {
LOG.debugf("File %s was not previously loaded, skipping validation prior to writing", path);
return;
}
// check if the original file was deleted by another transaction.
if (!Files.exists(path)) {
throw new IllegalStateException("File " + path + " was removed by another transaction");
}
// check if the original file was modified by another transaction.
BasicFileAttributes attr = Files.readAttributes(path, BasicFileAttributes.class);
long lastModifiedTime = attr.lastModifiedTime().toMillis();
if (this.lastModified.get(path).toMillis() < lastModifiedTime) {
throw new IllegalStateException("File " + path + " was changed by another transaction");
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public V registerEntityForChanges(V origEntity) {
final V watchedValue = super.registerEntityForChanges(origEntity);
return DeepCloner.DUMB_CLONER.entityFieldDelegate(watchedValue, new IdProtector(watchedValue));
}
private static class Crud<V extends AbstractEntity & UpdatableEntity, M> extends FileCrudOperations<V, M> {
private FileMapStorage<V, M> store;
public Crud(Class<V> entityClass, Function<String, Path> dataDirectoryFunc, Function<V, String[]> suggestedPath, boolean isExpirableEntity) {
super(entityClass, dataDirectoryFunc, suggestedPath, isExpirableEntity);
}
@Override
protected void touch(String proposedId, Path sp) throws IOException {
store.touch(proposedId, sp);
}
@Override
protected void registerRenameOnCommit(Path from, Path to) {
store.registerRenameOnCommit(from, to);
}
@Override
protected boolean removeIfExists(Path sp) {
return store.removeIfExists(sp);
}
@Override
protected String getTxId() {
return store.txId;
}
@Override
protected FileTime getLastModifiedTime(final Path sp) {
return store.getLastModifiedTime(sp);
}
@Override
protected void checkIsSafeToModify(final Path sp) {
store.checkIsSafeToModify(sp);
}
}
private class IdProtector extends EntityFieldDelegate.WithEntity<V> {
public IdProtector(V entity) {
super(entity);
}
@Override
public <T, EF extends java.lang.Enum<? extends org.keycloak.models.map.common.EntityField<V>> & org.keycloak.models.map.common.EntityField<V>> void set(EF field, T value) {
String id = entity.getId();
super.set(field, value);
checkIdMatches(id, field);
}
@Override
public <T, EF extends java.lang.Enum<? extends org.keycloak.models.map.common.EntityField<V>> & org.keycloak.models.map.common.EntityField<V>> void collectionAdd(EF field, T value) {
String id = entity.getId();
super.collectionAdd(field, value);
checkIdMatches(id, field);
}
@Override
public <T, EF extends java.lang.Enum<? extends org.keycloak.models.map.common.EntityField<V>> & org.keycloak.models.map.common.EntityField<V>> Object collectionRemove(EF field, T value) {
String id = entity.getId();
final Object res = super.collectionRemove(field, value);
checkIdMatches(id, field);
return res;
}
@Override
public <K, T, EF extends java.lang.Enum<? extends org.keycloak.models.map.common.EntityField<V>> & org.keycloak.models.map.common.EntityField<V>> void mapPut(EF field, K key, T value) {
String id = entity.getId();
super.mapPut(field, key, value);
checkIdMatches(id, field);
}
@Override
public <K, EF extends java.lang.Enum<? extends org.keycloak.models.map.common.EntityField<V>> & org.keycloak.models.map.common.EntityField<V>> Object mapRemove(EF field, K key) {
String id = entity.getId();
final Object res = super.mapRemove(field, key);
checkIdMatches(id, field);
return res;
}
private <EF extends java.lang.Enum<? extends org.keycloak.models.map.common.EntityField<V>> & org.keycloak.models.map.common.EntityField<V>> void checkIdMatches(String id, EF field) throws ReadOnlyException {
final String idNow = map.determineKeyFromValue(entity, "");
if (! Objects.equals(id, idNow)) {
if (idNow.endsWith(ID_COMPONENT_SEPARATOR) && id.startsWith(idNow)) {
return;
}
throw new ReadOnlyException("Cannot change " + field + " as that would change primary key");
}
}
@Override
public String toString() {
return super.toString() + " [protected ID]";
}
}
}

View file

@ -1,74 +0,0 @@
/*
* Copyright 2023 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.file;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.map.common.AbstractEntity;
import org.keycloak.models.map.common.ExpirableEntity;
import org.keycloak.models.map.common.SessionAttributesUtils;
import org.keycloak.models.map.common.UpdatableEntity;
import org.keycloak.models.map.storage.MapStorage;
import org.keycloak.models.map.storage.MapStorageProvider;
import org.keycloak.models.map.storage.MapStorageProviderFactory;
import org.keycloak.models.map.storage.ModelEntityUtil;
import org.keycloak.models.map.storage.chm.ConcurrentHashMapStorage;
import java.util.function.Function;
import static org.keycloak.models.map.storage.ModelEntityUtil.getModelName;
import static org.keycloak.models.map.storage.file.FileMapStorageProviderFactory.UNIQUE_HUMAN_READABLE_NAME_FIELD;
/**
* File-based {@link MapStorageProvider} implementation.
*
* @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a>
*/
public class FileMapStorageProvider implements MapStorageProvider {
private final KeycloakSession session;
private final FileMapStorageProviderFactory factory;
private final int factoryId;
public FileMapStorageProvider(KeycloakSession session, FileMapStorageProviderFactory factory, int factoryId) {
this.session = session;
this.factory = factory;
this.factoryId = factoryId;
}
@Override
@SuppressWarnings("unchecked")
public <V extends AbstractEntity, M> MapStorage<V, M> getMapStorage(Class<M> modelType, MapStorageProviderFactory.Flag... flags) {
return (MapStorage<V, M>) SessionAttributesUtils.createMapStorageIfAbsent(session, getClass(), modelType, factoryId, () -> createFileMapStorage(modelType));
}
private <V extends AbstractEntity & UpdatableEntity, M> ConcurrentHashMapStorage<?, V, M, FileCrudOperations<V, M>> createFileMapStorage(Class<M> modelType) {
String areaName = getModelName(modelType, modelType.getSimpleName());
final Class<V> et = ModelEntityUtil.getEntityType(modelType);
Function<V, String[]> uniqueHumanReadableField = (Function<V, String[]>) UNIQUE_HUMAN_READABLE_NAME_FIELD.get(et);
ConcurrentHashMapStorage mapStorage = FileMapStorage.newInstance(et,
factory.getDataDirectoryFunc(areaName),
((uniqueHumanReadableField == null) ? v -> v.getId() == null ? null : new String[]{v.getId()} : uniqueHumanReadableField),
ExpirableEntity.class.isAssignableFrom(et));
session.getTransactionManager().enlist(mapStorage);
return mapStorage;
}
@Override
public void close() {
}
}

View file

@ -1,156 +0,0 @@
/*
* Copyright 2023 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.file;
import org.keycloak.Config;
import org.keycloak.common.Profile;
import org.keycloak.component.AmphibianProviderFactory;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.RealmModel;
import org.keycloak.models.map.authorization.entity.MapPermissionTicketEntity;
import org.keycloak.models.map.authorization.entity.MapPolicyEntity;
import org.keycloak.models.map.authorization.entity.MapResourceEntity;
import org.keycloak.models.map.authorization.entity.MapResourceServerEntity;
import org.keycloak.models.map.authorization.entity.MapScopeEntity;
import org.keycloak.models.map.client.MapClientEntity;
import org.keycloak.models.map.clientscope.MapClientScopeEntity;
import org.keycloak.models.map.common.SessionAttributesUtils;
import org.keycloak.models.map.group.MapGroupEntity;
import org.keycloak.models.map.realm.MapRealmEntity;
import org.keycloak.models.map.role.MapRoleEntity;
import org.keycloak.models.map.storage.MapStorageProvider;
import org.keycloak.models.map.storage.MapStorageProviderFactory;
import org.keycloak.models.map.user.MapUserEntity;
import org.keycloak.provider.EnvironmentDependentProviderFactory;
import java.io.File;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.regex.Pattern;
import static java.util.Map.entry;
import static org.keycloak.models.map.storage.ModelEntityUtil.getModelName;
import static org.keycloak.models.map.storage.ModelEntityUtil.getModelNames;
/**
* A {@link MapStorageProviderFactory} that creates file-based {@link MapStorageProvider}s.
*
* @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a>
*/
public class FileMapStorageProviderFactory implements AmphibianProviderFactory<MapStorageProvider>,
MapStorageProviderFactory,
EnvironmentDependentProviderFactory {
public static final String PROVIDER_ID = "file";
private Path rootRealmsDirectory;
private final Map<String, Function<String, Path>> rootAreaDirectories = new HashMap<>(); // Function: (realmId) -> path
private final int factoryId = SessionAttributesUtils.grabNewFactoryIdentifier();
protected static final Map<Class<?>, Function<?, String[]>> UNIQUE_HUMAN_READABLE_NAME_FIELD = Map.ofEntries(
entry(MapClientEntity.class, ((Function<MapClientEntity, String[]>) v -> new String[] { v.getClientId() })),
entry(MapClientScopeEntity.class, ((Function<MapClientScopeEntity, String[]>) v -> new String[] { v.getName() })),
entry(MapGroupEntity.class, ((Function<MapGroupEntity, String[]>) v -> v.getParentId() == null
? new String[] { v.getName() }
: new String[] { v.getParentId(), v.getName() })),
entry(MapRealmEntity.class, ((Function<MapRealmEntity, String[]>) v -> new String[] { v.getName()})),
entry(MapRoleEntity.class, ((Function<MapRoleEntity, String[]>) (v -> v.getClientId() == null
? new String[] { v.getName() }
: new String[] { v.getClientId(), v.getName() }))),
entry(MapUserEntity.class, ((Function<MapUserEntity, String[]>) v -> new String[] { v.getUsername() })),
// authz
entry(MapResourceServerEntity.class, ((Function<MapResourceServerEntity, String[]>) v -> new String[] { v.getClientId() })),
entry(MapPolicyEntity.class, ((Function<MapPolicyEntity, String[]>) v -> new String[] { v.getResourceServerId(), v.getName() })),
entry(MapPermissionTicketEntity.class,((Function<MapPermissionTicketEntity, String[]>) v -> new String[] { v.getResourceServerId(), null })),
entry(MapResourceEntity.class, ((Function<MapResourceEntity, String[]>) v -> Objects.equals(v.getResourceServerId(), v.getOwner())
? new String[] { v.getResourceServerId(), v.getName() }
: new String[] { v.getResourceServerId(), v.getName(), v.getOwner() })),
entry(MapScopeEntity.class, ((Function<MapScopeEntity, String[]>) v -> new String[] { v.getResourceServerId(), v.getName() }))
);
@Override
public MapStorageProvider create(KeycloakSession session) {
return SessionAttributesUtils.createProviderIfAbsent(session, factoryId, FileMapStorageProvider.class, session1 -> new FileMapStorageProvider(session1, this, factoryId));
}
@Override
public String getHelpText() {
return "File Map Storage";
}
@Override
public boolean isSupported() {
return Profile.isFeatureEnabled(Profile.Feature.MAP_STORAGE);
}
@Override
public void init(Config.Scope config) {
final String dir = config.get("dir");
rootRealmsDirectory = dir == null ? null : Path.of(dir);
getModelNames().stream()
.filter(n -> ! Objects.equals(n, getModelName(RealmModel.class)))
.forEach(n -> rootAreaDirectories.put(n, getRootDir(rootRealmsDirectory, n, config.get("dir." + n))));
if (rootAreaDirectories != null) {
rootAreaDirectories.put(getModelName(RealmModel.class), realmId -> realmId == null ? rootRealmsDirectory : rootRealmsDirectory.resolve(realmId) );
}
}
private static final Pattern FORBIDDEN_CHARACTERS = Pattern.compile("[\\.\\" + File.separator + "]");
private static Function<String, Path> getRootDir(Path rootRealmsDirectory, String areaName, String dirFromConfig) {
if (dirFromConfig != null) {
Path p = Path.of(dirFromConfig);
return realmId -> p;
} else {
if (rootRealmsDirectory == null) {
return p -> { throw new IllegalStateException("Directory for " + areaName + " area not configured."); };
}
Path a = areaName.startsWith("authz-") ? Path.of("authz", areaName.substring(6)) : Path.of(areaName);
return realmId -> {
if (realmId == null || FORBIDDEN_CHARACTERS.matcher(realmId).find()) {
throw new IllegalArgumentException("Realm needed for constructing the path to " + areaName + " but not known or invalid: " + realmId);
}
final Path path = rootRealmsDirectory
.resolve(realmId)
.resolve(a);
return path;
};
}
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public String getId() {
return PROVIDER_ID;
}
public Function<String, Path> getDataDirectoryFunc(String areaName) {
return rootAreaDirectories.get(areaName);
}
}

View file

@ -1,266 +0,0 @@
/*
* Copyright 2023 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.file.common;
import org.keycloak.models.map.common.UndefinedValuesUtils;
import org.keycloak.models.map.storage.file.yaml.YamlParser;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import static org.keycloak.models.map.common.CastUtils.cast;
/**
* A class implementing a {@code BlockContext} interface represents a transformer
* from a primitive value / sequence / mapping representation as declared in YAML
* format into a Java object of type {@code V}, with ability to produce
* the {@link #getResult() resulting instance} of parsing.
*
* <p>
* This transformer handles only a values of a single node in structured file, i.e.
* single value (a primitive value, sequence or mapping). The root level
* is at the beginning of e.g. YAML or JSON document.
* Every mapping key and every sequence value then represents next level of nesting.
*
* @author hmlnarik
* @param <V> Type of the result
*/
public interface BlockContext<V> {
/**
* Writes the given value using {@link WritingMechanism}.
*
* @param value
* @param mech
*/
void writeValue(V value, WritingMechanism mech);
/**
* Called after reading a key of map entry in YAML file and before reading its value.
* The key of the entry is represented as {@code nameOfSubcontext} parameter, and
* provides means to specify a {@code YamlContext} for transforming the mapping value
* into appropriate Java object.
*
* @param nameOfSubcontext Key of the map entry
*
* @return Context used for transforming the value,
* or {@code null} if the default primitive / sequence / mapping context should be used instead.
*
* @see DefaultObjectContext
* @see DefaultListContext
* @see DefaultMapContext
*/
BlockContext<?> getContext(String nameOfSubcontext);
/**
* Modifies the {@link #getResult() result returned} from within this context by
* providing the read mapping entry {@code name} to given {@code value}.
* <p>
* Called after reading a map entry (both key and value) from the YAML file is finished.
* The entry is represented as {@code name} parameter (key part of the entry)
* and {@code value} (value part of the entry).
* <p>
* The method is called in the same order as the mapping items appear in the source YAML mapping.
*
* @param name
* @param value
*/
default void add(String name, Object value) { };
/**
* Modifies the {@link #getResult() result returned} from within this context by
* providing the read primitive value or a single sequence item in the {@code value} parameter.
* <p>
* Called after reading a primitive value or a single sequence item
* from the YAML file is finished.
* <p>
* If the parsed YAML part was a sequence, this method is called in the same order
* as the sequence items appear in the source YAML sequence.
*
* @param value
*/
default void add(Object value) { };
/**
* Returns the result of parsing the given part of YAML file.
* @return
*/
V getResult();
Class<?> getScalarType();
public static class DefaultObjectContext<T> implements BlockContext<T> {
private final Class<T> objectType;
private T result;
public DefaultObjectContext(Class<T> objectType) {
this.objectType = objectType;
}
public static DefaultObjectContext<Object> newDefaultObjectContext() {
return new DefaultObjectContext<>(Object.class);
}
@Override
public Class<T> getScalarType() {
return objectType;
}
@Override
public void add(Object value) {
result = (T) value;
}
@Override
public T getResult() {
return result;
}
@Override
public void writeValue(Object value, WritingMechanism mech) {
if (value == null || (value.getClass() != String.class && UndefinedValuesUtils.isUndefined(value))) return;
mech.writeObject(value);
}
@Override
public BlockContext<?> getContext(String nameOfSubcontext) {
return null;
}
}
public static class DefaultListContext<T> implements BlockContext<Collection<T>> {
private final List<T> result = new LinkedList<>();
protected final Class<T> itemClass;
public static DefaultListContext<Object> newDefaultListContext() {
return new DefaultListContext<>(Object.class);
}
public DefaultListContext(Class<T> itemClass) {
this.itemClass = itemClass;
}
@Override
public Class<T> getScalarType() {
return itemClass;
}
@Override
public void add(Object value) {
result.add(cast(value, itemClass));
}
@Override
public List<T> getResult() {
return result;
}
@Override
@SuppressWarnings("unchecked")
public void writeValue(Collection<T> value, WritingMechanism mech) {
if (UndefinedValuesUtils.isUndefined(value)) return;
mech.writeSequence(() -> value.forEach(v -> getContextByValue(v).writeValue(v, mech)));
}
@Override
public BlockContext<?> getContext(String nameOfSubcontext) {
return null;
}
private BlockContext getContextByValue(Object value) {
BlockContext res = getContext(YamlParser.ARRAY_CONTEXT);
if (res != null) {
return res;
}
if (value instanceof Collection) {
return new DefaultListContext<>(itemClass);
} else if (value instanceof Map) {
return DefaultMapContext.newDefaultMapContext();
} else {
return new DefaultObjectContext<>(itemClass);
}
}
}
public static class DefaultMapContext<T> implements BlockContext<Map<String, T>> {
private final Map<String, T> result = new LinkedHashMap<>();
protected final Class<T> itemClass;
public static DefaultMapContext<Object> newDefaultMapContext() {
return new DefaultMapContext<>(Object.class);
}
public DefaultMapContext(Class<T> itemClass) {
this.itemClass = itemClass;
}
@Override
public Class<T> getScalarType() {
return itemClass;
}
@Override
@SuppressWarnings("unchecked")
public void add(String name, Object value) {
result.put(name, (T) value);
}
@Override
public Map<String, T> getResult() {
return result;
}
@Override
public void writeValue(Map<String, T> value, WritingMechanism mech) {
if (UndefinedValuesUtils.isUndefined(value)) return;
mech.writeMapping(() -> {
final TreeMap<String, Object> sortedMap = new TreeMap<>(value);
sortedMap.forEach(
(key, val) -> mech.writePair(
key,
() -> getContext(key, val).writeValue(val, mech)
)
);
});
}
@Override
public BlockContext<T> getContext(String nameOfSubcontext) {
return null;
}
private BlockContext getContext(String nameOfSubcontext, Object value) {
BlockContext res = getContext(nameOfSubcontext);
if (res != null) {
return res;
}
if (value instanceof Collection) {
return new DefaultListContext<>(itemClass);
} else if (value instanceof Map) {
return DefaultMapContext.newDefaultMapContext();
} else {
return new DefaultObjectContext<>(itemClass);
}
}
}
}

View file

@ -1,62 +0,0 @@
/*
* Copyright 2023 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.file.common;
import java.util.LinkedList;
import java.util.function.Supplier;
/**
* A special stack suited for tracking the parser of a block language, and maintaining
* contextual information for block nesting position in the YAML file.
* <p>
* The intention is as follows:
* Initially, it contains a single {@link BlockContext} instance which represents
* the root context of the YAML tree. Every sequence item and mapping value
* in the YAML file leads to pushing a new {@link BlockContext} onto the stack
* which is created by the topmost {@link BlockContext#getContext(java.lang.String)}
* method of the topmost {@link BlockContext}. This context is removed from the stack
* once parsing of the respective sequence item or mapping pair is finished.
*
* @author hmlnarik
*/
public class BlockContextStack extends LinkedList<BlockContext<?>> {
public BlockContextStack(BlockContext<?> rootElement) {
push(rootElement);
}
/**
* Pushes the subcontext to the stack.
* <p>
* The subcontext is created by calling {@link BlockContext#getContext(java.lang.String)}
* method. If this method returns {@code null}, the control reverts to producing
* the subcontext using {@code nullProducer} which must return a valid {@link BlockContext}
* object (it <b>must not</b> return {@code null).
*
* @param name
* @param nullProducer
* @return
*/
public BlockContext<?> push(String name, Supplier<BlockContext<?>> nullProducer) {
BlockContext<?> context = peek().getContext(name);
if (context == null) {
context = nullProducer.get();
}
push(context);
return context;
}
}

View file

@ -1,289 +0,0 @@
/*
* Copyright 2023 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.file.common;
import org.keycloak.models.map.common.AbstractEntity;
import org.keycloak.models.map.common.DeepCloner;
import org.keycloak.models.map.common.EntityField;
import org.keycloak.models.map.common.UndefinedValuesUtils;
import org.keycloak.models.map.role.MapRoleEntityFields;
import org.keycloak.models.map.storage.ModelEntityUtil;
import org.keycloak.models.map.storage.file.common.BlockContext.DefaultListContext;
import org.keycloak.models.map.storage.file.common.BlockContext.DefaultMapContext;
import java.util.Collection;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.TreeSet;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.jboss.logging.Logger;
import static org.keycloak.models.map.common.CastUtils.cast;
/**
* {@link BlockContext} which handles any entity accompanied with {@link EntityField} field getters and setters,
* namely {@code Map*Entity} classes.
* @author hmlnarik
*/
public class MapEntityContext<T> implements BlockContext<T> {
private static final Logger LOG = Logger.getLogger(MapEntityContext.class);
private final Map<String, EntityField<?>> nameToEntityField;
private final Map<String, Supplier<? extends BlockContext<?>>> contextCreators;
protected final Class<T> objectClass;
protected final T result;
private static final Map<Class, Map<String, EntityField<?>>> CACHE_FIELD_TO_EF = new IdentityHashMap<>();
private static final Map<Class, Map<String, Supplier<? extends BlockContext<?>>>> CACHE_CLASS_TO_CC = new IdentityHashMap<>();
private final boolean topContext;
private boolean alreadyReadProperty = false;
public static final String SCHEMA_VERSION = "schemaVersion";
public MapEntityContext(Class<T> clazz) {
this(clazz, true);
}
@SuppressWarnings("unchecked")
public MapEntityContext(Class<T> clazz, boolean topContext) {
this(clazz,
CACHE_FIELD_TO_EF.computeIfAbsent(clazz, MapEntityContext::fieldsToEntityField),
CACHE_CLASS_TO_CC.computeIfAbsent(clazz, MapEntityContext::fieldsToContextCreators),
topContext
);
}
protected MapEntityContext(
Class<T> clazz,
Map<String, EntityField<?>> nameToEntityField,
Map<String, Supplier<? extends BlockContext<?>>> contextCreators,
boolean topContext) {
this.objectClass = clazz;
this.result = DeepCloner.DUMB_CLONER.newInstance(clazz);
this.nameToEntityField = nameToEntityField;
this.contextCreators = contextCreators;
this.topContext = topContext;
}
protected static <T> Map<String, Supplier<? extends BlockContext<?>>> fieldsToContextCreators(Class<T> type) {
if (! ModelEntityUtil.entityFieldsKnown(type)) {
return Collections.emptyMap();
}
return ModelEntityUtil.getEntityFields(type)
.map(ef -> Map.entry(ef, Optional.ofNullable(getDefaultContextCreator(ef))))
.filter(me -> me.getValue().isPresent())
.collect(Collectors.toMap(me -> me.getKey().getNameCamelCase(), me -> me.getValue().get()));
}
private static <T> Supplier<? extends BlockContext<?>> getDefaultContextCreator(EntityField<? super T> ef) {
final Class<?> collectionElementClass = ef.getCollectionElementClass();
if (collectionElementClass != Void.class) {
if (ModelEntityUtil.entityFieldsKnown(collectionElementClass)) {
return () -> new MapEntitySequenceYamlContext<>(collectionElementClass);
}
}
final Class<?> mapValueClass = ef.getMapValueClass();
if (mapValueClass != Void.class) {
if (ModelEntityUtil.entityFieldsKnown(mapValueClass)) {
return () -> new MapEntityMappingYamlContext<>(mapValueClass);
} else if (ATTRIBUTES_NAME.equals(ef.getName())) {
return StringListMapContext::new;
}
}
return null;
}
protected static final String ATTRIBUTES_NAME = MapRoleEntityFields.ATTRIBUTES.getName();
public static <T> Map<String, EntityField<?>> fieldsToEntityField(Class<T> type) {
return ModelEntityUtil.getEntityFields(type).collect(Collectors.toUnmodifiableMap(EntityField::getNameCamelCase, Function.identity()));
}
@SuppressWarnings("unchecked")
public static <T> boolean setEntityField(T result, EntityField<? super T> ef, Object value) {
LOG.tracef("Setting %s::%s field", ef, result.getClass());
if (ef == null) {
return false;
}
try {
if (ef.getCollectionElementClass() != Void.class && value instanceof Collection) {
Class<?> collectionElementClass = ef.getCollectionElementClass();
((Collection) value).forEach(v -> ef.collectionAdd(result, cast(v, collectionElementClass)));
} else if (ef.getMapKeyClass() != Void.class && value instanceof Map) {
Class<?> mapKeyClass = ef.getMapKeyClass();
Class<?> mapValueClass = ef.getMapValueClass();
((Map) value).forEach((k, v) -> ef.mapPut(result, cast(k, mapKeyClass), cast(v, mapValueClass)));
} else {
final Object origValue = ef.get(result);
if (origValue != null) {
LOG.warnf("Overwriting value of %s field", ef.getNameCamelCase());
}
ef.set(result, cast(value, ef.getFieldClass()));
}
} catch (Exception ex) {
throw new IllegalArgumentException("Exception thrown while setting " + ef + " field", ex);
}
return true;
}
@Override
public void add(String name, Object value) {
@SuppressWarnings("unchecked")
EntityField<? super T> ef = (EntityField<? super T>) nameToEntityField.get(name);
if (topContext && name.equals(SCHEMA_VERSION)) {
return; // TODO: Check appropriate schema version and potentially update parsing
}
if (! setEntityField(result, ef, value)) {
LOG.warnf("Ignoring field %s", name);
}
}
@Override
public Class<T> getScalarType() {
return this.objectClass;
}
@Override
public T getResult() {
return this.result;
}
@Override
public BlockContext<?> getContext(String nameOfSubcontext) {
if (topContext && nameOfSubcontext.equals(SCHEMA_VERSION)) {
if (alreadyReadProperty) {
LOG.warnf("%s must be the first property in the object YAML representation", SCHEMA_VERSION);
}
return null;
}
alreadyReadProperty = true;
Supplier<? extends BlockContext<?>> cc = contextCreators.get(nameOfSubcontext);
if (cc != null) {
return cc.get();
}
EntityField<?> ef = nameToEntityField.get(nameOfSubcontext);
if (ef != null) {
if (ef.getCollectionElementClass() != Void.class) {
return contextFor(ef.getCollectionElementClass(), MapEntitySequenceYamlContext::new, DefaultListContext::new);
} else if (ef.getMapValueClass() != Void.class) {
if (ef.getMapValueClass() == List.class || Collection.class.isAssignableFrom(ef.getMapValueClass())) {
return new StringListMapContext();
}
return contextFor(ef.getMapValueClass(), MapEntityMappingYamlContext::new, DefaultMapContext::new);
}
return contextFor(ef.getFieldClass(), MapEntityContext::new, DefaultObjectContext::new);
}
LOG.warnf("No special context set for field %s", nameOfSubcontext);
return null;
}
private static <T> BlockContext<?> contextFor(Class<T> clazz, Function<Class<T>, BlockContext<?>> mapContextCreator, Function<Class<T>, BlockContext<?>> defaultCreator) {
return ModelEntityUtil.entityFieldsKnown(clazz)
? mapContextCreator.apply(clazz)
: defaultCreator.apply(clazz);
}
@Override
public void writeValue(T entity, WritingMechanism mech) {
if (UndefinedValuesUtils.isUndefined(entity)) return;
mech.writeMapping(() -> {
if (topContext) {
mech.writePair(SCHEMA_VERSION, () -> mech.writeObject("1.0.Alpha1"));
}
TreeSet<String> contextNames = new TreeSet<>(nameToEntityField.keySet());
contextNames.addAll(contextCreators.keySet());
for (String contextName : contextNames) {
@SuppressWarnings("unchecked")
EntityField<T> ef = (EntityField<T>) nameToEntityField.get(contextName);
if (ef == null) {
continue;
}
if (topContext && (ef.getNameCamelCase().equals("id") || ef.getNameCamelCase().equals("realmId"))) {
continue;
}
Object fieldVal = ef.get(entity);
if (fieldVal != null) {
BlockContext context = getContext(contextName);
if (context != null) {
mech.writePair(contextName, () -> context.writeValue(fieldVal, mech));
}
}
}
});
}
@Override
public String toString() {
return "MapEntityContext[" + objectClass.getCanonicalName() + ']';
}
public static class MapEntitySequenceYamlContext<T> extends DefaultListContext<T> {
public MapEntitySequenceYamlContext(Class<T> itemClass) {
super(itemClass);
}
@Override
public BlockContext<?> getContext(String nameOfSubcontext) {
return ModelEntityUtil.entityFieldsKnown(itemClass)
? new MapEntityContext<>(itemClass, false)
: null;
}
@Override
public void add(String name, Object value) {
if (value instanceof AbstractEntity) {
((AbstractEntity) value).setId(name);
add(value);
} else {
throw new IllegalArgumentException("Sequence expected, mapping with " + name + " key found instead.");
}
}
}
public static class MapEntityMappingYamlContext<T> extends DefaultMapContext<T> {
public MapEntityMappingYamlContext(Class<T> mapValueClass) {
super(mapValueClass);
}
@Override
public BlockContext<T> getContext(String nameOfSubcontext) {
return ModelEntityUtil.entityFieldsKnown(itemClass)
? new MapEntityContext<>(itemClass, false)
: super.getContext(nameOfSubcontext);
}
}
}

View file

@ -1,112 +0,0 @@
/*
* Copyright 2023 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.file.common;
import java.util.Collection;
import java.util.LinkedList;
import java.util.Map;
import java.util.TreeMap;
import org.keycloak.models.map.common.UndefinedValuesUtils;
import org.keycloak.models.map.storage.file.common.BlockContext.DefaultListContext;
import org.keycloak.models.map.storage.file.common.BlockContext.DefaultMapContext;
import org.keycloak.models.map.storage.file.yaml.YamlParser;
import java.util.List;
/**
* Block context which suitable for properties stored in a {@code Map<String, List<String>>}
* which accepts string mapping key, and entry value is recognized both as a plain value
* (converted to string) or a list of values
*
* @author hmlnarik
*/
public class StringListMapContext extends DefaultMapContext<Collection<String>> {
@SuppressWarnings("unchecked")
public StringListMapContext() {
super((Class) Collection.class);
}
/**
* Returns a YAML attribute-like context where key of each element
* is stored in YAML file without a given prefix, and in the internal
* representation each key has that prefix.
*
* @param prefix
* @return
*/
public static StringListMapContext prefixed(String prefix) {
return new Prefixed(prefix);
}
@Override
public AttributeValueYamlContext getContext(String nameOfSubcontext) {
// regardless of the key name, the values need to be converted into List<String> which is the purpose of AttributeValueYamlContext
return new AttributeValueYamlContext();
}
@Override
public void writeValue(Map<String, Collection<String>> value, WritingMechanism mech) {
if (UndefinedValuesUtils.isUndefined(value)) return;
mech.writeMapping(() -> {
AttributeValueYamlContext c = getContext(YamlParser.ARRAY_CONTEXT);
for (Map.Entry<String, Collection<String>> entry : new TreeMap<>(value).entrySet()) {
Collection<String> attrValues = entry.getValue();
mech.writePair(entry.getKey(), () -> c.writeValue(attrValues, mech));
}
});
}
private static class Prefixed extends StringListMapContext {
protected final String prefix;
public Prefixed(String prefix) {
this.prefix = prefix;
}
@Override
public void add(String name, Object value) {
super.add(prefix + name, value);
}
}
public static class AttributeValueYamlContext extends DefaultListContext<String> {
public AttributeValueYamlContext() {
super(String.class);
}
@Override
public void writeValue(Collection<String> value, WritingMechanism mech) {
if (UndefinedValuesUtils.isUndefined(value)) return;
if (value.size() == 1) {
mech.writeObject(value.iterator().next());
} else {
//sequence
super.writeValue(value, mech);
}
}
@Override
public void add(Object value) {
if (value != null) {
super.add(String.valueOf(value));
}
}
}
}

View file

@ -1,54 +0,0 @@
/*
* Copyright 2023 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.file.common;
/**
* Class implementing this interface defines mechanism for writing basic structures: primitive types,
* sequences and maps.
*/
public interface WritingMechanism {
/**
* Writes a value of a primitive type ({@code null}, boolean, number, String).
* @param value
* @return
*/
WritingMechanism writeObject(Object value);
/**
* Writes a sequence, items of which are written using this mechanism in the {@code task}.
* @param task
* @return
*/
WritingMechanism writeSequence(Runnable task);
/**
* Writes a mapping, items of which are written using this mechanism in the {@code task}.
* @param task
* @return
*/
WritingMechanism writeMapping(Runnable task);
/**
* Writes a mapping key/value pair, items of which are written using this mechanism in the {@code task}.
* @param valueTask
* @return
*/
WritingMechanism writePair(String key, Runnable valueTask);
}

View file

@ -1,70 +0,0 @@
/*
* Copyright 2023 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.file.yaml;
import java.io.BufferedWriter;
import java.io.Closeable;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import org.snakeyaml.engine.v2.api.StreamDataWriter;
/**
*
* @author hmlnarik
*/
public class PathWriter implements StreamDataWriter, Closeable {
private final BufferedWriter writer;
public PathWriter(Path path) throws IOException {
this.writer = Files.newBufferedWriter(path);
}
@Override
public void write(String str) {
try {
this.writer.write(str);
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
@Override
public void write(String str, int off, int len) {
try {
this.writer.write(str, off, len);
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
@Override
public void flush() {
try {
this.writer.flush();
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
@Override
public void close() throws IOException {
writer.close();
}
}

View file

@ -1,70 +0,0 @@
/*
* Copyright 2023 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.file.yaml;
import java.util.LinkedList;
import java.util.concurrent.atomic.AtomicBoolean;
/**
*
* @author hmlnarik
*/
class RunOnlyOnce implements Runnable {
private final AtomicBoolean ran = new AtomicBoolean(false);
private final Runnable preTask;
private final Runnable postTask;
public RunOnlyOnce(Runnable preTask, Runnable postTask) {
this.preTask = preTask;
this.postTask = postTask;
}
@Override
public void run() {
if (ran.compareAndSet(false, true) && preTask != null) {
preTask.run();
}
}
public void runPostTask() {
if (hasRun() && postTask != null) {
postTask.run();
}
}
public boolean hasRun() {
return ran.get();
}
@Override
public String toString() {
return "RunOnlyOnce"
+ (hasRun() ? " - ran already" : "")
+ " " + preTask;
}
static class List extends LinkedList<RunOnlyOnce> {
@Override
public RunOnlyOnce removeLast() {
final RunOnlyOnce res = super.removeLast();
res.runPostTask();
return res;
}
}
}

View file

@ -1,285 +0,0 @@
/*
* Copyright 2023 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.file.yaml;
import org.keycloak.models.map.common.CastUtils;
import org.keycloak.models.map.storage.file.common.BlockContextStack;
import org.keycloak.models.map.storage.file.common.BlockContext.DefaultListContext;
import org.keycloak.models.map.storage.file.common.BlockContext.DefaultMapContext;
import org.keycloak.models.map.storage.file.common.BlockContext.DefaultObjectContext;
import java.io.InputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.EnumMap;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Supplier;
import org.jboss.logging.Logger;
import org.snakeyaml.engine.v2.api.LoadSettings;
import org.snakeyaml.engine.v2.api.YamlUnicodeReader;
import org.snakeyaml.engine.v2.constructor.StandardConstructor;
import org.snakeyaml.engine.v2.events.Event;
import org.snakeyaml.engine.v2.events.Event.ID;
import org.snakeyaml.engine.v2.events.NodeEvent;
import org.snakeyaml.engine.v2.events.ScalarEvent;
import org.snakeyaml.engine.v2.exceptions.ConstructorException;
import org.snakeyaml.engine.v2.nodes.ScalarNode;
import org.snakeyaml.engine.v2.nodes.Tag;
import org.snakeyaml.engine.v2.parser.Parser;
import org.snakeyaml.engine.v2.parser.ParserImpl;
import org.snakeyaml.engine.v2.resolver.JsonScalarResolver;
import org.snakeyaml.engine.v2.resolver.ScalarResolver;
import org.snakeyaml.engine.v2.scanner.StreamReader;
import org.keycloak.models.map.storage.file.common.BlockContext;
import java.util.Map;
import org.snakeyaml.engine.v2.constructor.ConstructScalar;
import org.snakeyaml.engine.v2.nodes.Node;
import static org.keycloak.common.util.StackUtil.getShortStackTrace;
/**
*
* @author hmlnarik
*/
public class YamlParser<E> {
private static final Logger LOG = Logger.getLogger(YamlParser.class);
public static final String ARRAY_CONTEXT = "$@[]@$";
private static final ScalarResolver RESOLVER = new JsonScalarResolver();
private final Parser parser;
private final BlockContextStack contextStack;
// Leverage SnakeYaml's translation of primitive values
private static final class MiniConstructor extends StandardConstructor {
public MiniConstructor() {
super(SETTINGS);
}
// This has been based on SnakeYaml's own org.snakeyaml.engine.v2.constructor.BaseConstructor.constructObjectNoCheck(Node node)
@SuppressWarnings(value = "unchecked")
public Object constructStandardJavaInstance(ScalarNode node) {
return findConstructorFor(node)
.map(constructor -> constructor.construct(node))
.orElseThrow(() -> new ConstructorException(null, Optional.empty(), "Could not determine a constructor for the tag " + node.getTag(), node.getStartMark()));
}
public static final MiniConstructor INSTANCE = new MiniConstructor();
}
private static final class NullConstructor extends ConstructScalar {
@Override
public Object construct(Node node) {
return null;
}
}
private static final LoadSettings SETTINGS = LoadSettings.builder()
.setAllowRecursiveKeys(false)
.setParseComments(false)
.setTagConstructors(Map.of(Tag.NULL, new NullConstructor()))
.build();
public static <E> E parse(Path path, BlockContext<E> initialContext) {
LOG.tracef("parse(%s,%s)%s", path, initialContext, getShortStackTrace());
Objects.requireNonNull(path, "Path invalid");
try (InputStream is = Files.newInputStream(path)) {
if (Files.size(path) == 0) {
return null;
}
Parser p = new ParserImpl(SETTINGS, new StreamReader(SETTINGS, new YamlUnicodeReader(is)));
return new YamlParser<>(p, initialContext).parse();
} catch (IOException ex) {
LOG.warn(ex);
return null;
}
}
protected YamlParser(Parser p, BlockContext<E> initialContext) {
this.parser = p;
this.contextStack = new BlockContextStack(initialContext);
}
@SuppressWarnings("unchecked")
protected <E> E parse() {
consumeEvent(Event.ID.StreamStart, "Expected a stream");
if (!parser.checkEvent(Event.ID.StreamEnd)) {
consumeEvent(Event.ID.DocumentStart, "Expected a document in the stream");
parseNode();
consumeEvent(Event.ID.DocumentEnd, "Expected a single document in the stream");
}
consumeEvent(Event.ID.StreamEnd, "Expected a single document in the stream");
return (E) contextStack.pop().getResult();
}
protected Object parseNode() {
if (parser.checkEvent(Event.ID.Alias)) {
throw new IllegalStateException("Aliases are not handled at this moment");
}
Event ev = parser.next();
if (!(ev instanceof NodeEvent)) {
throw new IllegalArgumentException("Invalid event " + ev);
}
// if (anchor != null) {
// node.setAnchor(anchor);
// anchors.put(anchor, node);
// }
// try {
switch (ev.getEventId()) {
case Scalar:
return parseScalar((ScalarEvent) ev);
case SequenceStart:
return parseSequence();
case MappingStart:
return parseMapping();
default:
throw new IllegalStateException("Event not expected " + ev);
}
// } finally {
// anchors.remove(anchor);
// }
}
/**
* Parses a sequence node inside the current context. Each sequence item is parsed in the context
* supplied by the current
* @return
*/
protected Object parseSequence() {
LOG.tracef("Parsing sequence");
BlockContext context = contextStack.peek();
while (! parser.checkEvent(Event.ID.SequenceEnd)) {
context.add(parseNodeInFreshContext(ARRAY_CONTEXT));
}
consumeEvent(Event.ID.SequenceEnd, "Expected end of sequence");
return context.getResult();
}
/**
* Parses a mapping node inside the current context. Each mapping value is parsed in the context
* supplied by the current context for the mapping key.
* @return
*/
protected Object parseMapping() {
LOG.tracef("Parsing mapping");
BlockContext context = contextStack.peek();
while (! parser.checkEvent(Event.ID.MappingEnd)) {
Object key = parseNodeInFreshContext();
LOG.tracef("Parsed mapping key: %s", key);
if (! (key instanceof String)) {
try {
key = CastUtils.cast(key, String.class);
} catch (IllegalStateException ex) {
throw new IllegalStateException("Invalid key in map: " + key);
}
}
Object value = parseNodeInFreshContext((String) key);
LOG.tracef("Parsed mapping value: %s", value);
context.add((String) key, value);
}
consumeEvent(Event.ID.MappingEnd, "Expected end of mapping");
return context.getResult();
}
/**
* Parses a scalar node inside the current context.
* @return
*/
protected Object parseScalar(ScalarEvent se) {
BlockContext context = contextStack.peek();
boolean implicit = se.getImplicit().canOmitTagInPlainScalar();
final Tag nodeTag;
Class ot = context.getScalarType();
nodeTag = constructTag(se.getTag(), se.getValue(), implicit, ot);
ScalarNode node = new ScalarNode(nodeTag, true, se.getValue(), se.getScalarStyle(), se.getStartMark(), se.getEndMark());
final Object value = MiniConstructor.INSTANCE.constructStandardJavaInstance(node);
context.add(value);
return context.getResult();
}
private static final EnumMap<Event.ID, Supplier<BlockContext<?>>> CONTEXT_CONSTRUCTORS = new EnumMap<>(Event.ID.class);
static {
CONTEXT_CONSTRUCTORS.put(ID.Scalar, DefaultObjectContext::newDefaultObjectContext);
CONTEXT_CONSTRUCTORS.put(ID.SequenceStart, DefaultListContext::newDefaultListContext);
CONTEXT_CONSTRUCTORS.put(ID.MappingStart, DefaultMapContext::newDefaultMapContext);
}
/**
* Ensure that the next event is the expectedEventId, otherwise throw an exception, and consume that event
*/
private Event consumeEvent(ID expectedEventId, String message) throws IllegalArgumentException {
if (! parser.checkEvent(expectedEventId)) {
Event event = parser.next();
throw new IllegalArgumentException(message + " at " + event.getStartMark());
}
return parser.next();
}
private static Tag constructTag(Optional<String> tag, String value, boolean implicit) {
// based on org.snakeyaml.engine.v2.composer.Composer.composeScalarNode(Optional<Anchor> anchor, List<CommentLine> blockComments)
return tag.filter(t -> ! "!".equals(t))
.map(Tag::new)
.orElseGet(() -> RESOLVER.resolve(value, implicit));
}
private Tag constructTag(Optional<String> tag, String value, boolean implicit, Class<?> ot) {
if (ot == String.class) {
return Tag.STR;
} else {
return constructTag(tag, value, implicit);
}
}
/**
* Parses the node in a context created for the given {@code key}.
* @param key
* @return
* @throws IllegalStateException
*/
private Object parseNodeInFreshContext(String key) throws IllegalStateException {
Supplier<BlockContext<?>> cc = CONTEXT_CONSTRUCTORS.get(parser.peekEvent().getEventId());
if (cc == null) {
throw new IllegalStateException("Invalid value in map with key " + key);
}
contextStack.push(key, cc);
Object value = parseNode();
contextStack.pop();
return value;
}
/**
* Parses the node in a fresh context {@link DefaultObjectContext}.
* @return
* @throws IllegalStateException
*/
private Object parseNodeInFreshContext() throws IllegalStateException {
contextStack.push(DefaultObjectContext.newDefaultObjectContext());
Object value = parseNode();
contextStack.pop();
return value;
}
}

View file

@ -1,157 +0,0 @@
/*
* Copyright 2023 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.file.yaml;
import org.keycloak.models.map.storage.file.common.WritingMechanism;
import java.io.Closeable;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Collections;
import java.util.LinkedList;
import java.util.Optional;
import java.util.function.Consumer;
import org.snakeyaml.engine.v2.common.FlowStyle;
import org.snakeyaml.engine.v2.common.ScalarStyle;
import org.snakeyaml.engine.v2.events.DocumentEndEvent;
import org.snakeyaml.engine.v2.events.DocumentStartEvent;
import org.snakeyaml.engine.v2.events.Event;
import org.snakeyaml.engine.v2.events.ImplicitTuple;
import org.snakeyaml.engine.v2.events.MappingEndEvent;
import org.snakeyaml.engine.v2.events.MappingStartEvent;
import org.snakeyaml.engine.v2.events.ScalarEvent;
import org.snakeyaml.engine.v2.events.SequenceEndEvent;
import org.snakeyaml.engine.v2.events.SequenceStartEvent;
import org.snakeyaml.engine.v2.events.StreamEndEvent;
import org.snakeyaml.engine.v2.events.StreamStartEvent;
import org.snakeyaml.engine.v2.nodes.Tag;
/**
* Mechanism which produces {@link Event}s for SnakeYaml v2 {@code Emitter}.
*
* @author vramik
*/
public class YamlWritingMechanism implements WritingMechanism, Closeable {
private final ImplicitTuple implicitTuple = new ImplicitTuple(true, true);
private final Consumer<Event> consumer;
private boolean runningPreTasks = false;
private final LinkedList<RunOnlyOnce> preTasks = new RunOnlyOnce.List();
public YamlWritingMechanism(Consumer<Event> consumer) {
this.consumer = consumer;
this.preTasks.add(new RunOnlyOnce(this::startDocument, this::endDocument));
}
@Override
public void close() {
endDocument();
}
@Override
public YamlWritingMechanism writeMapping(Runnable task) {
return writeObject(task, this::startMapping, this::endMapping);
}
@Override
public YamlWritingMechanism writeSequence(Runnable task) {
return writeObject(task, this::startSequence, this::endSequence);
}
@Override
public YamlWritingMechanism writePair(String key, Runnable task) {
return writeObject(task, () -> writeObject(key), null);
}
@Override
public YamlWritingMechanism writeObject(Object value) {
if (! runningPreTasks) {
runningPreTasks = true;
preTasks.forEach(RunOnlyOnce::run);
runningPreTasks = false;
}
this.consumer.accept(new ScalarEvent(Optional.empty(), determineTag(value), implicitTuple, value == null ? "null" : value.toString(), determineStyle(value)));
return this;
}
private void startDocument() {
this.consumer.accept(new StreamStartEvent());
this.consumer.accept(new DocumentStartEvent(false, Optional.empty(), Collections.emptyMap()));
}
private void endDocument() {
this.consumer.accept(new DocumentEndEvent(false));
this.consumer.accept(new StreamEndEvent());
}
private YamlWritingMechanism writeObject(Runnable taskWithOptionalWrite, Runnable preWriteTask, Runnable postWriteTask) {
RunOnlyOnce roo = new RunOnlyOnce(preWriteTask, postWriteTask);
try {
preTasks.addLast(roo);
taskWithOptionalWrite.run();
} finally {
preTasks.removeLast();
}
return this;
}
private void startSequence() {
this.consumer.accept(new SequenceStartEvent(Optional.empty(), Optional.of(Tag.SEQ.getValue()), true, FlowStyle.BLOCK));
}
private void endSequence() {
this.consumer.accept(new SequenceEndEvent());
}
private void startMapping() {
this.consumer.accept(new MappingStartEvent(Optional.empty(), Optional.of(Tag.MAP.getValue()), true, FlowStyle.BLOCK));
}
private void endMapping() {
this.consumer.accept(new MappingEndEvent());
}
private Optional<String> determineTag(Object value) {
if (value instanceof String) {
return Optional.of(Tag.STR.getValue());
} else if (value instanceof Boolean) {
return Optional.of(Tag.BOOL.getValue());
} else if (value instanceof Integer || value instanceof Long || value instanceof BigInteger) {
return Optional.of(Tag.INT.getValue());
} else if (value instanceof Float || value instanceof Double || value instanceof BigDecimal) {
return Optional.of(Tag.FLOAT.getValue());
} else if (value == null) {
return Optional.of(Tag.NULL.getValue());
} else {
return Optional.empty();
}
}
private ScalarStyle determineStyle(Object value) {
if (value instanceof String) {
String sValue = (String) value;
// TODO: Check numeric values and quote those as well
if ("null".equals(sValue)) {
return ScalarStyle.DOUBLE_QUOTED;
}
if (sValue.length() > 120 || sValue.lastIndexOf('\n') > 0) {
return ScalarStyle.FOLDED;
}
}
return ScalarStyle.PLAIN;
}
}

View file

@ -1,18 +0,0 @@
#
# Copyright 2022 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.
#
org.keycloak.models.map.storage.file.FileMapStorageProviderFactory

View file

@ -1,120 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>keycloak-model-pom</artifactId>
<groupId>org.keycloak</groupId>
<version>999.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>keycloak-model-map-hot-rod</artifactId>
<name>Keycloak Model Hot Rod</name>
<description/>
<properties>
<maven.compiler.release>11</maven.compiler.release>
<!-- overriding compiler plugin version as default 3.8.1-jboss-1 can't find multiple packages when building with java 11 -->
<version.compiler.plugin>3.8.1</version.compiler.plugin>
</properties>
<dependencies>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-model-map</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.infinispan</groupId>
<artifactId>infinispan-api</artifactId>
</dependency>
<dependency>
<groupId>org.infinispan</groupId>
<artifactId>infinispan-client-hotrod-jakarta</artifactId>
</dependency>
<dependency>
<groupId>org.infinispan</groupId>
<artifactId>infinispan-query-dsl</artifactId>
</dependency>
<dependency>
<groupId>org.infinispan</groupId>
<artifactId>infinispan-remote-query-client</artifactId>
</dependency>
<dependency>
<groupId>org.infinispan.protostream</groupId>
<artifactId>protostream-processor</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<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>
<dependency>
<groupId>jakarta.persistence</groupId>
<artifactId>jakarta.persistence-api</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${version.compiler.plugin}</version>
<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>
</annotationProcessorPaths>
<annotationProcessors>
<annotationProcessor>org.infinispan.protostream.annotations.impl.processor.AutoProtoSchemaBuilderAnnotationProcessor</annotationProcessor>
<annotationProcessor>org.keycloak.models.map.processor.GenerateHotRodEntityImplementationsProcessor</annotationProcessor>
</annotationProcessors>
</configuration>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>adding-test-dependency-on-map</id>
<activation>
<property>
<name>!maven.test.skip</name>
</property>
</activation>
<dependencies>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-model-map</artifactId>
<version>${project.version}</version>
<scope>test</scope>
<type>test-jar</type>
</dependency>
</dependencies>
</profile>
</profiles>
</project>

View file

@ -1,327 +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;
import org.infinispan.client.hotrod.MetadataValue;
import org.infinispan.client.hotrod.RemoteCache;
import org.infinispan.client.hotrod.Search;
import org.infinispan.commons.util.CloseableIterator;
import org.infinispan.query.dsl.Query;
import org.infinispan.query.dsl.QueryFactory;
import org.jboss.logging.Logger;
import org.keycloak.common.util.Time;
import org.keycloak.models.AbstractKeycloakTransaction;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.map.common.AbstractEntity;
import org.keycloak.models.map.common.DeepCloner;
import org.keycloak.models.map.common.ExpirableEntity;
import org.keycloak.models.map.common.StringKeyConverter;
import org.keycloak.models.map.storage.ModelEntityUtil;
import org.keycloak.models.map.storage.QueryParameters;
import org.keycloak.models.map.storage.CrudOperations;
import org.keycloak.models.map.storage.chm.MapFieldPredicates;
import org.keycloak.models.map.storage.chm.MapModelCriteriaBuilder;
import org.keycloak.models.map.storage.criteria.DefaultModelCriteria;
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.storage.hotRod.connections.DefaultHotRodConnectionProviderFactory;
import org.keycloak.models.map.storage.hotRod.connections.HotRodConnectionProvider;
import org.keycloak.models.map.storage.hotRod.locking.HotRodLocksUtils;
import org.keycloak.storage.SearchableModelField;
import org.keycloak.utils.LockObjectsForModification;
import jakarta.persistence.OptimisticLockException;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Spliterators;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import static org.keycloak.common.util.StackUtil.getShortStackTrace;
import static org.keycloak.models.map.storage.hotRod.common.HotRodUtils.paginateQuery;
import static org.keycloak.utils.StreamsUtil.closing;
public class HotRodCrudOperations<K, E extends AbstractHotRodEntity, V extends AbstractEntity & HotRodEntityDelegate<E>, M> implements CrudOperations<V, M> {
private static final Logger LOG = Logger.getLogger(HotRodCrudOperations.class);
private final KeycloakSession session;
private final RemoteCache<K, E> remoteCache;
protected final StringKeyConverter<K> keyConverter;
protected final HotRodEntityDescriptor<E, V> storedEntityDescriptor;
private final Function<E, V> delegateProducer;
protected final DeepCloner cloner;
protected boolean isExpirableEntity;
private final Map<SearchableModelField<? super M>, MapModelCriteriaBuilder.UpdatePredicatesFunc<K, V, M>> fieldPredicates;
private final Long lockTimeout;
private final RemoteCache<String, String> locksCache;
private final Map<K, Long> entityVersionCache = new HashMap<>();
public HotRodCrudOperations(KeycloakSession session, RemoteCache<K, E> remoteCache, StringKeyConverter<K> keyConverter, HotRodEntityDescriptor<E, V> storedEntityDescriptor, DeepCloner cloner, Long lockTimeout) {
this.session = session;
this.remoteCache = remoteCache;
this.keyConverter = keyConverter;
this.storedEntityDescriptor = storedEntityDescriptor;
this.cloner = cloner;
this.delegateProducer = storedEntityDescriptor.getHotRodDelegateProvider();
this.isExpirableEntity = ExpirableEntity.class.isAssignableFrom(ModelEntityUtil.getEntityType(storedEntityDescriptor.getModelTypeClass()));
this.fieldPredicates = MapFieldPredicates.getPredicates((Class<M>) storedEntityDescriptor.getModelTypeClass());
this.lockTimeout = lockTimeout;
HotRodConnectionProvider cacheProvider = session.getProvider(HotRodConnectionProvider.class);
this.locksCache = cacheProvider.getRemoteCache(DefaultHotRodConnectionProviderFactory.HOT_ROD_LOCKS_CACHE_NAME);
}
@Override
public V create(V value) {
K key = keyConverter.fromStringSafe(value.getId());
if (key == null) {
key = keyConverter.yieldNewUniqueKey();
value = cloner.from(keyConverter.keyToString(key), value);
}
if (isExpirableEntity) {
Long lifespan = getLifespan(value);
if (lifespan != null) {
if (lifespan > 0) {
remoteCache.putIfAbsent(key, value.getHotRodEntity(), lifespan, TimeUnit.MILLISECONDS);
} else {
LOG.warnf("Skipped creation of entity %s in storage due to negative/zero lifespan.", key);
}
return value;
}
}
remoteCache.putIfAbsent(key, value.getHotRodEntity());
return value;
}
private String getLockName(String key) {
return storedEntityDescriptor.getModelTypeClass().getName() + "_" + key;
}
@Override
public V read(String key) {
Objects.requireNonNull(key, "Key must be non-null");
K k = keyConverter.fromStringSafe(key);
if (LockObjectsForModification.isEnabled(session, storedEntityDescriptor.getModelTypeClass())) {
String lockName = getLockName(key);
HotRodLocksUtils.repeatPutIfAbsent(locksCache, lockName, Duration.ofMillis(lockTimeout), 50, true);
session.getTransactionManager().enlistAfterCompletion(new AbstractKeycloakTransaction() {
@Override
protected void commitImpl() {
HotRodLocksUtils.removeWithInstanceIdentifier(locksCache, lockName);
}
@Override
protected void rollbackImpl() {
HotRodLocksUtils.removeWithInstanceIdentifier(locksCache, lockName);
}
});
}
// Obtain value from Infinispan
MetadataValue<E> entityWithMetadata = remoteCache.getWithMetadata(k);
if (entityWithMetadata == null) return null;
// store entity version
LOG.tracef("Entity %s read in version %s.%s", key, entityWithMetadata.getVersion(), getShortStackTrace());
entityVersionCache.put(k, entityWithMetadata.getVersion());
// Create delegate that implements Map*Entity
return entityWithMetadata.getValue() != null ? delegateProducer.apply(entityWithMetadata.getValue()) : null;
}
@Override
public V update(V value) {
K key = keyConverter.fromStringSafe(value.getId());
if (isExpirableEntity) {
Long lifespan = getLifespan(value);
if (lifespan != null) {
if (lifespan > 0) {
if (!remoteCache.replaceWithVersion(key, value.getHotRodEntity(), entityVersionCache.get(key), lifespan, TimeUnit.MILLISECONDS, -1, TimeUnit.MILLISECONDS)) {
throw new OptimisticLockException("Entity " + key + " with version " + entityVersionCache.get(key) + " already changed by a different transaction.");
}
} else {
LOG.warnf("Removing entity %s from storage due to negative/zero lifespan.%s", key, getShortStackTrace());
if (!remoteCache.removeWithVersion(key, entityVersionCache.get(key))) {
throw new OptimisticLockException("Entity " + key + " with version " + entityVersionCache.get(key) + " already changed by a different transaction.");
}
}
return delegateProducer.apply(value.getHotRodEntity());
}
}
if (!remoteCache.replaceWithVersion(key, value.getHotRodEntity(), entityVersionCache.get(key))) {
throw new OptimisticLockException("Entity " + key + " with version " + entityVersionCache.get(key) + " already changed by a different transaction.");
}
return delegateProducer.apply(value.getHotRodEntity());
}
@Override
public boolean delete(String key) {
K k = keyConverter.fromStringSafe(key);
Long entityVersion = entityVersionCache.get(k);
if (entityVersion != null) {
if (!remoteCache.removeWithVersion(k, entityVersion)) {
throw new OptimisticLockException("Entity " + key + " with version " + entityVersion + " already changed by a different transaction.");
}
return true;
}
return remoteCache.remove(k) != null;
}
private static String toOrderString(QueryParameters.OrderBy<?> orderBy) {
SearchableModelField<?> field = orderBy.getModelField();
String modelFieldName = IckleQueryMapModelCriteriaBuilder.getFieldName(field);
String orderString = orderBy.getOrder().equals(QueryParameters.Order.ASCENDING) ? "ASC" : "DESC";
return modelFieldName + " " + orderString;
}
@Override
public Stream<V> read(QueryParameters<M> queryParameters) {
DefaultModelCriteria<M> dmc = queryParameters.getModelCriteriaBuilder();
// Optimization if the criteria contains only one id
String id = (String) dmc.getSingleRestrictionArgument("id");
if (id != null) {
// We have a criteria that contains "id EQ 'some_key'". We can change this to reading only some_key using read method and then apply the rest of criteria.
MapModelCriteriaBuilder<K,V,M> mapMcb = dmc.flashToModelCriteriaBuilder(new MapModelCriteriaBuilder<>(keyConverter, fieldPredicates));
V entity = read(id);
if (entity == null) {
return Stream.empty();
}
K k = keyConverter.fromString(id);
boolean fulfillsQueryCriteria = mapMcb.getKeyFilter().test(k) && mapMcb.getEntityFilter().test(entity);
if (!fulfillsQueryCriteria) {
// entity does not fulfill whole criteria, we can release lock now
if (LockObjectsForModification.isEnabled(session, storedEntityDescriptor.getModelTypeClass())) {
HotRodLocksUtils.removeWithInstanceIdentifier(locksCache, getLockName(id));
entityVersionCache.remove(k);
}
return Stream.empty();
}
return Stream.of(entity);
}
// workaround if the query contains us.clientId field, in which case don't read by id => read without optimistic locking.
// See https://issues.redhat.com/browse/ISPN-14537
if (!dmc.isEmpty() && dmc.partiallyEvaluate((field, op, arg) ->
field == UserSessionModel.SearchableFields.CLIENT_ID || field == UserSessionModel.SearchableFields.CORRESPONDING_SESSION_ID
).toString().contains("__TRUE__")) {
Query<E> query = prepareQueryWithPrefixAndParameters(null, queryParameters);
CloseableIterator<E> iterator = paginateQuery(query, queryParameters.getOffset(),
queryParameters.getLimit()).iterator();
return closing(StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, 0), false))
.onClose(iterator::close)
.filter(Objects::nonNull) // see https://github.com/keycloak/keycloak/issues/9271
.map(this.delegateProducer);
}
// Criteria does not contain only one id, we need to read ids without locking and then read entities one by one pessimistically or optimistically
Query<Object[]> query = prepareQueryWithPrefixAndParameters("SELECT id ", queryParameters);
CloseableIterator<Object[]> iterator = paginateQuery(query, queryParameters.getOffset(),
queryParameters.getLimit()).iterator();
return closing(StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, 0), false))
.onClose(iterator::close)
// Extract ids from the result
.map(a -> a[0])
.map(String.class::cast)
// read by id => this will register the entity in an ISPN transaction
.map(this::read)
// Entity can be removed in the meanwhile, we need to check for null
.filter(Objects::nonNull);
}
private <T> Query<T> prepareQueryWithPrefixAndParameters(String prefix, QueryParameters<M> queryParameters) {
IckleQueryMapModelCriteriaBuilder<E, M> iqmcb = queryParameters.getModelCriteriaBuilder()
.flashToModelCriteriaBuilder(createCriteriaBuilder());
String queryString = (prefix != null ? prefix : "") + iqmcb.getIckleQuery();
if (!queryParameters.getOrderBy().isEmpty()) {
queryString += " ORDER BY " + queryParameters.getOrderBy().stream().map(HotRodCrudOperations::toOrderString)
.collect(Collectors.joining(", "));
}
LOG.tracef("Preparing Ickle query: '%s'%s", queryString, getShortStackTrace());
QueryFactory queryFactory = Search.getQueryFactory(remoteCache);
Query<T> query = queryFactory.create(queryString);
query.setParameters(iqmcb.getParameters());
return query;
}
@Override
public long getCount(QueryParameters<M> queryParameters) {
IckleQueryMapModelCriteriaBuilder<E, M> iqmcb = queryParameters.getModelCriteriaBuilder()
.flashToModelCriteriaBuilder(createCriteriaBuilder());
String queryString = iqmcb.getIckleQuery();
LOG.tracef("Executing count Ickle query: %s", queryString);
QueryFactory queryFactory = Search.getQueryFactory(remoteCache);
Query<E> query = queryFactory.create(queryString);
query.setParameters(iqmcb.getParameters());
return query.execute().hitCount().orElse(0);
}
@Override
public long delete(QueryParameters<M> queryParameters) {
if (queryParameters.getLimit() != null || queryParameters.getOffset() != null) {
throw new IllegalArgumentException("HotRod storage does not support pagination for delete query");
}
Query<Object[]> query = prepareQueryWithPrefixAndParameters("DELETE ", queryParameters);
return query.executeStatement();
}
@Override
public boolean exists(String key) {
Objects.requireNonNull(key, "Key must be non-null");
K k = keyConverter.fromStringSafe(key);
return remoteCache.containsKey(k);
}
public IckleQueryMapModelCriteriaBuilder<E, M> createCriteriaBuilder() {
return new IckleQueryMapModelCriteriaBuilder<>(storedEntityDescriptor.getEntityTypeClass());
}
// V must be an instance of ExpirableEntity
// returns null if expiration field is not set
// in certain cases can return 0 or negative number, which needs to be handled carefully when using as ISPN lifespan
private Long getLifespan(V value) {
Long expiration = ((ExpirableEntity) value).getExpiration();
return expiration != null ? expiration - Time.currentTimeMillis() : null;
}
}

View file

@ -1,119 +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;
import org.infinispan.client.hotrod.RemoteCache;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.SingleUseObjectValueModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.map.common.AbstractEntity;
import org.keycloak.models.map.common.StringKeyConverter;
import org.keycloak.models.map.storage.MapStorage;
import org.keycloak.models.map.storage.MapStorageProvider;
import org.keycloak.models.map.storage.MapStorageProviderFactory;
import org.keycloak.models.map.storage.chm.ConcurrentHashMapStorage;
import org.keycloak.models.map.storage.chm.MapFieldPredicates;
import org.keycloak.models.map.storage.chm.MapModelCriteriaBuilder;
import org.keycloak.models.map.storage.chm.SingleUseObjectMapStorage;
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.storage.hotRod.connections.HotRodConnectionProvider;
import org.keycloak.models.map.storage.hotRod.transaction.AllAreasHotRodStoresWrapper;
import org.keycloak.models.map.storage.hotRod.transaction.HotRodRemoteTransactionWrapper;
import org.keycloak.models.map.storage.hotRod.userSession.HotRodUserSessionMapStorage;
import org.keycloak.storage.SearchableModelField;
import java.util.Map;
import static org.keycloak.models.map.storage.hotRod.HotRodMapStorageProviderFactory.CLIENT_SESSION_PREDICATES;
import static org.keycloak.models.map.storage.hotRod.HotRodMapStorageProviderFactory.CLONER;
public class HotRodMapStorageProvider implements MapStorageProvider {
private final KeycloakSession session;
private final HotRodMapStorageProviderFactory factory;
private final boolean jtaEnabled;
private final long lockTimeout;
private AllAreasHotRodStoresWrapper storesWrapper;
public HotRodMapStorageProvider(KeycloakSession session, HotRodMapStorageProviderFactory factory, boolean jtaEnabled, long lockTimeout) {
this.session = session;
this.factory = factory;
this.jtaEnabled = jtaEnabled;
this.lockTimeout = lockTimeout;
}
@Override
public <V extends AbstractEntity, M> MapStorage<V, M> getMapStorage(Class<M> modelType, MapStorageProviderFactory.Flag... flags) {
if (storesWrapper == null) initializeTransactionWrapper(modelType);
// We need to preload client session store before we load user session store to avoid recursive update of storages map
if (modelType == UserSessionModel.class) getMapStorage(AuthenticatedClientSessionModel.class, flags);
return (MapStorage<V, M>) storesWrapper.getOrCreateStoreForModel(modelType, () -> createHotRodMapStorage(session, modelType, flags));
}
private void initializeTransactionWrapper(Class<?> modelType) {
storesWrapper = new AllAreasHotRodStoresWrapper();
// Enlist the wrapper into prepare phase so the changes in the wrapper are executed before HotRod client provided transaction
session.getTransactionManager().enlistPrepare(storesWrapper);
// If JTA is enabled, the HotRod client provided transaction is automatically enlisted into JTA and we don't need to do anything here
if (!jtaEnabled) {
// If there is no JTA transaction enabled control HotRod client provided transaction manually using
// HotRodRemoteTransactionWrapper
HotRodConnectionProvider connectionProvider = session.getProvider(HotRodConnectionProvider.class);
HotRodEntityDescriptor<?, ?> entityDescriptor = factory.getEntityDescriptor(modelType);
RemoteCache<Object, Object> remoteCache = connectionProvider.getRemoteCache(entityDescriptor.getCacheName());
session.getTransactionManager().enlist(new HotRodRemoteTransactionWrapper(remoteCache.getTransactionManager()));
}
}
private <K, E extends AbstractHotRodEntity, V extends HotRodEntityDelegate<E> & AbstractEntity, M> ConcurrentHashMapStorage<K, V, M, ?> createHotRodMapStorage(KeycloakSession session, Class<M> modelType, MapStorageProviderFactory.Flag... flags) {
HotRodConnectionProvider connectionProvider = session.getProvider(HotRodConnectionProvider.class);
HotRodEntityDescriptor<E, V> entityDescriptor = (HotRodEntityDescriptor<E, V>) factory.getEntityDescriptor(modelType);
Map<SearchableModelField<? super M>, MapModelCriteriaBuilder.UpdatePredicatesFunc<String, V, M>> fieldPredicates = MapFieldPredicates.getPredicates((Class<M>) entityDescriptor.getModelTypeClass());
StringKeyConverter<String> kc = StringKeyConverter.StringKey.INSTANCE;
// TODO: This is messy, we should refactor this so we don't need to pass kc, entityDescriptor, CLONER to both MapStorage and CrudOperations
if (modelType == SingleUseObjectValueModel.class) {
return new SingleUseObjectMapStorage(new SingleUseObjectHotRodCrudOperations(session, connectionProvider.getRemoteCache(entityDescriptor.getCacheName()), kc, (HotRodEntityDescriptor) entityDescriptor, CLONER, lockTimeout), kc, CLONER, fieldPredicates);
} if (modelType == AuthenticatedClientSessionModel.class) {
return new ConcurrentHashMapStorage(new HotRodCrudOperations(session, connectionProvider.getRemoteCache(entityDescriptor.getCacheName()),
kc,
entityDescriptor,
CLONER, lockTimeout), kc, CLONER, CLIENT_SESSION_PREDICATES);
} if (modelType == UserSessionModel.class) {
return new HotRodUserSessionMapStorage(new HotRodCrudOperations(session, connectionProvider.getRemoteCache(entityDescriptor.getCacheName()),
kc,
entityDescriptor,
CLONER, lockTimeout), kc, CLONER, fieldPredicates, storesWrapper.getOrCreateStoreForModel(AuthenticatedClientSessionModel.class, () -> createHotRodMapStorage(session, AuthenticatedClientSessionModel.class, flags)));
} else {
return new ConcurrentHashMapStorage(new HotRodCrudOperations<>(session, connectionProvider.getRemoteCache(entityDescriptor.getCacheName()), kc, entityDescriptor, CLONER, lockTimeout), kc, CLONER, fieldPredicates);
}
}
@Override
public void close() {
}
}

View file

@ -1,216 +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;
import org.keycloak.Config;
import org.keycloak.common.Profile;
import org.keycloak.component.AmphibianProviderFactory;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.map.authSession.MapAuthenticationSessionEntity;
import org.keycloak.models.map.authSession.MapRootAuthenticationSessionEntity;
import org.keycloak.models.map.authorization.entity.MapPermissionTicketEntity;
import org.keycloak.models.map.authorization.entity.MapPolicyEntity;
import org.keycloak.models.map.authorization.entity.MapResourceEntity;
import org.keycloak.models.map.authorization.entity.MapResourceServerEntity;
import org.keycloak.models.map.authorization.entity.MapScopeEntity;
import org.keycloak.models.map.client.MapClientEntity;
import org.keycloak.models.map.client.MapProtocolMapperEntity;
import org.keycloak.models.map.clientscope.MapClientScopeEntity;
import org.keycloak.models.map.common.AbstractEntity;
import org.keycloak.models.map.common.DeepCloner;
import org.keycloak.models.map.common.SessionAttributesUtils;
import org.keycloak.models.map.events.MapAdminEventEntity;
import org.keycloak.models.map.events.MapAuthEventEntity;
import org.keycloak.models.map.group.MapGroupEntity;
import org.keycloak.models.map.loginFailure.MapUserLoginFailureEntity;
import org.keycloak.models.map.realm.MapRealmEntity;
import org.keycloak.models.map.realm.entity.MapAuthenticationExecutionEntity;
import org.keycloak.models.map.realm.entity.MapAuthenticationFlowEntity;
import org.keycloak.models.map.realm.entity.MapAuthenticatorConfigEntity;
import org.keycloak.models.map.realm.entity.MapClientInitialAccessEntity;
import org.keycloak.models.map.realm.entity.MapComponentEntity;
import org.keycloak.models.map.realm.entity.MapIdentityProviderEntity;
import org.keycloak.models.map.realm.entity.MapIdentityProviderMapperEntity;
import org.keycloak.models.map.realm.entity.MapOTPPolicyEntity;
import org.keycloak.models.map.realm.entity.MapRequiredActionProviderEntity;
import org.keycloak.models.map.realm.entity.MapRequiredCredentialEntity;
import org.keycloak.models.map.realm.entity.MapWebAuthnPolicyEntity;
import org.keycloak.models.map.role.MapRoleEntity;
import org.keycloak.models.map.singleUseObject.MapSingleUseObjectEntity;
import org.keycloak.models.map.storage.MapStorageProvider;
import org.keycloak.models.map.storage.MapStorageProviderFactory;
import org.keycloak.models.map.storage.chm.MapFieldPredicates;
import org.keycloak.models.map.storage.chm.MapModelCriteriaBuilder;
import org.keycloak.models.map.storage.hotRod.authSession.HotRodAuthenticationSessionEntityDelegate;
import org.keycloak.models.map.storage.hotRod.authSession.HotRodRootAuthenticationSessionEntityDelegate;
import org.keycloak.models.map.storage.hotRod.authorization.HotRodPermissionTicketEntityDelegate;
import org.keycloak.models.map.storage.hotRod.authorization.HotRodPolicyEntityDelegate;
import org.keycloak.models.map.storage.hotRod.authorization.HotRodResourceEntityDelegate;
import org.keycloak.models.map.storage.hotRod.authorization.HotRodResourceServerEntityDelegate;
import org.keycloak.models.map.storage.hotRod.authorization.HotRodScopeEntityDelegate;
import org.keycloak.models.map.storage.hotRod.client.HotRodClientEntityDelegate;
import org.keycloak.models.map.storage.hotRod.client.HotRodProtocolMapperEntityDelegate;
import org.keycloak.models.map.storage.hotRod.clientscope.HotRodClientScopeEntityDelegate;
import org.keycloak.models.map.storage.hotRod.common.AutogeneratedHotRodDescriptors;
import org.keycloak.models.map.storage.hotRod.common.HotRodEntityDescriptor;
import org.keycloak.models.map.storage.hotRod.events.HotRodAdminEventEntityDelegate;
import org.keycloak.models.map.storage.hotRod.events.HotRodAuthEventEntityDelegate;
import org.keycloak.models.map.storage.hotRod.group.HotRodGroupEntityDelegate;
import org.keycloak.models.map.storage.hotRod.loginFailure.HotRodUserLoginFailureEntityDelegate;
import org.keycloak.models.map.storage.hotRod.realm.HotRodRealmEntityDelegate;
import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodAuthenticationExecutionEntityDelegate;
import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodAuthenticationFlowEntityDelegate;
import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodAuthenticatorConfigEntityDelegate;
import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodClientInitialAccessEntityDelegate;
import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodComponentEntityDelegate;
import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodIdentityProviderEntityDelegate;
import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodIdentityProviderMapperEntityDelegate;
import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodOTPPolicyEntityDelegate;
import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodRequiredActionProviderEntityDelegate;
import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodRequiredCredentialEntityDelegate;
import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodWebAuthnPolicyEntityDelegate;
import org.keycloak.models.map.storage.hotRod.role.HotRodRoleEntityDelegate;
import org.keycloak.models.map.storage.hotRod.singleUseObject.HotRodSingleUseObjectEntityDelegate;
import org.keycloak.models.map.storage.hotRod.user.HotRodUserConsentEntityDelegate;
import org.keycloak.models.map.storage.hotRod.user.HotRodUserCredentialEntityDelegate;
import org.keycloak.models.map.storage.hotRod.user.HotRodUserEntityDelegate;
import org.keycloak.models.map.storage.hotRod.user.HotRodUserFederatedIdentityEntityDelegate;
import org.keycloak.models.map.storage.hotRod.userSession.HotRodAuthenticatedClientSessionEntity;
import org.keycloak.models.map.storage.hotRod.userSession.HotRodAuthenticatedClientSessionEntityDelegate;
import org.keycloak.models.map.storage.hotRod.userSession.HotRodUserSessionEntityDelegate;
import org.keycloak.models.map.user.MapUserConsentEntity;
import org.keycloak.models.map.user.MapUserCredentialEntity;
import org.keycloak.models.map.user.MapUserEntity;
import org.keycloak.models.map.user.MapUserFederatedIdentityEntity;
import org.keycloak.models.map.userSession.MapAuthenticatedClientSessionEntity;
import org.keycloak.models.map.userSession.MapUserSessionEntity;
import org.keycloak.provider.EnvironmentDependentProviderFactory;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.provider.ProviderConfigurationBuilder;
import org.keycloak.storage.SearchableModelField;
import org.keycloak.transaction.JtaTransactionManagerLookup;
import java.util.List;
import java.util.Map;
public class HotRodMapStorageProviderFactory implements AmphibianProviderFactory<MapStorageProvider>, MapStorageProviderFactory, EnvironmentDependentProviderFactory {
public static final String PROVIDER_ID = "hotrod";
private final int factoryId = SessionAttributesUtils.grabNewFactoryIdentifier();
private boolean jtaEnabled;
protected static final Map<SearchableModelField<AuthenticatedClientSessionModel>, MapModelCriteriaBuilder.UpdatePredicatesFunc<Object, AbstractEntity, AuthenticatedClientSessionModel>> CLIENT_SESSION_PREDICATES = MapFieldPredicates.basePredicates(HotRodAuthenticatedClientSessionEntity.ID);
private Long lockTimeout;
protected final static DeepCloner CLONER = new DeepCloner.Builder()
.constructor(MapRootAuthenticationSessionEntity.class, HotRodRootAuthenticationSessionEntityDelegate::new)
.constructor(MapAuthenticationSessionEntity.class, HotRodAuthenticationSessionEntityDelegate::new)
.constructor(MapClientEntity.class, HotRodClientEntityDelegate::new)
.constructor(MapProtocolMapperEntity.class, HotRodProtocolMapperEntityDelegate::new)
.constructor(MapClientScopeEntity.class, HotRodClientScopeEntityDelegate::new)
.constructor(MapGroupEntity.class, HotRodGroupEntityDelegate::new)
.constructor(MapRoleEntity.class, HotRodRoleEntityDelegate::new)
.constructor(MapSingleUseObjectEntity.class, HotRodSingleUseObjectEntityDelegate::new)
.constructor(MapUserEntity.class, HotRodUserEntityDelegate::new)
.constructor(MapUserCredentialEntity.class, HotRodUserCredentialEntityDelegate::new)
.constructor(MapUserFederatedIdentityEntity.class, HotRodUserFederatedIdentityEntityDelegate::new)
.constructor(MapUserConsentEntity.class, HotRodUserConsentEntityDelegate::new)
.constructor(MapUserLoginFailureEntity.class, HotRodUserLoginFailureEntityDelegate::new)
.constructor(MapRealmEntity.class, HotRodRealmEntityDelegate::new)
.constructor(MapAuthenticationExecutionEntity.class, HotRodAuthenticationExecutionEntityDelegate::new)
.constructor(MapAuthenticationFlowEntity.class, HotRodAuthenticationFlowEntityDelegate::new)
.constructor(MapAuthenticatorConfigEntity.class, HotRodAuthenticatorConfigEntityDelegate::new)
.constructor(MapClientInitialAccessEntity.class, HotRodClientInitialAccessEntityDelegate::new)
.constructor(MapComponentEntity.class, HotRodComponentEntityDelegate::new)
.constructor(MapIdentityProviderEntity.class, HotRodIdentityProviderEntityDelegate::new)
.constructor(MapIdentityProviderMapperEntity.class, HotRodIdentityProviderMapperEntityDelegate::new)
.constructor(MapOTPPolicyEntity.class, HotRodOTPPolicyEntityDelegate::new)
.constructor(MapRequiredActionProviderEntity.class, HotRodRequiredActionProviderEntityDelegate::new)
.constructor(MapRequiredCredentialEntity.class, HotRodRequiredCredentialEntityDelegate::new)
.constructor(MapWebAuthnPolicyEntity.class, HotRodWebAuthnPolicyEntityDelegate::new)
.constructor(MapUserSessionEntity.class, HotRodUserSessionEntityDelegate::new)
.constructor(MapAuthenticatedClientSessionEntity.class, HotRodAuthenticatedClientSessionEntityDelegate::new)
.constructor(MapResourceServerEntity.class, HotRodResourceServerEntityDelegate::new)
.constructor(MapResourceEntity.class, HotRodResourceEntityDelegate::new)
.constructor(MapScopeEntity.class, HotRodScopeEntityDelegate::new)
.constructor(MapPolicyEntity.class, HotRodPolicyEntityDelegate::new)
.constructor(MapPermissionTicketEntity.class, HotRodPermissionTicketEntityDelegate::new)
.constructor(MapAuthEventEntity.class, HotRodAuthEventEntityDelegate::new)
.constructor(MapAdminEventEntity.class, HotRodAdminEventEntityDelegate::new)
.build();
@Override
public MapStorageProvider create(KeycloakSession session) {
return SessionAttributesUtils.createProviderIfAbsent(session, factoryId, HotRodMapStorageProvider.class, session1 -> new HotRodMapStorageProvider(session1, this, jtaEnabled, lockTimeout));
}
public HotRodEntityDescriptor<?, ?> getEntityDescriptor(Class<?> c) {
return AutogeneratedHotRodDescriptors.ENTITY_DESCRIPTOR_MAP.get(c);
}
@Override
public void init(Config.Scope config) {
this.lockTimeout = config.getLong("lockTimeout", 10000L);
}
@Override
public void postInit(KeycloakSessionFactory factory) {
JtaTransactionManagerLookup jtaLookup = (JtaTransactionManagerLookup) factory.getProviderFactory(JtaTransactionManagerLookup.class);
jtaEnabled = jtaLookup != null && jtaLookup.getTransactionManager() != null;
}
@Override
public String getId() {
return PROVIDER_ID;
}
@Override
public boolean isSupported() {
return Profile.isFeatureEnabled(Profile.Feature.MAP_STORAGE);
}
@Override
public String getHelpText() {
return "HotRod map storage";
}
@Override
public List<ProviderConfigProperty> getConfigMetadata() {
return ProviderConfigurationBuilder.create()
.property()
.name("lockTimeout")
.type("long")
.defaultValue(10000L)
.helpText("The maximum time to wait in milliseconds when waiting for acquiring a pessimistic read lock. If set to negative there is no timeout configured.")
.add().build();
}
}

View file

@ -1,269 +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;
import org.keycloak.authorization.model.Policy;
import org.keycloak.authorization.model.Resource;
import org.keycloak.authorization.model.Scope;
import org.keycloak.events.Event;
import org.keycloak.models.ClientModel;
import org.keycloak.models.GroupModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.map.storage.ModelCriteriaBuilder;
import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity;
import org.keycloak.storage.SearchableModelField;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.UnaryOperator;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static org.keycloak.models.map.storage.hotRod.IckleQueryOperators.C;
import static org.keycloak.models.map.storage.hotRod.IckleQueryOperators.findAvailableNamedParam;
import static org.keycloak.models.map.storage.hotRod.common.CommonPrimitivesProtoSchemaInitializer.HOT_ROD_ENTITY_PACKAGE;
public class IckleQueryMapModelCriteriaBuilder<E extends AbstractHotRodEntity, M> implements ModelCriteriaBuilder<M, IckleQueryMapModelCriteriaBuilder<E, M>> {
private static final int INITIAL_BUILDER_CAPACITY = 250;
private final Class<E> hotRodEntityClass;
private final StringBuilder whereClauseBuilder = new StringBuilder(INITIAL_BUILDER_CAPACITY);
private final Map<String, Object> parameters;
private static final Pattern LIKE_PATTERN_DELIMITER = Pattern.compile("%+");
private static final Pattern NON_ANALYZED_FIELD_REGEX = Pattern.compile("[%_\\\\]");
public static final Map<SearchableModelField<?>, String> INFINISPAN_NAME_OVERRIDES = new HashMap<>();
public static final Set<SearchableModelField<?>> LOWERCASE_NORMALIZED_MODEL_FIELDS = new HashSet<>();
static {
INFINISPAN_NAME_OVERRIDES.put(ClientModel.SearchableFields.SCOPE_MAPPING_ROLE, "scopeMappings");
INFINISPAN_NAME_OVERRIDES.put(ClientModel.SearchableFields.ATTRIBUTE, "attributes");
INFINISPAN_NAME_OVERRIDES.put(GroupModel.SearchableFields.PARENT_ID, "parentId");
INFINISPAN_NAME_OVERRIDES.put(GroupModel.SearchableFields.ASSIGNED_ROLE, "grantedRoles");
INFINISPAN_NAME_OVERRIDES.put(GroupModel.SearchableFields.ATTRIBUTE, "attributes");
INFINISPAN_NAME_OVERRIDES.put(UserModel.SearchableFields.USERNAME_CASE_INSENSITIVE, "usernameLowercase");
INFINISPAN_NAME_OVERRIDES.put(UserModel.SearchableFields.USERNAME, "username");
INFINISPAN_NAME_OVERRIDES.put(UserModel.SearchableFields.SERVICE_ACCOUNT_CLIENT, "serviceAccountClientLink");
INFINISPAN_NAME_OVERRIDES.put(UserModel.SearchableFields.CONSENT_FOR_CLIENT, "userConsents.clientId");
INFINISPAN_NAME_OVERRIDES.put(UserModel.SearchableFields.CONSENT_WITH_CLIENT_SCOPE, "userConsents.grantedClientScopesIds");
INFINISPAN_NAME_OVERRIDES.put(UserModel.SearchableFields.ASSIGNED_ROLE, "rolesMembership");
INFINISPAN_NAME_OVERRIDES.put(UserModel.SearchableFields.ASSIGNED_GROUP, "groupsMembership");
INFINISPAN_NAME_OVERRIDES.put(UserModel.SearchableFields.ATTRIBUTE, "attributes");
INFINISPAN_NAME_OVERRIDES.put(UserModel.SearchableFields.IDP_AND_USER, "federatedIdentities");
INFINISPAN_NAME_OVERRIDES.put(RealmModel.SearchableFields.CLIENT_INITIAL_ACCESS, "clientInitialAccesses");
INFINISPAN_NAME_OVERRIDES.put(RealmModel.SearchableFields.COMPONENT_PROVIDER_TYPE, "components.providerType");
INFINISPAN_NAME_OVERRIDES.put(UserSessionModel.SearchableFields.IS_OFFLINE, "offline");
INFINISPAN_NAME_OVERRIDES.put(UserSessionModel.SearchableFields.CLIENT_ID, "authenticatedClientSessions.clientId");
INFINISPAN_NAME_OVERRIDES.put(Resource.SearchableFields.SCOPE_ID, "scopeIds");
INFINISPAN_NAME_OVERRIDES.put(Policy.SearchableFields.RESOURCE_ID, "resourceIds");
INFINISPAN_NAME_OVERRIDES.put(Policy.SearchableFields.SCOPE_ID, "scopeIds");
INFINISPAN_NAME_OVERRIDES.put(Policy.SearchableFields.ASSOCIATED_POLICY_ID, "associatedPolicyIds");
INFINISPAN_NAME_OVERRIDES.put(Policy.SearchableFields.CONFIG, "configs");
INFINISPAN_NAME_OVERRIDES.put(Event.SearchableFields.EVENT_TYPE, "type");
}
static {
LOWERCASE_NORMALIZED_MODEL_FIELDS.add(Policy.SearchableFields.NAME);
LOWERCASE_NORMALIZED_MODEL_FIELDS.add(Policy.SearchableFields.TYPE);
LOWERCASE_NORMALIZED_MODEL_FIELDS.add(Resource.SearchableFields.NAME);
LOWERCASE_NORMALIZED_MODEL_FIELDS.add(Resource.SearchableFields.TYPE);
LOWERCASE_NORMALIZED_MODEL_FIELDS.add(Scope.SearchableFields.NAME);
LOWERCASE_NORMALIZED_MODEL_FIELDS.add(ClientModel.SearchableFields.CLIENT_ID);
LOWERCASE_NORMALIZED_MODEL_FIELDS.add(GroupModel.SearchableFields.NAME);
LOWERCASE_NORMALIZED_MODEL_FIELDS.add(RoleModel.SearchableFields.NAME);
LOWERCASE_NORMALIZED_MODEL_FIELDS.add(RoleModel.SearchableFields.DESCRIPTION);
LOWERCASE_NORMALIZED_MODEL_FIELDS.add(UserModel.SearchableFields.USERNAME_CASE_INSENSITIVE);
LOWERCASE_NORMALIZED_MODEL_FIELDS.add(UserModel.SearchableFields.EMAIL);
LOWERCASE_NORMALIZED_MODEL_FIELDS.add(UserModel.SearchableFields.FIRST_NAME);
LOWERCASE_NORMALIZED_MODEL_FIELDS.add(UserModel.SearchableFields.LAST_NAME);
}
public IckleQueryMapModelCriteriaBuilder(Class<E> hotRodEntityClass, StringBuilder whereClauseBuilder, Map<String, Object> parameters) {
this.hotRodEntityClass = hotRodEntityClass;
this.whereClauseBuilder.append(whereClauseBuilder);
this.parameters = parameters;
}
public IckleQueryMapModelCriteriaBuilder(Class<E> hotRodEntityClass) {
this.hotRodEntityClass = hotRodEntityClass;
this.parameters = new HashMap<>();
}
public static String getFieldName(SearchableModelField<?> modelField) {
return INFINISPAN_NAME_OVERRIDES.getOrDefault(modelField, modelField.getName());
}
private static boolean notEmpty(StringBuilder builder) {
return builder.length() != 0;
}
@Override
public IckleQueryMapModelCriteriaBuilder<E, M> compare(SearchableModelField<? super M> modelField, Operator op, Object... value) {
StringBuilder newBuilder = new StringBuilder(INITIAL_BUILDER_CAPACITY);
newBuilder.append("(");
if (notEmpty(whereClauseBuilder)) {
newBuilder.append(whereClauseBuilder).append(" AND (");
}
Map<String, Object> newParameters = new HashMap<>(parameters);
newBuilder.append(IckleQueryWhereClauses.produceWhereClause(modelField, op, value, newParameters));
if (notEmpty(whereClauseBuilder)) {
newBuilder.append(")");
}
return new IckleQueryMapModelCriteriaBuilder<>(hotRodEntityClass, newBuilder.append(")"), newParameters);
}
private StringBuilder joinBuilders(IckleQueryMapModelCriteriaBuilder<E, M>[] builders, String delimiter) {
return new StringBuilder(INITIAL_BUILDER_CAPACITY).append("(").append(Arrays.stream(builders)
.map(IckleQueryMapModelCriteriaBuilder::getWhereClauseBuilder)
.filter(IckleQueryMapModelCriteriaBuilder::notEmpty)
.collect(Collectors.joining(delimiter))).append(")");
}
private Map<String, Object> joinParameters(IckleQueryMapModelCriteriaBuilder<E, M>[] builders) {
return Arrays.stream(builders)
.map(IckleQueryMapModelCriteriaBuilder::getParameters)
.map(Map::entrySet)
.flatMap(Collection::stream)
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}
@SuppressWarnings("unchecked")
private IckleQueryMapModelCriteriaBuilder<E, M>[] resolveNamedQueryConflicts(IckleQueryMapModelCriteriaBuilder<E, M>[] builders) {
final Set<String> existingKeys = new HashSet<>();
return Arrays.stream(builders).map(builder -> {
Map<String, Object> oldParameters = builder.getParameters();
if (oldParameters.keySet().stream().noneMatch(existingKeys::contains)) {
existingKeys.addAll(oldParameters.keySet());
return builder;
}
String newWhereClause = builder.getWhereClauseBuilder().toString();
Map<String, Object> newParameters = new HashMap<>();
for (String key : oldParameters.keySet()) {
if (existingKeys.contains(key)) {
// resolve conflict
String newNamedParameter = findAvailableNamedParam(existingKeys, key + "n");
newParameters.put(newNamedParameter, oldParameters.get(key));
newWhereClause = newWhereClause.replace(key, newNamedParameter);
existingKeys.add(newNamedParameter);
} else {
newParameters.put(key, oldParameters.get(key));
existingKeys.add(key);
}
}
return new IckleQueryMapModelCriteriaBuilder<>(hotRodEntityClass, new StringBuilder(newWhereClause), newParameters);
}).toArray(IckleQueryMapModelCriteriaBuilder[]::new);
}
@Override
public IckleQueryMapModelCriteriaBuilder<E, M> and(IckleQueryMapModelCriteriaBuilder<E, M>... builders) {
if (builders.length == 0) {
return new IckleQueryMapModelCriteriaBuilder<>(hotRodEntityClass);
}
builders = resolveNamedQueryConflicts(builders);
return new IckleQueryMapModelCriteriaBuilder<>(hotRodEntityClass, joinBuilders(builders, " AND "),
joinParameters(builders));
}
@Override
public IckleQueryMapModelCriteriaBuilder<E, M> or(IckleQueryMapModelCriteriaBuilder<E, M>... builders) {
if (builders.length == 0) {
return new IckleQueryMapModelCriteriaBuilder<>(hotRodEntityClass);
}
builders = resolveNamedQueryConflicts(builders);
return new IckleQueryMapModelCriteriaBuilder<>(hotRodEntityClass, joinBuilders(builders, " OR "),
joinParameters(builders));
}
@Override
public IckleQueryMapModelCriteriaBuilder<E, M> not(IckleQueryMapModelCriteriaBuilder<E, M> builder) {
StringBuilder newBuilder = new StringBuilder(INITIAL_BUILDER_CAPACITY);
StringBuilder originalBuilder = builder.getWhereClauseBuilder();
if (originalBuilder.length() != 0) {
newBuilder.append("not").append(originalBuilder);
}
return new IckleQueryMapModelCriteriaBuilder<>(hotRodEntityClass, newBuilder, builder.getParameters());
}
private StringBuilder getWhereClauseBuilder() {
return whereClauseBuilder;
}
public static Object sanitizeNonAnalyzed(Object value) {
if (value instanceof String) {
return sanitizeEachUnitAndReplaceDelimiter((String) value, IckleQueryMapModelCriteriaBuilder::sanitizeSingleUnitNonAnalyzed, "%");
}
return value;
}
private static String sanitizeEachUnitAndReplaceDelimiter(String value, UnaryOperator<String> sanitizeSingleUnit, String replacement) {
return LIKE_PATTERN_DELIMITER.splitAsStream(value)
.map(sanitizeSingleUnit)
.collect(Collectors.joining(replacement))
+ (value.endsWith("%") ? replacement : "");
}
private static String sanitizeSingleUnitNonAnalyzed(String value) {
return NON_ANALYZED_FIELD_REGEX.matcher(value).replaceAll("\\\\\\\\" + "$0");
}
/**
*
* @return Ickle query that represents this QueryBuilder
*/
public String getIckleQuery() {
return "FROM " + HOT_ROD_ENTITY_PACKAGE + "." + hotRodEntityClass.getSimpleName() + " " + C + ((whereClauseBuilder.length() != 0) ? " WHERE " + whereClauseBuilder : "");
}
/**
* Ickle queries are created using named parameters to avoid query injections; this method provides mapping
* between parameter names and corresponding values
*
* @return Mapping from name of the parameter to value
*/
public Map<String, Object> getParameters() {
return parameters;
}
}

View file

@ -1,197 +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;
import org.keycloak.models.map.storage.ModelCriteriaBuilder;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
/**
* This class provides knowledge on how to build Ickle query where clauses for specified {@link ModelCriteriaBuilder.Operator}.
* <p/>
* For example,
* <p/>
* for operator {@link ModelCriteriaBuilder.Operator#EQ} we concatenate left operand and right operand with equal sign:
* {@code fieldName = :parameterName}
* <p/>
* however, for operator {@link ModelCriteriaBuilder.Operator#EXISTS} we add following:
* <p/>
* {@code fieldName IS NOT NULL AND fieldName IS NOT EMPTY"}.
*
* For right side operands we use named parameters to avoid injection attacks. Mapping between named parameter and
* corresponding value is then saved into {@code Map<String, Object>} that is passed to each {@link ExpressionCombinator}.
*/
public class IckleQueryOperators {
private static final Pattern UNWANTED_CHARACTERS_REGEX = Pattern.compile("[^a-zA-Z\\d]");
public static final String C = "c";
private static final Map<ModelCriteriaBuilder.Operator, String> OPERATOR_TO_STRING = new HashMap<>();
private static final Map<ModelCriteriaBuilder.Operator, ExpressionCombinator> OPERATOR_TO_EXPRESSION_COMBINATORS = new HashMap<>();
static {
OPERATOR_TO_EXPRESSION_COMBINATORS.put(ModelCriteriaBuilder.Operator.IN, IckleQueryOperators::in);
OPERATOR_TO_EXPRESSION_COMBINATORS.put(ModelCriteriaBuilder.Operator.EXISTS, IckleQueryOperators::exists);
OPERATOR_TO_EXPRESSION_COMBINATORS.put(ModelCriteriaBuilder.Operator.NOT_EXISTS, IckleQueryOperators::notExists);
OPERATOR_TO_EXPRESSION_COMBINATORS.put(ModelCriteriaBuilder.Operator.ILIKE, IckleQueryOperators::like);
OPERATOR_TO_EXPRESSION_COMBINATORS.put(ModelCriteriaBuilder.Operator.LIKE, IckleQueryOperators::like);
OPERATOR_TO_STRING.put(ModelCriteriaBuilder.Operator.EQ, "=");
OPERATOR_TO_STRING.put(ModelCriteriaBuilder.Operator.NE, "!=");
OPERATOR_TO_STRING.put(ModelCriteriaBuilder.Operator.LT, "<");
OPERATOR_TO_STRING.put(ModelCriteriaBuilder.Operator.LE, "<=");
OPERATOR_TO_STRING.put(ModelCriteriaBuilder.Operator.GT, ">");
OPERATOR_TO_STRING.put(ModelCriteriaBuilder.Operator.GE, ">=");
OPERATOR_TO_STRING.put(ModelCriteriaBuilder.Operator.LIKE, "LIKE");
OPERATOR_TO_STRING.put(ModelCriteriaBuilder.Operator.ILIKE, "LIKE");
OPERATOR_TO_STRING.put(ModelCriteriaBuilder.Operator.IN, "IN");
}
@FunctionalInterface
private interface ExpressionCombinator {
/**
* Produces an Ickle query where clause for obtained parameters
*
* @param fieldName left side operand
* @param values right side operands
* @param parameters mapping between named parameters and actual parameter values
* @return resulting string that will be part of resulting
*/
String combine(String fieldName, Object[] values, Map<String, Object> parameters);
}
private static String exists(String modelFieldName, Object[] values, Map<String, Object> parameters) {
String field = C + "." + modelFieldName;
return field + " IS NOT NULL AND " + field + " IS NOT EMPTY";
}
private static String notExists(String modelFieldName, Object[] values, Map<String, Object> parameters) {
String field = C + "." + modelFieldName;
return field + " IS NULL OR " + field + " IS EMPTY";
}
private static String like(String modelFieldName, Object[] values, Map<String, Object> parameters) {
String sanitizedValue = (String) IckleQueryMapModelCriteriaBuilder.sanitizeNonAnalyzed(values[0]);
return singleValueOperator(ModelCriteriaBuilder.Operator.LIKE)
.combine(modelFieldName, new String[] {sanitizedValue}, parameters);
}
private static String in(String modelFieldName, Object[] values, Map<String, Object> parameters) {
if (values == null || values.length == 0) {
return "false";
}
final Collection<?> operands;
if (values.length == 1) {
final Object value0 = values[0];
if (value0 instanceof Collection) {
operands = (Collection) value0;
} else if (value0 instanceof Stream) {
try (Stream valueS = (Stream) value0) {
operands = (Set) valueS.collect(Collectors.toSet());
}
} else {
operands = Collections.singleton(value0);
}
} else {
operands = new HashSet<>(Arrays.asList(values));
}
return operands.isEmpty() ? "false" : C + "." + modelFieldName + " IN (" + operands.stream()
.map(operand -> {
String namedParam = findAvailableNamedParam(parameters.keySet(), modelFieldName);
parameters.put(namedParam, operand);
return ":" + namedParam;
})
.collect(Collectors.joining(", ")) +
")";
}
private static String removeForbiddenCharactersFromNamedParameter(String name) {
return UNWANTED_CHARACTERS_REGEX.matcher(name).replaceAll( "");
}
/**
* Maps {@code namePrefix} to next available parameter name. For example, if {@code namePrefix == "id"}
* and {@code existingNames} set already contains {@code id0} and {@code id1} it returns {@code id2}.
* Any character that is not an alphanumeric will be stripped, so that it works for prefixes like
* {@code "attributes.name"} as well.
*
* This method is used for computing available names for name query parameters.
* Instead of creating generic named parameters that would be hard to debug and read by humans, it creates readable
* named parameters from the prefix.
*
* @param existingNames set of parameter names that are already used in this Ickle query
* @param namePrefix name of the parameter
* @return next available parameter name
*/
public static String findAvailableNamedParam(Set<String> existingNames, String namePrefix) {
String namePrefixCleared = removeForbiddenCharactersFromNamedParameter(namePrefix);
return IntStream.iterate(0, i -> i + 1)
.boxed()
.map(num -> namePrefixCleared + num)
.filter(name -> !existingNames.contains(name))
.findFirst().orElseThrow(() -> new IllegalArgumentException("Cannot create Parameter name for " + namePrefix));
}
private static ExpressionCombinator singleValueOperator(ModelCriteriaBuilder.Operator op) {
return (modelFieldName, values, parameters) -> {
if (values.length != 1) throw new RuntimeException("Invalid arguments, expected (" + modelFieldName + "), got: " + Arrays.toString(values));
if (values[0] == null && op.equals(ModelCriteriaBuilder.Operator.EQ)) {
return C + "." + modelFieldName + " IS NULL";
}
String namedParameter = findAvailableNamedParam(parameters.keySet(), modelFieldName);
parameters.put(namedParameter, values[0]);
return C + "." + modelFieldName + " " + IckleQueryOperators.operatorToString(op) + " :" + namedParameter;
};
}
private static String operatorToString(ModelCriteriaBuilder.Operator op) {
return OPERATOR_TO_STRING.get(op);
}
private static ExpressionCombinator operatorToExpressionCombinator(ModelCriteriaBuilder.Operator op) {
return OPERATOR_TO_EXPRESSION_COMBINATORS.getOrDefault(op, singleValueOperator(op));
}
/**
* Provides a string containing where clause for given operator, field name and values
*
* @param op operator
* @param modelFieldName field name
* @param values values
* @param parameters mapping between named parameters and their values
* @return where clause
*/
public static String combineExpressions(ModelCriteriaBuilder.Operator op, String modelFieldName, Object[] values, Map<String, Object> parameters) {
return operatorToExpressionCombinator(op).combine(modelFieldName, values, parameters);
}
}

View file

@ -1,241 +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;
import org.keycloak.authorization.model.Policy;
import org.keycloak.events.Event;
import org.keycloak.events.admin.AdminEvent;
import org.keycloak.models.ClientModel;
import org.keycloak.models.GroupModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.map.storage.CriterionNotSupportedException;
import org.keycloak.models.map.storage.ModelCriteriaBuilder;
import org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.storage.SearchableModelField;
import org.keycloak.storage.StorageId;
import org.keycloak.util.EnumWithStableIndex;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.keycloak.models.map.storage.hotRod.IckleQueryMapModelCriteriaBuilder.LOWERCASE_NORMALIZED_MODEL_FIELDS;
import static org.keycloak.models.map.storage.hotRod.IckleQueryMapModelCriteriaBuilder.getFieldName;
/**
* This class provides knowledge on how to build Ickle query where clauses for specified {@link SearchableModelField}.
*
* For example,
* <p/>
* for {@link ClientModel.SearchableFields.CLIENT_ID} we use {@link IckleQueryOperators.ExpressionCombinator} for
* obtained {@link ModelCriteriaBuilder.Operator} and use it with field name corresponding to {@link ClientModel.SearchableFields.CLIENT_ID}
* <p/>
* however, for {@link ClientModel.SearchableFields.ATTRIBUTE} we need to compare attribute name and attribute value
* so we create where clause similar to the following:
* {@code (attributes.name = :attributeName) AND ( attributes.value = :attributeValue )}
*
*
*/
public class IckleQueryWhereClauses {
private static final Map<SearchableModelField<?>, WhereClauseProducer> WHERE_CLAUSE_PRODUCER_OVERRIDES = new HashMap<>();
static {
WHERE_CLAUSE_PRODUCER_OVERRIDES.put(ClientModel.SearchableFields.ATTRIBUTE, IckleQueryWhereClauses::whereClauseForAttributes);
WHERE_CLAUSE_PRODUCER_OVERRIDES.put(UserModel.SearchableFields.ATTRIBUTE, IckleQueryWhereClauses::whereClauseForAttributes);
WHERE_CLAUSE_PRODUCER_OVERRIDES.put(GroupModel.SearchableFields.ATTRIBUTE, IckleQueryWhereClauses::whereClauseForAttributes);
WHERE_CLAUSE_PRODUCER_OVERRIDES.put(UserModel.SearchableFields.IDP_AND_USER, IckleQueryWhereClauses::whereClauseForUserIdpAlias);
WHERE_CLAUSE_PRODUCER_OVERRIDES.put(UserModel.SearchableFields.CONSENT_CLIENT_FEDERATION_LINK, IckleQueryWhereClauses::whereClauseForConsentClientFederationLink);
WHERE_CLAUSE_PRODUCER_OVERRIDES.put(UserModel.SearchableFields.USERNAME_CASE_INSENSITIVE, IckleQueryWhereClauses::whereClauseForUsernameCaseInsensitive);
WHERE_CLAUSE_PRODUCER_OVERRIDES.put(UserSessionModel.SearchableFields.CORRESPONDING_SESSION_ID, IckleQueryWhereClauses::whereClauseForCorrespondingSessionId);
WHERE_CLAUSE_PRODUCER_OVERRIDES.put(Policy.SearchableFields.CONFIG, IckleQueryWhereClauses::whereClauseForPolicyConfig);
WHERE_CLAUSE_PRODUCER_OVERRIDES.put(Event.SearchableFields.EVENT_TYPE, IckleQueryWhereClauses::whereClauseForEnumWithStableIndex);
WHERE_CLAUSE_PRODUCER_OVERRIDES.put(AdminEvent.SearchableFields.OPERATION_TYPE, IckleQueryWhereClauses::whereClauseForEnumWithStableIndex);
}
@FunctionalInterface
private interface WhereClauseProducer {
String produceWhereClause(String modelFieldName, ModelCriteriaBuilder.Operator op, Object[] values, Map<String, Object> parameters);
}
private static String produceWhereClause(String modelFieldName, ModelCriteriaBuilder.Operator op, Object[] values, Map<String, Object> parameters) {
return IckleQueryOperators.combineExpressions(op, modelFieldName, values, parameters);
}
private static WhereClauseProducer whereClauseProducerForModelField(SearchableModelField<?> modelField) {
return WHERE_CLAUSE_PRODUCER_OVERRIDES.getOrDefault(modelField, IckleQueryWhereClauses::produceWhereClause);
}
/**
* Produces where clause for given {@link SearchableModelField}, {@link ModelCriteriaBuilder.Operator} and values
*
* @param modelField model field
* @param op operator
* @param values searched values
* @param parameters mapping between named parameters and corresponding values
* @return resulting where clause
*/
public static String produceWhereClause(SearchableModelField<?> modelField, ModelCriteriaBuilder.Operator op,
Object[] values, Map<String, Object> parameters) {
String fieldName = IckleQueryMapModelCriteriaBuilder.getFieldName(modelField);
if (op == ModelCriteriaBuilder.Operator.ILIKE && !LOWERCASE_NORMALIZED_MODEL_FIELDS.contains(modelField)) {
throw new CriterionNotSupportedException(modelField, op, "Attempt to search case-insensitively without lowercase normalizer applied on the field.");
}
if (op == ModelCriteriaBuilder.Operator.LIKE && LOWERCASE_NORMALIZED_MODEL_FIELDS.contains(modelField)) {
throw new CriterionNotSupportedException(modelField, op, "Attempt to search case-sensitively with lowercase-normalized field.");
}
return whereClauseProducerForModelField(modelField).produceWhereClause(fieldName, op, values, parameters);
}
private static String whereClauseForAttributes(String modelFieldName, ModelCriteriaBuilder.Operator op, Object[] values, Map<String, Object> parameters) {
if (values == null || values.length != 2) {
throw new CriterionNotSupportedException(ClientModel.SearchableFields.ATTRIBUTE, op, "Invalid arguments, expected attribute_name-value pair, got: " + Arrays.toString(values));
}
final Object attrName = values[0];
if (! (attrName instanceof String)) {
throw new CriterionNotSupportedException(ClientModel.SearchableFields.ATTRIBUTE, op, "Invalid arguments, expected (String attribute_name), got: " + Arrays.toString(values));
}
String attrNameS = (String) attrName;
Object[] realValues = new Object[values.length - 1];
System.arraycopy(values, 1, realValues, 0, values.length - 1);
// Clause for searching attribute name
String nameClause = IckleQueryOperators.combineExpressions(ModelCriteriaBuilder.Operator.EQ, modelFieldName + ".name", new Object[]{attrNameS}, parameters);
// Clause for searching attribute value
String valueClause = IckleQueryOperators.combineExpressions(op, modelFieldName + ".values", realValues, parameters);
return "(" + nameClause + ")" + " AND " + "(" + valueClause + ")";
}
private static String whereClauseForUserIdpAlias(String modelFieldName, ModelCriteriaBuilder.Operator op, Object[] values, Map<String, Object> parameters) {
if (op != ModelCriteriaBuilder.Operator.EQ) {
throw new CriterionNotSupportedException(UserModel.SearchableFields.IDP_AND_USER, op);
}
if (values == null || values.length == 0 || values.length > 2) {
throw new CriterionNotSupportedException(UserModel.SearchableFields.IDP_AND_USER, op, "Invalid arguments, expected (idp_alias) or (idp_alias, idp_user), got: " + Arrays.toString(values));
}
final Object idpAlias = values[0];
if (values.length == 1) {
return IckleQueryOperators.combineExpressions(op, modelFieldName + ".identityProvider", values, parameters);
} else if (idpAlias == null) {
final Object idpUserId = values[1];
return IckleQueryOperators.combineExpressions(op, modelFieldName + ".userId", new Object[] { idpUserId }, parameters);
} else {
final Object idpUserId = values[1];
// Clause for searching federated identity id
String idClause = IckleQueryOperators.combineExpressions(op, modelFieldName + ".identityProvider", new Object[]{ idpAlias }, parameters);
// Clause for searching federated identity userId
String userIdClause = IckleQueryOperators.combineExpressions(op, modelFieldName + ".userId", new Object[] { idpUserId }, parameters);
return "(" + idClause + ")" + " AND " + "(" + userIdClause + ")";
}
}
private static String whereClauseForConsentClientFederationLink(String modelFieldName, ModelCriteriaBuilder.Operator op, Object[] values, Map<String, Object> parameters) {
if (op != ModelCriteriaBuilder.Operator.EQ) {
throw new CriterionNotSupportedException(UserModel.SearchableFields.CONSENT_CLIENT_FEDERATION_LINK, op);
}
if (values == null || values.length != 1) {
throw new CriterionNotSupportedException(UserModel.SearchableFields.CONSENT_CLIENT_FEDERATION_LINK, op, "Invalid arguments, expected (federation_provider_id), got: " + Arrays.toString(values));
}
String providerId = new StorageId((String) values[0], "").getId();
return IckleQueryOperators.combineExpressions(ModelCriteriaBuilder.Operator.LIKE, getFieldName(UserModel.SearchableFields.CONSENT_FOR_CLIENT), new String[] {providerId + "%"}, parameters);
}
private static String whereClauseForCorrespondingSessionId(String modelFieldName, ModelCriteriaBuilder.Operator op, Object[] values, Map<String, Object> parameters) {
if (op != ModelCriteriaBuilder.Operator.EQ) {
throw new CriterionNotSupportedException(UserSessionModel.SearchableFields.CORRESPONDING_SESSION_ID, op);
}
if (values == null || values.length != 1) {
throw new CriterionNotSupportedException(UserSessionModel.SearchableFields.CORRESPONDING_SESSION_ID, op, "Invalid arguments, expected (corresponding_session:id), got: " + Arrays.toString(values));
}
// Clause for searching key
String nameClause = IckleQueryOperators.combineExpressions(op, "notes.key", new String[]{UserSessionModel.CORRESPONDING_SESSION_ID}, parameters);
// Clause for searching value
String valueClause = IckleQueryOperators.combineExpressions(op, "notes.value", values, parameters);
return "(" + nameClause + ")" + " AND " + "(" + valueClause + ")";
}
private static String whereClauseForPolicyConfig(String modelFieldName, ModelCriteriaBuilder.Operator op, Object[] values, Map<String, Object> parameters) {
if (values == null || values.length == 0) {
throw new CriterionNotSupportedException(Policy.SearchableFields.CONFIG, op, "Invalid arguments, expected (config_name, config_value_operator_arguments), got: " + Arrays.toString(values));
}
final Object attrName = values[0];
if (!(attrName instanceof String)) {
throw new CriterionNotSupportedException(Policy.SearchableFields.CONFIG, op, "Invalid arguments, expected (String config_name), got: " + Arrays.toString(values));
}
String attrNameS = (String) attrName;
Object[] realValues = new Object[values.length - 1];
System.arraycopy(values, 1, realValues, 0, values.length - 1);
boolean isNotExists = op.equals(ModelCriteriaBuilder.Operator.NOT_EXISTS);
if (isNotExists || op.equals(ModelCriteriaBuilder.Operator.EXISTS)) {
ModelCriteriaBuilder.Operator o = isNotExists ? ModelCriteriaBuilder.Operator.NE : ModelCriteriaBuilder.Operator.EQ;
return IckleQueryOperators.combineExpressions(o, modelFieldName + ".key", new String[] { attrNameS }, parameters);
}
String nameClause = IckleQueryOperators.combineExpressions(ModelCriteriaBuilder.Operator.EQ, modelFieldName + ".key", new String[] { attrNameS }, parameters);
if (realValues.length == 0) {
return nameClause;
}
String valueClause = IckleQueryOperators.combineExpressions(op, modelFieldName + ".value", realValues, parameters);
return "(" + nameClause + ")" + " AND " + "(" + valueClause + ")";
}
private static String whereClauseForEnumWithStableIndex(String modelFieldName, ModelCriteriaBuilder.Operator op, Object[] values, Map<String, Object> parameters) {
if (values != null && values.length == 1) {
if (values[0] instanceof EnumWithStableIndex) {
values[0] = ((EnumWithStableIndex) values[0]).getStableIndex();
} else if (values[0] instanceof Collection) {
values[0] = ((Collection<EnumWithStableIndex>) values[0]).stream().map(EnumWithStableIndex::getStableIndex).collect(Collectors.toSet());
} else if (values[0] instanceof Stream) {
values[0] = ((Stream<EnumWithStableIndex>) values[0]).map(EnumWithStableIndex::getStableIndex);
}
}
return produceWhereClause(modelFieldName, op, values, parameters);
}
private static String whereClauseForUsernameCaseInsensitive(String modelFieldName, ModelCriteriaBuilder.Operator op, Object[] values, Map<String, Object> parameters) {
for (int i = 0; i < values.length; i++) {
if (values[i] instanceof String) {
values[i] = KeycloakModelUtils.toLowerCaseSafe((String) values[i]);
}
}
return produceWhereClause(modelFieldName, op == ModelCriteriaBuilder.Operator.ILIKE ? ModelCriteriaBuilder.Operator.LIKE : op, values, parameters);
}
}

View file

@ -1,78 +0,0 @@
/*
* Copyright 2022 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;
import org.infinispan.client.hotrod.RemoteCache;
import org.keycloak.models.SingleUseObjectValueModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.map.common.DeepCloner;
import org.keycloak.models.map.common.StringKeyConverter;
import org.keycloak.models.map.storage.QueryParameters;
import org.keycloak.models.map.storage.chm.SingleUseObjectModelCriteriaBuilder;
import org.keycloak.models.map.storage.criteria.DefaultModelCriteria;
import org.keycloak.models.map.storage.hotRod.common.HotRodEntityDescriptor;
import org.keycloak.models.map.storage.hotRod.singleUseObject.HotRodSingleUseObjectEntity;
import org.keycloak.models.map.storage.hotRod.singleUseObject.HotRodSingleUseObjectEntityDelegate;
import java.util.stream.Stream;
/**
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
*/
public class SingleUseObjectHotRodCrudOperations
extends HotRodCrudOperations<String, HotRodSingleUseObjectEntity, HotRodSingleUseObjectEntityDelegate, SingleUseObjectValueModel> {
public SingleUseObjectHotRodCrudOperations(KeycloakSession session, RemoteCache<String, HotRodSingleUseObjectEntity> remoteCache, StringKeyConverter<String> keyConverter,
HotRodEntityDescriptor<HotRodSingleUseObjectEntity, HotRodSingleUseObjectEntityDelegate> storedEntityDescriptor,
DeepCloner cloner, Long lockTimeout) {
super(session, remoteCache, keyConverter, storedEntityDescriptor, cloner, lockTimeout);
}
@Override
public HotRodSingleUseObjectEntityDelegate create(HotRodSingleUseObjectEntityDelegate value) {
if (value.getId() == null) {
if (value.getObjectKey() != null) {
value.setId(value.getObjectKey());
}
}
return super.create(value);
}
@Override
public Stream<HotRodSingleUseObjectEntityDelegate> read(QueryParameters<SingleUseObjectValueModel> queryParameters) {
DefaultModelCriteria<SingleUseObjectValueModel> criteria = queryParameters.getModelCriteriaBuilder();
if (criteria == null) {
return Stream.empty();
}
SingleUseObjectModelCriteriaBuilder mcb = criteria.flashToModelCriteriaBuilder(createSingleUseObjectCriteriaBuilder());
if (mcb.isValid()) {
HotRodSingleUseObjectEntityDelegate value = read(mcb.getKey());
return value != null ? Stream.of(value) : Stream.empty();
}
return super.read(queryParameters);
}
private SingleUseObjectModelCriteriaBuilder createSingleUseObjectCriteriaBuilder() {
return new SingleUseObjectModelCriteriaBuilder();
}
}

View file

@ -1,125 +0,0 @@
/*
* Copyright 2022 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.authSession;
import org.infinispan.protostream.annotations.ProtoField;
import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation;
import org.keycloak.models.map.authSession.MapAuthenticationSessionEntity;
import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity;
import org.keycloak.models.map.storage.hotRod.common.HotRodPair;
import org.keycloak.models.map.storage.hotRod.common.HotRodTypesUtils;
import org.keycloak.models.map.storage.hotRod.common.UpdatableHotRodEntityDelegateImpl;
import org.keycloak.sessions.AuthenticationSessionModel;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
@GenerateHotRodEntityImplementation(
implementInterface = "org.keycloak.models.map.authSession.MapAuthenticationSessionEntity",
inherits = "org.keycloak.models.map.storage.hotRod.authSession.HotRodAuthenticationSessionEntity.AbstractHotRodAuthenticationSessionEntityDelegate"
)
public class HotRodAuthenticationSessionEntity extends AbstractHotRodEntity {
@ProtoField(number = 1)
public String tabId;
@ProtoField(number = 2)
public String clientUUID;
@ProtoField(number = 3)
public String authUserId;
@ProtoField(number = 4)
public Long timestamp;
@ProtoField(number = 5)
public String redirectUri;
@ProtoField(number = 6)
public String action;
@ProtoField(number = 7)
public Set<String> clientScopes;
@ProtoField(number = 8)
public Set<HotRodPair<String, Integer>> executionStatuses;
@ProtoField(number = 9)
public String protocol;
@ProtoField(number = 10)
public Set<HotRodPair<String, String>> clientNotes;
@ProtoField(number = 11)
public Set<HotRodPair<String, String>> authNotes;
@ProtoField(number = 12)
public Set<String> requiredActions;
@ProtoField(number = 13)
public Set<HotRodPair<String, String>> userSessionNotes;
public static abstract class AbstractHotRodAuthenticationSessionEntityDelegate extends UpdatableHotRodEntityDelegateImpl<HotRodAuthenticationSessionEntity> implements MapAuthenticationSessionEntity {
@Override
public Map<String, AuthenticationSessionModel.ExecutionStatus> getExecutionStatuses() {
Set<HotRodPair<String, Integer>> executionStatuses = getHotRodEntity().executionStatuses;
if (executionStatuses == null) {
return Collections.emptyMap();
}
return executionStatuses.stream().collect(Collectors.toMap(HotRodPair::getKey,
v -> AuthenticationSessionModel.ExecutionStatus.valueOfInteger(v.getValue())));
}
@Override
public void setExecutionStatuses(Map<String, AuthenticationSessionModel.ExecutionStatus> executionStatus) {
HotRodAuthenticationSessionEntity hotRodEntity = getHotRodEntity();
Set<HotRodPair<String, Integer>> executionStatusSet = executionStatus == null ? null :
executionStatus.entrySet().stream()
.map(e -> new HotRodPair<>(e.getKey(), e.getValue().getStableIndex()))
.collect(Collectors.toSet());
hotRodEntity.updated |= ! Objects.equals(hotRodEntity.executionStatuses, executionStatusSet);
hotRodEntity.executionStatuses = executionStatusSet;
}
@Override
public void setExecutionStatus(String authenticator, AuthenticationSessionModel.ExecutionStatus status) {
HotRodAuthenticationSessionEntity hotRodEntity = getHotRodEntity();
if (hotRodEntity.executionStatuses == null) {
hotRodEntity.executionStatuses = new HashSet<>();
}
boolean valueUndefined = status == null;
hotRodEntity.updated |= HotRodTypesUtils.removeFromSetByMapKey(hotRodEntity.executionStatuses, authenticator, HotRodTypesUtils::getKey);
hotRodEntity.updated |= !valueUndefined && hotRodEntity.executionStatuses.add(new HotRodPair<>(authenticator, status.getStableIndex()));
}
}
@Override
public boolean equals(Object o) {
return HotRodAuthenticationSessionEntityDelegate.entityEquals(this, o);
}
@Override
public int hashCode() {
return HotRodAuthenticationSessionEntityDelegate.entityHashCode(this);
}
}

View file

@ -1,126 +0,0 @@
/*
* Copyright 2022 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.authSession;
import org.infinispan.api.annotations.indexing.Basic;
import org.infinispan.api.annotations.indexing.Indexed;
import org.infinispan.protostream.GeneratedSchema;
import org.infinispan.protostream.annotations.AutoProtoSchemaBuilder;
import org.infinispan.protostream.annotations.ProtoDoc;
import org.infinispan.protostream.annotations.ProtoField;
import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation;
import org.keycloak.models.map.annotations.IgnoreForEntityImplementationGenerator;
import org.keycloak.models.map.authSession.MapAuthenticationSessionEntity;
import org.keycloak.models.map.authSession.MapRootAuthenticationSessionEntity;
import org.keycloak.models.map.common.UpdatableEntity;
import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity;
import org.keycloak.models.map.storage.hotRod.common.CommonPrimitivesProtoSchemaInitializer;
import org.keycloak.models.map.storage.hotRod.common.UpdatableHotRodEntityDelegateImpl;
import java.util.Collections;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
@GenerateHotRodEntityImplementation(
implementInterface = "org.keycloak.models.map.authSession.MapRootAuthenticationSessionEntity",
inherits = "org.keycloak.models.map.storage.hotRod.authSession.HotRodRootAuthenticationSessionEntity.AbstractHotRodRootAuthenticationSessionEntityDelegate",
topLevelEntity = true,
modelClass = "org.keycloak.sessions.RootAuthenticationSessionModel"
)
@Indexed
@ProtoDoc("schema-version: " + HotRodRootAuthenticationSessionEntity.VERSION)
public class HotRodRootAuthenticationSessionEntity extends AbstractHotRodEntity {
@IgnoreForEntityImplementationGenerator
public static final int VERSION = 1;
@AutoProtoSchemaBuilder(
includeClasses = {
HotRodRootAuthenticationSessionEntity.class,
HotRodAuthenticationSessionEntity.class
},
schemaFilePath = "proto/",
schemaPackageName = CommonPrimitivesProtoSchemaInitializer.HOT_ROD_ENTITY_PACKAGE,
dependsOn = {CommonPrimitivesProtoSchemaInitializer.class}
)
public interface HotRodRootAuthenticationSessionEntitySchema extends GeneratedSchema {
HotRodRootAuthenticationSessionEntitySchema INSTANCE = new HotRodRootAuthenticationSessionEntitySchemaImpl();
}
@Basic(projectable = true)
@ProtoField(number = 1)
public Integer entityVersion = VERSION;
@ProtoField(number = 2)
public String id;
@Basic(sortable = true)
@ProtoField(number = 3)
public String realmId;
@ProtoField(number = 4)
public Long timestamp;
@ProtoField(number = 5)
public Long expiration;
@ProtoField(number = 6)
public Set<HotRodAuthenticationSessionEntity> authenticationSessions;
public static abstract class AbstractHotRodRootAuthenticationSessionEntityDelegate extends UpdatableHotRodEntityDelegateImpl<HotRodRootAuthenticationSessionEntity> implements MapRootAuthenticationSessionEntity {
@Override
public String getId() {
return getHotRodEntity().id;
}
@Override
public void setId(String id) {
HotRodRootAuthenticationSessionEntity entity = getHotRodEntity();
if (entity.id != null) throw new IllegalStateException("Id cannot be changed");
entity.id = id;
entity.updated |= id != null;
}
@Override
public boolean isUpdated() {
HotRodRootAuthenticationSessionEntity rootAuthSession = getHotRodEntity();
return rootAuthSession.updated ||
Optional.ofNullable(getAuthenticationSessions()).orElseGet(Collections::emptySet).stream().anyMatch(MapAuthenticationSessionEntity::isUpdated);
}
@Override
public void clearUpdatedFlag() {
HotRodRootAuthenticationSessionEntity rootAuthSession = getHotRodEntity();
rootAuthSession.updated = false;
Optional.ofNullable(getAuthenticationSessions()).orElseGet(Collections::emptySet).forEach(UpdatableEntity::clearUpdatedFlag);
}
}
@Override
public boolean equals(Object o) {
return HotRodRootAuthenticationSessionEntityDelegate.entityEquals(this, o);
}
@Override
public int hashCode() {
return HotRodRootAuthenticationSessionEntityDelegate.entityHashCode(this);
}
}

View file

@ -1,126 +0,0 @@
/*
* Copyright 2022 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.authorization;
import org.infinispan.api.annotations.indexing.Basic;
import org.infinispan.api.annotations.indexing.Indexed;
import org.infinispan.protostream.GeneratedSchema;
import org.infinispan.protostream.annotations.AutoProtoSchemaBuilder;
import org.infinispan.protostream.annotations.ProtoDoc;
import org.infinispan.protostream.annotations.ProtoField;
import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation;
import org.keycloak.models.map.annotations.IgnoreForEntityImplementationGenerator;
import org.keycloak.models.map.authorization.entity.MapPermissionTicketEntity;
import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity;
import org.keycloak.models.map.storage.hotRod.common.CommonPrimitivesProtoSchemaInitializer;
import org.keycloak.models.map.storage.hotRod.common.UpdatableHotRodEntityDelegateImpl;
@GenerateHotRodEntityImplementation(
implementInterface = "org.keycloak.models.map.authorization.entity.MapPermissionTicketEntity",
inherits = "org.keycloak.models.map.storage.hotRod.authorization.HotRodPermissionTicketEntity.AbstractHotRodPermissionTicketEntity",
topLevelEntity = true,
modelClass = "org.keycloak.authorization.model.PermissionTicket",
cacheName = "authz"
)
@Indexed
@ProtoDoc("schema-version: " + HotRodPermissionTicketEntity.VERSION)
public class HotRodPermissionTicketEntity extends AbstractHotRodEntity {
@IgnoreForEntityImplementationGenerator
public static final int VERSION = 1;
@AutoProtoSchemaBuilder(
includeClasses = {
HotRodPermissionTicketEntity.class,
},
schemaFilePath = "proto/",
schemaPackageName = CommonPrimitivesProtoSchemaInitializer.HOT_ROD_ENTITY_PACKAGE)
public interface HotRodPermissionTicketEntitySchema extends GeneratedSchema {
HotRodPermissionTicketEntitySchema INSTANCE = new HotRodPermissionTicketEntitySchemaImpl();
}
@Basic(projectable = true)
@ProtoField(number = 1)
public Integer entityVersion = VERSION;
@Basic(projectable = true, sortable = true)
@ProtoField(number = 2)
public String id;
@Basic(sortable = true)
@ProtoField(number = 3)
public String realmId;
@Basic(sortable = true)
@ProtoField(number = 4)
public String owner;
@Basic(sortable = true)
@ProtoField(number = 5)
public String requester;
@ProtoField(number = 6)
public Long createdTimestamp;
@Basic(sortable = true)
@ProtoField(number = 7)
public Long grantedTimestamp;
@Basic(sortable = true)
@ProtoField(number = 8)
public String resourceId;
@Basic(sortable = true)
@ProtoField(number = 9)
public String scopeId;
@Basic(sortable = true)
@ProtoField(number = 10)
public String resourceServerId;
@Basic(sortable = true)
@ProtoField(number = 11)
public String policyId;
public static abstract class AbstractHotRodPermissionTicketEntity extends UpdatableHotRodEntityDelegateImpl<HotRodPermissionTicketEntity> implements MapPermissionTicketEntity {
@Override
public String getId() {
return getHotRodEntity().id;
}
@Override
public void setId(String id) {
HotRodPermissionTicketEntity entity = getHotRodEntity();
if (entity.id != null) throw new IllegalStateException("Id cannot be changed");
entity.id = id;
entity.updated |= id != null;
}
}
@Override
public boolean equals(Object o) {
return HotRodPermissionTicketEntityDelegate.entityEquals(this, o);
}
@Override
public int hashCode() {
return HotRodPermissionTicketEntityDelegate.entityHashCode(this);
}
}

View file

@ -1,150 +0,0 @@
/*
* Copyright 2022 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.authorization;
import org.infinispan.api.annotations.indexing.Basic;
import org.infinispan.api.annotations.indexing.Indexed;
import org.infinispan.api.annotations.indexing.Keyword;
import org.infinispan.protostream.GeneratedSchema;
import org.infinispan.protostream.annotations.AutoProtoSchemaBuilder;
import org.infinispan.protostream.annotations.ProtoDoc;
import org.infinispan.protostream.annotations.ProtoField;
import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation;
import org.keycloak.models.map.annotations.IgnoreForEntityImplementationGenerator;
import org.keycloak.models.map.authorization.entity.MapPolicyEntity;
import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity;
import org.keycloak.models.map.storage.hotRod.common.CommonPrimitivesProtoSchemaInitializer;
import org.keycloak.models.map.storage.hotRod.common.HotRodStringPair;
import org.keycloak.models.map.storage.hotRod.common.UpdatableHotRodEntityDelegateImpl;
import java.util.Objects;
import java.util.Set;
@GenerateHotRodEntityImplementation(
implementInterface = "org.keycloak.models.map.authorization.entity.MapPolicyEntity",
inherits = "org.keycloak.models.map.storage.hotRod.authorization.HotRodPolicyEntity.AbstractHotRodPolicyEntity",
topLevelEntity = true,
modelClass = "org.keycloak.authorization.model.Policy",
cacheName = "authz"
)
@Indexed
@ProtoDoc("schema-version: " + HotRodPolicyEntity.VERSION)
public class HotRodPolicyEntity extends AbstractHotRodEntity {
@IgnoreForEntityImplementationGenerator
public static final int VERSION = 1;
@AutoProtoSchemaBuilder(
includeClasses = {
HotRodPolicyEntity.class
},
schemaFilePath = "proto/",
schemaPackageName = CommonPrimitivesProtoSchemaInitializer.HOT_ROD_ENTITY_PACKAGE,
dependsOn = {CommonPrimitivesProtoSchemaInitializer.class}
)
public interface HotRodPolicyEntitySchema extends GeneratedSchema {
HotRodPolicyEntitySchema INSTANCE = new HotRodPolicyEntitySchemaImpl();
}
@Basic(projectable = true)
@ProtoField(number = 1)
public Integer entityVersion = VERSION;
@Basic(projectable = true, sortable = true)
@ProtoField(number = 2)
public String id;
@Basic(sortable = true)
@ProtoField(number = 3)
public String realmId;
@Keyword(sortable = true, normalizer = "lowercase")
@ProtoField(number = 4)
public String name;
@ProtoField(number = 5)
public String description;
@Keyword(sortable = true, normalizer = "lowercase")
@ProtoField(number = 6)
public String type;
@ProtoField(number = 7)
public Integer decisionStrategy;
@ProtoField(number = 8)
public Integer logic;
@Basic(sortable = true)
@ProtoField(number = 9)
public Set<HotRodStringPair> configs;
@Basic(sortable = true)
@ProtoField(number = 10)
public String resourceServerId;
@Basic(sortable = true)
@ProtoField(number = 11)
public Set<String> associatedPolicyIds;
@Basic(sortable = true)
@ProtoField(number = 12)
public Set<String> resourceIds;
@Basic(sortable = true)
@ProtoField(number = 13)
public Set<String> scopeIds;
@Basic(sortable = true)
@ProtoField(number = 14)
public String owner;
public static abstract class AbstractHotRodPolicyEntity extends UpdatableHotRodEntityDelegateImpl<HotRodPolicyEntity> implements MapPolicyEntity {
@Override
public String getId() {
return getHotRodEntity().id;
}
@Override
public void setId(String id) {
HotRodPolicyEntity entity = getHotRodEntity();
if (entity.id != null) throw new IllegalStateException("Id cannot be changed");
entity.id = id;
entity.updated |= id != null;
}
@Override
public void setName(String name) {
HotRodPolicyEntity entity = getHotRodEntity();
entity.updated |= ! Objects.equals(entity.name, name);
entity.name = name;
}
}
@Override
public boolean equals(Object o) {
return HotRodPolicyEntityDelegate.entityEquals(this, o);
}
@Override
public int hashCode() {
return HotRodPolicyEntityDelegate.entityHashCode(this);
}
}

View file

@ -1,146 +0,0 @@
/*
* Copyright 2022 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.authorization;
import org.infinispan.api.annotations.indexing.Basic;
import org.infinispan.api.annotations.indexing.Indexed;
import org.infinispan.api.annotations.indexing.Keyword;
import org.infinispan.protostream.GeneratedSchema;
import org.infinispan.protostream.annotations.AutoProtoSchemaBuilder;
import org.infinispan.protostream.annotations.ProtoDoc;
import org.infinispan.protostream.annotations.ProtoField;
import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation;
import org.keycloak.models.map.annotations.IgnoreForEntityImplementationGenerator;
import org.keycloak.models.map.authorization.entity.MapResourceEntity;
import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity;
import org.keycloak.models.map.storage.hotRod.common.CommonPrimitivesProtoSchemaInitializer;
import org.keycloak.models.map.storage.hotRod.common.HotRodAttributeEntityNonIndexed;
import org.keycloak.models.map.storage.hotRod.common.UpdatableHotRodEntityDelegateImpl;
import java.util.Objects;
import java.util.Set;
@GenerateHotRodEntityImplementation(
implementInterface = "org.keycloak.models.map.authorization.entity.MapResourceEntity",
inherits = "org.keycloak.models.map.storage.hotRod.authorization.HotRodResourceEntity.AbstractHotRodResourceEntity",
topLevelEntity = true,
modelClass = "org.keycloak.authorization.model.Resource",
cacheName = "authz"
)
@Indexed
@ProtoDoc("schema-version: " + HotRodResourceEntity.VERSION)
public class HotRodResourceEntity extends AbstractHotRodEntity {
@IgnoreForEntityImplementationGenerator
public static final int VERSION = 1;
@AutoProtoSchemaBuilder(
includeClasses = {
HotRodResourceEntity.class
},
schemaFilePath = "proto/",
schemaPackageName = CommonPrimitivesProtoSchemaInitializer.HOT_ROD_ENTITY_PACKAGE,
dependsOn = {CommonPrimitivesProtoSchemaInitializer.class}
)
public interface HotRodResourceEntitySchema extends GeneratedSchema {
HotRodResourceEntitySchema INSTANCE = new HotRodResourceEntitySchemaImpl();
}
@Basic(projectable = true)
@ProtoField(number = 1)
public Integer entityVersion = VERSION;
@Basic(projectable = true, sortable = true)
@ProtoField(number = 2)
public String id;
@Basic(sortable = true)
@ProtoField(number = 3)
public String realmId;
@Keyword(sortable = true, normalizer = "lowercase")
@ProtoField(number = 4)
public String name;
@ProtoField(number = 5)
public String displayName;
@Basic(sortable = true)
@ProtoField(number = 6)
public Set<String> uris;
@Keyword(sortable = true, normalizer = "lowercase")
@ProtoField(number = 7)
public String type;
@ProtoField(number = 8)
public String iconUri;
@Basic(sortable = true)
@ProtoField(number = 9)
public String owner;
@Basic(sortable = true)
@ProtoField(number = 10)
public Boolean ownerManagedAccess;
@Basic(sortable = true)
@ProtoField(number = 11)
public String resourceServerId;
@Basic(sortable = true)
@ProtoField(number = 12)
public Set<String> scopeIds;
@ProtoField(number = 13)
public Set<HotRodAttributeEntityNonIndexed> attributes;
public static abstract class AbstractHotRodResourceEntity extends UpdatableHotRodEntityDelegateImpl<HotRodResourceEntity> implements MapResourceEntity {
@Override
public String getId() {
return getHotRodEntity().id;
}
@Override
public void setId(String id) {
HotRodResourceEntity entity = getHotRodEntity();
if (entity.id != null) throw new IllegalStateException("Id cannot be changed");
entity.id = id;
entity.updated |= id != null;
}
@Override
public void setName(String name) {
HotRodResourceEntity entity = getHotRodEntity();
entity.updated |= ! Objects.equals(entity.name, name);
entity.name = name;
}
}
@Override
public boolean equals(Object o) {
return HotRodResourceEntityDelegate.entityEquals(this, o);
}
@Override
public int hashCode() {
return HotRodResourceEntityDelegate.entityHashCode(this);
}
}

View file

@ -1,109 +0,0 @@
/*
* Copyright 2022 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.authorization;
import org.infinispan.api.annotations.indexing.Basic;
import org.infinispan.api.annotations.indexing.Indexed;
import org.infinispan.protostream.GeneratedSchema;
import org.infinispan.protostream.annotations.AutoProtoSchemaBuilder;
import org.infinispan.protostream.annotations.ProtoDoc;
import org.infinispan.protostream.annotations.ProtoField;
import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation;
import org.keycloak.models.map.annotations.IgnoreForEntityImplementationGenerator;
import org.keycloak.models.map.authorization.entity.MapResourceServerEntity;
import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity;
import org.keycloak.models.map.storage.hotRod.common.CommonPrimitivesProtoSchemaInitializer;
import org.keycloak.models.map.storage.hotRod.common.UpdatableHotRodEntityDelegateImpl;
@GenerateHotRodEntityImplementation(
implementInterface = "org.keycloak.models.map.authorization.entity.MapResourceServerEntity",
inherits = "org.keycloak.models.map.storage.hotRod.authorization.HotRodResourceServerEntity.AbstractHotRodResourceServerEntity",
topLevelEntity = true,
modelClass = "org.keycloak.authorization.model.ResourceServer",
cacheName = "authz"
)
@ProtoDoc("schema-version: " + HotRodResourceServerEntity.VERSION)
@Indexed
public class HotRodResourceServerEntity extends AbstractHotRodEntity {
@IgnoreForEntityImplementationGenerator
public static final int VERSION = 1;
@AutoProtoSchemaBuilder(
includeClasses = {
HotRodResourceServerEntity.class,
},
schemaFilePath = "proto/",
schemaPackageName = CommonPrimitivesProtoSchemaInitializer.HOT_ROD_ENTITY_PACKAGE)
public interface HotRodResourceServerEntitySchema extends GeneratedSchema {
HotRodResourceServerEntitySchema INSTANCE = new HotRodResourceServerEntitySchemaImpl();
}
@Basic(projectable = true)
@ProtoField(number = 1)
public Integer entityVersion = VERSION;
@Basic(projectable = true, sortable = true)
@ProtoField(number = 2)
public String id;
@Basic(sortable = true)
@ProtoField(number = 3)
public String realmId;
@Basic(sortable = true)
@ProtoField(number = 4)
public String clientId;
@ProtoField(number = 5)
public Boolean allowRemoteResourceManagement;
@ProtoField(number = 6)
public Integer policyEnforcementMode;
@ProtoField(number = 7)
public Integer decisionStrategy;
public static abstract class AbstractHotRodResourceServerEntity extends UpdatableHotRodEntityDelegateImpl<HotRodResourceServerEntity> implements MapResourceServerEntity {
@Override
public String getId() {
return getHotRodEntity().id;
}
@Override
public void setId(String id) {
HotRodResourceServerEntity entity = getHotRodEntity();
if (entity.id != null) throw new IllegalStateException("Id cannot be changed");
entity.id = id;
entity.updated |= id != null;
}
}
@Override
public boolean equals(Object o) {
return HotRodResourceServerEntityDelegate.entityEquals(this, o);
}
@Override
public int hashCode() {
return HotRodResourceServerEntityDelegate.entityHashCode(this);
}
}

View file

@ -1,119 +0,0 @@
/*
* Copyright 2022 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.authorization;
import org.infinispan.api.annotations.indexing.Basic;
import org.infinispan.api.annotations.indexing.Indexed;
import org.infinispan.api.annotations.indexing.Keyword;
import org.infinispan.protostream.GeneratedSchema;
import org.infinispan.protostream.annotations.AutoProtoSchemaBuilder;
import org.infinispan.protostream.annotations.ProtoDoc;
import org.infinispan.protostream.annotations.ProtoField;
import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation;
import org.keycloak.models.map.annotations.IgnoreForEntityImplementationGenerator;
import org.keycloak.models.map.authorization.entity.MapScopeEntity;
import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity;
import org.keycloak.models.map.storage.hotRod.common.CommonPrimitivesProtoSchemaInitializer;
import org.keycloak.models.map.storage.hotRod.common.UpdatableHotRodEntityDelegateImpl;
import java.util.Objects;
@GenerateHotRodEntityImplementation(
implementInterface = "org.keycloak.models.map.authorization.entity.MapScopeEntity",
inherits = "org.keycloak.models.map.storage.hotRod.authorization.HotRodScopeEntity.AbstractHotRodScopeEntity",
topLevelEntity = true,
modelClass = "org.keycloak.authorization.model.Scope",
cacheName = "authz"
)
@Indexed
@ProtoDoc("schema-version: " + HotRodScopeEntity.VERSION)
public class HotRodScopeEntity extends AbstractHotRodEntity {
@IgnoreForEntityImplementationGenerator
public static final int VERSION = 1;
@AutoProtoSchemaBuilder(
includeClasses = {
HotRodScopeEntity.class
},
schemaFilePath = "proto/",
schemaPackageName = CommonPrimitivesProtoSchemaInitializer.HOT_ROD_ENTITY_PACKAGE)
public interface HotRodScopeEntitySchema extends GeneratedSchema {
HotRodScopeEntitySchema INSTANCE = new HotRodScopeEntitySchemaImpl();
}
@Basic(projectable = true)
@ProtoField(number = 1)
public Integer entityVersion = VERSION;
@Basic(projectable = true, sortable = true)
@ProtoField(number = 2)
public String id;
@Basic(sortable = true)
@ProtoField(number = 3)
public String realmId;
@Keyword(sortable = true, normalizer = "lowercase")
@ProtoField(number = 4)
public String name;
@ProtoField(number = 5)
public String displayName;
@ProtoField(number = 6)
public String iconUri;
@Basic(sortable = true)
@ProtoField(number = 7)
public String resourceServerId;
public static abstract class AbstractHotRodScopeEntity extends UpdatableHotRodEntityDelegateImpl<HotRodScopeEntity> implements MapScopeEntity {
@Override
public String getId() {
return getHotRodEntity().id;
}
@Override
public void setId(String id) {
HotRodScopeEntity entity = getHotRodEntity();
if (entity.id != null) throw new IllegalStateException("Id cannot be changed");
entity.id = id;
entity.updated |= id != null;
}
@Override
public void setName(String name) {
HotRodScopeEntity entity = getHotRodEntity();
entity.updated |= ! Objects.equals(entity.name, name);
entity.name = name;
}
}
@Override
public boolean equals(Object o) {
return HotRodScopeEntityDelegate.entityEquals(this, o);
}
@Override
public int hashCode() {
return HotRodScopeEntityDelegate.entityHashCode(this);
}
}

View file

@ -1,223 +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.client;
import org.infinispan.api.annotations.indexing.Basic;
import org.infinispan.api.annotations.indexing.Indexed;
import org.infinispan.api.annotations.indexing.Keyword;
import org.infinispan.protostream.GeneratedSchema;
import org.infinispan.protostream.annotations.AutoProtoSchemaBuilder;
import org.infinispan.protostream.annotations.ProtoDoc;
import org.infinispan.protostream.annotations.ProtoField;
import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation;
import org.keycloak.models.map.annotations.IgnoreForEntityImplementationGenerator;
import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity;
import org.keycloak.models.map.storage.hotRod.common.CommonPrimitivesProtoSchemaInitializer;
import org.keycloak.models.map.storage.hotRod.common.HotRodAttributeEntity;
import org.keycloak.models.map.storage.hotRod.common.HotRodPair;
import org.keycloak.models.map.client.MapClientEntity;
import org.keycloak.models.map.client.MapProtocolMapperEntity;
import org.keycloak.models.map.storage.hotRod.common.UpdatableHotRodEntityDelegateImpl;
import java.util.Collection;
import java.util.LinkedList;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;
@GenerateHotRodEntityImplementation(
implementInterface = "org.keycloak.models.map.client.MapClientEntity",
inherits = "org.keycloak.models.map.storage.hotRod.client.HotRodClientEntity.AbstractHotRodClientEntityDelegate",
topLevelEntity = true,
modelClass = "org.keycloak.models.ClientModel"
)
@Indexed
@ProtoDoc("schema-version: " + HotRodClientEntity.VERSION)
public class HotRodClientEntity extends AbstractHotRodEntity {
@IgnoreForEntityImplementationGenerator
public static final int VERSION = 1;
@AutoProtoSchemaBuilder(
includeClasses = {
HotRodClientEntity.class,
HotRodProtocolMapperEntity.class,
},
schemaFilePath = "proto/",
schemaPackageName = CommonPrimitivesProtoSchemaInitializer.HOT_ROD_ENTITY_PACKAGE,
dependsOn = {CommonPrimitivesProtoSchemaInitializer.class}
)
public interface HotRodClientEntitySchema extends GeneratedSchema {
HotRodClientEntitySchema INSTANCE = new HotRodClientEntitySchemaImpl();
}
@Basic(projectable = true)
@ProtoField(number = 1)
public Integer entityVersion = VERSION;
@Basic(projectable = true, sortable = true)
@ProtoField(number = 2)
public String id;
@Basic(sortable = true)
@ProtoField(number = 3)
public String realmId;
@Keyword(sortable = true, normalizer = "lowercase")
@ProtoField(number = 4)
public String clientId;
@ProtoField(number = 5)
public String name;
@ProtoField(number = 6)
public String description;
@ProtoField(number = 7)
public Set<String> redirectUris;
@ProtoField(number = 8)
public Boolean enabled;
@ProtoField(number = 9)
public Boolean alwaysDisplayInConsole;
@ProtoField(number = 10)
public String clientAuthenticatorType;
@ProtoField(number = 11)
public String secret;
@ProtoField(number = 12)
public String registrationToken;
@ProtoField(number = 13)
public String protocol;
@Basic(sortable = true)
@ProtoField(number = 14)
public Set<HotRodAttributeEntity> attributes;
@ProtoField(number = 15)
public Set<HotRodPair<String, String>> authenticationFlowBindingOverrides;
@ProtoField(number = 16)
public Boolean publicClient;
@ProtoField(number = 17)
public Boolean fullScopeAllowed;
@ProtoField(number = 18)
public Boolean frontchannelLogout;
@ProtoField(number = 19)
public Long notBefore;
@ProtoField(number = 20)
public Set<String> scope;
@ProtoField(number = 21)
public Set<String> webOrigins;
@ProtoField(number = 22)
public Set<HotRodProtocolMapperEntity> protocolMappers;
@ProtoField(number = 23)
public Set<HotRodPair<String, Boolean>> clientScopes;
@Basic(sortable = true)
@ProtoField(number = 24, collectionImplementation = LinkedList.class)
public Collection<String> scopeMappings;
@ProtoField(number = 25)
public Boolean surrogateAuthRequired;
@ProtoField(number = 26)
public String managementUrl;
@ProtoField(number = 27)
public String baseUrl;
@ProtoField(number = 28)
public Boolean bearerOnly;
@ProtoField(number = 29)
public Boolean consentRequired;
@ProtoField(number = 30)
public String rootUrl;
@ProtoField(number = 31)
public Boolean standardFlowEnabled;
@ProtoField(number = 32)
public Boolean implicitFlowEnabled;
@ProtoField(number = 33)
public Boolean directAccessGrantsEnabled;
@ProtoField(number = 34)
public Boolean serviceAccountsEnabled;
@ProtoField(number = 35)
public Integer nodeReRegistrationTimeout;
public static abstract class AbstractHotRodClientEntityDelegate extends UpdatableHotRodEntityDelegateImpl<HotRodClientEntity> implements MapClientEntity {
@Override
public String getId() {
return getHotRodEntity().id;
}
@Override
public void setId(String id) {
HotRodClientEntity entity = getHotRodEntity();
if (entity.id != null) throw new IllegalStateException("Id cannot be changed");
entity.id = id;
entity.updated |= id != null;
}
@Override
public void setClientId(String clientId) {
HotRodClientEntity entity = getHotRodEntity();
entity.updated |= ! Objects.equals(entity.clientId, clientId);
entity.clientId = clientId;
}
@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 boolean equals(Object o) {
return HotRodClientEntityDelegate.entityEquals(this, o);
}
@Override
public int hashCode() {
return HotRodClientEntityDelegate.entityHashCode(this);
}
}

View file

@ -1,49 +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.client;
import org.infinispan.protostream.annotations.ProtoField;
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.Set;
@GenerateHotRodEntityImplementation(implementInterface = "org.keycloak.models.map.client.MapProtocolMapperEntity")
public class HotRodProtocolMapperEntity extends AbstractHotRodEntity {
@ProtoField(number = 1)
public String id;
@ProtoField(number = 2)
public String name;
@ProtoField(number = 3)
public String protocol;
@ProtoField(number = 4)
public String protocolMapper;
@ProtoField(number = 5)
public Set<HotRodPair<String, String>> config;
@Override
public boolean equals(Object o) {
return HotRodProtocolMapperEntityDelegate.entityEquals(this, o);
}
@Override
public int hashCode() {
return HotRodProtocolMapperEntityDelegate.entityHashCode(this);
}
}

View file

@ -1,123 +0,0 @@
/*
* Copyright 2022 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.clientscope;
import org.infinispan.api.annotations.indexing.Basic;
import org.infinispan.api.annotations.indexing.Indexed;
import org.infinispan.protostream.GeneratedSchema;
import org.infinispan.protostream.annotations.AutoProtoSchemaBuilder;
import org.infinispan.protostream.annotations.ProtoDoc;
import org.infinispan.protostream.annotations.ProtoField;
import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation;
import org.keycloak.models.map.annotations.IgnoreForEntityImplementationGenerator;
import org.keycloak.models.map.client.MapProtocolMapperEntity;
import org.keycloak.models.map.clientscope.MapClientScopeEntity;
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.AbstractHotRodEntity;
import org.keycloak.models.map.storage.hotRod.common.CommonPrimitivesProtoSchemaInitializer;
import org.keycloak.models.map.storage.hotRod.common.HotRodAttributeEntityNonIndexed;
import org.keycloak.models.map.storage.hotRod.common.UpdatableHotRodEntityDelegateImpl;
import java.util.Collection;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
@GenerateHotRodEntityImplementation(
implementInterface = "org.keycloak.models.map.clientscope.MapClientScopeEntity",
inherits = "org.keycloak.models.map.storage.hotRod.clientscope.HotRodClientScopeEntity.AbstractHotRodClientScopeEntityDelegate",
topLevelEntity = true,
modelClass = "org.keycloak.models.ClientScopeModel"
)
@Indexed
@ProtoDoc("schema-version: " + HotRodClientScopeEntity.VERSION)
public class HotRodClientScopeEntity extends AbstractHotRodEntity {
@IgnoreForEntityImplementationGenerator
public static final int VERSION = 1;
@AutoProtoSchemaBuilder(
includeClasses = {
HotRodClientScopeEntity.class
},
schemaFilePath = "proto/",
schemaPackageName = CommonPrimitivesProtoSchemaInitializer.HOT_ROD_ENTITY_PACKAGE,
dependsOn = {CommonPrimitivesProtoSchemaInitializer.class, HotRodClientEntity.HotRodClientEntitySchema.class}
)
public interface HotRodClientScopeEntitySchema extends GeneratedSchema {
HotRodClientScopeEntitySchema INSTANCE = new HotRodClientScopeEntitySchemaImpl();
}
@Basic(projectable = true)
@ProtoField(number = 1)
public Integer entityVersion = VERSION;
@ProtoField(number = 2)
public String id;
@Basic(sortable = true)
@ProtoField(number = 3)
public String realmId;
@Basic(sortable = true)
@ProtoField(number = 4)
public String name;
@ProtoField(number = 5)
public String protocol;
@ProtoField(number = 6)
public String description;
@ProtoField(number = 7)
public Collection<String> scopeMappings;
@ProtoField(number = 8)
public Set<HotRodProtocolMapperEntity> protocolMappers;
@ProtoField(number = 9)
public Set<HotRodAttributeEntityNonIndexed> attributes;
public static abstract class AbstractHotRodClientScopeEntityDelegate extends UpdatableHotRodEntityDelegateImpl<HotRodClientScopeEntity> implements MapClientScopeEntity {
@Override
public String getId() {
return getHotRodEntity().id;
}
@Override
public void setId(String id) {
HotRodClientScopeEntity entity = getHotRodEntity();
if (entity.id != null) throw new IllegalStateException("Id cannot be changed");
entity.id = id;
entity.updated |= id != null;
}
}
@Override
public boolean equals(Object o) {
return HotRodClientScopeEntityDelegate.entityEquals(this, o);
}
@Override
public int hashCode() {
return HotRodClientScopeEntityDelegate.entityHashCode(this);
}
}

View file

@ -1,34 +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;
import org.keycloak.models.map.common.UpdatableEntity;
public abstract class AbstractHotRodEntity implements UpdatableEntity {
public boolean updated;
@Override
public boolean isUpdated() {
return this.updated;
}
@Override
public void clearUpdatedFlag() {
this.updated = false;
}
}

View file

@ -1,41 +0,0 @@
/*
* Copyright 2022 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.infinispan.protostream.GeneratedSchema;
import org.infinispan.protostream.annotations.AutoProtoSchemaBuilder;
/**
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
*/
@AutoProtoSchemaBuilder(
includeClasses = {
HotRodPair.class,
HotRodStringPair.class,
HotRodAttributeEntity.class,
HotRodAttributeEntityNonIndexed.class
},
schemaFileName = "CommonPrimitivesProtoSchema.proto",
schemaFilePath = "proto/",
schemaPackageName = CommonPrimitivesProtoSchemaInitializer.HOT_ROD_ENTITY_PACKAGE)
public interface CommonPrimitivesProtoSchemaInitializer extends GeneratedSchema {
String HOT_ROD_ENTITY_PACKAGE = "kc";
int COMMON_PRIMITIVES_VERSION = 1;
CommonPrimitivesProtoSchemaInitializer INSTANCE = new CommonPrimitivesProtoSchemaInitializerImpl();
}

View file

@ -1,79 +0,0 @@
/*
* Copyright 2022 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.infinispan.api.annotations.indexing.Basic;
import org.infinispan.api.annotations.indexing.Indexed;
import org.infinispan.protostream.annotations.ProtoField;
import java.util.List;
import java.util.Objects;
/**
* !!! Please do not change this class !!!
*
* If some change is needed please create a new version of this class and solve the migration on top-level entities.
*
*/
@Indexed
public class HotRodAttributeEntity {
@Basic(sortable = true)
@ProtoField(number = 1)
public String name;
@Basic(sortable = true)
@ProtoField(number = 2)
public List<String> values;
public HotRodAttributeEntity() {
}
public HotRodAttributeEntity(String name, List<String> values) {
this.name = name;
this.values = values;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<String> getValues() {
return values;
}
public void setValues(List<String> values) {
this.values = values;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
HotRodAttributeEntity that = (HotRodAttributeEntity) o;
return Objects.equals(name, that.name) && Objects.equals(values, that.values);
}
@Override
public int hashCode() {
return Objects.hash(name, values);
}
}

View file

@ -1,74 +0,0 @@
/*
* Copyright 2022 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.infinispan.protostream.annotations.ProtoField;
import java.util.List;
import java.util.Objects;
/**
* !!! Please do not change this class !!!
*
* If some change is needed please create a new version of this class and solve the migration on top-level entities.
*
*/
public class HotRodAttributeEntityNonIndexed {
@ProtoField(number = 1)
public String name;
@ProtoField(number = 2)
public List<String> values;
public HotRodAttributeEntityNonIndexed() {
}
public HotRodAttributeEntityNonIndexed(String name, List<String> values) {
this.name = name;
this.values = values;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<String> getValues() {
return values;
}
public void setValues(List<String> values) {
this.values = values;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
HotRodAttributeEntityNonIndexed that = (HotRodAttributeEntityNonIndexed) o;
return Objects.equals(name, that.name) && Objects.equals(values, that.values);
}
@Override
public int hashCode() {
return Objects.hash(name, values);
}
}

View file

@ -1,24 +0,0 @@
/*
* Copyright 2022 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.UpdatableEntity;
public interface HotRodEntityDelegate<E> extends UpdatableEntity {
E getHotRodEntity();
}

View file

@ -1,37 +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;
import org.infinispan.protostream.GeneratedSchema;
import java.util.function.Function;
public interface HotRodEntityDescriptor<E extends AbstractHotRodEntity, D extends HotRodEntityDelegate<E>> {
Class<?> getModelTypeClass();
Class<E> getEntityTypeClass();
String getCacheName();
Function<E, D> getHotRodDelegateProvider();
Integer getCurrentVersion();
GeneratedSchema getProtoSchema();
}

View file

@ -1,77 +0,0 @@
/*
* Copyright 2022 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.infinispan.protostream.WrappedMessage;
import org.infinispan.protostream.annotations.ProtoDoc;
import org.infinispan.protostream.annotations.ProtoField;
import java.util.Objects;
import static org.keycloak.models.map.storage.hotRod.common.CommonPrimitivesProtoSchemaInitializer.COMMON_PRIMITIVES_VERSION;
/**
* !!! Please do not change this class !!!
*
* If some change is needed please create a new version of this class and solve the migration on top-level entities.
*
*/
@ProtoDoc("schema-version: " + COMMON_PRIMITIVES_VERSION)
public class HotRodPair<T, V> {
@ProtoField(number = 1)
public WrappedMessage key;
@ProtoField(number = 2)
public WrappedMessage value;
public HotRodPair() {}
public HotRodPair(T first, V second) {
this.key = new WrappedMessage(first);
this.value = new WrappedMessage(second);
}
public T getKey() {
return key == null ? null : (T) key.getValue();
}
public V getValue() {
return value == null ? null : (V) value.getValue();
}
public void setKey(T first) {
this.key = new WrappedMessage(first);
}
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);
}
}

View file

@ -1,80 +0,0 @@
/*
* Copyright 2022 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.infinispan.api.annotations.indexing.Basic;
import org.infinispan.api.annotations.indexing.Indexed;
import org.infinispan.protostream.annotations.ProtoField;
import java.util.Objects;
/**
* !!! Please do not change this class !!!
*
* If some change is needed please create a new version of this class and solve the migration on top-level entities.
*
* Indexed Hot Rod pair entity where both key and value are {@link String} type. The entity should be used when
* there is a need to search by key or/and value. Otherwise {@link HotRodPair<String, String>} should be used.
*/
@Indexed
public class HotRodStringPair {
@Basic(sortable = true)
@ProtoField(number = 1)
public String key;
@Basic(sortable = true)
@ProtoField(number = 2)
public String value;
public HotRodStringPair() {}
public HotRodStringPair(String key, String value) {
this.key = key;
this.value = value;
}
public String getKey() {
return key;
}
public String getValue() {
return value;
}
public void setKey(String key) {
this.key = key;
}
public void setValue(String value) {
this.value = value;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
HotRodStringPair that = (HotRodStringPair) o;
return Objects.equals(key, that.key) && Objects.equals(value, that.value);
}
@Override
public int hashCode() {
return Objects.hash(key, value);
}
}

View file

@ -1,160 +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;
import org.keycloak.models.map.common.AbstractEntity;
import org.keycloak.models.map.common.DeepCloner;
import org.keycloak.models.map.storage.hotRod.authSession.HotRodAuthenticationSessionEntity;
import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodLocalizationTexts;
import org.keycloak.models.map.storage.hotRod.user.HotRodUserConsentEntity;
import org.keycloak.models.map.storage.hotRod.user.HotRodUserFederatedIdentityEntity;
import org.keycloak.models.map.storage.hotRod.userSession.AuthenticatedClientSessionReferenceOnlyFieldDelegate;
import org.keycloak.models.map.storage.hotRod.userSession.HotRodAuthenticatedClientSessionEntityReference;
import org.keycloak.models.map.userSession.MapAuthenticatedClientSessionEntity;
import java.util.HashMap;
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(HashMap::new, (m, v) -> m.put(keyProducer.apply(v), valueProducer.apply(v)), HashMap::putAll);
}
public static <T, V> HotRodPair<T, V> createHotRodPairFromMapEntry(Map.Entry<T, V> entry) {
return new HotRodPair<>(entry.getKey(), entry.getValue());
}
public static HotRodStringPair createHotRodStringPairFromMapEntry(Map.Entry<String, String> entry) {
return new HotRodStringPair(entry.getKey(), entry.getValue());
}
public static HotRodAttributeEntity createHotRodAttributeEntityFromMapEntry(Map.Entry<String, List<String>> entry) {
return new HotRodAttributeEntity(entry.getKey(), entry.getValue());
}
public static HotRodAttributeEntityNonIndexed createHotRodAttributeEntityNonIndexedFromMapEntry(Map.Entry<String, List<String>> entry) {
return new HotRodAttributeEntityNonIndexed(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(HotRodStringPair hotRodPair) {
return hotRodPair.getKey();
}
public static String getValue(HotRodStringPair hotRodPair) {
return hotRodPair.getValue();
}
public static String getKey(HotRodAttributeEntity attributeEntity) {
return attributeEntity.name;
}
public static String getKey(HotRodAttributeEntityNonIndexed attributeEntity) {
return attributeEntity.name;
}
public static List<String> getValue(HotRodAttributeEntity attributeEntity) {
return attributeEntity.values;
}
public static List<String> getValue(HotRodAttributeEntityNonIndexed attributeEntity) {
return attributeEntity.values;
}
public static String getKey(AbstractEntity entity) {
return entity.getId();
}
public static String getKey(HotRodUserFederatedIdentityEntity hotRodUserFederatedIdentityEntity) {
return hotRodUserFederatedIdentityEntity.identityProvider;
}
public static String getKey(HotRodUserConsentEntity hotRodUserConsentEntity) {
return hotRodUserConsentEntity.clientId;
}
public static <T, V> List<V> migrateList(List<T> p0, Function<T, V> migrator) {
return p0 == null ? null : p0.stream().map(migrator).collect(Collectors.toList());
}
public static <T, V> Set<V> migrateSet(Set<T> p0, Function<T, V> migrator) {
return p0 == null ? null : p0.stream().map(migrator).collect(Collectors.toSet());
}
public static String getKey(HotRodAuthenticationSessionEntity hotRodAuthenticationSessionEntity) {
return hotRodAuthenticationSessionEntity.tabId;
}
public static String getKey(HotRodLocalizationTexts hotRodLocalizationTexts) {
return hotRodLocalizationTexts.getLocale();
}
public static Map<String, String> getValue(HotRodLocalizationTexts hotRodLocalizationTexts) {
Set<HotRodPair<String, String>> values = hotRodLocalizationTexts.getValues();
return values == null ? null : values.stream().collect(Collectors.toMap(HotRodPair::getKey, HotRodPair::getValue));
}
public static HotRodLocalizationTexts migrateStringMapToHotRodLocalizationTexts(String p0, Map<String, String> p1) {
HotRodLocalizationTexts hotRodLocalizationTexts = new HotRodLocalizationTexts();
hotRodLocalizationTexts.setLocale(p0);
hotRodLocalizationTexts.setValues(migrateMapToSet(p1, HotRodTypesUtils::createHotRodPairFromMapEntry));
return hotRodLocalizationTexts;
}
public static HotRodAuthenticatedClientSessionEntityReference migrateMapAuthenticatedClientSessionEntityToHotRodAuthenticatedClientSessionEntityReference(MapAuthenticatedClientSessionEntity p0) {
return new HotRodAuthenticatedClientSessionEntityReference(p0.getClientId(), p0.getId());
}
public static MapAuthenticatedClientSessionEntity migrateHotRodAuthenticatedClientSessionEntityReferenceToMapAuthenticatedClientSessionEntity(HotRodAuthenticatedClientSessionEntityReference collectionItem) {
return DeepCloner.DUMB_CLONER.entityFieldDelegate(MapAuthenticatedClientSessionEntity.class, new AuthenticatedClientSessionReferenceOnlyFieldDelegate(collectionItem));
}
}

View file

@ -1,41 +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;
import org.infinispan.query.dsl.Query;
/**
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
*/
public class HotRodUtils {
public static final int DEFAULT_MAX_RESULTS = Integer.MAX_VALUE >> 1;
public static <T> Query<T> paginateQuery(Query<T> query, Integer first, Integer max) {
if (first != null && first > 0) {
query = query.startOffset(first);
}
if (max != null && max >= 0) {
query = query.maxResults(max);
} else {
// Infinispan uses default max value equal to 100
// We need to change this to support more returned values
query = query.maxResults(DEFAULT_MAX_RESULTS);
}
return query;
}
}

View file

@ -1,70 +0,0 @@
/*
* Copyright 2022 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 java.util.regex.Matcher;
import java.util.regex.Pattern;
public class HotRodVersionUtils {
private static final Pattern schemaVersionPattern = Pattern.compile("schema-version: (\\d+)$", Pattern.MULTILINE);
/**
* Decides whether {@code version1} is older than {@code version2}
*
* @param version1 first version
* @param version2 second version
* @return returns true when {@code version1} is older than {@code version2} and false when versions are equal
* or {@code version2} is older than {@code version1}
*/
public static boolean isVersion2NewerThanVersion1(Integer version1, Integer version2) {
return version1 < version2;
}
/**
* Decides whether {@code version1} and {@code version2} are adjacent values (there are no versions between these)
*
* @param version1 first version
* @param version2 second version
* @return returns true when {@code version1} and {@code version2} are adjacent, false otherwise
*/
public static boolean adjacentVersions(Integer version1, Integer version2) {
return Math.abs(version1 - version2) == 1;
}
/**
* Searches given {@code protoFile} string for occurrences of string schema-version: X, where X is version of current
* schema in the {@code protoFile} string
*
* @param protoFile schema to search
* @return Integer object representing version of schema within {@code protoFile} or {@code null} if not found
* @throws IllegalStateException if file contains more than one version definitions
*/
public static Integer parseSchemaVersionFromProtoFile(String protoFile) {
Matcher matcher = schemaVersionPattern.matcher(protoFile);
if (matcher.find()) {
if (matcher.groupCount() > 1) {
throw new IllegalStateException("More than one occurrence of schema-version definitions within one proto file " + protoFile);
}
return Integer.parseInt(matcher.group(1));
}
return null;
}
}

View file

@ -1,33 +0,0 @@
/*
* Copyright 2022 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.UpdatableEntity;
public abstract class UpdatableHotRodEntityDelegateImpl<E extends UpdatableEntity> implements HotRodEntityDelegate<E> {
@Override
public boolean isUpdated() {
return getHotRodEntity().isUpdated();
}
@Override
public void clearUpdatedFlag() {
getHotRodEntity().clearUpdatedFlag();
}
}

View file

@ -1,42 +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.connections;
import org.infinispan.client.hotrod.RemoteCache;
import org.infinispan.client.hotrod.RemoteCacheManager;
/**
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
*/
public class DefaultHotRodConnectionProvider implements HotRodConnectionProvider {
private RemoteCacheManager remoteCacheManager;
public DefaultHotRodConnectionProvider(RemoteCacheManager remoteCacheManager) {
this.remoteCacheManager = remoteCacheManager;
}
@Override
public <K, V> RemoteCache<K, V> getRemoteCache(String name) {
return remoteCacheManager.getCache(name);
}
@Override
public void close() {
}
}

View file

@ -1,287 +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.connections;
import org.infinispan.client.hotrod.RemoteCache;
import org.infinispan.client.hotrod.RemoteCacheManager;
import org.infinispan.client.hotrod.RemoteCacheManagerAdmin;
import org.infinispan.client.hotrod.configuration.ClientIntelligence;
import org.infinispan.client.hotrod.configuration.ConfigurationBuilder;
import org.infinispan.client.hotrod.configuration.NearCacheMode;
import org.infinispan.client.hotrod.configuration.TransactionMode;
import org.infinispan.commons.marshall.ProtoStreamMarshaller;
import org.infinispan.commons.tx.lookup.TransactionManagerLookup;
import org.infinispan.protostream.GeneratedSchema;
import org.infinispan.query.remote.client.ProtobufMetadataManagerConstants;
import org.jboss.logging.Logger;
import org.keycloak.common.Profile;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.map.storage.hotRod.locking.HotRodLocksUtils;
import org.keycloak.models.map.storage.hotRod.common.HotRodEntityDescriptor;
import org.keycloak.models.map.storage.hotRod.common.CommonPrimitivesProtoSchemaInitializer;
import org.keycloak.models.map.storage.hotRod.common.HotRodVersionUtils;
import org.keycloak.models.map.storage.hotRod.transaction.HotRodTransactionManagerLookup;
import org.keycloak.provider.EnvironmentDependentProviderFactory;
import java.net.URI;
import java.net.URISyntaxException;
import java.time.Duration;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import static org.keycloak.models.map.storage.hotRod.common.AutogeneratedHotRodDescriptors.ENTITY_DESCRIPTOR_MAP;
import static org.keycloak.models.map.storage.hotRod.common.HotRodVersionUtils.adjacentVersions;
import static org.keycloak.models.map.storage.hotRod.common.HotRodVersionUtils.isVersion2NewerThanVersion1;
/**
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
*/
public class DefaultHotRodConnectionProviderFactory implements HotRodConnectionProviderFactory, EnvironmentDependentProviderFactory {
public static final String PROVIDER_ID = "default";
public static final String SCRIPT_CACHE = "___script_cache";
public static final String HOT_ROD_LOCKS_CACHE_NAME = "locks";
private static final String HOT_ROD_INIT_LOCK_NAME = "HOT_ROD_INIT_LOCK";
private static final Logger LOG = Logger.getLogger(DefaultHotRodConnectionProviderFactory.class);
private org.keycloak.Config.Scope config;
private volatile RemoteCacheManager remoteCacheManager;
private TransactionManagerLookup transactionManagerLookup;
@Override
public HotRodConnectionProvider create(KeycloakSession session) {
if (remoteCacheManager == null) {
synchronized (this) {
if (remoteCacheManager == null) {
lazyInit(session);
}
}
}
return new DefaultHotRodConnectionProvider(remoteCacheManager);
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
if (remoteCacheManager != null) {
remoteCacheManager.close();
remoteCacheManager = null;
}
}
@Override
public String getId() {
return PROVIDER_ID;
}
@Override
public void init(org.keycloak.Config.Scope config) {
this.config = config;
}
public void lazyInit(KeycloakSession session) {
LOG.debugf("Initializing HotRod client connection to Infinispan server.");
transactionManagerLookup = new HotRodTransactionManagerLookup(session);
ConfigurationBuilder remoteBuilder = new ConfigurationBuilder();
remoteBuilder.addServer()
.host(config.get("host", "localhost"))
.port(config.getInt("port", 11222))
.clientIntelligence(ClientIntelligence.HASH_DISTRIBUTION_AWARE)
.marshaller(new ProtoStreamMarshaller());
if (config.getBoolean("enableSecurity", true)) {
remoteBuilder.security()
.authentication()
.saslMechanism("SCRAM-SHA-512")
.username(config.get("username", "admin"))
.password(config.get("password", "admin"))
.realm(config.get("realm", "default"));
}
LOG.debugf("Configuring remote caches.");
configureRemoteCaches(remoteBuilder);
remoteBuilder.addContextInitializer(CommonPrimitivesProtoSchemaInitializer.INSTANCE);
ENTITY_DESCRIPTOR_MAP.values().stream().map(HotRodEntityDescriptor::getProtoSchema).forEach(remoteBuilder::addContextInitializer);
// Configure settings necessary for locking
configureLocking(remoteBuilder);
remoteCacheManager = new RemoteCacheManager(remoteBuilder.build());
// Acquire initial phase lock to avoid concurrent schema update
RemoteCache<String, String> locksCache = remoteCacheManager.getCache(HOT_ROD_LOCKS_CACHE_NAME);
try {
HotRodLocksUtils.repeatPutIfAbsent(locksCache, HOT_ROD_INIT_LOCK_NAME, Duration.ofMillis(900), 50, false);
Set<String> remoteCaches = ENTITY_DESCRIPTOR_MAP.values().stream()
.map(HotRodEntityDescriptor::getCacheName).collect(Collectors.toSet());
LOG.debugf("Uploading proto schema to Infinispan server.");
registerSchemata();
String reindexCaches = config.get("reindexCaches", null);
RemoteCacheManagerAdmin administration = remoteCacheManager.administration();
if (reindexCaches != null && reindexCaches.equals("all")) {
LOG.infof("Reindexing all caches. This can take a long time to complete. While the rebuild operation is in progress, queries might return fewer results.");
remoteCaches.stream()
.peek(remoteCacheManager::getCache) // access the caches to force their creation, otherwise reindexing fails if cache doesn't exist
.forEach(administration::reindexCache);
} else if (reindexCaches != null && !reindexCaches.isEmpty()) {
Arrays.stream(reindexCaches.split(","))
.map(String::trim)
.filter(e -> !e.isEmpty())
.filter(remoteCaches::contains)
.peek(cacheName -> LOG.infof("Reindexing %s cache. This can take a long time to complete. While the rebuild operation is in progress, queries might return fewer results.", cacheName))
.peek(remoteCacheManager::getCache) // access the caches to force their creation, otherwise reindexing fails if cache doesn't exist
.forEach(administration::reindexCache);
}
LOG.infof("HotRod client configuration was successful.");
} finally {
if (!HotRodLocksUtils.removeWithInstanceIdentifier(locksCache, HOT_ROD_INIT_LOCK_NAME)) {
throw new RuntimeException("Cannot release HotRod init lock");
}
}
}
private void configureLocking(ConfigurationBuilder builder) {
builder.remoteCache(HOT_ROD_LOCKS_CACHE_NAME)
.configurationURI(getCacheConfigUri(HOT_ROD_LOCKS_CACHE_NAME));
}
private void registerSchemata() {
final RemoteCache<String, String> protoMetadataCache = remoteCacheManager.getCache(ProtobufMetadataManagerConstants.PROTOBUF_METADATA_CACHE_NAME);
Set<String> cachesForIndexUpdate = new HashSet<>();
// First add Common classes definitions
GeneratedSchema commonSchema = CommonPrimitivesProtoSchemaInitializer.INSTANCE;
String currentProtoFile = protoMetadataCache.get(commonSchema.getProtoFileName());
// there is no proto file deployed on the server
if (currentProtoFile == null) {
protoMetadataCache.put(commonSchema.getProtoFileName(), commonSchema.getProtoFile());
}
else if (isUpdateNeeded(commonSchema.getProtoFileName(), CommonPrimitivesProtoSchemaInitializer.COMMON_PRIMITIVES_VERSION, currentProtoFile)) {
protoMetadataCache.put(commonSchema.getProtoFileName(), commonSchema.getProtoFile());
// if there is a change in common primitives, update all caches as we don't track in what areas are these common primitives used
cachesForIndexUpdate = ENTITY_DESCRIPTOR_MAP.values().stream().map(HotRodEntityDescriptor::getCacheName).collect(Collectors.toSet());
}
// Add schema for each entity descriptor
for (HotRodEntityDescriptor<?,?> descriptor : ENTITY_DESCRIPTOR_MAP.values()) {
GeneratedSchema schema = descriptor.getProtoSchema();
currentProtoFile = protoMetadataCache.get(schema.getProtoFileName());
// there is no proto file deployed on the server
if (currentProtoFile == null) {
protoMetadataCache.put(schema.getProtoFileName(), schema.getProtoFile());
}
else if (isUpdateNeeded(schema.getProtoFileName(), descriptor.getCurrentVersion(), currentProtoFile)) {
protoMetadataCache.put(schema.getProtoFileName(), schema.getProtoFile());
cachesForIndexUpdate.add(descriptor.getCacheName());
}
}
String errors = protoMetadataCache.get(ProtobufMetadataManagerConstants.ERRORS_KEY_SUFFIX);
if (errors != null) {
for (String errorFile : errors.split("\n")) {
LOG.errorf("\nThere was an error in proto file: %s\n" +
"Error message: %s\n" +
"Current proto schema: %s",
errorFile,
protoMetadataCache.get(errorFile + ProtobufMetadataManagerConstants.ERRORS_KEY_SUFFIX),
protoMetadataCache.get(errorFile));
}
throw new IllegalStateException("Some Protobuf schema files contain errors: " + errors);
}
// update index schema for caches, where a proto schema was updated
RemoteCacheManagerAdmin administration = remoteCacheManager.administration();
cachesForIndexUpdate.forEach(administration::updateIndexSchema);
}
/**
* Decides whether the schema should be updated
* @return true if schema in the server is obsolete or doesn't exist, false otherwise
* @throws IllegalStateException when schema in the server is older than one version behind
*/
private boolean isUpdateNeeded(String protoFileName, int currentSchemaVersion, String currentProtoFileDeployed) {
// If there is no proto file deployed in the server return true
if (currentProtoFileDeployed == null) return true;
// Parse currently deployed schema version
Integer deployedSchemaVersion = HotRodVersionUtils.parseSchemaVersionFromProtoFile(currentProtoFileDeployed);
if (deployedSchemaVersion == null) {
LOG.errorf("Schema %s does not contain expected schema-version definition:\n%s", protoFileName, currentProtoFileDeployed);
throw new IllegalStateException("Deployed schema " + protoFileName + " does not contain expected schema-version definition. See log for more details.");
}
if (currentSchemaVersion != deployedSchemaVersion && !adjacentVersions(deployedSchemaVersion, currentSchemaVersion)) {
// Infinispan server contains schema that is older than {current_version - 1}, upgrade needs to be done sequentially
throw new IllegalStateException("Infinispan server contains too old schema version for " + protoFileName);
}
return isVersion2NewerThanVersion1(deployedSchemaVersion, currentSchemaVersion);
}
private void configureRemoteCaches(ConfigurationBuilder builder) {
Consumer<String> configurator = configurationBuilderConsumer(builder);
ENTITY_DESCRIPTOR_MAP.values().stream()
.map(HotRodEntityDescriptor::getCacheName)
.distinct()
.forEach(configurator);
}
private static URI getCacheConfigUri(String cacheName) {
try {
return DefaultHotRodConnectionProviderFactory.class.getClassLoader().getResource("config/" + cacheName + "-cache-config.xml").toURI();
} catch (URISyntaxException e) {
throw new RuntimeException("Cannot read the cache configuration for cache + " + cacheName, e);
}
}
private Consumer<String> configurationBuilderConsumer(ConfigurationBuilder builder) {
return cacheName -> {
LOG.debugf("Configuring cache %s", cacheName);
builder.remoteCache(cacheName)
.configurationURI(getCacheConfigUri(cacheName))
.transactionMode(TransactionMode.FULL_XA)
.transactionManagerLookup(transactionManagerLookup)
.nearCacheMode(config.scope(cacheName).getBoolean("nearCacheEnabled", config.getBoolean("nearCacheEnabled", true)) ? NearCacheMode.INVALIDATED : NearCacheMode.DISABLED)
.nearCacheMaxEntries(config.scope(cacheName).getInt("nearCacheMaxEntries", config.getInt("nearCacheMaxEntries", 10000)))
.nearCacheUseBloomFilter(config.scope(cacheName).getBoolean("nearCacheBloomFilter", config.getBoolean("nearCacheBloomFilter", false)));
};
}
@Override
public boolean isSupported() {
return Profile.isFeatureEnabled(Profile.Feature.MAP_STORAGE);
}
}

View file

@ -1,36 +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.connections;
import org.infinispan.client.hotrod.RemoteCache;
import org.keycloak.provider.Provider;
/**
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
*/
public interface HotRodConnectionProvider extends Provider {
/**
* Returns a remote Infinispan cache specified by the given name.
* @param name String Name of the remote cache.
* @param <K> key
* @param <V> value
* @return A remote Infinispan cache specified by name.
*/
<K, V> RemoteCache<K, V> getRemoteCache(String name);
}

View file

@ -1,26 +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.connections;
import org.keycloak.provider.ProviderFactory;
/**
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
*/
public interface HotRodConnectionProviderFactory extends ProviderFactory<HotRodConnectionProvider> {
}

View file

@ -1,50 +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.connections;
import org.keycloak.provider.Provider;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.provider.Spi;
/**
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
*/
public class HotRodConnectionSpi implements Spi {
public static final String NAME = "connectionsHotRod";
@Override
public boolean isInternal() {
return true;
}
@Override
public String getName() {
return NAME;
}
@Override
public Class<? extends Provider> getProviderClass() {
return HotRodConnectionProvider.class;
}
@Override
public Class<? extends ProviderFactory> getProviderFactoryClass() {
return HotRodConnectionProviderFactory.class;
}
}

View file

@ -1,131 +0,0 @@
/*
* Copyright 2022 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.events;
import org.infinispan.api.annotations.indexing.Basic;
import org.infinispan.api.annotations.indexing.Indexed;
import org.infinispan.protostream.GeneratedSchema;
import org.infinispan.protostream.annotations.AutoProtoSchemaBuilder;
import org.infinispan.protostream.annotations.ProtoDoc;
import org.infinispan.protostream.annotations.ProtoField;
import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation;
import org.keycloak.models.map.annotations.IgnoreForEntityImplementationGenerator;
import org.keycloak.models.map.events.MapAdminEventEntity;
import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity;
import org.keycloak.models.map.storage.hotRod.common.CommonPrimitivesProtoSchemaInitializer;
import org.keycloak.models.map.storage.hotRod.common.UpdatableHotRodEntityDelegateImpl;
@GenerateHotRodEntityImplementation(
implementInterface = "org.keycloak.models.map.events.MapAdminEventEntity",
inherits = "org.keycloak.models.map.storage.hotRod.events.HotRodAdminEventEntity.AbstractHotRodAdminEventEntityDelegate",
topLevelEntity = true,
modelClass = "org.keycloak.events.admin.AdminEvent"
)
@Indexed
@ProtoDoc("schema-version: " + HotRodAdminEventEntity.VERSION)
public class HotRodAdminEventEntity extends AbstractHotRodEntity {
@IgnoreForEntityImplementationGenerator
public static final int VERSION = 1;
@AutoProtoSchemaBuilder(
includeClasses = {
HotRodAdminEventEntity.class
},
schemaFilePath = "proto/",
schemaPackageName = CommonPrimitivesProtoSchemaInitializer.HOT_ROD_ENTITY_PACKAGE)
public interface HotRodAdminEventEntitySchema extends GeneratedSchema {
HotRodAdminEventEntitySchema INSTANCE = new HotRodAdminEventEntitySchemaImpl();
}
@Basic(projectable = true)
@ProtoField(number = 1)
public Integer entityVersion = VERSION;
@ProtoField(number = 2)
public String id;
@ProtoField(number = 3)
public Long expiration;
@Basic(sortable = true)
@ProtoField(number = 4)
public Long timestamp;
@Basic(sortable = true)
@ProtoField(number = 5)
public Integer operationType;
@Basic(sortable = true)
@ProtoField(number = 6)
public String authClientId;
@Basic(sortable = true)
@ProtoField(number = 7)
public String authIpAddress;
@Basic(sortable = true)
@ProtoField(number = 8)
public String authRealmId;
@Basic(sortable = true)
@ProtoField(number = 9)
public String authUserId;
@ProtoField(number = 10)
public String error;
@Basic(sortable = true)
@ProtoField(number = 11)
public String realmId;
@ProtoField(number = 12)
public String representation;
@Basic(sortable = true)
@ProtoField(number = 13)
public String resourcePath;
@Basic(sortable = true)
@ProtoField(number = 14)
public String resourceType;
public static abstract class AbstractHotRodAdminEventEntityDelegate extends UpdatableHotRodEntityDelegateImpl<HotRodAdminEventEntity> implements MapAdminEventEntity {
@Override
public String getId() {
return getHotRodEntity().id;
}
@Override
public void setId(String id) {
HotRodAdminEventEntity entity = getHotRodEntity();
if (entity.id != null) throw new IllegalStateException("Id cannot be changed");
entity.id = id;
entity.updated |= id != null;
}
}
@Override
public boolean equals(Object o) {
return HotRodAdminEventEntityDelegate.entityEquals(this, o);
}
@Override
public int hashCode() {
return HotRodAdminEventEntityDelegate.entityHashCode(this);
}
}

View file

@ -1,127 +0,0 @@
/*
* Copyright 2022 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.events;
import org.infinispan.api.annotations.indexing.Basic;
import org.infinispan.api.annotations.indexing.Indexed;
import org.infinispan.protostream.GeneratedSchema;
import org.infinispan.protostream.annotations.AutoProtoSchemaBuilder;
import org.infinispan.protostream.annotations.ProtoDoc;
import org.infinispan.protostream.annotations.ProtoField;
import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation;
import org.keycloak.models.map.annotations.IgnoreForEntityImplementationGenerator;
import org.keycloak.models.map.events.MapAuthEventEntity;
import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity;
import org.keycloak.models.map.storage.hotRod.common.CommonPrimitivesProtoSchemaInitializer;
import org.keycloak.models.map.storage.hotRod.common.HotRodPair;
import org.keycloak.models.map.storage.hotRod.common.UpdatableHotRodEntityDelegateImpl;
import java.util.Set;
@GenerateHotRodEntityImplementation(
implementInterface = "org.keycloak.models.map.events.MapAuthEventEntity",
inherits = "org.keycloak.models.map.storage.hotRod.events.HotRodAuthEventEntity.AbstractHotRodAuthEventEntityDelegate",
topLevelEntity = true,
modelClass = "org.keycloak.events.Event"
)
@Indexed
@ProtoDoc("schema-version: " + HotRodAuthEventEntity.VERSION)
public class HotRodAuthEventEntity extends AbstractHotRodEntity {
@IgnoreForEntityImplementationGenerator
public static final int VERSION = 1;
@AutoProtoSchemaBuilder(
includeClasses = {
HotRodAuthEventEntity.class
},
schemaFilePath = "proto/",
schemaPackageName = CommonPrimitivesProtoSchemaInitializer.HOT_ROD_ENTITY_PACKAGE,
dependsOn = {CommonPrimitivesProtoSchemaInitializer.class}
)
public interface HotRodAuthEventEntitySchema extends GeneratedSchema {
HotRodAuthEventEntitySchema INSTANCE = new HotRodAuthEventEntitySchemaImpl();
}
@Basic(projectable = true)
@ProtoField(number = 1)
public Integer entityVersion = VERSION;
@ProtoField(number = 2)
public String id;
@Basic(sortable = true)
@ProtoField(number = 3)
public Integer type;
@ProtoField(number = 4)
public Long expiration;
@Basic(sortable = true)
@ProtoField(number = 5)
public Long timestamp;
@Basic(sortable = true)
@ProtoField(number = 6)
public String clientId;
@ProtoField(number = 7)
public String error;
@Basic(sortable = true)
@ProtoField(number = 8)
public String ipAddress;
@Basic(sortable = true)
@ProtoField(number = 9)
public String realmId;
@ProtoField(number = 10)
public String sessionId;
@Basic(sortable = true)
@ProtoField(number = 11)
public String userId;
@ProtoField(number = 12)
public Set<HotRodPair<String, String>> details;
public static abstract class AbstractHotRodAuthEventEntityDelegate extends UpdatableHotRodEntityDelegateImpl<HotRodAuthEventEntity> implements MapAuthEventEntity {
@Override
public String getId() {
return getHotRodEntity().id;
}
@Override
public void setId(String id) {
HotRodAuthEventEntity entity = getHotRodEntity();
if (entity.id != null) throw new IllegalStateException("Id cannot be changed");
entity.id = id;
entity.updated |= id != null;
}
}
@Override
public boolean equals(Object o) {
return HotRodAuthEventEntityDelegate.entityEquals(this, o);
}
@Override
public int hashCode() {
return HotRodAuthEventEntityDelegate.entityHashCode(this);
}
}

View file

@ -1,123 +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.group;
import org.infinispan.api.annotations.indexing.Basic;
import org.infinispan.api.annotations.indexing.Indexed;
import org.infinispan.api.annotations.indexing.Keyword;
import org.infinispan.protostream.GeneratedSchema;
import org.infinispan.protostream.annotations.AutoProtoSchemaBuilder;
import org.infinispan.protostream.annotations.ProtoDoc;
import org.infinispan.protostream.annotations.ProtoField;
import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation;
import org.keycloak.models.map.annotations.IgnoreForEntityImplementationGenerator;
import org.keycloak.models.map.group.MapGroupEntity;
import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity;
import org.keycloak.models.map.storage.hotRod.common.CommonPrimitivesProtoSchemaInitializer;
import org.keycloak.models.map.storage.hotRod.common.HotRodAttributeEntity;
import org.keycloak.models.map.storage.hotRod.common.UpdatableHotRodEntityDelegateImpl;
import java.util.Objects;
import java.util.Set;
@GenerateHotRodEntityImplementation(
implementInterface = "org.keycloak.models.map.group.MapGroupEntity",
inherits = "org.keycloak.models.map.storage.hotRod.group.HotRodGroupEntity.AbstractHotRodGroupEntityDelegate",
topLevelEntity = true,
modelClass = "org.keycloak.models.GroupModel"
)
@Indexed
@ProtoDoc("schema-version: " + HotRodGroupEntity.VERSION)
public class HotRodGroupEntity extends AbstractHotRodEntity {
@IgnoreForEntityImplementationGenerator
public static final int VERSION = 1;
@AutoProtoSchemaBuilder(
includeClasses = {
HotRodGroupEntity.class
},
schemaFilePath = "proto/",
schemaPackageName = CommonPrimitivesProtoSchemaInitializer.HOT_ROD_ENTITY_PACKAGE,
dependsOn = {CommonPrimitivesProtoSchemaInitializer.class}
)
public interface HotRodGroupEntitySchema extends GeneratedSchema {
HotRodGroupEntitySchema INSTANCE = new HotRodGroupEntitySchemaImpl();
}
public static abstract class AbstractHotRodGroupEntityDelegate extends UpdatableHotRodEntityDelegateImpl<HotRodGroupEntity> implements MapGroupEntity {
@Override
public String getId() {
return getHotRodEntity().id;
}
@Override
public void setId(String id) {
HotRodGroupEntity entity = getHotRodEntity();
if (entity.id != null) throw new IllegalStateException("Id cannot be changed");
entity.id = id;
entity.updated |= id != null;
}
@Override
public void setName(String name) {
HotRodGroupEntity entity = getHotRodEntity();
entity.updated |= ! Objects.equals(entity.name, name);
entity.name = name;
}
}
@Basic(projectable = true)
@ProtoField(number = 1)
public Integer entityVersion = VERSION;
@Basic(projectable = true, sortable = true)
@ProtoField(number = 2)
public String id;
@Basic(sortable = true)
@ProtoField(number = 3)
public String realmId;
@Keyword(sortable = true, normalizer = "lowercase")
@ProtoField(number = 4)
public String name;
@Basic(sortable = true)
@ProtoField(number = 5)
public String parentId;
@Basic(sortable = true)
@ProtoField(number = 6)
public Set<HotRodAttributeEntity> attributes;
@Basic(sortable = true)
@ProtoField(number = 7)
public Set<String> grantedRoles;
@Override
public boolean equals(Object o) {
return HotRodGroupEntityDelegate.entityEquals(this, o);
}
@Override
public int hashCode() {
return HotRodGroupEntityDelegate.entityHashCode(this);
}
}

View file

@ -1,77 +0,0 @@
/*
* Copyright 2022 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.locking;
import org.infinispan.client.hotrod.RemoteCache;
import org.jboss.logging.Logger;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionTaskWithResult;
import org.keycloak.models.locking.GlobalLockProvider;
import org.keycloak.models.locking.LockAcquiringTimeoutException;
import org.keycloak.models.utils.KeycloakModelUtils;
import java.time.Duration;
import java.util.Objects;
import static org.keycloak.common.util.StackUtil.getShortStackTrace;
public class HotRodGlobalLockProvider implements GlobalLockProvider {
private static final Logger LOG = Logger.getLogger(HotRodGlobalLockProvider.class);
private final KeycloakSession session;
private final RemoteCache<String, String> locksCache;
private final long defaultTimeoutMilliseconds;
public HotRodGlobalLockProvider(KeycloakSession session, RemoteCache<String, String> locksCache, long defaultTimeoutMilliseconds) {
this.locksCache = locksCache;
this.defaultTimeoutMilliseconds = defaultTimeoutMilliseconds;
this.session = session;
}
@Override
public <V> V withLock(String lockName, Duration timeToWaitForLock, KeycloakSessionTaskWithResult<V> task) throws LockAcquiringTimeoutException {
Objects.requireNonNull(lockName, "lockName cannot be null");
if (timeToWaitForLock == null) {
// Set default timeout if null provided
timeToWaitForLock = Duration.ofMillis(defaultTimeoutMilliseconds);
}
try {
LOG.debugf("Acquiring lock [%s].%s", lockName, getShortStackTrace());
HotRodLocksUtils.repeatPutIfAbsent(locksCache, lockName, timeToWaitForLock, 50, false);
LOG.debugf("Lock acquired [%s]. Continuing with task execution.", lockName);
return KeycloakModelUtils.runJobInTransactionWithResult(session.getKeycloakSessionFactory(), task);
} finally {
LOG.debugf("Releasing lock [%s].%s", lockName, getShortStackTrace());
boolean result = HotRodLocksUtils.removeWithInstanceIdentifier(locksCache, lockName);
LOG.debugf("Lock [%s] release resulted with %s", lockName, result);
}
}
@Override
public void forceReleaseAllLocks() {
locksCache.clear();
}
@Override
public void close() {
}
}

View file

@ -1,76 +0,0 @@
/*
* Copyright 2022 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.locking;
import org.infinispan.client.hotrod.RemoteCache;
import org.keycloak.Config;
import org.keycloak.common.Profile;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.locking.GlobalLockProvider;
import org.keycloak.models.locking.GlobalLockProviderFactory;
import org.keycloak.models.map.storage.hotRod.connections.HotRodConnectionProvider;
import org.keycloak.provider.EnvironmentDependentProviderFactory;
public class HotRodGlobalLockProviderFactory implements GlobalLockProviderFactory, EnvironmentDependentProviderFactory {
public static final String PROVIDER_ID = "hotrod";
protected static final String HOT_ROD_LOCKS_CACHE = "locks";
private RemoteCache<String, String> locksCache;
private long defaultTimeoutMilliseconds;
@Override
public GlobalLockProvider create(KeycloakSession session) {
if (locksCache == null) {
lazyInit(session);
}
return new HotRodGlobalLockProvider(session, locksCache, defaultTimeoutMilliseconds);
}
private void lazyInit(KeycloakSession session) {
HotRodConnectionProvider hotRodConnectionProvider = session.getProvider(HotRodConnectionProvider.class);
locksCache = hotRodConnectionProvider.getRemoteCache(HOT_ROD_LOCKS_CACHE);
}
@Override
public void init(Config.Scope config) {
defaultTimeoutMilliseconds = config.getLong("defaultTimeoutMilliseconds", 5000L);
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@Override
public String getId() {
return PROVIDER_ID;
}
@Override
public boolean isSupported() {
return Profile.isFeatureEnabled(Profile.Feature.MAP_STORAGE);
}
}

View file

@ -1,117 +0,0 @@
/*
* Copyright 2022 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.locking;
import org.infinispan.client.hotrod.Flag;
import org.infinispan.client.hotrod.RemoteCache;
import org.keycloak.common.util.Retry;
import org.keycloak.common.util.Time;
import org.keycloak.models.locking.LockAcquiringTimeoutException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicReference;
public class HotRodLocksUtils {
public static final String SEPARATOR = ";";
/**
* Repeatedly attempts to put an entry with the key {@code lockName}
* to the {@code locksCache}. Succeeds only if there is no entry with
* the same key already.
* <p/>
* The value of created entry is equal to instance identifier. It is
* possible to make the method succeed even if the value already exists
* with the same instance identifier. This behaviour is enabled using
* {@code isReentrant} switch.
* <p/>
* Execution of this method is time bounded, if this method does not
* succeed within {@code timeoutMilliseconds} it gives up and returns
* false.
* <p/>
* There is a pause after each unsuccessful attempt equal to
* {@code repeatInterval} milliseconds
*
* @param locksCache Cache that will be used for putting the value
* @param lockName Name of the entry
* @param timeout duration to wait until the lock is acquired
* @param repeatInterval Number of milliseconds to wait after each
* unsuccessful attempt
* @param isReentrant if this is set to true, the method succeeds also when the value for given key is
* equal to the instance identifier
* @throws LockAcquiringTimeoutException the key {@code lockName} was NOT put into the {@code map}
* within time boundaries
* @throws IllegalStateException when a {@code lock} value found in the storage has wrong format. It is expected
* the lock value has the following format {@code 'timeAcquired;keycloakInstanceIdentifier'}
*/
public static void repeatPutIfAbsent(RemoteCache<String, String> locksCache, String lockName, Duration timeout, int repeatInterval, boolean isReentrant) throws LockAcquiringTimeoutException {
final AtomicReference<String> currentOwnerRef = new AtomicReference<>(null);
try {
Retry.executeWithBackoff(i -> {
String curr = locksCache.withFlags(Flag.FORCE_RETURN_VALUE).putIfAbsent(lockName, Time.currentTimeMillis() + SEPARATOR + getKeycloakInstanceIdentifier());
currentOwnerRef.set(curr);
if (curr != null) {
if (!isReentrant || !curr.endsWith(SEPARATOR + getKeycloakInstanceIdentifier())) {
throw new AssertionError("Acquiring lock in iteration " + i + " was not successful");
}
}
}, timeout, repeatInterval);
} catch (AssertionError ex) {
String currentOwner = currentOwnerRef.get();
String[] split = currentOwner == null ? null : currentOwner.split(SEPARATOR, 2);
if (currentOwner == null || split.length != 2) throw new IllegalStateException("Bad lock value format found in storage for lock " + lockName + ". " +
"It is expected the format to be 'timeAcquired;keycloakInstanceIdentifier' but was " + currentOwner);
throw new LockAcquiringTimeoutException(lockName, split[1], Instant.ofEpochMilli(Long.parseLong(split[0])));
}
}
private static String getKeycloakInstanceIdentifier() {
long pid = ProcessHandle.current().pid();
String hostname;
try {
hostname = InetAddress.getLocalHost().getHostName();
} catch (UnknownHostException e) {
hostname = "unknown-host";
}
String threadName = Thread.currentThread().getName();
return threadName + "#" + pid + "@" + hostname;
}
/**
* Removes the entry with key {@code lockName} from map if the value
* of the entry is equal to this node's identifier
*
* @param map Map that will be used for removing
* @param lockName Name of the entry
* @return true if the entry was removed, false otherwise
*/
public static boolean removeWithInstanceIdentifier(ConcurrentMap<String, String> map, String lockName) {
String value = map.get(lockName);
if (value != null && value.endsWith(getKeycloakInstanceIdentifier())) {
map.remove(lockName);
return true;
} else {
return false;
}
}
}

View file

@ -1,118 +0,0 @@
/*
* Copyright 2022 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.loginFailure;
import org.infinispan.api.annotations.indexing.Basic;
import org.infinispan.api.annotations.indexing.Indexed;
import org.infinispan.protostream.GeneratedSchema;
import org.infinispan.protostream.annotations.AutoProtoSchemaBuilder;
import org.infinispan.protostream.annotations.ProtoDoc;
import org.infinispan.protostream.annotations.ProtoField;
import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation;
import org.keycloak.models.map.annotations.IgnoreForEntityImplementationGenerator;
import org.keycloak.models.map.loginFailure.MapUserLoginFailureEntity;
import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity;
import org.keycloak.models.map.storage.hotRod.common.CommonPrimitivesProtoSchemaInitializer;
import org.keycloak.models.map.storage.hotRod.common.UpdatableHotRodEntityDelegateImpl;
@GenerateHotRodEntityImplementation(
implementInterface = "org.keycloak.models.map.loginFailure.MapUserLoginFailureEntity",
inherits = "org.keycloak.models.map.storage.hotRod.loginFailure.HotRodUserLoginFailureEntity.AbstractHotRodUserLoginFailureEntityDelegate",
topLevelEntity = true,
modelClass = "org.keycloak.models.UserLoginFailureModel"
)
@Indexed
@ProtoDoc("schema-version: " + HotRodUserLoginFailureEntity.VERSION)
public class HotRodUserLoginFailureEntity extends AbstractHotRodEntity {
@IgnoreForEntityImplementationGenerator
public static final int VERSION = 1;
@AutoProtoSchemaBuilder(
includeClasses = {
HotRodUserLoginFailureEntity.class
},
schemaFilePath = "proto/",
schemaPackageName = CommonPrimitivesProtoSchemaInitializer.HOT_ROD_ENTITY_PACKAGE)
public interface HotRodUserLoginFailureEntitySchema extends GeneratedSchema {
HotRodUserLoginFailureEntitySchema INSTANCE = new HotRodUserLoginFailureEntitySchemaImpl();
}
@Basic(projectable = true)
@ProtoField(number = 1)
public Integer entityVersion = VERSION;
@ProtoField(number = 2)
public String id;
@Basic(sortable = true)
@ProtoField(number = 3)
public String realmId;
@Basic(sortable = true)
@ProtoField(number = 4)
public String userId;
@ProtoField(number = 5)
public Long failedLoginNotBefore;
@ProtoField(number = 6)
public Integer numFailures;
@ProtoField(number = 7)
public Long lastFailure;
@ProtoField(number = 8)
public String lastIPFailure;
public static abstract class AbstractHotRodUserLoginFailureEntityDelegate extends UpdatableHotRodEntityDelegateImpl<HotRodUserLoginFailureEntity> implements MapUserLoginFailureEntity {
@Override
public String getId() {
return getHotRodEntity().id;
}
@Override
public void setId(String id) {
HotRodUserLoginFailureEntity entity = getHotRodEntity();
if (entity.id != null) throw new IllegalStateException("Id cannot be changed");
entity.id = id;
entity.updated |= id != null;
}
@Override
public void clearFailures() {
HotRodUserLoginFailureEntity entity = getHotRodEntity();
entity.updated |= getFailedLoginNotBefore() != null || getNumFailures() != null || getLastFailure() != null || getLastIPFailure() != null;
setFailedLoginNotBefore(null);
setNumFailures(null);
setLastFailure(null);
setLastIPFailure(null);
}
}
@Override
public boolean equals(Object o) {
return HotRodUserLoginFailureEntityDelegate.entityEquals(this, o);
}
@Override
public int hashCode() {
return HotRodUserLoginFailureEntityDelegate.entityHashCode(this);
}
}

View file

@ -1,356 +0,0 @@
/*
* Copyright 2022 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.realm;
import org.infinispan.api.annotations.indexing.Basic;
import org.infinispan.api.annotations.indexing.Indexed;
import org.infinispan.protostream.GeneratedSchema;
import org.infinispan.protostream.annotations.AutoProtoSchemaBuilder;
import org.infinispan.protostream.annotations.ProtoDoc;
import org.infinispan.protostream.annotations.ProtoField;
import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation;
import org.keycloak.models.map.annotations.IgnoreForEntityImplementationGenerator;
import org.keycloak.models.map.common.UpdatableEntity;
import org.keycloak.models.map.realm.MapRealmEntity;
import org.keycloak.models.map.realm.entity.MapAuthenticationExecutionEntity;
import org.keycloak.models.map.realm.entity.MapAuthenticationFlowEntity;
import org.keycloak.models.map.realm.entity.MapAuthenticatorConfigEntity;
import org.keycloak.models.map.realm.entity.MapClientInitialAccessEntity;
import org.keycloak.models.map.realm.entity.MapComponentEntity;
import org.keycloak.models.map.realm.entity.MapIdentityProviderEntity;
import org.keycloak.models.map.realm.entity.MapIdentityProviderMapperEntity;
import org.keycloak.models.map.realm.entity.MapOTPPolicyEntity;
import org.keycloak.models.map.realm.entity.MapRequiredActionProviderEntity;
import org.keycloak.models.map.realm.entity.MapRequiredCredentialEntity;
import org.keycloak.models.map.realm.entity.MapWebAuthnPolicyEntity;
import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity;
import org.keycloak.models.map.storage.hotRod.common.CommonPrimitivesProtoSchemaInitializer;
import org.keycloak.models.map.storage.hotRod.common.HotRodAttributeEntityNonIndexed;
import org.keycloak.models.map.storage.hotRod.common.HotRodPair;
import org.keycloak.models.map.storage.hotRod.common.UpdatableHotRodEntityDelegateImpl;
import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodAuthenticationExecutionEntity;
import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodAuthenticationExecutionEntityDelegate;
import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodAuthenticationFlowEntity;
import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodAuthenticationFlowEntityDelegate;
import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodAuthenticatorConfigEntity;
import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodAuthenticatorConfigEntityDelegate;
import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodClientInitialAccessEntity;
import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodClientInitialAccessEntityDelegate;
import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodComponentEntity;
import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodComponentEntityDelegate;
import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodIdentityProviderEntity;
import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodIdentityProviderMapperEntity;
import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodIdentityProviderMapperEntityDelegate;
import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodLocalizationTexts;
import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodOTPPolicyEntity;
import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodRequiredActionProviderEntity;
import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodRequiredActionProviderEntityDelegate;
import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodRequiredCredentialEntity;
import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodWebAuthnPolicyEntity;
import java.util.Collections;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import static org.keycloak.models.map.common.ExpirationUtils.isExpired;
@GenerateHotRodEntityImplementation(
implementInterface = "org.keycloak.models.map.realm.MapRealmEntity",
inherits = "org.keycloak.models.map.storage.hotRod.realm.HotRodRealmEntity.AbstractHotRodRealmEntityDelegate",
topLevelEntity = true,
modelClass = "org.keycloak.models.RealmModel"
)
@Indexed
@ProtoDoc("schema-version: " + HotRodRealmEntity.VERSION)
public class HotRodRealmEntity extends AbstractHotRodEntity {
@IgnoreForEntityImplementationGenerator
public static final int VERSION = 1;
@AutoProtoSchemaBuilder(
includeClasses = {
HotRodAuthenticationExecutionEntity.class,
HotRodAuthenticationFlowEntity.class,
HotRodAuthenticatorConfigEntity.class,
HotRodClientInitialAccessEntity.class,
HotRodComponentEntity.class,
HotRodIdentityProviderEntity.class,
HotRodIdentityProviderMapperEntity.class,
HotRodLocalizationTexts.class,
HotRodOTPPolicyEntity.class,
HotRodRequiredActionProviderEntity.class,
HotRodRequiredCredentialEntity.class,
HotRodWebAuthnPolicyEntity.class,
HotRodRealmEntity.class
},
schemaFilePath = "proto/",
schemaPackageName = CommonPrimitivesProtoSchemaInitializer.HOT_ROD_ENTITY_PACKAGE,
dependsOn = {CommonPrimitivesProtoSchemaInitializer.class}
)
public interface HotRodRealmEntitySchema extends GeneratedSchema {
HotRodRealmEntitySchema INSTANCE = new HotRodRealmEntitySchemaImpl();
}
@Basic(projectable = true)
@ProtoField(number = 1)
public Integer entityVersion = VERSION;
@ProtoField(number = 2)
public String id;
@Basic(sortable = true)
@ProtoField(number = 3)
public String name;
@ProtoField(number = 4)
public Boolean adminEventsDetailsEnabled;
@ProtoField(number = 5)
public Boolean adminEventsEnabled;
@ProtoField(number = 6)
public Boolean allowUserManagedAccess;
@ProtoField(number = 7)
public Boolean duplicateEmailsAllowed;
@ProtoField(number = 8)
public Boolean editUsernameAllowed;
@ProtoField(number = 9)
public Boolean enabled;
@ProtoField(number = 10)
public Boolean eventsEnabled;
@ProtoField(number = 11)
public Boolean internationalizationEnabled;
@ProtoField(number = 12)
public Boolean loginWithEmailAllowed;
@ProtoField(number = 13)
public Boolean offlineSessionMaxLifespanEnabled;
@ProtoField(number = 14)
public Boolean registrationAllowed;
@ProtoField(number = 15)
public Boolean registrationEmailAsUsername;
@ProtoField(number = 16)
public Boolean rememberMe;
@ProtoField(number = 17)
public Boolean resetPasswordAllowed;
@ProtoField(number = 18)
public Boolean revokeRefreshToken;
@ProtoField(number = 19)
public Boolean verifyEmail;
@ProtoField(number = 20)
public Integer accessCodeLifespan;
@ProtoField(number = 21)
public Integer accessCodeLifespanLogin;
@ProtoField(number = 22)
public Integer accessCodeLifespanUserAction;
@ProtoField(number = 23)
public Integer accessTokenLifespan;
@ProtoField(number = 24)
public Integer accessTokenLifespanForImplicitFlow;
@ProtoField(number = 25)
public Integer actionTokenGeneratedByAdminLifespan;
@ProtoField(number = 26)
public Integer clientOfflineSessionIdleTimeout;
@ProtoField(number = 27)
public Integer clientOfflineSessionMaxLifespan;
@ProtoField(number = 28)
public Integer clientSessionIdleTimeout;
@ProtoField(number = 29)
public Integer clientSessionMaxLifespan;
@ProtoField(number = 30)
public Long notBefore;
@ProtoField(number = 31)
public Integer offlineSessionIdleTimeout;
@ProtoField(number = 32)
public Integer offlineSessionMaxLifespan;
@ProtoField(number = 33)
public Integer refreshTokenMaxReuse;
@ProtoField(number = 34)
public Integer ssoSessionIdleTimeout;
@ProtoField(number = 35)
public Integer ssoSessionIdleTimeoutRememberMe;
@ProtoField(number = 36)
public Integer ssoSessionMaxLifespan;
@ProtoField(number = 37)
public Integer ssoSessionMaxLifespanRememberMe;
@ProtoField(number = 38)
public Long eventsExpiration;
@ProtoField(number = 39)
public HotRodOTPPolicyEntity oTPPolicy;
@ProtoField(number = 40)
public HotRodWebAuthnPolicyEntity webAuthnPolicy;
@ProtoField(number = 41)
public HotRodWebAuthnPolicyEntity webAuthnPolicyPasswordless;
@ProtoField(number = 42)
public String accountTheme;
@ProtoField(number = 43)
public String adminTheme;
@ProtoField(number = 44)
public String browserFlow;
@ProtoField(number = 45)
public String clientAuthenticationFlow;
@ProtoField(number = 46)
public String defaultLocale;
@ProtoField(number = 47)
public String defaultRoleId;
@ProtoField(number = 48)
public String directGrantFlow;
@ProtoField(number = 49)
public String displayName;
@ProtoField(number = 50)
public String displayNameHtml;
@ProtoField(number = 51)
public String dockerAuthenticationFlow;
@ProtoField(number = 52)
public String emailTheme;
@ProtoField(number = 53)
public String loginTheme;
@ProtoField(number = 54)
public String masterAdminClient;
@ProtoField(number = 55)
public String passwordPolicy;
@ProtoField(number = 56)
public String registrationFlow;
@ProtoField(number = 57)
public String resetCredentialsFlow;
@ProtoField(number = 58)
public String sslRequired;
@ProtoField(number = 59)
public Set<HotRodAttributeEntityNonIndexed> attributes;
@ProtoField(number = 60)
public Set<HotRodLocalizationTexts> localizationTexts;
@ProtoField(number = 61)
public Set<HotRodPair<String, String>> browserSecurityHeaders;
@ProtoField(number = 62)
public Set<HotRodPair<String, String>> smtpConfig;
@ProtoField(number = 63)
public Set<HotRodAuthenticationExecutionEntity> authenticationExecutions;
@ProtoField(number = 64)
public Set<HotRodAuthenticationFlowEntity> authenticationFlows;
@ProtoField(number = 65)
public Set<HotRodAuthenticatorConfigEntity> authenticatorConfigs;
@Basic(sortable = true)
@ProtoField(number = 66)
public Set<HotRodClientInitialAccessEntity> clientInitialAccesses;
@Basic(sortable = true)
@ProtoField(number = 67)
public Set<HotRodComponentEntity> components;
@ProtoField(number = 68)
public Set<HotRodIdentityProviderEntity> identityProviders;
@ProtoField(number = 69)
public Set<HotRodIdentityProviderMapperEntity> identityProviderMappers;
@ProtoField(number = 70)
public Set<HotRodRequiredActionProviderEntity> requiredActionProviders;
@ProtoField(number = 71)
public Set<HotRodRequiredCredentialEntity> requiredCredentials;
@ProtoField(number = 72)
public Set<String> defaultClientScopeIds;
@ProtoField(number = 73)
public Set<String> defaultGroupIds;
@ProtoField(number = 74)
public Set<String> enabledEventTypes;
@ProtoField(number = 75)
public Set<String> eventsListeners;
@ProtoField(number = 76)
public Set<String> optionalClientScopeIds;
@ProtoField(number = 77)
public Set<String> supportedLocales;
public static abstract class AbstractHotRodRealmEntityDelegate extends UpdatableHotRodEntityDelegateImpl<HotRodRealmEntity> implements MapRealmEntity {
@Override
public String getId() {
return getHotRodEntity().id;
}
@Override
public void setId(String id) {
HotRodRealmEntity entity = getHotRodEntity();
if (entity.id != null) throw new IllegalStateException("Id cannot be changed");
entity.id = id;
entity.updated |= id != null;
}
@Override
public boolean isUpdated() {
return getHotRodEntity().updated
|| Optional.ofNullable(getAuthenticationExecutions()).orElseGet(Collections::emptySet).stream().anyMatch(MapAuthenticationExecutionEntity::isUpdated)
|| Optional.ofNullable(getAuthenticationFlows()).orElseGet(Collections::emptySet).stream().anyMatch(MapAuthenticationFlowEntity::isUpdated)
|| Optional.ofNullable(getAuthenticatorConfigs()).orElseGet(Collections::emptySet).stream().anyMatch(MapAuthenticatorConfigEntity::isUpdated)
|| Optional.ofNullable(getClientInitialAccesses()).orElseGet(Collections::emptySet).stream().anyMatch(MapClientInitialAccessEntity::isUpdated)
|| Optional.ofNullable(getComponents()).orElseGet(Collections::emptySet).stream().anyMatch(MapComponentEntity::isUpdated)
|| Optional.ofNullable(getIdentityProviders()).orElseGet(Collections::emptySet).stream().anyMatch(MapIdentityProviderEntity::isUpdated)
|| Optional.ofNullable(getIdentityProviderMappers()).orElseGet(Collections::emptySet).stream().anyMatch(MapIdentityProviderMapperEntity::isUpdated)
|| Optional.ofNullable(getRequiredActionProviders()).orElseGet(Collections::emptySet).stream().anyMatch(MapRequiredActionProviderEntity::isUpdated)
|| Optional.ofNullable(getRequiredCredentials()).orElseGet(Collections::emptySet).stream().anyMatch(MapRequiredCredentialEntity::isUpdated)
|| Optional.ofNullable(getOTPPolicy()).map(MapOTPPolicyEntity::isUpdated).orElse(false)
|| Optional.ofNullable(getWebAuthnPolicy()).map(MapWebAuthnPolicyEntity::isUpdated).orElse(false)
|| Optional.ofNullable(getWebAuthnPolicyPasswordless()).map(MapWebAuthnPolicyEntity::isUpdated).orElse(false);
}
@Override
public void clearUpdatedFlag() {
getHotRodEntity().updated = false;
Optional.ofNullable(getAuthenticationExecutions()).orElseGet(Collections::emptySet).forEach(UpdatableEntity::clearUpdatedFlag);
Optional.ofNullable(getAuthenticationFlows()).orElseGet(Collections::emptySet).forEach(UpdatableEntity::clearUpdatedFlag);
Optional.ofNullable(getAuthenticatorConfigs()).orElseGet(Collections::emptySet).forEach(UpdatableEntity::clearUpdatedFlag);
Optional.ofNullable(getClientInitialAccesses()).orElseGet(Collections::emptySet).forEach(UpdatableEntity::clearUpdatedFlag);
Optional.ofNullable(getComponents()).orElseGet(Collections::emptySet).forEach(UpdatableEntity::clearUpdatedFlag);
Optional.ofNullable(getIdentityProviders()).orElseGet(Collections::emptySet).forEach(UpdatableEntity::clearUpdatedFlag);
Optional.ofNullable(getIdentityProviderMappers()).orElseGet(Collections::emptySet).forEach(UpdatableEntity::clearUpdatedFlag);
Optional.ofNullable(getRequiredActionProviders()).orElseGet(Collections::emptySet).forEach(UpdatableEntity::clearUpdatedFlag);
Optional.ofNullable(getRequiredCredentials()).orElseGet(Collections::emptySet).forEach(UpdatableEntity::clearUpdatedFlag);
Optional.ofNullable(getOTPPolicy()).ifPresent(UpdatableEntity::clearUpdatedFlag);
Optional.ofNullable(getWebAuthnPolicy()).ifPresent(UpdatableEntity::clearUpdatedFlag);
Optional.ofNullable(getWebAuthnPolicyPasswordless()).ifPresent(UpdatableEntity::clearUpdatedFlag);
}
@Override
public boolean hasClientInitialAccess() {
Set<MapClientInitialAccessEntity> cias = getClientInitialAccesses();
return cias != null && !cias.isEmpty();
}
@Override
public void removeExpiredClientInitialAccesses() {
Set<MapClientInitialAccessEntity> cias = getClientInitialAccesses();
if (cias != null)
cias.stream()
.filter(this::checkIfExpired)
.map(MapClientInitialAccessEntity::getId)
.collect(Collectors.toSet())
.forEach(this::removeClientInitialAccess);
}
private boolean checkIfExpired(MapClientInitialAccessEntity cia) {
return cia.getRemainingCount() < 1 || isExpired(cia, true);
}
}
@Override
public boolean equals(Object o) {
return HotRodRealmEntityDelegate.entityEquals(this, o);
}
@Override
public int hashCode() {
return HotRodRealmEntityDelegate.entityHashCode(this);
}
}

View file

@ -1,36 +0,0 @@
package org.keycloak.models.map.storage.hotRod.realm.entity;
import org.infinispan.protostream.annotations.ProtoField;
import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation;
import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity;
@GenerateHotRodEntityImplementation(
implementInterface = "org.keycloak.models.map.realm.entity.MapAuthenticationExecutionEntity"
)
public class HotRodAuthenticationExecutionEntity extends AbstractHotRodEntity {
@ProtoField(number = 1)
public String id;
@ProtoField(number = 2)
public Boolean autheticatorFlow;
@ProtoField(number = 3)
public Integer priority;
@ProtoField(number = 4)
public Integer requirement;
@ProtoField(number = 5)
public String authenticator;
@ProtoField(number = 6)
public String authenticatorConfig;
@ProtoField(number = 7)
public String flowId;
@ProtoField(number = 8)
public String parentFlowId;
@Override
public boolean equals(Object o) {
return HotRodAuthenticationExecutionEntityDelegate.entityEquals(this, o);
}
@Override
public int hashCode() {
return HotRodAuthenticationExecutionEntityDelegate.entityHashCode(this);
}
}

View file

@ -1,32 +0,0 @@
package org.keycloak.models.map.storage.hotRod.realm.entity;
import org.infinispan.protostream.annotations.ProtoField;
import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation;
import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity;
@GenerateHotRodEntityImplementation(
implementInterface = "org.keycloak.models.map.realm.entity.MapAuthenticationFlowEntity"
)
public class HotRodAuthenticationFlowEntity extends AbstractHotRodEntity {
@ProtoField(number = 1)
public String id;
@ProtoField(number = 2)
public Boolean builtIn;
@ProtoField(number = 3)
public Boolean topLevel;
@ProtoField(number = 4)
public String alias;
@ProtoField(number = 5)
public String description;
@ProtoField(number = 6)
public String providerId;
@Override
public boolean equals(Object o) {
return HotRodAuthenticationFlowEntityDelegate.entityEquals(this, o);
}
@Override
public int hashCode() {
return HotRodAuthenticationFlowEntityDelegate.entityHashCode(this);
}
}

View file

@ -1,29 +0,0 @@
package org.keycloak.models.map.storage.hotRod.realm.entity;
import org.infinispan.protostream.annotations.ProtoField;
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.Set;
@GenerateHotRodEntityImplementation(
implementInterface = "org.keycloak.models.map.realm.entity.MapAuthenticatorConfigEntity"
)
public class HotRodAuthenticatorConfigEntity extends AbstractHotRodEntity {
@ProtoField(number = 1)
public String id;
@ProtoField(number = 2)
public String alias;
@ProtoField(number = 3)
public Set<HotRodPair<String, String>> config;
@Override
public boolean equals(Object o) {
return HotRodAuthenticatorConfigEntityDelegate.entityEquals(this, o);
}
@Override
public int hashCode() {
return HotRodAuthenticatorConfigEntityDelegate.entityHashCode(this);
}
}

View file

@ -1,30 +0,0 @@
package org.keycloak.models.map.storage.hotRod.realm.entity;
import org.infinispan.protostream.annotations.ProtoField;
import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation;
import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity;
@GenerateHotRodEntityImplementation(
implementInterface = "org.keycloak.models.map.realm.entity.MapClientInitialAccessEntity"
)
public class HotRodClientInitialAccessEntity extends AbstractHotRodEntity {
@ProtoField(number = 1)
public String id;
@ProtoField(number = 2)
public Integer count;
@ProtoField(number = 3)
public Long expiration;
@ProtoField(number = 4)
public Integer remainingCount;
@ProtoField(number = 5)
public Long timestamp;
@Override
public boolean equals(Object o) {
return HotRodClientInitialAccessEntityDelegate.entityEquals(this, o);
}
@Override
public int hashCode() {
return HotRodClientInitialAccessEntityDelegate.entityHashCode(this);
}
}

View file

@ -1,43 +0,0 @@
package org.keycloak.models.map.storage.hotRod.realm.entity;
import org.infinispan.api.annotations.indexing.Basic;
import org.infinispan.api.annotations.indexing.Indexed;
import org.infinispan.protostream.annotations.ProtoField;
import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation;
import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity;
import org.keycloak.models.map.storage.hotRod.common.HotRodAttributeEntityNonIndexed;
import java.util.Set;
@GenerateHotRodEntityImplementation(
implementInterface = "org.keycloak.models.map.realm.entity.MapComponentEntity"
)
@Indexed
public class HotRodComponentEntity extends AbstractHotRodEntity {
@ProtoField(number = 1)
public String id;
@ProtoField(number = 2)
public String name;
@ProtoField(number = 3)
public String parentId;
@ProtoField(number = 4)
public String providerId;
@Basic(sortable = true)
@ProtoField(number = 5)
public String providerType;
@ProtoField(number = 6)
public String subType;
@ProtoField(number = 7)
public Set<HotRodAttributeEntityNonIndexed> config;
@Override
public boolean equals(Object o) {
return HotRodComponentEntityDelegate.entityEquals(this, o);
}
@Override
public int hashCode() {
return HotRodComponentEntityDelegate.entityHashCode(this);
}
}

View file

@ -1,49 +0,0 @@
package org.keycloak.models.map.storage.hotRod.realm.entity;
import org.infinispan.protostream.annotations.ProtoField;
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.Set;
@GenerateHotRodEntityImplementation(
implementInterface = "org.keycloak.models.map.realm.entity.MapIdentityProviderEntity"
)
public class HotRodIdentityProviderEntity extends AbstractHotRodEntity {
@ProtoField(number = 1)
public String id;
@ProtoField(number = 2)
public Boolean addReadTokenRoleOnCreate;
@ProtoField(number = 3)
public Boolean authenticateByDefault;
@ProtoField(number = 4)
public Boolean enabled;
@ProtoField(number = 5)
public Boolean linkOnly;
@ProtoField(number = 6)
public Boolean storeToken;
@ProtoField(number = 7)
public Boolean trustEmail;
@ProtoField(number = 8)
public String alias;
@ProtoField(number = 9)
public String displayName;
@ProtoField(number = 10)
public String firstBrokerLoginFlowId;
@ProtoField(number = 11)
public String postBrokerLoginFlowId;
@ProtoField(number = 12)
public String providerId;
@ProtoField(number = 13)
public Set<HotRodPair<String, String>> config;
@Override
public boolean equals(Object o) {
return HotRodIdentityProviderEntityDelegate.entityEquals(this, o);
}
@Override
public int hashCode() {
return HotRodIdentityProviderEntityDelegate.entityHashCode(this);
}
}

View file

@ -1,33 +0,0 @@
package org.keycloak.models.map.storage.hotRod.realm.entity;
import org.infinispan.protostream.annotations.ProtoField;
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.Set;
@GenerateHotRodEntityImplementation(
implementInterface = "org.keycloak.models.map.realm.entity.MapIdentityProviderMapperEntity"
)
public class HotRodIdentityProviderMapperEntity extends AbstractHotRodEntity {
@ProtoField(number = 1)
public String id;
@ProtoField(number = 2)
public String name;
@ProtoField(number = 3)
public String identityProviderAlias;
@ProtoField(number = 4)
public String identityProviderMapper;
@ProtoField(number = 5)
public Set<HotRodPair<String, String>> config;
@Override
public boolean equals(Object o) {
return HotRodIdentityProviderMapperEntityDelegate.entityEquals(this, o);
}
@Override
public int hashCode() {
return HotRodIdentityProviderMapperEntityDelegate.entityHashCode(this);
}
}

View file

@ -1,61 +0,0 @@
/*
* Copyright 2022 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.realm.entity;
import org.infinispan.protostream.annotations.ProtoField;
import org.keycloak.models.map.storage.hotRod.common.HotRodPair;
import java.util.Objects;
import java.util.Set;
public class HotRodLocalizationTexts {
@ProtoField(number = 1)
public String locale;
@ProtoField(number = 2)
public Set<HotRodPair<String, String>> values;
public String getLocale() {
return locale;
}
public void setLocale(String locale) {
this.locale = locale;
}
public Set<HotRodPair<String, String>> getValues() {
return values;
}
public void setValues(Set<HotRodPair<String, String>> values) {
this.values = values;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
HotRodLocalizationTexts that = (HotRodLocalizationTexts) o;
return Objects.equals(locale, that.locale) && Objects.equals(values, that.values);
}
@Override
public int hashCode() {
return Objects.hash(locale, values);
}
}

View file

@ -1,34 +0,0 @@
package org.keycloak.models.map.storage.hotRod.realm.entity;
import org.infinispan.protostream.annotations.ProtoField;
import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation;
import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity;
@GenerateHotRodEntityImplementation(
implementInterface = "org.keycloak.models.map.realm.entity.MapOTPPolicyEntity"
)
public class HotRodOTPPolicyEntity extends AbstractHotRodEntity {
@ProtoField(number = 1)
public Integer otpPolicyDigits;
@ProtoField(number = 2)
public Integer otpPolicyInitialCounter;
@ProtoField(number = 3)
public Integer otpPolicyLookAheadWindow;
@ProtoField(number = 4)
public Integer otpPolicyPeriod;
@ProtoField(number = 5)
public String otpPolicyAlgorithm;
@ProtoField(number = 6)
public String otpPolicyType;
@ProtoField(number = 7)
public Boolean otpPolicyCodeReusable;
@Override
public boolean equals(Object o) {
return HotRodOTPPolicyEntityDelegate.entityEquals(this, o);
}
@Override
public int hashCode() {
return HotRodOTPPolicyEntityDelegate.entityHashCode(this);
}
}

View file

@ -1,39 +0,0 @@
package org.keycloak.models.map.storage.hotRod.realm.entity;
import org.infinispan.protostream.annotations.ProtoField;
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.Set;
@GenerateHotRodEntityImplementation(
implementInterface = "org.keycloak.models.map.realm.entity.MapRequiredActionProviderEntity"
)
public class HotRodRequiredActionProviderEntity extends AbstractHotRodEntity {
@ProtoField(number = 1)
public String id;
@ProtoField(number = 2)
public String name;
@ProtoField(number = 3)
public Boolean defaultAction;
@ProtoField(number = 4)
public Boolean enabled;
@ProtoField(number = 5)
public Integer priority;
@ProtoField(number = 6)
public String alias;
@ProtoField(number = 7)
public String providerId;
@ProtoField(number = 8)
public Set<HotRodPair<String, String>> config;
@Override
public boolean equals(Object o) {
return HotRodRequiredActionProviderEntityDelegate.entityEquals(this, o);
}
@Override
public int hashCode() {
return HotRodRequiredActionProviderEntityDelegate.entityHashCode(this);
}
}

View file

@ -1,28 +0,0 @@
package org.keycloak.models.map.storage.hotRod.realm.entity;
import org.infinispan.protostream.annotations.ProtoField;
import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation;
import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity;
@GenerateHotRodEntityImplementation(
implementInterface = "org.keycloak.models.map.realm.entity.MapRequiredCredentialEntity"
)
public class HotRodRequiredCredentialEntity extends AbstractHotRodEntity {
@ProtoField(number = 1)
public Boolean input;
@ProtoField(number = 2)
public Boolean secret;
@ProtoField(number = 3)
public String formLabel;
@ProtoField(number = 4)
public String type;
@Override
public boolean equals(Object o) {
return HotRodRequiredCredentialEntityDelegate.entityEquals(this, o);
}
@Override
public int hashCode() {
return HotRodRequiredCredentialEntityDelegate.entityHashCode(this);
}
}

View file

@ -1,44 +0,0 @@
package org.keycloak.models.map.storage.hotRod.realm.entity;
import org.infinispan.protostream.annotations.ProtoField;
import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation;
import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity;
import java.util.List;
@GenerateHotRodEntityImplementation(
implementInterface = "org.keycloak.models.map.realm.entity.MapWebAuthnPolicyEntity"
)
public class HotRodWebAuthnPolicyEntity extends AbstractHotRodEntity {
@ProtoField(number = 1)
public Boolean avoidSameAuthenticatorRegister;
@ProtoField(number = 2)
public Integer createTimeout;
@ProtoField(number = 3)
public String attestationConveyancePreference;
@ProtoField(number = 4)
public String authenticatorAttachment;
@ProtoField(number = 5)
public String requireResidentKey;
@ProtoField(number = 6)
public String rpEntityName;
@ProtoField(number = 7)
public String rpId;
@ProtoField(number = 8)
public String userVerificationRequirement;
@ProtoField(number = 9)
public List<String> acceptableAaguids;
@ProtoField(number = 10)
public List<String> signatureAlgorithms;
@ProtoField(number = 11)
public List<String> extraOrigins;
@Override
public boolean equals(Object o) {
return HotRodWebAuthnPolicyEntityDelegate.entityEquals(this, o);
}
@Override
public int hashCode() {
return HotRodWebAuthnPolicyEntityDelegate.entityHashCode(this);
}
}

View file

@ -1,133 +0,0 @@
/*
* Copyright 2022 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.role;
import org.infinispan.api.annotations.indexing.Basic;
import org.infinispan.api.annotations.indexing.Indexed;
import org.infinispan.api.annotations.indexing.Keyword;
import org.infinispan.protostream.GeneratedSchema;
import org.infinispan.protostream.annotations.AutoProtoSchemaBuilder;
import org.infinispan.protostream.annotations.ProtoDoc;
import org.infinispan.protostream.annotations.ProtoField;
import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation;
import org.keycloak.models.map.annotations.IgnoreForEntityImplementationGenerator;
import org.keycloak.models.map.role.MapRoleEntity;
import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity;
import org.keycloak.models.map.storage.hotRod.common.CommonPrimitivesProtoSchemaInitializer;
import org.keycloak.models.map.storage.hotRod.common.HotRodAttributeEntityNonIndexed;
import org.keycloak.models.map.storage.hotRod.common.UpdatableHotRodEntityDelegateImpl;
import java.util.Objects;
import java.util.Set;
@GenerateHotRodEntityImplementation(
implementInterface = "org.keycloak.models.map.role.MapRoleEntity",
inherits = "org.keycloak.models.map.storage.hotRod.role.HotRodRoleEntity.AbstractHotRodRoleEntityDelegate",
topLevelEntity = true,
modelClass = "org.keycloak.models.RoleModel"
)
@Indexed
@ProtoDoc("schema-version: " + HotRodRoleEntity.VERSION)
public class HotRodRoleEntity extends AbstractHotRodEntity {
@IgnoreForEntityImplementationGenerator
public static final int VERSION = 1;
@AutoProtoSchemaBuilder(
includeClasses = {
HotRodRoleEntity.class
},
schemaFilePath = "proto/",
schemaPackageName = CommonPrimitivesProtoSchemaInitializer.HOT_ROD_ENTITY_PACKAGE,
dependsOn = {CommonPrimitivesProtoSchemaInitializer.class}
)
public interface HotRodRoleEntitySchema extends GeneratedSchema {
HotRodRoleEntitySchema INSTANCE = new HotRodRoleEntitySchemaImpl();
}
public static abstract class AbstractHotRodRoleEntityDelegate extends UpdatableHotRodEntityDelegateImpl<HotRodRoleEntity> implements MapRoleEntity {
@Override
public String getId() {
return getHotRodEntity().id;
}
@Override
public void setId(String id) {
HotRodRoleEntity entity = getHotRodEntity();
if (entity.id != null) throw new IllegalStateException("Id cannot be changed");
entity.id = id;
entity.updated |= id != null;
}
@Override
public void setClientId(String clientId) {
HotRodRoleEntity entity = getHotRodEntity();
entity.updated |= ! Objects.equals(entity.clientId, clientId);
entity.clientId = clientId;
}
@Override
public void setName(String name) {
HotRodRoleEntity entity = getHotRodEntity();
entity.updated |= ! Objects.equals(entity.name, name);
entity.name = name;
}
}
@Basic(projectable = true)
@ProtoField(number = 1)
public Integer entityVersion = VERSION;
@Basic(projectable = true, sortable = true)
@ProtoField(number = 2)
public String id;
@Basic(sortable = true)
@ProtoField(number = 3)
public String realmId;
@Keyword(sortable = true, normalizer = "lowercase")
@ProtoField(number = 4)
public String name;
@Keyword(sortable = true, normalizer = "lowercase")
@ProtoField(number = 5)
public String description;
@Basic(sortable = true)
@ProtoField(number = 7)
public String clientId;
@Basic(sortable = true)
@ProtoField(number = 8)
public Set<String> compositeRoles;
@ProtoField(number = 9)
public Set<HotRodAttributeEntityNonIndexed> attributes;
@Override
public boolean equals(Object o) {
return HotRodRoleEntityDelegate.entityEquals(this, o);
}
@Override
public int hashCode() {
return HotRodRoleEntityDelegate.entityHashCode(this);
}
}

View file

@ -1,103 +0,0 @@
/*
* Copyright 2022 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.singleUseObject;
import org.infinispan.api.annotations.indexing.Basic;
import org.infinispan.api.annotations.indexing.Indexed;
import org.infinispan.protostream.GeneratedSchema;
import org.infinispan.protostream.annotations.AutoProtoSchemaBuilder;
import org.infinispan.protostream.annotations.ProtoDoc;
import org.infinispan.protostream.annotations.ProtoField;
import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation;
import org.keycloak.models.map.annotations.IgnoreForEntityImplementationGenerator;
import org.keycloak.models.map.singleUseObject.MapSingleUseObjectEntity;
import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity;
import org.keycloak.models.map.storage.hotRod.common.CommonPrimitivesProtoSchemaInitializer;
import org.keycloak.models.map.storage.hotRod.common.HotRodPair;
import org.keycloak.models.map.storage.hotRod.common.UpdatableHotRodEntityDelegateImpl;
import java.util.Set;
@GenerateHotRodEntityImplementation(
implementInterface = "org.keycloak.models.map.singleUseObject.MapSingleUseObjectEntity",
inherits = "org.keycloak.models.map.storage.hotRod.singleUseObject.HotRodSingleUseObjectEntity.AbstractHotRodSingleUseObjectEntityDelegate",
topLevelEntity = true,
modelClass = "org.keycloak.models.SingleUseObjectValueModel"
)
@Indexed
@ProtoDoc("schema-version: " + HotRodSingleUseObjectEntity.VERSION)
public class HotRodSingleUseObjectEntity extends AbstractHotRodEntity {
@IgnoreForEntityImplementationGenerator
public static final int VERSION = 1;
@AutoProtoSchemaBuilder(
includeClasses = {
HotRodSingleUseObjectEntity.class
},
schemaFilePath = "proto/",
schemaPackageName = CommonPrimitivesProtoSchemaInitializer.HOT_ROD_ENTITY_PACKAGE,
dependsOn = {CommonPrimitivesProtoSchemaInitializer.class}
)
public interface HotRodSingleUseObjectEntitySchema extends GeneratedSchema {
HotRodSingleUseObjectEntitySchema INSTANCE = new HotRodSingleUseObjectEntitySchemaImpl();
}
@Basic(projectable = true)
@ProtoField(number = 1)
public Integer entityVersion = VERSION;
@ProtoField(number = 2)
public String id;
@ProtoField(number = 3)
public String objectKey;
@ProtoField(number = 7)
public Long expiration;
@ProtoField(number = 8)
public Set<HotRodPair<String, String>> notes;
public static abstract class AbstractHotRodSingleUseObjectEntityDelegate extends UpdatableHotRodEntityDelegateImpl<HotRodSingleUseObjectEntity> implements MapSingleUseObjectEntity {
@Override
public String getId() {
HotRodSingleUseObjectEntity hotRodEntity = getHotRodEntity();
return hotRodEntity != null ? hotRodEntity.id : null;
}
@Override
public void setId(String id) {
HotRodSingleUseObjectEntity entity = getHotRodEntity();
if (entity.id != null) throw new IllegalStateException("Id cannot be changed");
entity.id = id;
entity.updated |= id != null;
}
}
@Override
public boolean equals(Object o) {
return HotRodSingleUseObjectEntityDelegate.entityEquals(this, o);
}
@Override
public int hashCode() {
return HotRodSingleUseObjectEntityDelegate.entityHashCode(this);
}
}

View file

@ -1,59 +0,0 @@
/*
* Copyright 2022 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.transaction;
import org.keycloak.models.AbstractKeycloakTransaction;
import org.keycloak.models.map.storage.chm.ConcurrentHashMapStorage;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;
/**
* This wrapper encapsulates stores from all areas. This is needed because we need to control when the changes
* from each area are applied to make sure it is performed before the HotRod client provided transaction is committed.
*/
public class AllAreasHotRodStoresWrapper extends AbstractKeycloakTransaction {
private final Map<Class<?>, ConcurrentHashMapStorage<?, ?, ?, ?>> MapKeycloakStoresMap = new ConcurrentHashMap<>();
public ConcurrentHashMapStorage<?, ?, ?, ?> getOrCreateStoreForModel(Class<?> modelType, Supplier<ConcurrentHashMapStorage<?, ?, ?, ?>> supplier) {
ConcurrentHashMapStorage<?, ?, ?, ?> store = MapKeycloakStoresMap.computeIfAbsent(modelType, t -> supplier.get());
if (!store.isActive()) {
store.begin();
}
return store;
}
@Override
protected void commitImpl() {
MapKeycloakStoresMap.values().forEach(ConcurrentHashMapStorage::commit);
}
@Override
protected void rollbackImpl() {
MapKeycloakStoresMap.values().forEach(ConcurrentHashMapStorage::rollback);
}
@Override
public void setRollbackOnly() {
super.setRollbackOnly();
MapKeycloakStoresMap.values().forEach(ConcurrentHashMapStorage::setRollbackOnly);
}
}

View file

@ -1,105 +0,0 @@
/*
* Copyright 2022 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.transaction;
import org.keycloak.models.KeycloakTransaction;
import jakarta.transaction.HeuristicMixedException;
import jakarta.transaction.HeuristicRollbackException;
import jakarta.transaction.NotSupportedException;
import jakarta.transaction.RollbackException;
import jakarta.transaction.Status;
import jakarta.transaction.SystemException;
import jakarta.transaction.TransactionManager;
/**
* When no JTA transaction is present in the runtime this wrapper is used
* to enlist HotRod client provided transaction to our
* {@link KeycloakTransactionManager}. If JTA transaction is present this should
* not be used.
*/
public class HotRodRemoteTransactionWrapper implements KeycloakTransaction {
private final TransactionManager transactionManager;
public HotRodRemoteTransactionWrapper(TransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
@Override
public void begin() {
try {
if (transactionManager.getStatus() == Status.STATUS_NO_TRANSACTION) {
transactionManager.begin();
}
} catch (NotSupportedException | SystemException e) {
throw new RuntimeException(e);
}
}
@Override
public void commit() {
try {
if (transactionManager.getStatus() == Status.STATUS_ACTIVE) {
transactionManager.commit();
}
} catch (HeuristicRollbackException | SystemException | HeuristicMixedException | RollbackException e) {
throw new RuntimeException(e);
}
}
@Override
public void rollback() {
try {
if (transactionManager.getStatus() == Status.STATUS_ACTIVE) {
transactionManager.rollback();
}
} catch (SystemException e) {
throw new RuntimeException(e);
}
}
@Override
public void setRollbackOnly() {
try {
if (transactionManager.getStatus() == Status.STATUS_ACTIVE) {
transactionManager.setRollbackOnly();
}
} catch (SystemException e) {
throw new RuntimeException(e);
}
}
@Override
public boolean getRollbackOnly() {
try {
return transactionManager.getStatus() == Status.STATUS_MARKED_ROLLBACK;
} catch (SystemException e) {
throw new RuntimeException(e);
}
}
@Override
public boolean isActive() {
try {
return transactionManager.getStatus() == Status.STATUS_ACTIVE;
} catch (SystemException e) {
throw new RuntimeException(e);
}
}
}

View file

@ -1,51 +0,0 @@
/*
* Copyright 2022 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.transaction;
import org.infinispan.client.hotrod.transaction.manager.RemoteTransactionManager;
import org.infinispan.commons.tx.lookup.TransactionManagerLookup;
import org.keycloak.models.KeycloakSession;
import org.keycloak.transaction.JtaTransactionManagerLookup;
import jakarta.transaction.TransactionManager;
/**
* HotRod client provides its own {@link org.infinispan.client.hotrod.transaction.lookup.GenericTransactionManagerLookup}
* that is able to locate variety of JTA transaction implementation present
* in the runtime. We need to make sure we use JTA only when it is detected
* by other parts of Keycloak (such as {@link org.keycloak.models.KeycloakTransactionManager}),
* therefore we implemented this custom TransactionManagerLookup that locates
* JTA transaction using {@link JtaTransactionManagerLookup} provider
*
*/
public class HotRodTransactionManagerLookup implements TransactionManagerLookup {
private final TransactionManager transactionManager;
public HotRodTransactionManagerLookup(KeycloakSession session) {
JtaTransactionManagerLookup jtaLookup = session.getProvider(JtaTransactionManagerLookup.class);
TransactionManager txManager = jtaLookup != null ? jtaLookup.getTransactionManager() : null;
transactionManager = txManager != null ? txManager : RemoteTransactionManager.getInstance();
}
@Override
public TransactionManager getTransactionManager() throws Exception {
return transactionManager;
}
}

View file

@ -1,54 +0,0 @@
/*
* Copyright 2022 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.user;
import org.infinispan.api.annotations.indexing.Basic;
import org.infinispan.api.annotations.indexing.Indexed;
import org.infinispan.protostream.annotations.ProtoField;
import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation;
import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity;
import java.util.Set;
@GenerateHotRodEntityImplementation(implementInterface = "org.keycloak.models.map.user.MapUserConsentEntity")
@Indexed
public class HotRodUserConsentEntity extends AbstractHotRodEntity {
@Basic(sortable = true)
@ProtoField(number = 1)
public String clientId;
@Basic(sortable = true)
@ProtoField(number = 2)
public Set<String> grantedClientScopesIds;
@ProtoField(number = 3)
public Long createdDate;
@ProtoField(number = 4)
public Long lastUpdatedDate;
@Override
public boolean equals(Object o) {
return HotRodUserConsentEntityDelegate.entityEquals(this, o);
}
@Override
public int hashCode() {
return HotRodUserConsentEntityDelegate.entityHashCode(this);
}
}

View file

@ -1,58 +0,0 @@
/*
* Copyright 2022 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.user;
import org.infinispan.protostream.annotations.ProtoField;
import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation;
import org.keycloak.models.map.storage.hotRod.client.HotRodProtocolMapperEntityDelegate;
import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity;
@GenerateHotRodEntityImplementation(implementInterface = "org.keycloak.models.map.user.MapUserCredentialEntity")
public class HotRodUserCredentialEntity extends AbstractHotRodEntity {
@ProtoField(number = 1)
public String id;
@ProtoField(number = 2)
public String type;
@ProtoField(number = 3)
public String userLabel;
@ProtoField(number = 4)
public Long createdDate;
@ProtoField(number = 5)
public String secretData;
@ProtoField(number = 6)
public String credentialData;
@ProtoField(number = 7)
public Integer priority;
@Override
public boolean equals(Object o) {
return HotRodUserCredentialEntityDelegate.entityEquals(this, o);
}
@Override
public int hashCode() {
return HotRodUserCredentialEntityDelegate.entityHashCode(this);
}
}

View file

@ -1,284 +0,0 @@
/*
* Copyright 2022 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.user;
import org.infinispan.api.annotations.indexing.Basic;
import org.infinispan.api.annotations.indexing.Indexed;
import org.infinispan.api.annotations.indexing.Keyword;
import org.infinispan.protostream.GeneratedSchema;
import org.infinispan.protostream.annotations.AutoProtoSchemaBuilder;
import org.infinispan.protostream.annotations.ProtoDoc;
import org.infinispan.protostream.annotations.ProtoField;
import org.jboss.logging.Logger;
import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation;
import org.keycloak.models.map.annotations.IgnoreForEntityImplementationGenerator;
import org.keycloak.models.map.common.UpdatableEntity;
import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity;
import org.keycloak.models.map.storage.hotRod.common.CommonPrimitivesProtoSchemaInitializer;
import org.keycloak.models.map.storage.hotRod.common.HotRodAttributeEntity;
import org.keycloak.models.map.storage.hotRod.common.UpdatableHotRodEntityDelegateImpl;
import org.keycloak.models.map.user.MapUserConsentEntity;
import org.keycloak.models.map.user.MapUserCredentialEntity;
import org.keycloak.models.map.user.MapUserEntity;
import org.keycloak.models.map.user.MapUserFederatedIdentityEntity;
import org.keycloak.models.utils.KeycloakModelUtils;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
@GenerateHotRodEntityImplementation(
implementInterface = "org.keycloak.models.map.user.MapUserEntity",
inherits = "org.keycloak.models.map.storage.hotRod.user.HotRodUserEntity.AbstractHotRodUserEntityDelegate",
topLevelEntity = true,
modelClass = "org.keycloak.models.UserModel"
)
@Indexed
@ProtoDoc("schema-version: " + HotRodUserEntity.VERSION)
public class HotRodUserEntity extends AbstractHotRodEntity {
@IgnoreForEntityImplementationGenerator
public static final int VERSION = 1;
@AutoProtoSchemaBuilder(
includeClasses = {
HotRodUserEntity.class,
HotRodUserConsentEntity.class,
HotRodUserCredentialEntity.class,
HotRodUserFederatedIdentityEntity.class},
schemaFilePath = "proto/",
schemaPackageName = CommonPrimitivesProtoSchemaInitializer.HOT_ROD_ENTITY_PACKAGE,
dependsOn = {CommonPrimitivesProtoSchemaInitializer.class}
)
public interface HotRodUserEntitySchema extends GeneratedSchema {
HotRodUserEntitySchema INSTANCE = new HotRodUserEntitySchemaImpl();
}
@IgnoreForEntityImplementationGenerator
private static final Logger LOG = Logger.getLogger(HotRodUserEntity.class);
@Basic(projectable = true)
@ProtoField(number = 1)
public Integer entityVersion = VERSION;
@Basic(projectable = true, sortable = true)
@ProtoField(number = 2)
public String id;
@Basic(sortable = true)
@ProtoField(number = 3)
public String realmId;
@Basic(sortable = true)
@ProtoField(number = 4)
public String username;
@Keyword(sortable = true, normalizer = "lowercase")
@ProtoField(number = 5)
public String usernameLowercase;
@Keyword(sortable = true, normalizer = "lowercase")
@ProtoField(number = 6)
public String firstName;
@ProtoField(number = 7)
public Long createdTimestamp;
@Keyword(sortable = true, normalizer = "lowercase")
@ProtoField(number = 8)
public String lastName;
@Keyword(sortable = true, normalizer = "lowercase")
@ProtoField(number = 9)
public String email;
/**
* TODO: Workaround for ISPN-8584
*
* This index shouldn't be there as majority of object will be enabled == true
*
* When this index is missing Ickle queries like following:
* FROM kc.HotRodUserEntity c WHERE (c.realmId = "admin-client-test" AND c.enabled = true AND c.email : "user*")
* fail with:
* Error: {"error":{"message":"Error executing search","cause":"Unexpected condition type (FullTextTermExpr): PROP(email):'user*'"}}
*
* In other words it is not possible to combine searching for Analyzed field and non-indexed field in one Ickle query
*/
@Basic(sortable = true)
@ProtoField(number = 10)
public Boolean enabled;
/**
* TODO: Workaround for ISPN-8584
*
* When this index is missing Ickle queries like following:
* FROM kc.HotRodUserEntity c WHERE (c.realmId = "admin-client-test" AND c.enabled = true AND c.email : "user*")
* fail with:
* Error: {"error":{"message":"Error executing search","cause":"Unexpected condition type (FullTextTermExpr): PROP(email):'user*'"}}
*
* In other words it is not possible to combine searching for Analyzed field and non-indexed field in one Ickle query
*/
@Basic(sortable = true)
@ProtoField(number = 11)
public Boolean emailVerified;
// This is necessary to be able to dynamically switch unique email constraints on and off in the realm settings
@ProtoField(number = 12)
public String emailConstraint;
@Basic(sortable = true)
@ProtoField(number = 13)
public Set<HotRodAttributeEntity> attributes;
@ProtoField(number = 14)
public Set<String> requiredActions;
@ProtoField(number = 15)
public List<HotRodUserCredentialEntity> credentials;
@Basic(sortable = true)
@ProtoField(number = 16)
public Set<HotRodUserFederatedIdentityEntity> federatedIdentities;
@Basic(sortable = true)
@ProtoField(number = 17)
public Set<HotRodUserConsentEntity> userConsents;
@Basic(sortable = true)
@ProtoField(number = 18)
public Set<String> groupsMembership = new HashSet<>();
@Basic(sortable = true)
@ProtoField(number = 19)
public Set<String> rolesMembership = new HashSet<>();
@Basic(sortable = true)
@ProtoField(number = 20)
public String federationLink;
@Basic(sortable = true)
@ProtoField(number = 21)
public String serviceAccountClientLink;
@ProtoField(number = 22)
public Long notBefore;
public static abstract class AbstractHotRodUserEntityDelegate extends UpdatableHotRodEntityDelegateImpl<HotRodUserEntity> implements MapUserEntity {
@Override
public String getId() {
return getHotRodEntity().id;
}
@Override
public void setId(String id) {
HotRodUserEntity entity = getHotRodEntity();
if (entity.id != null) throw new IllegalStateException("Id cannot be changed");
entity.id = id;
entity.updated |= id != null;
}
@Override
public void setEmail(String email, boolean duplicateEmailsAllowed) {
this.setEmail(email);
this.setEmailConstraint(email == null || duplicateEmailsAllowed ? KeycloakModelUtils.generateId() : email);
}
@Override
public void setUsername(String username) {
HotRodUserEntity entity = getHotRodEntity();
entity.updated |= ! Objects.equals(entity.username, username);
entity.username = username;
entity.usernameLowercase = username == null ? null : username.toLowerCase();
}
@Override
public boolean isUpdated() {
return getHotRodEntity().updated
|| Optional.ofNullable(getUserConsents()).orElseGet(Collections::emptySet).stream().anyMatch(MapUserConsentEntity::isUpdated)
|| Optional.ofNullable(getCredentials()).orElseGet(Collections::emptyList).stream().anyMatch(MapUserCredentialEntity::isUpdated)
|| Optional.ofNullable(getFederatedIdentities()).orElseGet(Collections::emptySet).stream().anyMatch(MapUserFederatedIdentityEntity::isUpdated);
}
@Override
public void clearUpdatedFlag() {
getHotRodEntity().updated = false;
Optional.ofNullable(getUserConsents()).orElseGet(Collections::emptySet).forEach(UpdatableEntity::clearUpdatedFlag);
Optional.ofNullable(getCredentials()).orElseGet(Collections::emptyList).forEach(UpdatableEntity::clearUpdatedFlag);
Optional.ofNullable(getFederatedIdentities()).orElseGet(Collections::emptySet).forEach(UpdatableEntity::clearUpdatedFlag);
}
@Override
public Boolean moveCredential(String credentialId, String newPreviousCredentialId) {
// 1 - Get all credentials from the entity.
List<HotRodUserCredentialEntity> credentialsList = getHotRodEntity().credentials;
// 2 - Find indexes of our and newPrevious credential
int ourCredentialIndex = -1;
int newPreviousCredentialIndex = -1;
HotRodUserCredentialEntity ourCredential = null;
int i = 0;
for (HotRodUserCredentialEntity credential : credentialsList) {
if (credentialId.equals(credential.id)) {
ourCredentialIndex = i;
ourCredential = credential;
} else if(newPreviousCredentialId != null && newPreviousCredentialId.equals(credential.id)) {
newPreviousCredentialIndex = i;
}
i++;
}
if (ourCredentialIndex == -1) {
LOG.warnf("Not found credential with id [%s] of user [%s]", credentialId, getUsername());
return false;
}
if (newPreviousCredentialId != null && newPreviousCredentialIndex == -1) {
LOG.warnf("Can't move up credential with id [%s] of user [%s]", credentialId, getUsername());
return false;
}
// 3 - Compute index where we move our credential
int toMoveIndex = newPreviousCredentialId==null ? 0 : newPreviousCredentialIndex + 1;
// 4 - Insert our credential to new position, remove it from the old position
if (toMoveIndex == ourCredentialIndex) return true;
credentialsList.add(toMoveIndex, ourCredential);
int indexToRemove = toMoveIndex < ourCredentialIndex ? ourCredentialIndex + 1 : ourCredentialIndex;
credentialsList.remove(indexToRemove);
getHotRodEntity().updated = true;
return true;
}
}
@Override
public boolean equals(Object o) {
return HotRodUserEntityDelegate.entityEquals(this, o);
}
@Override
public int hashCode() {
return HotRodUserEntityDelegate.entityHashCode(this);
}
}

View file

@ -1,53 +0,0 @@
/*
* Copyright 2022 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.user;
import org.infinispan.api.annotations.indexing.Basic;
import org.infinispan.api.annotations.indexing.Indexed;
import org.infinispan.protostream.annotations.ProtoField;
import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation;
import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity;
@GenerateHotRodEntityImplementation(implementInterface = "org.keycloak.models.map.user.MapUserFederatedIdentityEntity")
@Indexed
public class HotRodUserFederatedIdentityEntity extends AbstractHotRodEntity {
@Basic(sortable = true)
@ProtoField(number = 1)
public String identityProvider;
@ProtoField(number = 2)
public String token;
@Basic(sortable = true)
@ProtoField(number = 3)
public String userId;
@ProtoField(number = 4)
public String userName;
@Override
public boolean equals(Object o) {
return HotRodUserFederatedIdentityEntityDelegate.entityEquals(this, o);
}
@Override
public int hashCode() {
return HotRodUserFederatedIdentityEntityDelegate.entityHashCode(this);
}
}

View file

@ -1,77 +0,0 @@
/*
* Copyright 2022 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.userSession;
import org.keycloak.models.map.common.EntityField;
import org.keycloak.models.map.common.delegate.EntityFieldDelegate;
import org.keycloak.models.map.userSession.MapAuthenticatedClientSessionEntity;
import org.keycloak.models.map.userSession.MapAuthenticatedClientSessionEntityFields;
public class AuthenticatedClientSessionReferenceOnlyFieldDelegate implements EntityFieldDelegate<MapAuthenticatedClientSessionEntity> {
private final HotRodAuthenticatedClientSessionEntityReference reference;
public AuthenticatedClientSessionReferenceOnlyFieldDelegate(HotRodAuthenticatedClientSessionEntityReference reference) {
this.reference = reference;
}
@Override
public boolean isUpdated() {
return false;
}
@Override
public <EF extends Enum<? extends EntityField<MapAuthenticatedClientSessionEntity>> & EntityField<MapAuthenticatedClientSessionEntity>> Object get(EF field) {
switch ((MapAuthenticatedClientSessionEntityFields)field) {
case ID: return reference.getClientSessionId();
case CLIENT_ID: return reference.getClientId();
}
return null;
}
@Override
public <T, EF extends java.lang.Enum<? extends org.keycloak.models.map.common.EntityField<org.keycloak.models.map.userSession.MapAuthenticatedClientSessionEntity>> & org.keycloak.models.map.common.EntityField<org.keycloak.models.map.userSession.MapAuthenticatedClientSessionEntity>> void set(EF field, T value) {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public <T, EF extends java.lang.Enum<? extends org.keycloak.models.map.common.EntityField<org.keycloak.models.map.userSession.MapAuthenticatedClientSessionEntity>> & org.keycloak.models.map.common.EntityField<org.keycloak.models.map.userSession.MapAuthenticatedClientSessionEntity>> void collectionAdd(EF field, T value) {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public <T, EF extends java.lang.Enum<? extends org.keycloak.models.map.common.EntityField<org.keycloak.models.map.userSession.MapAuthenticatedClientSessionEntity>> & org.keycloak.models.map.common.EntityField<org.keycloak.models.map.userSession.MapAuthenticatedClientSessionEntity>> Object collectionRemove(EF field, T value) {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public <K, EF extends java.lang.Enum<? extends org.keycloak.models.map.common.EntityField<org.keycloak.models.map.userSession.MapAuthenticatedClientSessionEntity>> & org.keycloak.models.map.common.EntityField<org.keycloak.models.map.userSession.MapAuthenticatedClientSessionEntity>> Object mapGet(EF field, K key) {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public <K, T, EF extends java.lang.Enum<? extends org.keycloak.models.map.common.EntityField<org.keycloak.models.map.userSession.MapAuthenticatedClientSessionEntity>> & org.keycloak.models.map.common.EntityField<org.keycloak.models.map.userSession.MapAuthenticatedClientSessionEntity>> void mapPut(EF field, K key, T value) {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public <K, EF extends java.lang.Enum<? extends org.keycloak.models.map.common.EntityField<org.keycloak.models.map.userSession.MapAuthenticatedClientSessionEntity>> & org.keycloak.models.map.common.EntityField<org.keycloak.models.map.userSession.MapAuthenticatedClientSessionEntity>> Object mapRemove(EF field, K key) {
throw new UnsupportedOperationException("Not supported yet.");
}
}

View file

@ -1,136 +0,0 @@
/*
* Copyright 2022 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.userSession;
import org.infinispan.api.annotations.indexing.Basic;
import org.infinispan.api.annotations.indexing.Indexed;
import org.infinispan.protostream.GeneratedSchema;
import org.infinispan.protostream.annotations.AutoProtoSchemaBuilder;
import org.infinispan.protostream.annotations.ProtoDoc;
import org.infinispan.protostream.annotations.ProtoField;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation;
import org.keycloak.models.map.annotations.IgnoreForEntityImplementationGenerator;
import org.keycloak.models.map.storage.hotRod.authorization.HotRodResourceServerEntity;
import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity;
import org.keycloak.models.map.storage.hotRod.common.CommonPrimitivesProtoSchemaInitializer;
import org.keycloak.models.map.storage.hotRod.common.HotRodPair;
import org.keycloak.models.map.storage.hotRod.common.UpdatableHotRodEntityDelegateImpl;
import org.keycloak.models.map.userSession.MapAuthenticatedClientSessionEntity;
import org.keycloak.storage.SearchableModelField;
import java.util.Set;
@GenerateHotRodEntityImplementation(
implementInterface = "org.keycloak.models.map.userSession.MapAuthenticatedClientSessionEntity",
inherits = "org.keycloak.models.map.storage.hotRod.userSession.HotRodAuthenticatedClientSessionEntity.AbstractHotRodAuthenticatedClientSessionEntityDelegate",
topLevelEntity = true,
modelClass = "org.keycloak.models.AuthenticatedClientSessionModel",
cacheName = "org.keycloak.models.map.storage.ModelEntityUtil.getModelName(org.keycloak.models.UserSessionModel.class)" // Use the same cache name as user-sessions
)
@ProtoDoc("schema-version: " + HotRodResourceServerEntity.VERSION)
@Indexed
public class HotRodAuthenticatedClientSessionEntity extends AbstractHotRodEntity {
@IgnoreForEntityImplementationGenerator
public static final int VERSION = 1;
@IgnoreForEntityImplementationGenerator
public static final SearchableModelField<AuthenticatedClientSessionModel> ID = new SearchableModelField<>("id", String.class);
@AutoProtoSchemaBuilder(
includeClasses = {
HotRodAuthenticatedClientSessionEntity.class
},
schemaFilePath = "proto/",
schemaPackageName = CommonPrimitivesProtoSchemaInitializer.HOT_ROD_ENTITY_PACKAGE,
dependsOn = {CommonPrimitivesProtoSchemaInitializer.class}
)
public interface HotRodAuthenticatedClientSessionEntitySchema extends GeneratedSchema {
HotRodAuthenticatedClientSessionEntitySchema INSTANCE = new HotRodAuthenticatedClientSessionEntitySchemaImpl();
}
@Basic(projectable = true)
@ProtoField(number = 1)
public Integer entityVersion = VERSION;
@Basic(projectable = true, sortable = true)
@ProtoField(number = 2)
public String id;
@ProtoField(number = 3)
public String realmId;
@ProtoField(number = 4)
public String clientId;
@ProtoField(number = 5)
public String authMethod;
@ProtoField(number = 6)
public String redirectUri;
@ProtoField(number = 7)
public Long timestamp;
@ProtoField(number = 8)
public Long expiration;
@ProtoField(number = 9)
public String action;
@ProtoField(number = 10)
public Set<HotRodPair<String, String>> notes;
@ProtoField(number = 11)
public String currentRefreshToken;
@ProtoField(number = 12)
public Integer currentRefreshTokenUseCount;
@ProtoField(number = 13)
public Boolean offline;
public static abstract class AbstractHotRodAuthenticatedClientSessionEntityDelegate extends UpdatableHotRodEntityDelegateImpl<HotRodAuthenticatedClientSessionEntity> implements MapAuthenticatedClientSessionEntity {
@Override
public void setId(String id) {
HotRodAuthenticatedClientSessionEntity entity = getHotRodEntity();
if (entity.id != null) throw new IllegalStateException("Id cannot be changed");
entity.id = id;
entity.updated |= id != null;
}
@Override
public void setClientId(String clientId) {
HotRodAuthenticatedClientSessionEntity entity = getHotRodEntity();
if (entity.clientId != null) throw new IllegalStateException("ClientId cannot be changed");
entity.clientId = clientId;
entity.updated |= clientId != null;
}
}
@Override
public boolean equals(Object o) {
return HotRodAuthenticatedClientSessionEntityDelegate.entityEquals(this, o);
}
@Override
public int hashCode() {
return HotRodAuthenticatedClientSessionEntityDelegate.entityHashCode(this);
}
}

View file

@ -1,56 +0,0 @@
/*
* Copyright 2022 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.userSession;
import org.keycloak.models.ModelIllegalStateException;
import org.keycloak.models.map.common.EntityField;
import org.keycloak.models.map.common.delegate.DelegateProvider;
import org.keycloak.models.map.userSession.MapAuthenticatedClientSessionEntity;
import org.keycloak.models.map.userSession.MapAuthenticatedClientSessionEntityFields;
public abstract class HotRodAuthenticatedClientSessionEntityDelegateProvider implements DelegateProvider<MapAuthenticatedClientSessionEntity> {
private MapAuthenticatedClientSessionEntity fullClientSessionData;
private MapAuthenticatedClientSessionEntity idClientIdReferenceOnly;
public HotRodAuthenticatedClientSessionEntityDelegateProvider(MapAuthenticatedClientSessionEntity idClientIdReferenceOnly) {
this.idClientIdReferenceOnly = idClientIdReferenceOnly;
}
@Override
public MapAuthenticatedClientSessionEntity getDelegate(boolean isRead, Enum<? extends EntityField<MapAuthenticatedClientSessionEntity>> field, Object... parameters) {
if (fullClientSessionData != null) return fullClientSessionData;
if (isRead) {
switch ((MapAuthenticatedClientSessionEntityFields) field) {
case ID:
case CLIENT_ID:
return idClientIdReferenceOnly;
}
}
fullClientSessionData = loadClientSessionFromDatabase();
if (fullClientSessionData == null) {
throw new ModelIllegalStateException("Unable to retrieve client session data with id: " + idClientIdReferenceOnly.getId());
}
return fullClientSessionData;
}
public abstract MapAuthenticatedClientSessionEntity loadClientSessionFromDatabase();
}

View file

@ -1,48 +0,0 @@
/*
* Copyright 2022 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.userSession;
import org.infinispan.api.annotations.indexing.Basic;
import org.infinispan.api.annotations.indexing.Indexed;
import org.infinispan.protostream.annotations.ProtoField;
@Indexed
public class HotRodAuthenticatedClientSessionEntityReference {
@Basic(sortable = true)
@ProtoField(number = 1)
public String clientId;
@ProtoField(number = 2)
public String clientSessionId;
public HotRodAuthenticatedClientSessionEntityReference() {}
public HotRodAuthenticatedClientSessionEntityReference(String clientId, String clientSessionId) {
this.clientId = clientId;
this.clientSessionId = clientSessionId;
}
public String getClientId() {
return clientId;
}
public String getClientSessionId() {
return clientSessionId;
}
}

View file

@ -1,186 +0,0 @@
/*
* Copyright 2022 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.userSession;
import org.infinispan.api.annotations.indexing.Basic;
import org.infinispan.api.annotations.indexing.Indexed;
import org.infinispan.protostream.GeneratedSchema;
import org.infinispan.protostream.annotations.AutoProtoSchemaBuilder;
import org.infinispan.protostream.annotations.ProtoDoc;
import org.infinispan.protostream.annotations.ProtoField;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation;
import org.keycloak.models.map.annotations.IgnoreForEntityImplementationGenerator;
import org.keycloak.models.map.storage.hotRod.authorization.HotRodResourceServerEntity;
import org.keycloak.models.map.common.UpdatableEntity;
import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity;
import org.keycloak.models.map.storage.hotRod.common.CommonPrimitivesProtoSchemaInitializer;
import org.keycloak.models.map.storage.hotRod.common.HotRodStringPair;
import org.keycloak.models.map.storage.hotRod.common.HotRodTypesUtils;
import org.keycloak.models.map.storage.hotRod.common.UpdatableHotRodEntityDelegateImpl;
import org.keycloak.models.map.userSession.MapAuthenticatedClientSessionEntity;
import org.keycloak.models.map.userSession.MapUserSessionEntity;
import java.util.Collections;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
@GenerateHotRodEntityImplementation(
implementInterface = "org.keycloak.models.map.userSession.MapUserSessionEntity",
inherits = "org.keycloak.models.map.storage.hotRod.userSession.HotRodUserSessionEntity.AbstractHotRodUserSessionEntityDelegate",
topLevelEntity = true,
modelClass = "org.keycloak.models.UserSessionModel"
)
@Indexed
@ProtoDoc("schema-version: " + HotRodResourceServerEntity.VERSION)
public class HotRodUserSessionEntity extends AbstractHotRodEntity {
@IgnoreForEntityImplementationGenerator
public static final int VERSION = 1;
@AutoProtoSchemaBuilder(
includeClasses = {
HotRodUserSessionEntity.class,
HotRodAuthenticatedClientSessionEntityReference.class,
},
schemaFilePath = "proto/",
schemaPackageName = CommonPrimitivesProtoSchemaInitializer.HOT_ROD_ENTITY_PACKAGE,
dependsOn = {CommonPrimitivesProtoSchemaInitializer.class}
)
public interface HotRodUserSessionEntitySchema extends GeneratedSchema {
HotRodUserSessionEntitySchema INSTANCE = new HotRodUserSessionEntitySchemaImpl();
}
@Basic(projectable = true)
@ProtoField(number = 1)
public Integer entityVersion = VERSION;
@ProtoField(number = 2)
public String id;
@Basic(sortable = true)
@ProtoField(number = 3)
public String realmId;
@Basic(sortable = true)
@ProtoField(number = 4)
public String userId;
@Basic(sortable = true)
@ProtoField(number = 5)
public String brokerSessionId;
@Basic(sortable = true)
@ProtoField(number = 6)
public String brokerUserId;
@ProtoField(number = 7)
public String loginUsername;
@ProtoField(number = 8)
public String ipAddress;
@ProtoField(number = 9)
public String authMethod;
@ProtoField(number = 10)
public Boolean rememberMe;
@ProtoField(number = 11)
public Long timestamp;
@Basic(sortable = true)
@ProtoField(number = 12)
public Long lastSessionRefresh;
@ProtoField(number = 13)
public Long expiration;
@Basic(sortable = true)
@ProtoField(number = 14)
public Set<HotRodStringPair> notes;
@ProtoField(number = 15)
public Integer state;
@Basic(sortable = true)
@ProtoField(number = 16)
public Set<HotRodAuthenticatedClientSessionEntityReference> authenticatedClientSessions;
@Basic(sortable = true)
@ProtoField(number = 17)
public Boolean offline;
public static abstract class AbstractHotRodUserSessionEntityDelegate extends UpdatableHotRodEntityDelegateImpl<HotRodUserSessionEntity> implements MapUserSessionEntity {
@Override
public String getId() {
return getHotRodEntity().id;
}
@Override
public void setId(String id) {
HotRodUserSessionEntity entity = getHotRodEntity();
if (entity.id != null) throw new IllegalStateException("Id cannot be changed");
entity.id = id;
entity.updated |= id != null;
}
@Override
public UserSessionModel.SessionPersistenceState getPersistenceState() {
return UserSessionModel.SessionPersistenceState.PERSISTENT;
}
@Override
public void setPersistenceState(UserSessionModel.SessionPersistenceState persistenceState) {
if (persistenceState != null && UserSessionModel.SessionPersistenceState.PERSISTENT != persistenceState) {
throw new IllegalArgumentException("Transient session should not be stored in the HotRod.");
}
}
@Override
public boolean isUpdated() {
return getHotRodEntity().updated
|| Optional.ofNullable(getAuthenticatedClientSessions()).orElseGet(Collections::emptySet).stream().anyMatch(UpdatableEntity::isUpdated);
}
@Override
public void clearUpdatedFlag() {
getHotRodEntity().updated = false;
Optional.ofNullable(getAuthenticatedClientSessions()).orElseGet(Collections::emptySet).forEach(UpdatableEntity::clearUpdatedFlag);
}
@Override
public void clearAuthenticatedClientSessions() {
HotRodUserSessionEntity entity = getHotRodEntity();
entity.updated = entity.authenticatedClientSessions != null;
entity.authenticatedClientSessions = null;
}
}
@Override
public boolean equals(Object o) {
return HotRodUserSessionEntityDelegate.entityEquals(this, o);
}
@Override
public int hashCode() {
return HotRodUserSessionEntityDelegate.entityHashCode(this);
}
}

View file

@ -1,174 +0,0 @@
/*
* Copyright 2022 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.userSession;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.map.common.AbstractEntity;
import org.keycloak.models.map.common.DeepCloner;
import org.keycloak.models.map.common.StringKeyConverter;
import org.keycloak.models.map.common.delegate.SimpleDelegateProvider;
import org.keycloak.models.map.storage.QueryParameters;
import org.keycloak.models.map.storage.CrudOperations;
import org.keycloak.models.map.storage.chm.ConcurrentHashMapCrudOperations;
import org.keycloak.models.map.storage.chm.ConcurrentHashMapStorage;
import org.keycloak.models.map.storage.chm.MapModelCriteriaBuilder;
import org.keycloak.models.map.storage.criteria.DefaultModelCriteria;
import org.keycloak.models.map.userSession.MapAuthenticatedClientSessionEntity;
import org.keycloak.models.map.userSession.MapAuthenticatedClientSessionEntityDelegate;
import org.keycloak.models.map.userSession.MapUserSessionEntity;
import org.keycloak.models.map.userSession.MapUserSessionEntityDelegate;
import org.keycloak.storage.SearchableModelField;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator.IN;
public class HotRodUserSessionMapStorage<K> extends ConcurrentHashMapStorage<K, MapUserSessionEntity, UserSessionModel, CrudOperations<MapUserSessionEntity, UserSessionModel>> {
private final ConcurrentHashMapStorage<String, MapAuthenticatedClientSessionEntity, AuthenticatedClientSessionModel, CrudOperations<MapAuthenticatedClientSessionEntity, AuthenticatedClientSessionModel>> clientSessionStore;
public HotRodUserSessionMapStorage(CrudOperations<MapUserSessionEntity, UserSessionModel> map,
StringKeyConverter<K> keyConverter,
DeepCloner cloner,
Map<SearchableModelField<? super UserSessionModel>, MapModelCriteriaBuilder.UpdatePredicatesFunc<K, MapUserSessionEntity, UserSessionModel>> fieldPredicates,
ConcurrentHashMapStorage<String, MapAuthenticatedClientSessionEntity, AuthenticatedClientSessionModel, CrudOperations<MapAuthenticatedClientSessionEntity, AuthenticatedClientSessionModel>> clientSessionStore
) {
super(map, keyConverter, cloner, fieldPredicates);
this.clientSessionStore = clientSessionStore;
}
private MapAuthenticatedClientSessionEntity wrapClientSessionEntityToClientSessionAwareDelegate(MapAuthenticatedClientSessionEntity d) {
return new MapAuthenticatedClientSessionEntityDelegate(new HotRodAuthenticatedClientSessionEntityDelegateProvider(d) {
@Override
public MapAuthenticatedClientSessionEntity loadClientSessionFromDatabase() {
return clientSessionStore.read(d.getId());
}
});
}
private MapUserSessionEntity wrapUserSessionEntityToClientSessionAwareDelegate(MapUserSessionEntity entity) {
if (entity == null) return null;
return new MapUserSessionEntityDelegate(new SimpleDelegateProvider<>(entity)) {
private boolean filterAndRemoveNotExpired(MapAuthenticatedClientSessionEntity clientSession) {
if (!clientSessionStore.exists(clientSession.getId())) {
// If client session does not exist, remove the reference to it from userSessionEntity loaded in this transaction
entity.removeAuthenticatedClientSession(clientSession.getClientId());
return false;
}
return true;
}
@Override
public Set<MapAuthenticatedClientSessionEntity> getAuthenticatedClientSessions() {
Set<MapAuthenticatedClientSessionEntity> clientSessions = super.getAuthenticatedClientSessions();
return clientSessions == null ? null : clientSessions.stream()
// Find whether client session still exists in Infinispan and if not, remove the reference from user session
.filter(this::filterAndRemoveNotExpired)
.map(HotRodUserSessionMapStorage.this::wrapClientSessionEntityToClientSessionAwareDelegate)
.collect(Collectors.toSet());
}
@Override
public Optional<MapAuthenticatedClientSessionEntity> getAuthenticatedClientSession(String clientUUID) {
return super.getAuthenticatedClientSession(clientUUID)
// Find whether client session still exists in Infinispan and if not, remove the reference from user sessionZ
.filter(this::filterAndRemoveNotExpired)
.map(HotRodUserSessionMapStorage.this::wrapClientSessionEntityToClientSessionAwareDelegate);
}
@Override
public void addAuthenticatedClientSession(MapAuthenticatedClientSessionEntity clientSession) {
super.addAuthenticatedClientSession(clientSession);
clientSessionStore.create(clientSession);
}
@Override
public Boolean removeAuthenticatedClientSession(String clientUUID) {
Optional<MapAuthenticatedClientSessionEntity> clientSession = getAuthenticatedClientSession(clientUUID);
if (!clientSession.isPresent()) {
return false;
}
return super.removeAuthenticatedClientSession(clientUUID) && clientSessionStore.delete(clientSession.get().getId());
}
@Override
public void clearAuthenticatedClientSessions() {
Set<MapAuthenticatedClientSessionEntity> clientSessions = super.getAuthenticatedClientSessions();
if (clientSessions != null) {
clientSessionStore.delete(QueryParameters.withCriteria(
DefaultModelCriteria.<AuthenticatedClientSessionModel>criteria()
.compare(HotRodAuthenticatedClientSessionEntity.ID, IN, clientSessions.stream()
.map(MapAuthenticatedClientSessionEntity::getId))
));
}
super.clearAuthenticatedClientSessions();
}
};
}
@Override
public MapUserSessionEntity read(String sKey) {
return wrapUserSessionEntityToClientSessionAwareDelegate(super.read(sKey));
}
@Override
public Stream<MapUserSessionEntity> read(QueryParameters<UserSessionModel> queryParameters) {
return super.read(queryParameters).map(this::wrapUserSessionEntityToClientSessionAwareDelegate);
}
@Override
public MapUserSessionEntity create(MapUserSessionEntity value) {
return wrapUserSessionEntityToClientSessionAwareDelegate(super.create(value));
}
@Override
public boolean delete(String key) {
MapUserSessionEntity uSession = read(key);
Set<MapAuthenticatedClientSessionEntity> clientSessions = uSession.getAuthenticatedClientSessions();
if (clientSessions != null) {
clientSessionStore.delete(QueryParameters.withCriteria(
DefaultModelCriteria.<AuthenticatedClientSessionModel>criteria()
.compare(HotRodAuthenticatedClientSessionEntity.ID, IN, clientSessions.stream()
.map(MapAuthenticatedClientSessionEntity::getId))
));
}
return super.delete(key);
}
@Override
public long delete(QueryParameters<UserSessionModel> queryParameters) {
clientSessionStore.delete(QueryParameters.withCriteria(
DefaultModelCriteria.<AuthenticatedClientSessionModel>criteria()
.compare(HotRodAuthenticatedClientSessionEntity.ID, IN, read(queryParameters)
.flatMap(userSession -> Optional.ofNullable(userSession.getAuthenticatedClientSessions()).orElse(Collections.emptySet()).stream().map(AbstractEntity::getId)))
));
return super.delete(queryParameters);
}
}

Some files were not shown because too many files have changed in this diff Show more