From 83306963e8e873e73dcbeddf58ed1d6eea405b74 Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Mon, 8 Aug 2016 12:32:36 -0400 Subject: [PATCH] jta transaction abstraction --- .../configuration/keycloak-server.json | 8 +++ examples/providers/user-storage-jpa/README.md | 13 +++++ examples/providers/user-storage-jpa/pom.xml | 11 +---- .../EjbExampleUserStorageProviderFactory.java | 30 ++---------- server-spi/pom.xml | 5 ++ .../storage/UserStorageProviderSpi.java | 2 +- .../DefaultKeycloakSessionFactory.java | 34 ++++++++----- .../JBossJtaTransactionManagerLookup.java} | 28 +++++++---- .../keycloak/transaction/JtaRegistration.java | 40 +++++++++++++++ .../JtaTransactionManagerLookup.java | 43 ++++++++++++++++ .../JtaTransactionWrapper.java | 2 +- .../TransactionManagerLookupSpi.java | 49 +++++++++++++++++++ .../UserTransactionWrapper.java | 2 +- .../services/org.keycloak.provider.Spi | 1 + ...ak.transaction.JtaTransactionManagerLookup | 1 + .../resources/META-INF/keycloak-server.json | 8 +++ .../resources/META-INF/keycloak-server.json | 8 +++ .../KeycloakProviderDependencyProcessor.java | 2 - 18 files changed, 224 insertions(+), 63 deletions(-) create mode 100755 examples/providers/user-storage-jpa/README.md rename services/src/main/java/org/keycloak/{services/JtaRegistration.java => transaction/JBossJtaTransactionManagerLookup.java} (72%) create mode 100644 services/src/main/java/org/keycloak/transaction/JtaRegistration.java create mode 100644 services/src/main/java/org/keycloak/transaction/JtaTransactionManagerLookup.java rename services/src/main/java/org/keycloak/{services => transaction}/JtaTransactionWrapper.java (98%) create mode 100755 services/src/main/java/org/keycloak/transaction/TransactionManagerLookupSpi.java rename services/src/main/java/org/keycloak/{services => transaction}/UserTransactionWrapper.java (98%) create mode 100644 services/src/main/resources/META-INF/services/org.keycloak.transaction.JtaTransactionManagerLookup diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/content/standalone/configuration/keycloak-server.json b/distribution/feature-packs/server-feature-pack/src/main/resources/content/standalone/configuration/keycloak-server.json index 89263bf4e59..b2f106792ab 100755 --- a/distribution/feature-packs/server-feature-pack/src/main/resources/content/standalone/configuration/keycloak-server.json +++ b/distribution/feature-packs/server-feature-pack/src/main/resources/content/standalone/configuration/keycloak-server.json @@ -75,5 +75,13 @@ "default": { "cacheContainer" : "java:comp/env/infinispan/Keycloak" } + }, + + "jta-lookup": { + "provider": "${keycloak.jta.lookup.provider:jboss}", + "jboss" : { + "enabled": true + } + } } \ No newline at end of file diff --git a/examples/providers/user-storage-jpa/README.md b/examples/providers/user-storage-jpa/README.md new file mode 100755 index 00000000000..f965ef254ab --- /dev/null +++ b/examples/providers/user-storage-jpa/README.md @@ -0,0 +1,13 @@ +Example User Storage Provider with EJB and JPA +=================================================== + +This is an example of the User Storage SPI implemented using EJB and JPA. To deploy this provider you must have Keycloak +running in standalone or standalone-ha mode. Then type the follow maven command: + + mvn clean install wildfly:deploy + +Login and go to the User Federation tab and you should now see your deployed provider in the add-provider list box. +Add the provider, save it, then any new user you create will be stored and in the custom store you implemented. You +can modify the example and hot deploy it using the above maven command again. + +This example uses the built in in-memory datasource that comes with keycloak: ExampleDS. diff --git a/examples/providers/user-storage-jpa/pom.xml b/examples/providers/user-storage-jpa/pom.xml index 75c3a6f9808..9702c46af08 100755 --- a/examples/providers/user-storage-jpa/pom.xml +++ b/examples/providers/user-storage-jpa/pom.xml @@ -23,7 +23,7 @@ 2.1.0-SNAPSHOT - Properties Authentication Provider Example + User Storage JPA Provider Exapmle 4.0.0 @@ -75,14 +75,7 @@ 1.8 - - org.jboss.as.plugins - jboss-as-maven-plugin - - false - - - + org.wildfly.plugins wildfly-maven-plugin diff --git a/examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/EjbExampleUserStorageProviderFactory.java b/examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/EjbExampleUserStorageProviderFactory.java index 4491c98f195..a1db65d1b80 100644 --- a/examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/EjbExampleUserStorageProviderFactory.java +++ b/examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/EjbExampleUserStorageProviderFactory.java @@ -49,35 +49,11 @@ public class EjbExampleUserStorageProviderFactory implements UserStorageProvider @Override public String getId() { - return "example-user-storage"; + return "example-user-storage-jpa"; } @Override - public void init(Config.Scope config) { - - } - - @Override - public void postInit(KeycloakSessionFactory factory) { - } - - static List OPTIONS = new LinkedList<>(); - static { - ProviderConfigProperty prop = new ProviderConfigProperty("propertyFile", "Property File", "file that contains name value pairs", ProviderConfigProperty.STRING_TYPE, null); - OPTIONS.add(prop); - prop = new ProviderConfigProperty("federatedStorage", "User Federated Storage", "use federated storage?", ProviderConfigProperty.BOOLEAN_TYPE, null); - OPTIONS.add(prop); - - } - @Override - public List getConfigProperties() { - return OPTIONS; - } - - - - @Override - public void close() { - + public String getHelpText() { + return "JPA Example User Storage Provider"; } } diff --git a/server-spi/pom.xml b/server-spi/pom.xml index 62a5b0d82f8..ad1c17b79b1 100755 --- a/server-spi/pom.xml +++ b/server-spi/pom.xml @@ -36,6 +36,11 @@ + + org.jboss.spec.javax.transaction + jboss-transaction-api_1.2_spec + provided + org.jboss.resteasy resteasy-jaxrs diff --git a/server-spi/src/main/java/org/keycloak/storage/UserStorageProviderSpi.java b/server-spi/src/main/java/org/keycloak/storage/UserStorageProviderSpi.java index 344e0a06808..4027ea3942b 100755 --- a/server-spi/src/main/java/org/keycloak/storage/UserStorageProviderSpi.java +++ b/server-spi/src/main/java/org/keycloak/storage/UserStorageProviderSpi.java @@ -28,7 +28,7 @@ public class UserStorageProviderSpi implements Spi { @Override public boolean isInternal() { - return true; + return false; } @Override diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java index 3b0a572396a..45bef3c3ea4 100755 --- a/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java +++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java @@ -28,15 +28,16 @@ import org.keycloak.provider.ProviderManager; import org.keycloak.provider.ProviderManagerDeployer; import org.keycloak.provider.ProviderManagerRegistry; import org.keycloak.provider.Spi; -import org.keycloak.services.ServicesLogger; +import org.keycloak.transaction.JtaRegistration; +import org.keycloak.transaction.JtaTransactionManagerLookup; +import org.keycloak.transaction.JtaTransactionWrapper; -import java.util.Collections; +import javax.transaction.TransactionManager; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.util.ServiceLoader; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; @@ -48,7 +49,7 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory, Pr private Map, String> provider = new HashMap<>(); private volatile Map, Map> factoriesMap = new HashMap<>(); protected CopyOnWriteArrayList listeners = new CopyOnWriteArrayList<>(); - private JtaRegistration jta; + private TransactionManager tm; // TODO: Likely should be changed to int and use Time.currentTime() to be compatible with all our "time" reps protected long serverStartupTimestamp; @@ -72,7 +73,6 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory, Pr public void init() { serverStartupTimestamp = System.currentTimeMillis(); - jta = new JtaRegistration(); ProviderManager pm = new ProviderManager(getClass().getClassLoader(), Config.scope().getArray("providers")); spis.addAll(pm.loadSpis()); @@ -96,6 +96,9 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory, Pr } // make the session factory ready for hot deployment ProviderManagerRegistry.SINGLETON.setDeployer(this); + + JtaTransactionManagerLookup lookup = (JtaTransactionManagerLookup)getProviderFactory(JtaTransactionManagerLookup.class); + if (lookup != null) tm = lookup.getTransactionManager(); } protected Map, Map> getFactoriesCopy() { Map, Map> copy = new HashMap<>(); @@ -190,15 +193,18 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory, Pr } Config.Scope scope = Config.scope(spi.getName(), provider); - factory.init(scope); + if (scope.getBoolean("enabled", true)) { - if (spi.isInternal() && !isInternal(factory)) { - logger.spiMayChange(factory.getId(), factory.getClass().getName(), spi.getName()); + factory.init(scope); + + if (spi.isInternal() && !isInternal(factory)) { + logger.spiMayChange(factory.getId(), factory.getClass().getName(), spi.getName()); + } + + factories.put(factory.getId(), factory); + + logger.debugv("Loaded SPI {0} (provider = {1})", spi.getName(), provider); } - - factories.put(factory.getId(), factory); - - logger.debugv("Loaded SPI {0} (provider = {1})", spi.getName(), provider); } else { for (ProviderFactory factory : pm.load(spi)) { Config.Scope scope = Config.scope(spi.getName(), factory.getId()); @@ -276,7 +282,9 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory, Pr public KeycloakSession create() { KeycloakSession session = new DefaultKeycloakSession(this); - jta.begin(session); + if (tm != null) { + session.getTransactionManager().enlist(new JtaTransactionWrapper(tm)); + } return session; } diff --git a/services/src/main/java/org/keycloak/services/JtaRegistration.java b/services/src/main/java/org/keycloak/transaction/JBossJtaTransactionManagerLookup.java similarity index 72% rename from services/src/main/java/org/keycloak/services/JtaRegistration.java rename to services/src/main/java/org/keycloak/transaction/JBossJtaTransactionManagerLookup.java index cdd6e94d632..9d0be4a46eb 100644 --- a/services/src/main/java/org/keycloak/services/JtaRegistration.java +++ b/services/src/main/java/org/keycloak/transaction/JBossJtaTransactionManagerLookup.java @@ -14,25 +14,31 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.keycloak.services; +package org.keycloak.transaction; -import org.keycloak.models.KeycloakSession; +import org.keycloak.Config; +import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.services.ServicesLogger; import javax.naming.InitialContext; import javax.naming.NamingException; -import javax.transaction.Transaction; import javax.transaction.TransactionManager; /** * @author Bill Burke * @version $Revision: 1 $ */ -public class JtaRegistration { +public class JBossJtaTransactionManagerLookup implements JtaTransactionManagerLookup { private static final ServicesLogger logger = ServicesLogger.ROOT_LOGGER; - private TransactionManager tm; - public JtaRegistration() { + @Override + public TransactionManager getTransactionManager() { + return tm; + } + + @Override + public void init(Config.Scope config) { try { InitialContext ctx = new InitialContext(); tm = (TransactionManager)ctx.lookup("java:jboss/TransactionManager"); @@ -45,9 +51,13 @@ public class JtaRegistration { } - public void begin(KeycloakSession session) { - if (tm == null) return; + @Override + public void postInit(KeycloakSessionFactory factory) { - session.getTransactionManager().enlist(new JtaTransactionWrapper(tm)); + } + + @Override + public String getId() { + return "jboss"; } } diff --git a/services/src/main/java/org/keycloak/transaction/JtaRegistration.java b/services/src/main/java/org/keycloak/transaction/JtaRegistration.java new file mode 100644 index 00000000000..1800c52c47c --- /dev/null +++ b/services/src/main/java/org/keycloak/transaction/JtaRegistration.java @@ -0,0 +1,40 @@ +/* + * Copyright 2016 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.transaction; + +import org.keycloak.models.KeycloakSession; +import org.keycloak.services.ServicesLogger; + +import javax.naming.InitialContext; +import javax.naming.NamingException; +import javax.transaction.TransactionManager; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class JtaRegistration { + + + + public void begin(KeycloakSession session) { + TransactionManager tm = session.getProvider(JtaTransactionManagerLookup.class).getTransactionManager(); + if (tm == null) return; + + session.getTransactionManager().enlist(new JtaTransactionWrapper(tm)); + } +} diff --git a/services/src/main/java/org/keycloak/transaction/JtaTransactionManagerLookup.java b/services/src/main/java/org/keycloak/transaction/JtaTransactionManagerLookup.java new file mode 100644 index 00000000000..ebcf52ec880 --- /dev/null +++ b/services/src/main/java/org/keycloak/transaction/JtaTransactionManagerLookup.java @@ -0,0 +1,43 @@ +/* + * Copyright 2016 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.transaction; + +import org.keycloak.models.KeycloakSession; +import org.keycloak.provider.Provider; +import org.keycloak.provider.ProviderFactory; + +import javax.transaction.TransactionManager; + +/** + * JTA TransactionManager lookup + * + * @author Bill Burke + * @version $Revision: 1 $ + */ +public interface JtaTransactionManagerLookup extends Provider, ProviderFactory { + @Override + default void close() { + + } + + @Override + default JtaTransactionManagerLookup create(KeycloakSession session) { + return this; + } + + TransactionManager getTransactionManager(); +} diff --git a/services/src/main/java/org/keycloak/services/JtaTransactionWrapper.java b/services/src/main/java/org/keycloak/transaction/JtaTransactionWrapper.java similarity index 98% rename from services/src/main/java/org/keycloak/services/JtaTransactionWrapper.java rename to services/src/main/java/org/keycloak/transaction/JtaTransactionWrapper.java index 5eb1233c26b..ecc3071a0d0 100644 --- a/services/src/main/java/org/keycloak/services/JtaTransactionWrapper.java +++ b/services/src/main/java/org/keycloak/transaction/JtaTransactionWrapper.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.keycloak.services; +package org.keycloak.transaction; import org.keycloak.models.KeycloakTransaction; diff --git a/services/src/main/java/org/keycloak/transaction/TransactionManagerLookupSpi.java b/services/src/main/java/org/keycloak/transaction/TransactionManagerLookupSpi.java new file mode 100755 index 00000000000..f45d897ee46 --- /dev/null +++ b/services/src/main/java/org/keycloak/transaction/TransactionManagerLookupSpi.java @@ -0,0 +1,49 @@ +/* + * Copyright 2016 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.transaction; + +import org.keycloak.provider.Provider; +import org.keycloak.provider.ProviderFactory; +import org.keycloak.provider.Spi; + +/** + * @author Stian Thorgersen + */ +public class TransactionManagerLookupSpi implements Spi { + + @Override + public boolean isInternal() { + return true; + } + + @Override + public String getName() { + return "jta-lookup"; + } + + @Override + public Class getProviderClass() { + return JtaTransactionManagerLookup.class; + } + + @Override + public Class getProviderFactoryClass() { + return JtaTransactionManagerLookup.class; + } + +} diff --git a/services/src/main/java/org/keycloak/services/UserTransactionWrapper.java b/services/src/main/java/org/keycloak/transaction/UserTransactionWrapper.java similarity index 98% rename from services/src/main/java/org/keycloak/services/UserTransactionWrapper.java rename to services/src/main/java/org/keycloak/transaction/UserTransactionWrapper.java index a1063210ee9..c838b188c48 100644 --- a/services/src/main/java/org/keycloak/services/UserTransactionWrapper.java +++ b/services/src/main/java/org/keycloak/transaction/UserTransactionWrapper.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.keycloak.services; +package org.keycloak.transaction; import org.keycloak.models.KeycloakTransaction; diff --git a/services/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/services/src/main/resources/META-INF/services/org.keycloak.provider.Spi index 55b31a09f9a..77cba5e1efb 100755 --- a/services/src/main/resources/META-INF/services/org.keycloak.provider.Spi +++ b/services/src/main/resources/META-INF/services/org.keycloak.provider.Spi @@ -18,4 +18,5 @@ org.keycloak.exportimport.ClientDescriptionConverterSpi org.keycloak.wellknown.WellKnownSpi org.keycloak.services.clientregistration.ClientRegistrationSpi +org.keycloak.transaction.TransactionManagerLookupSpi diff --git a/services/src/main/resources/META-INF/services/org.keycloak.transaction.JtaTransactionManagerLookup b/services/src/main/resources/META-INF/services/org.keycloak.transaction.JtaTransactionManagerLookup new file mode 100644 index 00000000000..bc968c4cb5d --- /dev/null +++ b/services/src/main/resources/META-INF/services/org.keycloak.transaction.JtaTransactionManagerLookup @@ -0,0 +1 @@ +org.keycloak.transaction.JBossJtaTransactionManagerLookup \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json b/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json index c42d254a3f3..99e8614d0ed 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json @@ -130,5 +130,13 @@ "hostname-verification-policy": "${keycloak.truststore.policy:WILDCARD}", "disabled": "${keycloak.truststore.disabled:false}" } + }, + + "jta-lookup": { + "provider": "${keycloak.jta.lookup.provider:jboss}", + "jboss" : { + "enabled": true + } + } } diff --git a/testsuite/integration/src/test/resources/META-INF/keycloak-server.json b/testsuite/integration/src/test/resources/META-INF/keycloak-server.json index 16ded246e31..d3f87c98d1e 100755 --- a/testsuite/integration/src/test/resources/META-INF/keycloak-server.json +++ b/testsuite/integration/src/test/resources/META-INF/keycloak-server.json @@ -97,5 +97,13 @@ }, "scripting": { + }, + + "jta-lookup": { + "provider": "${keycloak.jta.lookup.provider:jboss}", + "jboss" : { + "enabled": true + } + } } \ No newline at end of file diff --git a/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/KeycloakProviderDependencyProcessor.java b/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/KeycloakProviderDependencyProcessor.java index aa0c1b28ccf..5a5936f9a01 100644 --- a/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/KeycloakProviderDependencyProcessor.java +++ b/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/KeycloakProviderDependencyProcessor.java @@ -61,8 +61,6 @@ public class KeycloakProviderDependencyProcessor implements DeploymentUnitProces if (!isKeycloakProviderDeployment(deploymentUnit)) return; - logger.info("FOUND KEYCLOAK PROVIDER DEPLOYMENT!!!!: " + deploymentUnit.getName()); - final ModuleSpecification moduleSpecification = deploymentUnit.getAttachment(Attachments.MODULE_SPECIFICATION); final ModuleLoader moduleLoader = Module.getBootModuleLoader(); moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_COMMON, false, false, false, false));