KEYCLOAK-3440
This commit is contained in:
parent
15d31a202f
commit
da135389c7
20 changed files with 330 additions and 130 deletions
|
@ -25,6 +25,7 @@
|
||||||
</resources>
|
</resources>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
<module name="javax.transaction.api"/>
|
||||||
<module name="org.keycloak.keycloak-common"/>
|
<module name="org.keycloak.keycloak-common"/>
|
||||||
<module name="org.keycloak.keycloak-core"/>
|
<module name="org.keycloak.keycloak-core"/>
|
||||||
<module name="org.keycloak.keycloak-server-spi"/>
|
<module name="org.keycloak.keycloak-server-spi"/>
|
||||||
|
|
|
@ -33,5 +33,6 @@
|
||||||
<module name="javax.ws.rs.api"/>
|
<module name="javax.ws.rs.api"/>
|
||||||
<module name="org.apache.httpcomponents"/>
|
<module name="org.apache.httpcomponents"/>
|
||||||
<module name="org.jboss.resteasy.resteasy-jaxrs"/>
|
<module name="org.jboss.resteasy.resteasy-jaxrs"/>
|
||||||
|
<module name="javax.transaction.api"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</module>
|
</module>
|
||||||
|
|
|
@ -74,6 +74,11 @@
|
||||||
<artifactId>junit</artifactId>
|
<artifactId>junit</artifactId>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jboss.spec.javax.transaction</groupId>
|
||||||
|
<artifactId>jboss-transaction-api_1.2_spec</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|
|
@ -17,6 +17,8 @@
|
||||||
|
|
||||||
package org.keycloak.connections.jpa;
|
package org.keycloak.connections.jpa;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
|
||||||
import javax.persistence.EntityManager;
|
import javax.persistence.EntityManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -24,6 +26,7 @@ import javax.persistence.EntityManager;
|
||||||
*/
|
*/
|
||||||
public class DefaultJpaConnectionProvider implements JpaConnectionProvider {
|
public class DefaultJpaConnectionProvider implements JpaConnectionProvider {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(DefaultJpaConnectionProvider.class);
|
||||||
private final EntityManager em;
|
private final EntityManager em;
|
||||||
|
|
||||||
public DefaultJpaConnectionProvider(EntityManager em) {
|
public DefaultJpaConnectionProvider(EntityManager em) {
|
||||||
|
@ -37,6 +40,7 @@ public class DefaultJpaConnectionProvider implements JpaConnectionProvider {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
|
logger.trace("DefaultJpaConnectionProvider close()");
|
||||||
em.close();
|
em.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,12 +29,22 @@ import java.util.Map;
|
||||||
import javax.naming.InitialContext;
|
import javax.naming.InitialContext;
|
||||||
import javax.persistence.EntityManager;
|
import javax.persistence.EntityManager;
|
||||||
import javax.persistence.EntityManagerFactory;
|
import javax.persistence.EntityManagerFactory;
|
||||||
|
import javax.persistence.SynchronizationType;
|
||||||
import javax.sql.DataSource;
|
import javax.sql.DataSource;
|
||||||
|
import javax.transaction.InvalidTransactionException;
|
||||||
|
import javax.transaction.Synchronization;
|
||||||
|
import javax.transaction.SystemException;
|
||||||
|
import javax.transaction.Transaction;
|
||||||
|
import javax.transaction.TransactionManager;
|
||||||
|
import javax.transaction.UserTransaction;
|
||||||
|
|
||||||
import org.hibernate.ejb.AvailableSettings;
|
import org.hibernate.ejb.AvailableSettings;
|
||||||
|
import org.hibernate.engine.transaction.jta.platform.internal.AbstractJtaPlatform;
|
||||||
|
import org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.Config;
|
import org.keycloak.Config;
|
||||||
import org.keycloak.connections.jpa.updater.JpaUpdaterProvider;
|
import org.keycloak.connections.jpa.updater.JpaUpdaterProvider;
|
||||||
|
import org.keycloak.connections.jpa.updater.liquibase.conn.LiquibaseConnectionProvider;
|
||||||
import org.keycloak.connections.jpa.util.JpaUtils;
|
import org.keycloak.connections.jpa.util.JpaUtils;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
@ -45,6 +55,7 @@ import org.keycloak.provider.ServerInfoAwareProviderFactory;
|
||||||
import org.keycloak.models.dblock.DBLockManager;
|
import org.keycloak.models.dblock.DBLockManager;
|
||||||
import org.keycloak.ServerStartupError;
|
import org.keycloak.ServerStartupError;
|
||||||
import org.keycloak.timer.TimerProvider;
|
import org.keycloak.timer.TimerProvider;
|
||||||
|
import org.keycloak.transaction.JtaTransactionManagerLookup;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
@ -63,13 +74,26 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
|
||||||
|
|
||||||
private Map<String, String> operationalInfo;
|
private Map<String, String> operationalInfo;
|
||||||
|
|
||||||
|
private boolean jtaEnabled;
|
||||||
|
private JtaTransactionManagerLookup jtaLookup;
|
||||||
|
|
||||||
|
private KeycloakSessionFactory factory;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JpaConnectionProvider create(KeycloakSession session) {
|
public JpaConnectionProvider create(KeycloakSession session) {
|
||||||
|
logger.trace("Create JpaConnectionProvider");
|
||||||
lazyInit(session);
|
lazyInit(session);
|
||||||
|
|
||||||
EntityManager em = emf.createEntityManager();
|
EntityManager em = null;
|
||||||
|
if (!jtaEnabled) {
|
||||||
|
logger.trace("enlisting EntityManager in JpaKeycloakTransaction");
|
||||||
|
em = emf.createEntityManager();
|
||||||
|
} else {
|
||||||
|
|
||||||
|
em = emf.createEntityManager(SynchronizationType.SYNCHRONIZED);
|
||||||
|
}
|
||||||
em = PersistenceExceptionConverter.create(em);
|
em = PersistenceExceptionConverter.create(em);
|
||||||
session.getTransactionManager().enlist(new JpaKeycloakTransaction(em));
|
if (!jtaEnabled) session.getTransactionManager().enlist(new JpaKeycloakTransaction(em));
|
||||||
return new DefaultJpaConnectionProvider(em);
|
return new DefaultJpaConnectionProvider(em);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,13 +116,25 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void postInit(KeycloakSessionFactory factory) {
|
public void postInit(KeycloakSessionFactory factory) {
|
||||||
|
this.factory = factory;
|
||||||
|
checkJtaEnabled(factory);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void checkJtaEnabled(KeycloakSessionFactory factory) {
|
||||||
|
jtaLookup = (JtaTransactionManagerLookup) factory.getProviderFactory(JtaTransactionManagerLookup.class);
|
||||||
|
if (jtaLookup != null) {
|
||||||
|
if (jtaLookup.getTransactionManager() != null) {
|
||||||
|
jtaEnabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void lazyInit(KeycloakSession session) {
|
private void lazyInit(KeycloakSession session) {
|
||||||
if (emf == null) {
|
if (emf == null) {
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
if (emf == null) {
|
if (emf == null) {
|
||||||
|
KeycloakModelUtils.suspendJtaTransaction(session.getKeycloakSessionFactory(), () -> {
|
||||||
logger.debug("Initializing JPA connections");
|
logger.debug("Initializing JPA connections");
|
||||||
|
|
||||||
Map<String, Object> properties = new HashMap<String, Object>();
|
Map<String, Object> properties = new HashMap<String, Object>();
|
||||||
|
@ -107,7 +143,7 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
|
||||||
|
|
||||||
String dataSource = config.get("dataSource");
|
String dataSource = config.get("dataSource");
|
||||||
if (dataSource != null) {
|
if (dataSource != null) {
|
||||||
if (config.getBoolean("jta", false)) {
|
if (config.getBoolean("jta", jtaEnabled)) {
|
||||||
properties.put(AvailableSettings.JTA_DATASOURCE, dataSource);
|
properties.put(AvailableSettings.JTA_DATASOURCE, dataSource);
|
||||||
} else {
|
} else {
|
||||||
properties.put(AvailableSettings.NON_JTA_DATASOURCE, dataSource);
|
properties.put(AvailableSettings.NON_JTA_DATASOURCE, dataSource);
|
||||||
|
@ -155,7 +191,21 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.trace("Creating EntityManagerFactory");
|
logger.trace("Creating EntityManagerFactory");
|
||||||
emf = JpaUtils.createEntityManagerFactory(session, unitName, properties, getClass().getClassLoader());
|
logger.tracev("***** create EMF jtaEnabled {0} ", jtaEnabled);
|
||||||
|
if (jtaEnabled) {
|
||||||
|
properties.put(org.hibernate.cfg.AvailableSettings.JTA_PLATFORM, new AbstractJtaPlatform() {
|
||||||
|
@Override
|
||||||
|
protected TransactionManager locateTransactionManager() {
|
||||||
|
return jtaLookup.getTransactionManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected UserTransaction locateUserTransaction() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
emf = JpaUtils.createEntityManagerFactory(session, unitName, properties, getClass().getClassLoader(), jtaEnabled);
|
||||||
logger.trace("EntityManagerFactory created");
|
logger.trace("EntityManagerFactory created");
|
||||||
|
|
||||||
if (globalStatsInterval != -1) {
|
if (globalStatsInterval != -1) {
|
||||||
|
@ -171,6 +221,7 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,14 +23,13 @@ import java.sql.SQLException;
|
||||||
import liquibase.Liquibase;
|
import liquibase.Liquibase;
|
||||||
import liquibase.exception.DatabaseException;
|
import liquibase.exception.DatabaseException;
|
||||||
import liquibase.exception.LiquibaseException;
|
import liquibase.exception.LiquibaseException;
|
||||||
import liquibase.exception.LockException;
|
|
||||||
import liquibase.lockservice.LockService;
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.connections.jpa.JpaConnectionProvider;
|
import org.keycloak.connections.jpa.JpaConnectionProvider;
|
||||||
import org.keycloak.connections.jpa.JpaConnectionProviderFactory;
|
import org.keycloak.connections.jpa.JpaConnectionProviderFactory;
|
||||||
import org.keycloak.connections.jpa.updater.liquibase.conn.LiquibaseConnectionProvider;
|
import org.keycloak.connections.jpa.updater.liquibase.conn.LiquibaseConnectionProvider;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.dblock.DBLockProvider;
|
import org.keycloak.models.dblock.DBLockProvider;
|
||||||
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
@ -57,6 +56,7 @@ public class LiquibaseDBLockProvider implements DBLockProvider {
|
||||||
this.session = session;
|
this.session = session;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void lazyInit() {
|
private void lazyInit() {
|
||||||
if (!initialized) {
|
if (!initialized) {
|
||||||
LiquibaseConnectionProvider liquibaseProvider = session.getProvider(LiquibaseConnectionProvider.class);
|
LiquibaseConnectionProvider liquibaseProvider = session.getProvider(LiquibaseConnectionProvider.class);
|
||||||
|
@ -92,6 +92,8 @@ public class LiquibaseDBLockProvider implements DBLockProvider {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void waitForLock() {
|
public void waitForLock() {
|
||||||
|
KeycloakModelUtils.suspendJtaTransaction(session.getKeycloakSessionFactory(), () -> {
|
||||||
|
|
||||||
lazyInit();
|
lazyInit();
|
||||||
|
|
||||||
while (maxAttempts > 0) {
|
while (maxAttempts > 0) {
|
||||||
|
@ -111,16 +113,20 @@ public class LiquibaseDBLockProvider implements DBLockProvider {
|
||||||
throw re;
|
throw re;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void releaseLock() {
|
public void releaseLock() {
|
||||||
|
KeycloakModelUtils.suspendJtaTransaction(session.getKeycloakSessionFactory(), () -> {
|
||||||
lazyInit();
|
lazyInit();
|
||||||
|
|
||||||
lockService.releaseLock();
|
lockService.releaseLock();
|
||||||
lockService.reset();
|
lockService.reset();
|
||||||
factory.setHasLock(false);
|
factory.setHasLock(false);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -136,6 +142,7 @@ public class LiquibaseDBLockProvider implements DBLockProvider {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void destroyLockInfo() {
|
public void destroyLockInfo() {
|
||||||
|
KeycloakModelUtils.suspendJtaTransaction(session.getKeycloakSessionFactory(), () -> {
|
||||||
lazyInit();
|
lazyInit();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -146,11 +153,14 @@ public class LiquibaseDBLockProvider implements DBLockProvider {
|
||||||
logger.error("Failed to destroy lock table");
|
logger.error("Failed to destroy lock table");
|
||||||
safeRollbackConnection();
|
safeRollbackConnection();
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
|
KeycloakModelUtils.suspendJtaTransaction(session.getKeycloakSessionFactory(), () -> {
|
||||||
safeCloseConnection();
|
safeCloseConnection();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void safeRollbackConnection() {
|
private void safeRollbackConnection() {
|
||||||
|
|
|
@ -21,6 +21,8 @@ import org.hibernate.boot.registry.classloading.internal.ClassLoaderServiceImpl;
|
||||||
import org.hibernate.jpa.boot.internal.ParsedPersistenceXmlDescriptor;
|
import org.hibernate.jpa.boot.internal.ParsedPersistenceXmlDescriptor;
|
||||||
import org.hibernate.jpa.boot.internal.PersistenceXmlParser;
|
import org.hibernate.jpa.boot.internal.PersistenceXmlParser;
|
||||||
import org.hibernate.jpa.boot.spi.Bootstrap;
|
import org.hibernate.jpa.boot.spi.Bootstrap;
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.connections.jpa.DefaultJpaConnectionProviderFactory;
|
||||||
import org.keycloak.connections.jpa.entityprovider.JpaEntityProvider;
|
import org.keycloak.connections.jpa.entityprovider.JpaEntityProvider;
|
||||||
import org.keycloak.connections.jpa.entityprovider.ProxyClassLoader;
|
import org.keycloak.connections.jpa.entityprovider.ProxyClassLoader;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
@ -46,8 +48,9 @@ public class JpaUtils {
|
||||||
return (schema==null) ? tableName : schema + "." + tableName;
|
return (schema==null) ? tableName : schema + "." + tableName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static EntityManagerFactory createEntityManagerFactory(KeycloakSession session, String unitName, Map<String, Object> properties, ClassLoader classLoader) {
|
public static EntityManagerFactory createEntityManagerFactory(KeycloakSession session, String unitName, Map<String, Object> properties, ClassLoader classLoader, boolean jta) {
|
||||||
PersistenceXmlParser parser = new PersistenceXmlParser(new ClassLoaderServiceImpl(classLoader), PersistenceUnitTransactionType.RESOURCE_LOCAL);
|
PersistenceUnitTransactionType txType = jta ? PersistenceUnitTransactionType.JTA : PersistenceUnitTransactionType.RESOURCE_LOCAL;
|
||||||
|
PersistenceXmlParser parser = new PersistenceXmlParser(new ClassLoaderServiceImpl(classLoader), txType);
|
||||||
List<ParsedPersistenceXmlDescriptor> persistenceUnits = parser.doResolve(properties);
|
List<ParsedPersistenceXmlDescriptor> persistenceUnits = parser.doResolve(properties);
|
||||||
for (ParsedPersistenceXmlDescriptor persistenceUnit : persistenceUnits) {
|
for (ParsedPersistenceXmlDescriptor persistenceUnit : persistenceUnits) {
|
||||||
if (persistenceUnit.getName().equals(unitName)) {
|
if (persistenceUnit.getName().equals(unitName)) {
|
||||||
|
@ -58,6 +61,7 @@ public class JpaUtils {
|
||||||
}
|
}
|
||||||
// Now build the entity manager factory, supplying a proxy classloader, so Hibernate will be able
|
// Now build the entity manager factory, supplying a proxy classloader, so Hibernate will be able
|
||||||
// to find and load the extra provided entities. Set the provided classloader as parent classloader.
|
// to find and load the extra provided entities. Set the provided classloader as parent classloader.
|
||||||
|
persistenceUnit.setTransactionType(txType);
|
||||||
return Bootstrap.getEntityManagerFactoryBuilder(persistenceUnit, properties,
|
return Bootstrap.getEntityManagerFactoryBuilder(persistenceUnit, properties,
|
||||||
new ProxyClassLoader(providedEntities, classLoader)).build();
|
new ProxyClassLoader(providedEntities, classLoader)).build();
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,21 @@ package org.keycloak.models;
|
||||||
*/
|
*/
|
||||||
public interface KeycloakTransactionManager extends KeycloakTransaction {
|
public interface KeycloakTransactionManager extends KeycloakTransaction {
|
||||||
|
|
||||||
|
enum JTAPolicy {
|
||||||
|
/**
|
||||||
|
* Do not interact with JTA at all
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
NOT_SUPPORTED,
|
||||||
|
/**
|
||||||
|
* A new JTA Transaction will be created when Keycloak TM begin() is called. If an existing JTA transaction
|
||||||
|
* exists, it is suspended and resumed after the Keycloak transaction finishes.
|
||||||
|
*/
|
||||||
|
REQUIRES_NEW,
|
||||||
|
}
|
||||||
|
|
||||||
|
JTAPolicy getJTAPolicy();
|
||||||
|
void setJTAPolicy(JTAPolicy policy);
|
||||||
void enlist(KeycloakTransaction transaction);
|
void enlist(KeycloakTransaction transaction);
|
||||||
void enlistAfterCompletion(KeycloakTransaction transaction);
|
void enlistAfterCompletion(KeycloakTransaction transaction);
|
||||||
|
|
||||||
|
|
|
@ -44,8 +44,14 @@ import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.representations.idm.CertificateRepresentation;
|
import org.keycloak.representations.idm.CertificateRepresentation;
|
||||||
import org.keycloak.common.util.CertificateUtils;
|
import org.keycloak.common.util.CertificateUtils;
|
||||||
import org.keycloak.common.util.PemUtils;
|
import org.keycloak.common.util.PemUtils;
|
||||||
|
import org.keycloak.transaction.JtaTransactionManagerLookup;
|
||||||
|
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
import javax.naming.InitialContext;
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
import javax.transaction.InvalidTransactionException;
|
||||||
|
import javax.transaction.SystemException;
|
||||||
|
import javax.transaction.Transaction;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.StringWriter;
|
import java.io.StringWriter;
|
||||||
import java.security.Key;
|
import java.security.Key;
|
||||||
|
@ -56,6 +62,7 @@ import java.security.PrivateKey;
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.sql.DriverManager;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
@ -63,6 +70,7 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set of helper methods, which are useful in various model implementations.
|
* Set of helper methods, which are useful in various model implementations.
|
||||||
|
@ -303,6 +311,7 @@ public final class KeycloakModelUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static String getMasterRealmAdminApplicationClientId(String realmName) {
|
public static String getMasterRealmAdminApplicationClientId(String realmName) {
|
||||||
return realmName + "-realm";
|
return realmName + "-realm";
|
||||||
}
|
}
|
||||||
|
@ -651,4 +660,33 @@ public final class KeycloakModelUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void suspendJtaTransaction(KeycloakSessionFactory factory, Runnable runnable) {
|
||||||
|
JtaTransactionManagerLookup lookup = (JtaTransactionManagerLookup)factory.getProviderFactory(JtaTransactionManagerLookup.class);
|
||||||
|
Transaction suspended = null;
|
||||||
|
try {
|
||||||
|
if (lookup != null) {
|
||||||
|
if (lookup.getTransactionManager() != null) {
|
||||||
|
try {
|
||||||
|
suspended = lookup.getTransactionManager().suspend();
|
||||||
|
} catch (SystemException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
runnable.run();
|
||||||
|
} finally {
|
||||||
|
if (suspended != null) {
|
||||||
|
try {
|
||||||
|
lookup.getTransactionManager().resume(suspended);
|
||||||
|
} catch (InvalidTransactionException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
} catch (SystemException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,3 +62,4 @@ org.keycloak.models.cache.authorization.CachedStoreFactorySpi
|
||||||
org.keycloak.protocol.oidc.TokenIntrospectionSpi
|
org.keycloak.protocol.oidc.TokenIntrospectionSpi
|
||||||
org.keycloak.policy.PasswordPolicySpi
|
org.keycloak.policy.PasswordPolicySpi
|
||||||
org.keycloak.policy.PasswordPolicyManagerSpi
|
org.keycloak.policy.PasswordPolicyManagerSpi
|
||||||
|
org.keycloak.transaction.TransactionManagerLookupSpi
|
||||||
|
|
|
@ -27,6 +27,7 @@ import org.keycloak.scripting.ScriptingProvider;
|
||||||
import org.keycloak.storage.UserStorageManager;
|
import org.keycloak.storage.UserStorageManager;
|
||||||
import org.keycloak.storage.federated.UserFederatedStorageProvider;
|
import org.keycloak.storage.federated.UserFederatedStorageProvider;
|
||||||
|
|
||||||
|
import javax.transaction.TransactionManager;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -51,7 +52,7 @@ public class DefaultKeycloakSession implements KeycloakSession {
|
||||||
|
|
||||||
public DefaultKeycloakSession(DefaultKeycloakSessionFactory factory) {
|
public DefaultKeycloakSession(DefaultKeycloakSessionFactory factory) {
|
||||||
this.factory = factory;
|
this.factory = factory;
|
||||||
this.transactionManager = new DefaultKeycloakTransactionManager();
|
this.transactionManager = new DefaultKeycloakTransactionManager(this);
|
||||||
federationManager = new UserFederationManager(this);
|
federationManager = new UserFederationManager(this);
|
||||||
context = new DefaultKeycloakContext(this);
|
context = new DefaultKeycloakContext(this);
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,7 +49,6 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory, Pr
|
||||||
private Map<Class<? extends Provider>, String> provider = new HashMap<>();
|
private Map<Class<? extends Provider>, String> provider = new HashMap<>();
|
||||||
private volatile Map<Class<? extends Provider>, Map<String, ProviderFactory>> factoriesMap = new HashMap<>();
|
private volatile Map<Class<? extends Provider>, Map<String, ProviderFactory>> factoriesMap = new HashMap<>();
|
||||||
protected CopyOnWriteArrayList<ProviderEventListener> listeners = new CopyOnWriteArrayList<>();
|
protected CopyOnWriteArrayList<ProviderEventListener> listeners = new CopyOnWriteArrayList<>();
|
||||||
private TransactionManager tm;
|
|
||||||
|
|
||||||
// TODO: Likely should be changed to int and use Time.currentTime() to be compatible with all our "time" reps
|
// TODO: Likely should be changed to int and use Time.currentTime() to be compatible with all our "time" reps
|
||||||
protected long serverStartupTimestamp;
|
protected long serverStartupTimestamp;
|
||||||
|
@ -97,8 +96,6 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory, Pr
|
||||||
// make the session factory ready for hot deployment
|
// make the session factory ready for hot deployment
|
||||||
ProviderManagerRegistry.SINGLETON.setDeployer(this);
|
ProviderManagerRegistry.SINGLETON.setDeployer(this);
|
||||||
|
|
||||||
JtaTransactionManagerLookup lookup = (JtaTransactionManagerLookup)getProviderFactory(JtaTransactionManagerLookup.class);
|
|
||||||
if (lookup != null) tm = lookup.getTransactionManager();
|
|
||||||
}
|
}
|
||||||
protected Map<Class<? extends Provider>, Map<String, ProviderFactory>> getFactoriesCopy() {
|
protected Map<Class<? extends Provider>, Map<String, ProviderFactory>> getFactoriesCopy() {
|
||||||
Map<Class<? extends Provider>, Map<String, ProviderFactory>> copy = new HashMap<>();
|
Map<Class<? extends Provider>, Map<String, ProviderFactory>> copy = new HashMap<>();
|
||||||
|
@ -282,9 +279,6 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory, Pr
|
||||||
|
|
||||||
public KeycloakSession create() {
|
public KeycloakSession create() {
|
||||||
KeycloakSession session = new DefaultKeycloakSession(this);
|
KeycloakSession session = new DefaultKeycloakSession(this);
|
||||||
if (tm != null) {
|
|
||||||
session.getTransactionManager().enlist(new JtaTransactionWrapper(tm));
|
|
||||||
}
|
|
||||||
return session;
|
return session;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,9 +16,13 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.services;
|
package org.keycloak.services;
|
||||||
|
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakTransaction;
|
import org.keycloak.models.KeycloakTransaction;
|
||||||
import org.keycloak.models.KeycloakTransactionManager;
|
import org.keycloak.models.KeycloakTransactionManager;
|
||||||
|
import org.keycloak.transaction.JtaTransactionManagerLookup;
|
||||||
|
import org.keycloak.transaction.JtaTransactionWrapper;
|
||||||
|
|
||||||
|
import javax.transaction.TransactionManager;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@ -34,6 +38,12 @@ public class DefaultKeycloakTransactionManager implements KeycloakTransactionMan
|
||||||
private List<KeycloakTransaction> afterCompletion = new LinkedList<KeycloakTransaction>();
|
private List<KeycloakTransaction> afterCompletion = new LinkedList<KeycloakTransaction>();
|
||||||
private boolean active;
|
private boolean active;
|
||||||
private boolean rollback;
|
private boolean rollback;
|
||||||
|
private KeycloakSession session;
|
||||||
|
private JTAPolicy jtaPolicy = JTAPolicy.REQUIRES_NEW;
|
||||||
|
|
||||||
|
public DefaultKeycloakTransactionManager(KeycloakSession session) {
|
||||||
|
this.session = session;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void enlist(KeycloakTransaction transaction) {
|
public void enlist(KeycloakTransaction transaction) {
|
||||||
|
@ -62,12 +72,31 @@ public class DefaultKeycloakTransactionManager implements KeycloakTransactionMan
|
||||||
prepare.add(transaction);
|
prepare.add(transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JTAPolicy getJTAPolicy() {
|
||||||
|
return jtaPolicy;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setJTAPolicy(JTAPolicy policy) {
|
||||||
|
jtaPolicy = policy;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void begin() {
|
public void begin() {
|
||||||
if (active) {
|
if (active) {
|
||||||
throw new IllegalStateException("Transaction already active");
|
throw new IllegalStateException("Transaction already active");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (jtaPolicy == JTAPolicy.REQUIRES_NEW) {
|
||||||
|
JtaTransactionManagerLookup jtaLookup = session.getProvider(JtaTransactionManagerLookup.class);
|
||||||
|
TransactionManager tm = jtaLookup.getTransactionManager();
|
||||||
|
if (tm != null) {
|
||||||
|
enlist(new JtaTransactionWrapper(tm));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (KeycloakTransaction tx : transactions) {
|
for (KeycloakTransaction tx : transactions) {
|
||||||
tx.begin();
|
tx.begin();
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,10 +45,13 @@ import org.keycloak.services.scheduled.ClusterAwareScheduledTaskRunner;
|
||||||
import org.keycloak.services.util.JsonConfigProvider;
|
import org.keycloak.services.util.JsonConfigProvider;
|
||||||
import org.keycloak.services.util.ObjectMapperResolver;
|
import org.keycloak.services.util.ObjectMapperResolver;
|
||||||
import org.keycloak.timer.TimerProvider;
|
import org.keycloak.timer.TimerProvider;
|
||||||
|
import org.keycloak.transaction.JtaTransactionManagerLookup;
|
||||||
import org.keycloak.util.JsonSerialization;
|
import org.keycloak.util.JsonSerialization;
|
||||||
import org.keycloak.common.util.SystemEnvProperties;
|
import org.keycloak.common.util.SystemEnvProperties;
|
||||||
|
|
||||||
import javax.servlet.ServletContext;
|
import javax.servlet.ServletContext;
|
||||||
|
import javax.transaction.SystemException;
|
||||||
|
import javax.transaction.Transaction;
|
||||||
import javax.ws.rs.core.Application;
|
import javax.ws.rs.core.Application;
|
||||||
import javax.ws.rs.core.Context;
|
import javax.ws.rs.core.Context;
|
||||||
import javax.ws.rs.core.UriInfo;
|
import javax.ws.rs.core.UriInfo;
|
||||||
|
@ -155,11 +158,28 @@ public class KeycloakApplication extends Application {
|
||||||
// Migrate model, bootstrap master realm, import realms and create admin user. This is done with acquired dbLock
|
// Migrate model, bootstrap master realm, import realms and create admin user. This is done with acquired dbLock
|
||||||
protected ExportImportManager migrateAndBootstrap() {
|
protected ExportImportManager migrateAndBootstrap() {
|
||||||
ExportImportManager exportImportManager;
|
ExportImportManager exportImportManager;
|
||||||
|
logger.debug("Calling migrateModel");
|
||||||
migrateModel();
|
migrateModel();
|
||||||
|
|
||||||
|
logger.debug("bootstrap");
|
||||||
KeycloakSession session = sessionFactory.create();
|
KeycloakSession session = sessionFactory.create();
|
||||||
try {
|
try {
|
||||||
session.getTransactionManager().begin();
|
session.getTransactionManager().begin();
|
||||||
|
JtaTransactionManagerLookup lookup = (JtaTransactionManagerLookup) sessionFactory.getProviderFactory(JtaTransactionManagerLookup.class);
|
||||||
|
if (lookup != null) {
|
||||||
|
if (lookup.getTransactionManager() != null) {
|
||||||
|
try {
|
||||||
|
Transaction transaction = lookup.getTransactionManager().getTransaction();
|
||||||
|
logger.debugv("bootstrap current transaction? {0}", transaction != null);
|
||||||
|
if (transaction != null) {
|
||||||
|
logger.debugv("bootstrap current transaction status? {0}", transaction.getStatus());
|
||||||
|
}
|
||||||
|
} catch (SystemException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
ApplianceBootstrap applianceBootstrap = new ApplianceBootstrap(session);
|
ApplianceBootstrap applianceBootstrap = new ApplianceBootstrap(session);
|
||||||
exportImportManager = new ExportImportManager(session);
|
exportImportManager = new ExportImportManager(session);
|
||||||
|
|
|
@ -184,6 +184,8 @@ public class UsersResource {
|
||||||
return ErrorResponse.exists("User is read only!");
|
return ErrorResponse.exists("User is read only!");
|
||||||
} catch (ModelException me) {
|
} catch (ModelException me) {
|
||||||
return ErrorResponse.exists("Could not update user!");
|
return ErrorResponse.exists("Could not update user!");
|
||||||
|
} catch (Exception me) { // JPA may be committed by JTA which can't
|
||||||
|
return ErrorResponse.exists("Could not update user!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,9 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.transaction;
|
package org.keycloak.transaction;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.models.KeycloakTransaction;
|
import org.keycloak.models.KeycloakTransaction;
|
||||||
|
import org.keycloak.storage.UserStorageManager;
|
||||||
|
|
||||||
import javax.transaction.InvalidTransactionException;
|
import javax.transaction.InvalidTransactionException;
|
||||||
import javax.transaction.NotSupportedException;
|
import javax.transaction.NotSupportedException;
|
||||||
|
@ -31,16 +33,22 @@ import javax.transaction.UserTransaction;
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public class JtaTransactionWrapper implements KeycloakTransaction {
|
public class JtaTransactionWrapper implements KeycloakTransaction {
|
||||||
|
private static final Logger logger = Logger.getLogger(JtaTransactionWrapper.class);
|
||||||
protected TransactionManager tm;
|
protected TransactionManager tm;
|
||||||
protected Transaction ut;
|
protected Transaction ut;
|
||||||
protected Transaction suspended;
|
protected Transaction suspended;
|
||||||
|
protected Exception ended;
|
||||||
|
|
||||||
public JtaTransactionWrapper(TransactionManager tm) {
|
public JtaTransactionWrapper(TransactionManager tm) {
|
||||||
this.tm = tm;
|
this.tm = tm;
|
||||||
try {
|
try {
|
||||||
|
|
||||||
suspended = tm.suspend();
|
suspended = tm.suspend();
|
||||||
|
logger.debug("new JtaTransactionWrapper");
|
||||||
|
logger.debugv("was existing? {0}", suspended != null);
|
||||||
tm.begin();
|
tm.begin();
|
||||||
ut = tm.getTransaction();
|
ut = tm.getTransaction();
|
||||||
|
//ended = new Exception();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
@ -53,16 +61,20 @@ public class JtaTransactionWrapper implements KeycloakTransaction {
|
||||||
@Override
|
@Override
|
||||||
public void commit() {
|
public void commit() {
|
||||||
try {
|
try {
|
||||||
ut.commit();
|
logger.debug("JtaTransactionWrapper commit");
|
||||||
|
tm.commit();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
|
} finally {
|
||||||
|
end();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void rollback() {
|
public void rollback() {
|
||||||
try {
|
try {
|
||||||
ut.rollback();
|
logger.debug("JtaTransactionWrapper rollback");
|
||||||
|
tm.rollback();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -74,7 +86,7 @@ public class JtaTransactionWrapper implements KeycloakTransaction {
|
||||||
@Override
|
@Override
|
||||||
public void setRollbackOnly() {
|
public void setRollbackOnly() {
|
||||||
try {
|
try {
|
||||||
ut.setRollbackOnly();
|
tm.setRollbackOnly();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
@ -83,7 +95,7 @@ public class JtaTransactionWrapper implements KeycloakTransaction {
|
||||||
@Override
|
@Override
|
||||||
public boolean getRollbackOnly() {
|
public boolean getRollbackOnly() {
|
||||||
try {
|
try {
|
||||||
return ut.getStatus() == Status.STATUS_MARKED_ROLLBACK;
|
return tm.getStatus() == Status.STATUS_MARKED_ROLLBACK;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
@ -92,15 +104,28 @@ public class JtaTransactionWrapper implements KeycloakTransaction {
|
||||||
@Override
|
@Override
|
||||||
public boolean isActive() {
|
public boolean isActive() {
|
||||||
try {
|
try {
|
||||||
return ut.getStatus() == Status.STATUS_ACTIVE;
|
return tm.getStatus() == Status.STATUS_ACTIVE;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void finalize() throws Throwable {
|
||||||
|
if (ended != null) {
|
||||||
|
logger.error("TX didn't close at position", ended);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
protected void end() {
|
protected void end() {
|
||||||
|
ended = null;
|
||||||
|
logger.debug("JtaTransactionWrapper end");
|
||||||
if (suspended != null) {
|
if (suspended != null) {
|
||||||
try {
|
try {
|
||||||
|
logger.debug("JtaTransactionWrapper resuming suspended");
|
||||||
tm.resume(suspended);
|
tm.resume(suspended);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
|
|
|
@ -18,5 +18,4 @@
|
||||||
org.keycloak.exportimport.ClientDescriptionConverterSpi
|
org.keycloak.exportimport.ClientDescriptionConverterSpi
|
||||||
org.keycloak.wellknown.WellKnownSpi
|
org.keycloak.wellknown.WellKnownSpi
|
||||||
org.keycloak.services.clientregistration.ClientRegistrationSpi
|
org.keycloak.services.clientregistration.ClientRegistrationSpi
|
||||||
org.keycloak.transaction.TransactionManagerLookupSpi
|
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
<password>sa</password>
|
<password>sa</password>
|
||||||
</security>
|
</security>
|
||||||
</datasource>
|
</datasource>
|
||||||
<datasource jndi-name="java:jboss/datasources/KeycloakDS" jta="false" pool-name="KeycloakDS" enabled="true" use-java-context="true">
|
<datasource jndi-name="java:jboss/datasources/KeycloakDS" pool-name="KeycloakDS" enabled="true" use-java-context="true">
|
||||||
<?KEYCLOAK_DS_CONNECTION_URL?>
|
<?KEYCLOAK_DS_CONNECTION_URL?>
|
||||||
<driver>h2</driver>
|
<driver>h2</driver>
|
||||||
<security>
|
<security>
|
||||||
|
|
Loading…
Reference in a new issue