diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/JpaEntityMigration.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/JpaEntityMigration.java new file mode 100644 index 0000000000..7fd55bb9d3 --- /dev/null +++ b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/JpaEntityMigration.java @@ -0,0 +1,51 @@ +/* + * 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.jpa.hibernate.jsonb; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.BiFunction; +import java.util.function.Function; +import org.keycloak.models.map.storage.jpa.client.JpaClientMapStorage; +import org.keycloak.models.map.storage.jpa.client.entity.JpaClientMetadata; +import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaClientMigration; + +public class JpaEntityMigration { + + static final Map, BiFunction> MIGRATIONS = new HashMap<>(); + static { + MIGRATIONS.put(JpaClientMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, JpaClientMapStorage.SUPPORTED_VERSION, tree, JpaClientMigration.MIGRATORS)); + } + + private static ObjectNode migrateTreeTo(int entityVersion, Integer supportedVersion, ObjectNode node, List> migrators) { + if (entityVersion > supportedVersion + 1) throw new IllegalArgumentException("Incompatible entity version: " + entityVersion + ", supportedVersion: " + supportedVersion); + + if (entityVersion < supportedVersion) { + while (entityVersion < supportedVersion) { + Function migrator = migrators.get(entityVersion); + if (migrator != null) { + node = migrator.apply(node); + } + entityVersion++; + } + } + return node; + } + +} diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/JsonbType.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/JsonbType.java index 872b17eb67..710c5da91c 100644 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/JsonbType.java +++ b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/JsonbType.java @@ -36,6 +36,7 @@ import java.sql.Types; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Properties; import org.hibernate.HibernateException; import org.hibernate.type.AbstractSingleColumnStandardBasicType; import org.hibernate.type.descriptor.ValueBinder; @@ -47,17 +48,17 @@ import org.hibernate.type.descriptor.java.MutableMutabilityPlan; import org.hibernate.type.descriptor.sql.BasicBinder; import org.hibernate.type.descriptor.sql.BasicExtractor; import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; -import org.keycloak.models.map.client.MapClientEntity; +import org.hibernate.usertype.DynamicParameterizedType; import org.keycloak.models.map.client.MapProtocolMapperEntity; import org.keycloak.models.map.client.MapProtocolMapperEntityImpl; import org.keycloak.models.map.common.DeepCloner; +import org.keycloak.models.map.common.EntityWithAttributes; import org.keycloak.models.map.common.Serialization.IgnoreUpdatedMixIn; import org.keycloak.models.map.common.Serialization.IgnoredTypeMixIn; import org.keycloak.models.map.common.UpdatableEntity; -import org.keycloak.models.map.storage.jpa.client.entity.JpaClientMetadata; -import org.keycloak.models.map.storage.jpa.client.JpaClientMapStorage; +import static org.keycloak.models.map.storage.jpa.hibernate.jsonb.JpaEntityMigration.MIGRATIONS; -public class JsonbType extends AbstractSingleColumnStandardBasicType { +public class JsonbType extends AbstractSingleColumnStandardBasicType implements DynamicParameterizedType { public static final JsonbType INSTANCE = new JsonbType(); public static final ObjectMapper MAPPER = new ObjectMapper() @@ -70,15 +71,20 @@ public class JsonbType extends AbstractSingleColumnStandardBasicType { .registerModule(new SimpleModule().addAbstractTypeMapping(MapProtocolMapperEntity.class, MapProtocolMapperEntityImpl.class)) .addMixIn(UpdatableEntity.class, IgnoreUpdatedMixIn.class) .addMixIn(DeepCloner.class, IgnoredTypeMixIn.class) - .addMixIn(MapClientEntity.class, IgnoredClientFieldsMixIn.class); + .addMixIn(EntityWithAttributes.class, IgnoredMetadataFieldsMixIn.class); - abstract class IgnoredClientFieldsMixIn { + abstract class IgnoredMetadataFieldsMixIn { @JsonIgnore public abstract String getId(); @JsonIgnore public abstract Map> getAttributes(); } public JsonbType() { - super(JsonbSqlTypeDescriptor.INSTANCE, JsonbJavaTypeDescriptor.INSTANCE); + super(JsonbSqlTypeDescriptor.INSTANCE, new JsonbJavaTypeDescriptor()); + } + + @Override + public void setParameterValues(Properties parameters) { + ((JsonbJavaTypeDescriptor) getJavaTypeDescriptor()).setParameterValues(parameters); } @Override @@ -148,9 +154,14 @@ public class JsonbType extends AbstractSingleColumnStandardBasicType { } } - private static class JsonbJavaTypeDescriptor extends AbstractTypeDescriptor { + private static class JsonbJavaTypeDescriptor extends AbstractTypeDescriptor implements DynamicParameterizedType { - private static final JsonbJavaTypeDescriptor INSTANCE = new JsonbJavaTypeDescriptor(); + private Class valueType; + + @Override + public void setParameterValues(Properties parameters) { + valueType = ((ParameterType) parameters.get(PARAMETER_TYPE)).getReturnedClass(); + } public JsonbJavaTypeDescriptor() { super(Object.class, new MutableMutabilityPlan() { @@ -166,26 +177,29 @@ public class JsonbType extends AbstractSingleColumnStandardBasicType { } @Override + @SuppressWarnings("unchecked") public Object fromString(String json) { try { ObjectNode tree = MAPPER.readValue(json, ObjectNode.class); JsonNode ev = tree.get("entityVersion"); if (ev == null || ! ev.isInt()) throw new IllegalArgumentException("unable to read entity version from " + json); - int entityVersion = ev.asInt(); + Integer entityVersion = ev.asInt(); - if (entityVersion > JpaClientMapStorage.SUPPORTED_VERSION + 1) throw new IllegalArgumentException("Incompatible entity version: " + entityVersion + ", supportedVersion: " + JpaClientMapStorage.SUPPORTED_VERSION); + tree = migrate(tree, entityVersion); - if (entityVersion < JpaClientMapStorage.SUPPORTED_VERSION) { - tree = JpaClientMigration.migrateTreeTo(entityVersion, JpaClientMapStorage.SUPPORTED_VERSION, tree); - } - return MAPPER.treeToValue(tree, JpaClientMetadata.class); + return MAPPER.treeToValue(tree, valueType); } catch (IOException e) { throw new HibernateException("unable to read", e); } } + private ObjectNode migrate(ObjectNode tree, Integer entityVersion) { + return MIGRATIONS.getOrDefault(valueType, (node, version) -> node).apply(tree, entityVersion); + } + @Override + @SuppressWarnings("unchecked") public X unwrap(Object value, Class type, WrapperOptions options) { if (value == null) return null; diff --git a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/JpaClientMigration.java b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaClientMigration.java similarity index 61% rename from model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/JpaClientMigration.java rename to model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaClientMigration.java index d9bcdc3226..349e84ce22 100644 --- a/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/JpaClientMigration.java +++ b/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/hibernate/jsonb/migration/JpaClientMigration.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.keycloak.models.map.storage.jpa.hibernate.jsonb; +package org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration; import com.fasterxml.jackson.databind.node.ObjectNode; import java.util.Arrays; @@ -23,19 +23,7 @@ import java.util.function.Function; public class JpaClientMigration { - private static final List> MIGRATORS = Arrays.asList( + public static final List> MIGRATORS = Arrays.asList( o -> o // no migration yet ); - - public static ObjectNode migrateTreeTo(int currentVersion, int targetVersion, ObjectNode node) { - while (currentVersion < targetVersion) { - Function migrator = MIGRATORS.get(currentVersion); - if (migrator != null) { - node = migrator.apply(node); - } - currentVersion++; - } - return node; - } - }