Remove map related modules
Signed-off-by: vramik <vramik@redhat.com> Closes #24100
This commit is contained in:
parent
8acb6c1845
commit
926be135e8
605 changed files with 44 additions and 69299 deletions
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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;
|
||||
|
||||
}
|
|
@ -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";
|
||||
}
|
|
@ -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 "";
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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 {
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 + "());" );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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.", ""));
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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]";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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() {
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
|
@ -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>
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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() {
|
||||
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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() {
|
||||
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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> {
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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() {
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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.");
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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
Loading…
Reference in a new issue