KEYCLOAK-3440

This commit is contained in:
Bill Burke 2016-09-07 23:11:28 -04:00
parent 15d31a202f
commit da135389c7
20 changed files with 330 additions and 130 deletions

View file

@ -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"/>

View file

@ -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>

View file

@ -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>

View file

@ -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();
} }

View file

@ -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
} }
} }
} }
});
} }
} }
} }

View file

@ -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() {

View file

@ -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();
} }

View file

@ -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);

View file

@ -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);
}
}
}
}
} }

View file

@ -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

View file

@ -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);
} }

View file

@ -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;
} }

View file

@ -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();
} }

View file

@ -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);

View file

@ -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!");
} }
} }

View file

@ -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);

View file

@ -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

View file

@ -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>