parent
fe0cb36284
commit
13e02d5f09
19 changed files with 1032 additions and 262 deletions
|
@ -17,6 +17,7 @@
|
|||
package org.keycloak.models.map.storage.jpa;
|
||||
|
||||
public interface Constants {
|
||||
public static final Integer SUPPORTED_VERSION_CLIENT = 1;
|
||||
public static final Integer SUPPORTED_VERSION_ROLE = 1;
|
||||
public static final Integer CURRENT_SCHEMA_VERSION_CLIENT = 1;
|
||||
public static final Integer CURRENT_SCHEMA_VERSION_CLIENT_SCOPE = 1;
|
||||
public static final Integer CURRENT_SCHEMA_VERSION_ROLE = 1;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,168 @@
|
|||
/*
|
||||
* 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.jpa;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.stream.Stream;
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.criteria.CriteriaBuilder;
|
||||
import javax.persistence.criteria.CriteriaDelete;
|
||||
import javax.persistence.criteria.CriteriaQuery;
|
||||
import javax.persistence.criteria.Order;
|
||||
import javax.persistence.criteria.Predicate;
|
||||
import javax.persistence.criteria.Root;
|
||||
import javax.persistence.criteria.Selection;
|
||||
import org.keycloak.connections.jpa.JpaKeycloakTransaction;
|
||||
import static org.keycloak.models.jpa.PaginationUtils.paginateQuery;
|
||||
import org.keycloak.models.map.common.AbstractEntity;
|
||||
import org.keycloak.models.map.common.StringKeyConvertor;
|
||||
import org.keycloak.models.map.common.StringKeyConvertor.UUIDKey;
|
||||
import org.keycloak.models.map.storage.MapKeycloakTransaction;
|
||||
import org.keycloak.models.map.storage.QueryParameters;
|
||||
import static org.keycloak.models.map.storage.jpa.JpaMapStorageProviderFactory.CLONER;
|
||||
import static org.keycloak.utils.StreamsUtil.closing;
|
||||
|
||||
public abstract class JpaMapKeycloakTransaction<RE extends JpaRootEntity, E extends AbstractEntity, M> extends JpaKeycloakTransaction implements MapKeycloakTransaction<E, M> {
|
||||
|
||||
private final Class<RE> entityType;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public JpaMapKeycloakTransaction(Class<RE> entityType, EntityManager em) {
|
||||
super(em);
|
||||
this.entityType = entityType;
|
||||
}
|
||||
|
||||
protected abstract Selection<? extends RE> selectCbConstruct(CriteriaBuilder cb, Root<RE> root);
|
||||
protected abstract void setEntityVersion(JpaRootEntity entity);
|
||||
protected abstract JpaModelCriteriaBuilder createJpaModelCriteriaBuilder();
|
||||
protected abstract E mapToEntityDelegate(RE original);
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public E create(E mapEntity) {
|
||||
JpaRootEntity jpaEntity = entityType.cast(CLONER.from(mapEntity));
|
||||
CLONER.from(mapEntity);
|
||||
if (mapEntity.getId() == null) {
|
||||
jpaEntity.setId(StringKeyConvertor.UUIDKey.INSTANCE.yieldNewUniqueKey().toString());
|
||||
}
|
||||
setEntityVersion(jpaEntity);
|
||||
em.persist(jpaEntity);
|
||||
return (E) jpaEntity;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public E read(String key) {
|
||||
if (key == null) return null;
|
||||
UUID uuid = StringKeyConvertor.UUIDKey.INSTANCE.fromStringSafe(key);
|
||||
if (uuid == null) return null;
|
||||
return (E) em.find(entityType, uuid);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Stream<E> read(QueryParameters<M> queryParameters) {
|
||||
JpaModelCriteriaBuilder mcb = queryParameters.getModelCriteriaBuilder()
|
||||
.flashToModelCriteriaBuilder(createJpaModelCriteriaBuilder());
|
||||
|
||||
CriteriaBuilder cb = em.getCriteriaBuilder();
|
||||
CriteriaQuery<RE> query = cb.createQuery(entityType);
|
||||
Root<RE> root = query.from(entityType);
|
||||
query.select(selectCbConstruct(cb, root));
|
||||
|
||||
//ordering
|
||||
if (!queryParameters.getOrderBy().isEmpty()) {
|
||||
List<Order> orderByList = new LinkedList<>();
|
||||
for (QueryParameters.OrderBy<M> order : queryParameters.getOrderBy()) {
|
||||
switch (order.getOrder()) {
|
||||
case ASCENDING:
|
||||
orderByList.add(cb.asc(root.get(order.getModelField().getName())));
|
||||
break;
|
||||
case DESCENDING:
|
||||
orderByList.add(cb.desc(root.get(order.getModelField().getName())));
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException("Unknown ordering.");
|
||||
}
|
||||
}
|
||||
query.orderBy(orderByList);
|
||||
}
|
||||
|
||||
BiFunction<CriteriaBuilder, Root<RE>, Predicate> predicateFunc = mcb.getPredicateFunc();
|
||||
if (predicateFunc != null) query.where(predicateFunc.apply(cb, root));
|
||||
|
||||
return closing(paginateQuery(em.createQuery(query), queryParameters.getOffset(), queryParameters.getLimit()).getResultStream())
|
||||
.map(this::mapToEntityDelegate);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public long getCount(QueryParameters<M> queryParameters) {
|
||||
JpaModelCriteriaBuilder mcb = queryParameters.getModelCriteriaBuilder()
|
||||
.flashToModelCriteriaBuilder(createJpaModelCriteriaBuilder());
|
||||
|
||||
CriteriaBuilder cb = em.getCriteriaBuilder();
|
||||
|
||||
CriteriaQuery<Long> countQuery = cb.createQuery(Long.class);
|
||||
Root<RE> root = countQuery.from(entityType);
|
||||
countQuery.select(cb.count(root));
|
||||
|
||||
BiFunction<CriteriaBuilder, Root<RE>, Predicate> predicateFunc = mcb.getPredicateFunc();
|
||||
if (predicateFunc != null) countQuery.where(predicateFunc.apply(cb, root));
|
||||
|
||||
return em.createQuery(countQuery).getSingleResult();
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public boolean delete(String key) {
|
||||
if (key == null) return false;
|
||||
UUID uuid = UUIDKey.INSTANCE.fromStringSafe(key);
|
||||
if (uuid == null) return false;
|
||||
em.remove(em.getReference(entityType, uuid));
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public long delete(QueryParameters<M> queryParameters) {
|
||||
JpaModelCriteriaBuilder mcb = queryParameters.getModelCriteriaBuilder()
|
||||
.flashToModelCriteriaBuilder(createJpaModelCriteriaBuilder());
|
||||
|
||||
CriteriaBuilder cb = em.getCriteriaBuilder();
|
||||
|
||||
CriteriaDelete<RE> deleteQuery = cb.createCriteriaDelete(entityType);
|
||||
|
||||
Root<RE> root = deleteQuery.from(entityType);
|
||||
|
||||
BiFunction<CriteriaBuilder, Root<RE>, Predicate> predicateFunc = mcb.getPredicateFunc();
|
||||
if (predicateFunc != null) deleteQuery.where(predicateFunc.apply(cb, root));
|
||||
|
||||
// TODO find out if the flush and clear are needed here or not, since delete(QueryParameters)
|
||||
// is not used yet from the code it's difficult to investigate its potential purpose here
|
||||
// according to https://thorben-janssen.com/5-common-hibernate-mistakes-that-cause-dozens-of-unexpected-queries/#Remove_Child_Entities_With_a_Bulk_Operation
|
||||
// it seems it is necessary unless it is sure that any of removed entities wasn't fetched
|
||||
// Once KEYCLOAK-19697 is done we could test our scenarios and see if we need the flush and clear
|
||||
// em.flush();
|
||||
// em.clear();
|
||||
|
||||
return em.createQuery(deleteQuery).executeUpdate();
|
||||
}
|
||||
}
|
|
@ -39,6 +39,7 @@ import org.keycloak.common.util.StringPropertyReplacer;
|
|||
import org.keycloak.component.AmphibianProviderFactory;
|
||||
import org.keycloak.connections.jpa.util.JpaUtils;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.ClientScopeModel;
|
||||
import org.keycloak.models.map.storage.jpa.client.entity.JpaClientEntity;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
|
@ -51,6 +52,8 @@ import org.keycloak.models.map.storage.MapKeycloakTransaction;
|
|||
import org.keycloak.models.map.storage.MapStorageProvider;
|
||||
import org.keycloak.models.map.storage.MapStorageProviderFactory;
|
||||
import org.keycloak.models.map.storage.jpa.client.JpaClientMapKeycloakTransaction;
|
||||
import org.keycloak.models.map.storage.jpa.clientscope.JpaClientScopeMapKeycloakTransaction;
|
||||
import org.keycloak.models.map.storage.jpa.clientscope.entity.JpaClientScopeEntity;
|
||||
import org.keycloak.models.map.storage.jpa.role.JpaRoleMapKeycloakTransaction;
|
||||
import org.keycloak.models.map.storage.jpa.role.entity.JpaRoleEntity;
|
||||
import org.keycloak.models.map.storage.jpa.updater.MapJpaUpdaterProvider;
|
||||
|
@ -73,12 +76,15 @@ public class JpaMapStorageProviderFactory implements
|
|||
//client
|
||||
.constructor(JpaClientEntity.class, JpaClientEntity::new)
|
||||
.constructor(MapProtocolMapperEntity.class, MapProtocolMapperEntityImpl::new)
|
||||
//client-scope
|
||||
.constructor(JpaClientScopeEntity.class, JpaClientScopeEntity::new)
|
||||
//role
|
||||
.constructor(JpaRoleEntity.class, JpaRoleEntity::new)
|
||||
.build();
|
||||
|
||||
private static final Map<Class<?>, Function<EntityManager, MapKeycloakTransaction>> MODEL_TO_TX = new HashMap<>();
|
||||
static {
|
||||
MODEL_TO_TX.put(ClientScopeModel.class, JpaClientScopeMapKeycloakTransaction::new);
|
||||
MODEL_TO_TX.put(ClientModel.class, JpaClientMapKeycloakTransaction::new);
|
||||
MODEL_TO_TX.put(RoleModel.class, JpaRoleMapKeycloakTransaction::new);
|
||||
}
|
||||
|
|
|
@ -16,13 +16,27 @@
|
|||
*/
|
||||
package org.keycloak.models.map.storage.jpa;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.keycloak.models.map.common.AbstractEntity;
|
||||
|
||||
/**
|
||||
* Interface for all root entities in the JPA storage.
|
||||
*/
|
||||
public interface JpaRootEntity {
|
||||
public interface JpaRootEntity extends AbstractEntity, Serializable {
|
||||
|
||||
/**
|
||||
* Version of the JPA entity used for optimistic locking
|
||||
*/
|
||||
int getVersion();
|
||||
|
||||
/**
|
||||
* @return current supported version of the JPA entity used for schema versioning.
|
||||
*/
|
||||
Integer getEntityVersion();
|
||||
|
||||
/**
|
||||
* @param entityVersion sets current supported version to JPA entity.
|
||||
*/
|
||||
void setEntityVersion(Integer entityVersion);
|
||||
}
|
||||
|
|
|
@ -16,65 +16,30 @@
|
|||
*/
|
||||
package org.keycloak.models.map.storage.jpa.client;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Stream;
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.criteria.CriteriaBuilder;
|
||||
import javax.persistence.criteria.CriteriaDelete;
|
||||
import javax.persistence.criteria.CriteriaQuery;
|
||||
import javax.persistence.criteria.Order;
|
||||
import javax.persistence.criteria.Root;
|
||||
import org.keycloak.connections.jpa.JpaKeycloakTransaction;
|
||||
import javax.persistence.criteria.Selection;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import static org.keycloak.models.jpa.PaginationUtils.paginateQuery;
|
||||
import org.keycloak.models.map.client.MapClientEntity;
|
||||
import org.keycloak.models.map.client.MapClientEntityDelegate;
|
||||
import org.keycloak.models.map.common.StringKeyConvertor.UUIDKey;
|
||||
import org.keycloak.models.map.storage.jpa.client.delegate.JpaClientDelegateProvider;
|
||||
import org.keycloak.models.map.storage.jpa.client.entity.JpaClientEntity;
|
||||
import static org.keycloak.models.map.storage.jpa.JpaMapStorageProviderFactory.CLONER;
|
||||
import org.keycloak.models.map.storage.MapKeycloakTransaction;
|
||||
import org.keycloak.models.map.storage.QueryParameters;
|
||||
import static org.keycloak.models.map.storage.jpa.Constants.SUPPORTED_VERSION_CLIENT;
|
||||
import static org.keycloak.utils.StreamsUtil.closing;
|
||||
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_CLIENT;
|
||||
import org.keycloak.models.map.storage.jpa.JpaMapKeycloakTransaction;
|
||||
import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder;
|
||||
import org.keycloak.models.map.storage.jpa.JpaRootEntity;
|
||||
import org.keycloak.models.map.storage.jpa.client.delegate.JpaClientDelegateProvider;
|
||||
|
||||
public class JpaClientMapKeycloakTransaction extends JpaKeycloakTransaction implements MapKeycloakTransaction<MapClientEntity, ClientModel> {
|
||||
public class JpaClientMapKeycloakTransaction extends JpaMapKeycloakTransaction<JpaClientEntity, MapClientEntity, ClientModel> {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public JpaClientMapKeycloakTransaction(EntityManager em) {
|
||||
super(em);
|
||||
super(JpaClientEntity.class, em);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MapClientEntity create(MapClientEntity mapEntity) {
|
||||
JpaClientEntity jpaEntity = (JpaClientEntity) CLONER.from(mapEntity);
|
||||
if (mapEntity.getId() == null) {
|
||||
jpaEntity.setId(UUIDKey.INSTANCE.yieldNewUniqueKey().toString());
|
||||
}
|
||||
jpaEntity.setEntityVersion(SUPPORTED_VERSION_CLIENT);
|
||||
em.persist(jpaEntity);
|
||||
return jpaEntity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MapClientEntity read(String key) {
|
||||
if (key == null) return null;
|
||||
UUID uuid = UUIDKey.INSTANCE.fromStringSafe(key);
|
||||
if (uuid == null) return null;
|
||||
|
||||
return em.find(JpaClientEntity.class, uuid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<MapClientEntity> read(QueryParameters<ClientModel> queryParameters) {
|
||||
JpaClientModelCriteriaBuilder mcb = queryParameters.getModelCriteriaBuilder()
|
||||
.flashToModelCriteriaBuilder(new JpaClientModelCriteriaBuilder());
|
||||
|
||||
CriteriaBuilder cb = em.getCriteriaBuilder();
|
||||
CriteriaQuery<JpaClientEntity> query = cb.createQuery(JpaClientEntity.class);
|
||||
Root<JpaClientEntity> root = query.from(JpaClientEntity.class);
|
||||
query.select(cb.construct(JpaClientEntity.class,
|
||||
public Selection<JpaClientEntity> selectCbConstruct(CriteriaBuilder cb, Root<JpaClientEntity> root) {
|
||||
return cb.construct(JpaClientEntity.class,
|
||||
root.get("id"),
|
||||
root.get("version"),
|
||||
root.get("entityVersion"),
|
||||
|
@ -82,81 +47,21 @@ public class JpaClientMapKeycloakTransaction extends JpaKeycloakTransaction impl
|
|||
root.get("clientId"),
|
||||
root.get("protocol"),
|
||||
root.get("enabled")
|
||||
));
|
||||
|
||||
//ordering
|
||||
if (!queryParameters.getOrderBy().isEmpty()) {
|
||||
List<Order> orderByList = new LinkedList<>();
|
||||
for (QueryParameters.OrderBy<ClientModel> order : queryParameters.getOrderBy()) {
|
||||
switch (order.getOrder()) {
|
||||
case ASCENDING:
|
||||
orderByList.add(cb.asc(root.get(order.getModelField().getName())));
|
||||
break;
|
||||
case DESCENDING:
|
||||
orderByList.add(cb.desc(root.get(order.getModelField().getName())));
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException("Unknown ordering.");
|
||||
}
|
||||
}
|
||||
query.orderBy(orderByList);
|
||||
}
|
||||
|
||||
if (mcb.getPredicateFunc() != null) query.where(mcb.getPredicateFunc().apply(cb, root));
|
||||
|
||||
return closing(
|
||||
paginateQuery(em.createQuery(query), queryParameters.getOffset(), queryParameters.getLimit())
|
||||
.getResultStream())
|
||||
.map(c -> new MapClientEntityDelegate(new JpaClientDelegateProvider(c, em)));
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getCount(QueryParameters<ClientModel> queryParameters) {
|
||||
JpaClientModelCriteriaBuilder mcb = queryParameters.getModelCriteriaBuilder()
|
||||
.flashToModelCriteriaBuilder(new JpaClientModelCriteriaBuilder());
|
||||
|
||||
CriteriaBuilder cb = em.getCriteriaBuilder();
|
||||
|
||||
CriteriaQuery<Long> countQuery = cb.createQuery(Long.class);
|
||||
Root<JpaClientEntity> root = countQuery.from(JpaClientEntity.class);
|
||||
countQuery.select(cb.count(root));
|
||||
|
||||
if (mcb.getPredicateFunc() != null) countQuery.where(mcb.getPredicateFunc().apply(cb, root));
|
||||
|
||||
return em.createQuery(countQuery).getSingleResult();
|
||||
public void setEntityVersion(JpaRootEntity entity) {
|
||||
entity.setEntityVersion(CURRENT_SCHEMA_VERSION_CLIENT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean delete(String key) {
|
||||
if (key == null) return false;
|
||||
UUID uuid = UUIDKey.INSTANCE.fromStringSafe(key);
|
||||
if (uuid == null) return false;
|
||||
em.remove(em.getReference(JpaClientEntity.class, uuid));
|
||||
return true;
|
||||
public JpaModelCriteriaBuilder createJpaModelCriteriaBuilder() {
|
||||
return new JpaClientModelCriteriaBuilder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long delete(QueryParameters<ClientModel> queryParameters) {
|
||||
JpaClientModelCriteriaBuilder mcb = queryParameters.getModelCriteriaBuilder()
|
||||
.flashToModelCriteriaBuilder(new JpaClientModelCriteriaBuilder());
|
||||
|
||||
CriteriaBuilder cb = em.getCriteriaBuilder();
|
||||
|
||||
CriteriaDelete<JpaClientEntity> deleteQuery = cb.createCriteriaDelete(JpaClientEntity.class);
|
||||
|
||||
Root<JpaClientEntity> root = deleteQuery.from(JpaClientEntity.class);
|
||||
|
||||
if (mcb.getPredicateFunc() != null) deleteQuery.where(mcb.getPredicateFunc().apply(cb, root));
|
||||
|
||||
// TODO find out if the flush and clear are needed here or not, since delete(QueryParameters)
|
||||
// is not used yet from the code it's difficult to investigate its potential purpose here
|
||||
// according to https://thorben-janssen.com/5-common-hibernate-mistakes-that-cause-dozens-of-unexpected-queries/#Remove_Child_Entities_With_a_Bulk_Operation
|
||||
// it seems it is necessary unless it is sure that any of removed entities wasn't fetched
|
||||
// Once KEYCLOAK-19697 is done we could test our scenarios and see if we need the flush and clear
|
||||
// em.flush();
|
||||
// em.clear();
|
||||
|
||||
return em.createQuery(deleteQuery).executeUpdate();
|
||||
protected MapClientEntity mapToEntityDelegate(JpaClientEntity original) {
|
||||
return new MapClientEntityDelegate(new JpaClientDelegateProvider(original, em));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
*/
|
||||
package org.keycloak.models.map.storage.jpa.client.entity;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
|
@ -44,8 +43,7 @@ import org.hibernate.annotations.TypeDefs;
|
|||
import org.keycloak.models.map.client.MapClientEntity.AbstractClientEntity;
|
||||
import org.keycloak.models.map.client.MapProtocolMapperEntity;
|
||||
import org.keycloak.models.map.common.DeepCloner;
|
||||
import static org.keycloak.models.map.storage.jpa.Constants.SUPPORTED_VERSION_CLIENT;
|
||||
|
||||
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_CLIENT;
|
||||
import org.keycloak.models.map.storage.jpa.JpaRootEntity;
|
||||
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType;
|
||||
|
||||
|
@ -62,7 +60,7 @@ import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType;
|
|||
)
|
||||
})
|
||||
@TypeDefs({@TypeDef(name = "jsonb", typeClass = JsonbType.class)})
|
||||
public class JpaClientEntity extends AbstractClientEntity implements Serializable, JpaRootEntity {
|
||||
public class JpaClientEntity extends AbstractClientEntity implements JpaRootEntity {
|
||||
|
||||
@Id
|
||||
@Column
|
||||
|
@ -137,16 +135,18 @@ public class JpaClientEntity extends AbstractClientEntity implements Serializabl
|
|||
*/
|
||||
private void checkEntityVersionForUpdate() {
|
||||
Integer ev = getEntityVersion();
|
||||
if (ev != null && ev < SUPPORTED_VERSION_CLIENT) {
|
||||
setEntityVersion(SUPPORTED_VERSION_CLIENT);
|
||||
if (ev != null && ev < CURRENT_SCHEMA_VERSION_CLIENT) {
|
||||
setEntityVersion(CURRENT_SCHEMA_VERSION_CLIENT);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getEntityVersion() {
|
||||
if (isMetadataInitialized()) return metadata.getEntityVersion();
|
||||
return entityVersion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEntityVersion(Integer entityVersion) {
|
||||
metadata.setEntityVersion(entityVersion);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* 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.jpa.clientscope;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.criteria.CriteriaBuilder;
|
||||
import javax.persistence.criteria.Root;
|
||||
import javax.persistence.criteria.Selection;
|
||||
import org.keycloak.models.ClientScopeModel;
|
||||
import org.keycloak.models.map.clientscope.MapClientScopeEntity;
|
||||
import org.keycloak.models.map.clientscope.MapClientScopeEntityDelegate;
|
||||
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_CLIENT_SCOPE;
|
||||
import org.keycloak.models.map.storage.jpa.JpaMapKeycloakTransaction;
|
||||
import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder;
|
||||
import org.keycloak.models.map.storage.jpa.JpaRootEntity;
|
||||
import org.keycloak.models.map.storage.jpa.clientscope.delegate.JpaClientScopeDelegateProvider;
|
||||
import org.keycloak.models.map.storage.jpa.clientscope.entity.JpaClientScopeEntity;
|
||||
|
||||
public class JpaClientScopeMapKeycloakTransaction extends JpaMapKeycloakTransaction<JpaClientScopeEntity, MapClientScopeEntity, ClientScopeModel> {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public JpaClientScopeMapKeycloakTransaction(EntityManager em) {
|
||||
super(JpaClientScopeEntity.class, em);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Selection<JpaClientScopeEntity> selectCbConstruct(CriteriaBuilder cb, Root<JpaClientScopeEntity> root) {
|
||||
return cb.construct(JpaClientScopeEntity.class,
|
||||
root.get("id"),
|
||||
root.get("version"),
|
||||
root.get("entityVersion"),
|
||||
root.get("realmId"),
|
||||
root.get("name"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEntityVersion(JpaRootEntity entity) {
|
||||
entity.setEntityVersion(CURRENT_SCHEMA_VERSION_CLIENT_SCOPE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JpaModelCriteriaBuilder createJpaModelCriteriaBuilder() {
|
||||
return new JpaClientScopeModelCriteriaBuilder();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MapClientScopeEntity mapToEntityDelegate(JpaClientScopeEntity original) {
|
||||
return new MapClientScopeEntityDelegate(new JpaClientScopeDelegateProvider(original, em));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* 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.jpa.clientscope;
|
||||
|
||||
import java.util.function.BiFunction;
|
||||
import javax.persistence.criteria.CriteriaBuilder;
|
||||
import javax.persistence.criteria.Predicate;
|
||||
import javax.persistence.criteria.Root;
|
||||
import org.keycloak.models.ClientScopeModel;
|
||||
import org.keycloak.models.ClientScopeModel.SearchableFields;
|
||||
import org.keycloak.models.map.storage.CriterionNotSupportedException;
|
||||
import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder;
|
||||
import org.keycloak.models.map.storage.jpa.clientscope.entity.JpaClientScopeEntity;
|
||||
import org.keycloak.storage.SearchableModelField;
|
||||
|
||||
public class JpaClientScopeModelCriteriaBuilder extends JpaModelCriteriaBuilder<JpaClientScopeEntity, ClientScopeModel, JpaClientScopeModelCriteriaBuilder> {
|
||||
|
||||
public JpaClientScopeModelCriteriaBuilder() {
|
||||
super(JpaClientScopeModelCriteriaBuilder::new);
|
||||
}
|
||||
|
||||
private JpaClientScopeModelCriteriaBuilder(BiFunction<CriteriaBuilder, Root<JpaClientScopeEntity>, Predicate> predicateFunc) {
|
||||
super(JpaClientScopeModelCriteriaBuilder::new, predicateFunc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JpaClientScopeModelCriteriaBuilder compare(SearchableModelField<? super ClientScopeModel> modelField, Operator op, Object... value) {
|
||||
switch (op) {
|
||||
case EQ:
|
||||
if (modelField.equals(SearchableFields.REALM_ID) ||
|
||||
modelField.equals(SearchableFields.NAME)) {
|
||||
|
||||
validateValue(value, modelField, op, String.class);
|
||||
|
||||
return new JpaClientScopeModelCriteriaBuilder((cb, root) ->
|
||||
cb.equal(root.get(modelField.getName()), value[0])
|
||||
);
|
||||
} else {
|
||||
throw new CriterionNotSupportedException(modelField, op);
|
||||
}
|
||||
|
||||
default:
|
||||
throw new CriterionNotSupportedException(modelField, op);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* 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.jpa.clientscope.delegate;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.criteria.CriteriaBuilder;
|
||||
import javax.persistence.criteria.CriteriaQuery;
|
||||
import javax.persistence.criteria.JoinType;
|
||||
import javax.persistence.criteria.Root;
|
||||
|
||||
import org.keycloak.models.map.clientscope.MapClientScopeEntity;
|
||||
import org.keycloak.models.map.clientscope.MapClientScopeEntityFields;
|
||||
import org.keycloak.models.map.common.EntityField;
|
||||
import org.keycloak.models.map.common.delegate.DelegateProvider;
|
||||
import org.keycloak.models.map.storage.jpa.clientscope.entity.JpaClientScopeEntity;
|
||||
|
||||
public class JpaClientScopeDelegateProvider implements DelegateProvider<MapClientScopeEntity> {
|
||||
|
||||
private JpaClientScopeEntity delegate;
|
||||
private final EntityManager em;
|
||||
|
||||
public JpaClientScopeDelegateProvider(JpaClientScopeEntity delegate, EntityManager em) {
|
||||
this.delegate = delegate;
|
||||
this.em = em;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MapClientScopeEntity getDelegate(boolean isRead, Enum<? extends EntityField<MapClientScopeEntity>> field, Object... parameters) {
|
||||
if (delegate.isMetadataInitialized()) return delegate;
|
||||
if (isRead) {
|
||||
if (field instanceof MapClientScopeEntityFields) {
|
||||
switch ((MapClientScopeEntityFields) field) {
|
||||
case ID:
|
||||
case REALM_ID:
|
||||
case NAME:
|
||||
return delegate;
|
||||
|
||||
case ATTRIBUTES:
|
||||
CriteriaBuilder cb = em.getCriteriaBuilder();
|
||||
CriteriaQuery<JpaClientScopeEntity> query = cb.createQuery(JpaClientScopeEntity.class);
|
||||
Root<JpaClientScopeEntity> root = query.from(JpaClientScopeEntity.class);
|
||||
root.fetch("attributes", JoinType.INNER);
|
||||
query.select(root).where(cb.equal(root.get("id"), UUID.fromString(delegate.getId())));
|
||||
|
||||
delegate = em.createQuery(query).getSingleResult();
|
||||
break;
|
||||
|
||||
default:
|
||||
delegate = em.find(JpaClientScopeEntity.class, UUID.fromString(delegate.getId()));
|
||||
}
|
||||
} else throw new IllegalStateException("Not a valid client scope field: " + field);
|
||||
} else {
|
||||
delegate = em.find(JpaClientScopeEntity.class, UUID.fromString(delegate.getId()));
|
||||
}
|
||||
return delegate;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
* 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.jpa.clientscope.entity;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.FetchType;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.ManyToOne;
|
||||
import javax.persistence.Table;
|
||||
import org.hibernate.annotations.Nationalized;
|
||||
|
||||
@Entity
|
||||
@Table(name = "client_scope_attribute")
|
||||
public class JpaClientScopeAttributeEntity implements Serializable {
|
||||
|
||||
@Id
|
||||
@Column
|
||||
@GeneratedValue
|
||||
private UUID id;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name="fk_client_scope")
|
||||
private JpaClientScopeEntity clientScope;
|
||||
|
||||
@Column
|
||||
private String name;
|
||||
|
||||
@Nationalized
|
||||
@Column
|
||||
private String value;
|
||||
|
||||
public JpaClientScopeAttributeEntity() {
|
||||
}
|
||||
|
||||
public JpaClientScopeAttributeEntity(JpaClientScopeEntity clientScope, String name, String value) {
|
||||
this.clientScope = clientScope;
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public UUID getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public JpaClientScopeEntity getClientScope() {
|
||||
return clientScope;
|
||||
}
|
||||
|
||||
public void setClientScope(JpaClientScopeEntity clientScope) {
|
||||
this.clientScope = clientScope;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getClass().hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!(obj instanceof JpaClientScopeAttributeEntity)) return false;
|
||||
JpaClientScopeAttributeEntity that = (JpaClientScopeAttributeEntity) obj;
|
||||
return Objects.equals(getClientScope(), that.getClientScope()) &&
|
||||
Objects.equals(getName(), that.getName()) &&
|
||||
Objects.equals(getValue(), that.getValue());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,293 @@
|
|||
/*
|
||||
* 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.jpa.clientscope.entity;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.persistence.Basic;
|
||||
import javax.persistence.CascadeType;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.FetchType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.OneToMany;
|
||||
import javax.persistence.Table;
|
||||
import javax.persistence.UniqueConstraint;
|
||||
import javax.persistence.Version;
|
||||
import org.hibernate.annotations.Type;
|
||||
import org.hibernate.annotations.TypeDef;
|
||||
import org.hibernate.annotations.TypeDefs;
|
||||
import org.keycloak.models.map.client.MapProtocolMapperEntity;
|
||||
import org.keycloak.models.map.clientscope.MapClientScopeEntity.AbstractClientScopeEntity;
|
||||
import org.keycloak.models.map.common.DeepCloner;
|
||||
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_CLIENT_SCOPE;
|
||||
import org.keycloak.models.map.storage.jpa.JpaRootEntity;
|
||||
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType;
|
||||
|
||||
/**
|
||||
* There are some fields marked by {@code @Column(insertable = false, updatable = false)}.
|
||||
* Those fields are automatically generated by database from json field,
|
||||
* therefore marked as non-insertable and non-updatable to instruct hibernate.
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "client_scope", uniqueConstraints = {@UniqueConstraint(columnNames = {"realmId", "name"})})
|
||||
@TypeDefs({@TypeDef(name = "jsonb", typeClass = JsonbType.class)})
|
||||
public class JpaClientScopeEntity extends AbstractClientScopeEntity implements JpaRootEntity {
|
||||
|
||||
@Id
|
||||
@Column
|
||||
private UUID id;
|
||||
|
||||
//used for implicit optimistic locking
|
||||
@Version
|
||||
@Column
|
||||
private int version;
|
||||
|
||||
@Type(type = "jsonb")
|
||||
@Column(columnDefinition = "jsonb")
|
||||
private final JpaClientScopeMetadata metadata;
|
||||
|
||||
@Column(insertable = false, updatable = false)
|
||||
@Basic(fetch = FetchType.LAZY)
|
||||
private Integer entityVersion;
|
||||
|
||||
@Column(insertable = false, updatable = false)
|
||||
@Basic(fetch = FetchType.LAZY)
|
||||
private String realmId;
|
||||
|
||||
@Column(insertable = false, updatable = false)
|
||||
@Basic(fetch = FetchType.LAZY)
|
||||
private String name;
|
||||
|
||||
@OneToMany(mappedBy = "clientScope", cascade = CascadeType.PERSIST, orphanRemoval = true)
|
||||
private final Set<JpaClientScopeAttributeEntity> attributes = new HashSet<>();
|
||||
|
||||
/**
|
||||
* No-argument constructor, used by hibernate to instantiate entities.
|
||||
*/
|
||||
public JpaClientScopeEntity() {
|
||||
this.metadata = new JpaClientScopeMetadata();
|
||||
}
|
||||
|
||||
public JpaClientScopeEntity(DeepCloner cloner) {
|
||||
this.metadata = new JpaClientScopeMetadata(cloner);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by hibernate when calling cb.construct from read(QueryParameters) method.
|
||||
* It is used to select client without metadata(json) field.
|
||||
*/
|
||||
public JpaClientScopeEntity(UUID id, int version, Integer entityVersion, String realmId, String name) {
|
||||
this.id = id;
|
||||
this.version = version;
|
||||
this.entityVersion = entityVersion;
|
||||
this.realmId = realmId;
|
||||
this.name = name;
|
||||
this.metadata = null;
|
||||
}
|
||||
|
||||
public boolean isMetadataInitialized() {
|
||||
return metadata != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* In case of any update on entity, we want to update the entityVerion
|
||||
* to current one.
|
||||
*/
|
||||
private void checkEntityVersionForUpdate() {
|
||||
Integer ev = getEntityVersion();
|
||||
if (ev != null && ev < CURRENT_SCHEMA_VERSION_CLIENT_SCOPE) {
|
||||
setEntityVersion(CURRENT_SCHEMA_VERSION_CLIENT_SCOPE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getEntityVersion() {
|
||||
if (isMetadataInitialized()) return metadata.getEntityVersion();
|
||||
return entityVersion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEntityVersion(Integer entityVersion) {
|
||||
metadata.setEntityVersion(entityVersion);
|
||||
}
|
||||
|
||||
public int getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return id == null ? null : id.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setId(String id) {
|
||||
this.id = id == null ? null : UUID.fromString(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRealmId() {
|
||||
if (isMetadataInitialized()) return metadata.getRealmId();
|
||||
return realmId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRealmId(String realmId) {
|
||||
checkEntityVersionForUpdate();
|
||||
metadata.setRealmId(realmId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<MapProtocolMapperEntity> getProtocolMappers() {
|
||||
return metadata.getProtocolMappers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addProtocolMapper(MapProtocolMapperEntity mapping) {
|
||||
checkEntityVersionForUpdate();
|
||||
metadata.addProtocolMapper(mapping);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addScopeMapping(String id) {
|
||||
checkEntityVersionForUpdate();
|
||||
metadata.addScopeMapping(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeScopeMapping(String id) {
|
||||
checkEntityVersionForUpdate();
|
||||
metadata.removeScopeMapping(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getScopeMappings() {
|
||||
return metadata.getScopeMappings();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return metadata.getDescription();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDescription(String description) {
|
||||
checkEntityVersionForUpdate();
|
||||
metadata.setDescription(description);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
if (isMetadataInitialized()) return metadata.getName();
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setName(String name) {
|
||||
checkEntityVersionForUpdate();
|
||||
metadata.setName(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProtocol() {
|
||||
return metadata.getProtocol();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProtocol(String protocol) {
|
||||
checkEntityVersionForUpdate();
|
||||
metadata.setProtocol(protocol);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAttribute(String name) {
|
||||
checkEntityVersionForUpdate();
|
||||
for (Iterator<JpaClientScopeAttributeEntity> iterator = attributes.iterator(); iterator.hasNext();) {
|
||||
JpaClientScopeAttributeEntity attr = iterator.next();
|
||||
if (Objects.equals(attr.getName(), name)) {
|
||||
iterator.remove();
|
||||
attr.setClientScope(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttribute(String name, List<String> values) {
|
||||
checkEntityVersionForUpdate();
|
||||
removeAttribute(name);
|
||||
for (String value : values) {
|
||||
JpaClientScopeAttributeEntity attribute = new JpaClientScopeAttributeEntity(this, name, value);
|
||||
attributes.add(attribute);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getAttribute(String name) {
|
||||
return attributes.stream()
|
||||
.filter(a -> Objects.equals(a.getName(), name))
|
||||
.map(JpaClientScopeAttributeEntity::getValue)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, List<String>> getAttributes() {
|
||||
Map<String, List<String>> result = new HashMap<>();
|
||||
for (JpaClientScopeAttributeEntity attribute : attributes) {
|
||||
List<String> values = result.getOrDefault(attribute.getName(), new LinkedList<>());
|
||||
values.add(attribute.getValue());
|
||||
result.put(attribute.getName(), values);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttributes(Map<String, List<String>> attributes) {
|
||||
checkEntityVersionForUpdate();
|
||||
for (Iterator<JpaClientScopeAttributeEntity> iterator = this.attributes.iterator(); iterator.hasNext();) {
|
||||
JpaClientScopeAttributeEntity attr = iterator.next();
|
||||
iterator.remove();
|
||||
attr.setClientScope(null);
|
||||
}
|
||||
if (attributes != null) {
|
||||
for (Map.Entry<String, List<String>> attrEntry : attributes.entrySet()) {
|
||||
setAttribute(attrEntry.getKey(), attrEntry.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getClass().hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!(obj instanceof JpaClientScopeEntity)) return false;
|
||||
return Objects.equals(getId(), ((JpaClientScopeEntity) obj).getId());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* 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.jpa.clientscope.entity;
|
||||
|
||||
import java.io.Serializable;
|
||||
import org.keycloak.models.map.clientscope.MapClientScopeEntityImpl;
|
||||
import org.keycloak.models.map.common.DeepCloner;
|
||||
|
||||
public class JpaClientScopeMetadata extends MapClientScopeEntityImpl implements Serializable {
|
||||
|
||||
public JpaClientScopeMetadata(DeepCloner cloner) {
|
||||
super(cloner);
|
||||
}
|
||||
|
||||
public JpaClientScopeMetadata() {
|
||||
super();
|
||||
}
|
||||
|
||||
private Integer entityVersion;
|
||||
|
||||
public Integer getEntityVersion() {
|
||||
return entityVersion;
|
||||
}
|
||||
|
||||
public void setEntityVersion(Integer entityVersion) {
|
||||
this.entityVersion = entityVersion;
|
||||
}
|
||||
|
||||
}
|
|
@ -22,19 +22,25 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_CLIENT;
|
||||
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_CLIENT_SCOPE;
|
||||
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_ROLE;
|
||||
import org.keycloak.models.map.storage.jpa.client.entity.JpaClientMetadata;
|
||||
import org.keycloak.models.map.storage.jpa.clientscope.entity.JpaClientScopeMetadata;
|
||||
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaClientMigration;
|
||||
import static org.keycloak.models.map.storage.jpa.Constants.SUPPORTED_VERSION_CLIENT;
|
||||
import static org.keycloak.models.map.storage.jpa.Constants.SUPPORTED_VERSION_ROLE;
|
||||
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaClientScopeMigration;
|
||||
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaRoleMigration;
|
||||
import org.keycloak.models.map.storage.jpa.role.entity.JpaRoleMetadata;
|
||||
|
||||
|
||||
|
||||
public class JpaEntityMigration {
|
||||
|
||||
static final Map<Class<?>, BiFunction<ObjectNode, Integer, ObjectNode>> MIGRATIONS = new HashMap<>();
|
||||
static {
|
||||
MIGRATIONS.put(JpaClientMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, SUPPORTED_VERSION_CLIENT, tree, JpaClientMigration.MIGRATORS));
|
||||
MIGRATIONS.put(JpaRoleMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, SUPPORTED_VERSION_ROLE, tree, JpaRoleMigration.MIGRATORS));
|
||||
MIGRATIONS.put(JpaClientMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_CLIENT, tree, JpaClientMigration.MIGRATORS));
|
||||
MIGRATIONS.put(JpaClientScopeMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_CLIENT_SCOPE, tree, JpaClientScopeMigration.MIGRATORS));
|
||||
MIGRATIONS.put(JpaRoleMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_ROLE, tree, JpaRoleMigration.MIGRATORS));
|
||||
}
|
||||
|
||||
private static ObjectNode migrateTreeTo(int entityVersion, Integer supportedVersion, ObjectNode node, List<Function<ObjectNode, ObjectNode>> migrators) {
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* 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.jpa.hibernate.jsonb.migration;
|
||||
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class JpaClientScopeMigration {
|
||||
|
||||
public static final List<Function<ObjectNode, ObjectNode>> MIGRATORS = Arrays.asList(
|
||||
o -> o // no migration yet
|
||||
);
|
||||
}
|
|
@ -16,65 +16,30 @@
|
|||
*/
|
||||
package org.keycloak.models.map.storage.jpa.role;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Stream;
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.criteria.CriteriaBuilder;
|
||||
import javax.persistence.criteria.CriteriaDelete;
|
||||
import javax.persistence.criteria.CriteriaQuery;
|
||||
import javax.persistence.criteria.Order;
|
||||
import javax.persistence.criteria.Root;
|
||||
import org.keycloak.connections.jpa.JpaKeycloakTransaction;
|
||||
import javax.persistence.criteria.Selection;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import static org.keycloak.models.jpa.PaginationUtils.paginateQuery;
|
||||
import org.keycloak.models.map.common.StringKeyConvertor.UUIDKey;
|
||||
import org.keycloak.models.map.role.MapRoleEntity;
|
||||
import org.keycloak.models.map.role.MapRoleEntityDelegate;
|
||||
import org.keycloak.models.map.storage.MapKeycloakTransaction;
|
||||
import org.keycloak.models.map.storage.QueryParameters;
|
||||
import static org.keycloak.models.map.storage.jpa.Constants.SUPPORTED_VERSION_ROLE;
|
||||
import static org.keycloak.models.map.storage.jpa.JpaMapStorageProviderFactory.CLONER;
|
||||
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_ROLE;
|
||||
import org.keycloak.models.map.storage.jpa.JpaMapKeycloakTransaction;
|
||||
import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder;
|
||||
import org.keycloak.models.map.storage.jpa.JpaRootEntity;
|
||||
import org.keycloak.models.map.storage.jpa.role.delegate.JpaRoleDelegateProvider;
|
||||
import org.keycloak.models.map.storage.jpa.role.entity.JpaRoleEntity;
|
||||
import static org.keycloak.utils.StreamsUtil.closing;
|
||||
|
||||
public class JpaRoleMapKeycloakTransaction extends JpaKeycloakTransaction implements MapKeycloakTransaction<MapRoleEntity, RoleModel> {
|
||||
public class JpaRoleMapKeycloakTransaction extends JpaMapKeycloakTransaction<JpaRoleEntity, MapRoleEntity, RoleModel> {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public JpaRoleMapKeycloakTransaction(EntityManager em) {
|
||||
super(em);
|
||||
super(JpaRoleEntity.class, em);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MapRoleEntity create(MapRoleEntity mapEntity) {
|
||||
JpaRoleEntity jpaEntity = (JpaRoleEntity) CLONER.from(mapEntity);
|
||||
if (mapEntity.getId() == null) {
|
||||
jpaEntity.setId(UUIDKey.INSTANCE.yieldNewUniqueKey().toString());
|
||||
}
|
||||
jpaEntity.setEntityVersion(SUPPORTED_VERSION_ROLE);
|
||||
em.persist(jpaEntity);
|
||||
return jpaEntity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MapRoleEntity read(String key) {
|
||||
if (key == null) return null;
|
||||
UUID uuid = UUIDKey.INSTANCE.fromStringSafe(key);
|
||||
if (uuid == null) return null;
|
||||
|
||||
return em.find(JpaRoleEntity.class, uuid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<MapRoleEntity> read(QueryParameters<RoleModel> queryParameters) {
|
||||
JpaRoleModelCriteriaBuilder mcb = queryParameters.getModelCriteriaBuilder()
|
||||
.flashToModelCriteriaBuilder(new JpaRoleModelCriteriaBuilder());
|
||||
|
||||
CriteriaBuilder cb = em.getCriteriaBuilder();
|
||||
CriteriaQuery<JpaRoleEntity> query = cb.createQuery(JpaRoleEntity.class);
|
||||
Root<JpaRoleEntity> root = query.from(JpaRoleEntity.class);
|
||||
query.select(cb.construct(JpaRoleEntity.class,
|
||||
public Selection<JpaRoleEntity> selectCbConstruct(CriteriaBuilder cb, Root<JpaRoleEntity> root) {
|
||||
return cb.construct(JpaRoleEntity.class,
|
||||
root.get("id"),
|
||||
root.get("version"),
|
||||
root.get("entityVersion"),
|
||||
|
@ -82,80 +47,21 @@ public class JpaRoleMapKeycloakTransaction extends JpaKeycloakTransaction implem
|
|||
root.get("clientId"),
|
||||
root.get("name"),
|
||||
root.get("description")
|
||||
));
|
||||
|
||||
if (!queryParameters.getOrderBy().isEmpty()) {
|
||||
List<Order> orderByList = new LinkedList<>();
|
||||
for (QueryParameters.OrderBy<RoleModel> order : queryParameters.getOrderBy()) {
|
||||
switch (order.getOrder()) {
|
||||
case ASCENDING:
|
||||
orderByList.add(cb.asc(root.get(order.getModelField().getName())));
|
||||
break;
|
||||
case DESCENDING:
|
||||
orderByList.add(cb.desc(root.get(order.getModelField().getName())));
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException("Unknown ordering.");
|
||||
}
|
||||
}
|
||||
query.orderBy(orderByList);
|
||||
}
|
||||
|
||||
if (mcb.getPredicateFunc() != null) query.where(mcb.getPredicateFunc().apply(cb, root));
|
||||
|
||||
return closing(
|
||||
paginateQuery(em.createQuery(query), queryParameters.getOffset(), queryParameters.getLimit())
|
||||
.getResultStream())
|
||||
.map(r -> new MapRoleEntityDelegate(new JpaRoleDelegateProvider(r, em)));
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getCount(QueryParameters<RoleModel> queryParameters) {
|
||||
JpaRoleModelCriteriaBuilder mcb = queryParameters.getModelCriteriaBuilder()
|
||||
.flashToModelCriteriaBuilder(new JpaRoleModelCriteriaBuilder());
|
||||
|
||||
CriteriaBuilder cb = em.getCriteriaBuilder();
|
||||
|
||||
CriteriaQuery<Long> countQuery = cb.createQuery(Long.class);
|
||||
Root<JpaRoleEntity> root = countQuery.from(JpaRoleEntity.class);
|
||||
countQuery.select(cb.count(root));
|
||||
|
||||
if (mcb.getPredicateFunc() != null) countQuery.where(mcb.getPredicateFunc().apply(cb, root));
|
||||
|
||||
return em.createQuery(countQuery).getSingleResult();
|
||||
public void setEntityVersion(JpaRootEntity entity) {
|
||||
entity.setEntityVersion(CURRENT_SCHEMA_VERSION_ROLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean delete(String key) {
|
||||
if (key == null) return false;
|
||||
UUID uuid = UUIDKey.INSTANCE.fromStringSafe(key);
|
||||
if (uuid == null) return false;
|
||||
em.remove(em.getReference(JpaRoleEntity.class, uuid));
|
||||
return true;
|
||||
public JpaModelCriteriaBuilder createJpaModelCriteriaBuilder() {
|
||||
return new JpaRoleModelCriteriaBuilder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long delete(QueryParameters<RoleModel> queryParameters) {
|
||||
JpaRoleModelCriteriaBuilder mcb = queryParameters.getModelCriteriaBuilder()
|
||||
.flashToModelCriteriaBuilder(new JpaRoleModelCriteriaBuilder());
|
||||
|
||||
CriteriaBuilder cb = em.getCriteriaBuilder();
|
||||
|
||||
CriteriaDelete<JpaRoleEntity> deleteQuery = cb.createCriteriaDelete(JpaRoleEntity.class);
|
||||
|
||||
Root<JpaRoleEntity> root = deleteQuery.from(JpaRoleEntity.class);
|
||||
|
||||
if (mcb.getPredicateFunc() != null) deleteQuery.where(mcb.getPredicateFunc().apply(cb, root));
|
||||
|
||||
// TODO find out if the flush and clear are needed here or not, since delete(QueryParameters)
|
||||
// is not used yet from the code it's difficult to investigate its potential purpose here
|
||||
// according to https://thorben-janssen.com/5-common-hibernate-mistakes-that-cause-dozens-of-unexpected-queries/#Remove_Child_Entities_With_a_Bulk_Operation
|
||||
// it seems it is necessary unless it is sure that any of removed entities wasn't fetched
|
||||
// Once KEYCLOAK-19697 is done we could test our scenarios and see if we need the flush and clear
|
||||
// em.flush();
|
||||
// em.clear();
|
||||
|
||||
return em.createQuery(deleteQuery).executeUpdate();
|
||||
protected MapRoleEntity mapToEntityDelegate(JpaRoleEntity original) {
|
||||
return new MapRoleEntityDelegate(new JpaRoleDelegateProvider(original, em));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
*/
|
||||
package org.keycloak.models.map.storage.jpa.role.entity;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
|
@ -42,8 +41,7 @@ import org.hibernate.annotations.TypeDef;
|
|||
import org.hibernate.annotations.TypeDefs;
|
||||
import org.keycloak.models.map.common.DeepCloner;
|
||||
import org.keycloak.models.map.role.MapRoleEntity.AbstractRoleEntity;
|
||||
import static org.keycloak.models.map.storage.jpa.Constants.SUPPORTED_VERSION_ROLE;
|
||||
|
||||
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_ROLE;
|
||||
import org.keycloak.models.map.storage.jpa.JpaRootEntity;
|
||||
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType;
|
||||
|
||||
|
@ -55,7 +53,7 @@ import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType;
|
|||
@Entity
|
||||
@Table(name = "role", uniqueConstraints = {@UniqueConstraint(columnNames = {"realmId", "clientId", "name"})})
|
||||
@TypeDefs({@TypeDef(name = "jsonb", typeClass = JsonbType.class)})
|
||||
public class JpaRoleEntity extends AbstractRoleEntity implements Serializable, JpaRootEntity {
|
||||
public class JpaRoleEntity extends AbstractRoleEntity implements JpaRootEntity {
|
||||
|
||||
@Id
|
||||
@Column
|
||||
|
@ -129,16 +127,18 @@ public class JpaRoleEntity extends AbstractRoleEntity implements Serializable, J
|
|||
*/
|
||||
private void checkEntityVersionForUpdate() {
|
||||
Integer ev = getEntityVersion();
|
||||
if (ev != null && ev < SUPPORTED_VERSION_ROLE) {
|
||||
setEntityVersion(SUPPORTED_VERSION_ROLE);
|
||||
if (ev != null && ev < CURRENT_SCHEMA_VERSION_ROLE) {
|
||||
setEntityVersion(CURRENT_SCHEMA_VERSION_ROLE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getEntityVersion() {
|
||||
if (isMetadataInitialized()) return metadata.getEntityVersion();
|
||||
return entityVersion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEntityVersion(Integer entityVersion) {
|
||||
metadata.setEntityVersion(entityVersion);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd
|
||||
http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd">
|
||||
|
||||
<!-- format of id of changeSet: client-scopes-${org.keycloak.models.map.storage.jpa.Constants.SUPPORTED_VERSION_CLIENT_SCOPE} -->
|
||||
<changeSet author="keycloak" id="client-scopes-1">
|
||||
|
||||
<createTable tableName="client_scope">
|
||||
<column name="id" type="UUID">
|
||||
<constraints primaryKey="true" nullable="false"/>
|
||||
</column>
|
||||
<column name="version" type="INTEGER" defaultValueNumeric="0">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="metadata" type="json"/>
|
||||
</createTable>
|
||||
<ext:addGeneratedColumn tableName="client_scope">
|
||||
<ext:column name="entityversion" type="INTEGER" jsonColumn="metadata" jsonProperty="entityVersion"/>
|
||||
<ext:column name="realmid" type="VARCHAR(36)" jsonColumn="metadata" jsonProperty="fRealmId"/>
|
||||
<ext:column name="name" type="VARCHAR(255)" jsonColumn="metadata" jsonProperty="fName"/>
|
||||
</ext:addGeneratedColumn>
|
||||
<createIndex tableName="client_scope" indexName="client_scope_entityVersion">
|
||||
<column name="entityversion"/>
|
||||
</createIndex>
|
||||
<createIndex tableName="client_scope" indexName="client_realmId_name" unique="true">
|
||||
<column name="realmid"/>
|
||||
<column name="name"/>
|
||||
</createIndex>
|
||||
|
||||
<createTable tableName="client_scope_attribute">
|
||||
<column name="id" type="UUID">
|
||||
<constraints primaryKey="true" nullable="false"/>
|
||||
</column>
|
||||
<column name="fk_client_scope" type="UUID">
|
||||
<constraints foreignKeyName="client_scope_attr_fk_client_scope_fkey" references="client_scope(id)" deleteCascade="true"/>
|
||||
</column>
|
||||
<column name="name" type="VARCHAR(255)"/>
|
||||
<column name="value" type="text"/>
|
||||
</createTable>
|
||||
<createIndex tableName="client_scope_attribute" indexName="client_scope_attr_fk_client_scope">
|
||||
<column name="fk_client_scope"/>
|
||||
</createIndex>
|
||||
<createIndex tableName="client_scope_attribute" indexName="client_scope_attr_name_value">
|
||||
<column name="name"/>
|
||||
<column name="VALUE(255)" valueComputed="VALUE(255)"/>
|
||||
</createIndex>
|
||||
<modifySql dbms="postgresql">
|
||||
<replace replace="VALUE(255)" with="(value::varchar(250))"/>
|
||||
</modifySql>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
|
@ -0,0 +1,23 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
|
||||
<!-- format of id of changelog file names: jpa-client-scopes-changelog-${org.keycloak.models.map.storage.jpa.Constants.SUPPORTED_VERSION_CLIENT_SCOPE}.xml -->
|
||||
<include file="META-INF/client-scopes/jpa-client-scopes-changelog-1.xml"/>
|
||||
</databaseChangeLog>
|
|
@ -1,6 +1,9 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
|
||||
<persistence-unit name="keycloak-jpa-default">
|
||||
<!--client-scopes-->
|
||||
<class>org.keycloak.models.map.storage.jpa.clientscope.entity.JpaClientScopeEntity</class>
|
||||
<class>org.keycloak.models.map.storage.jpa.clientscope.entity.JpaClientScopeAttributeEntity</class>
|
||||
<!--clients-->
|
||||
<class>org.keycloak.models.map.storage.jpa.client.entity.JpaClientEntity</class>
|
||||
<class>org.keycloak.models.map.storage.jpa.client.entity.JpaClientAttributeEntity</class>
|
||||
|
|
Loading…
Reference in a new issue