keycloak-developers

jta transaction abstraction

8/8/2016 1:32:36 PM

Changes

Details

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 89263bf..b2f1067 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/pom.xml b/examples/providers/user-storage-jpa/pom.xml
index 75c3a6f..9702c46 100755
--- a/examples/providers/user-storage-jpa/pom.xml
+++ b/examples/providers/user-storage-jpa/pom.xml
@@ -23,7 +23,7 @@
         <version>2.1.0-SNAPSHOT</version>
     </parent>
 
-    <name>Properties Authentication Provider Example</name>
+    <name>User Storage JPA Provider Exapmle</name>
     <description/>
     <modelVersion>4.0.0</modelVersion>
 
@@ -75,14 +75,7 @@
                     <target>1.8</target>
                 </configuration>
             </plugin>
-            <plugin>
-                <groupId>org.jboss.as.plugins</groupId>
-                <artifactId>jboss-as-maven-plugin</artifactId>
-                <configuration>
-                    <skip>false</skip>
-                </configuration>
-            </plugin>
-            <plugin>
+             <plugin>
                 <groupId>org.wildfly.plugins</groupId>
                 <artifactId>wildfly-maven-plugin</artifactId>
                 <configuration>
diff --git a/examples/providers/user-storage-jpa/README.md b/examples/providers/user-storage-jpa/README.md
new file mode 100755
index 0000000..f965ef2
--- /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/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 4491c98..a1db65d 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<ProviderConfigProperty> 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<ProviderConfigProperty> 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 62a5b0d..ad1c17b 100755
--- a/server-spi/pom.xml
+++ b/server-spi/pom.xml
@@ -37,6 +37,11 @@
 
     <dependencies>
         <dependency>
+            <groupId>org.jboss.spec.javax.transaction</groupId>
+            <artifactId>jboss-transaction-api_1.2_spec</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
             <groupId>org.jboss.resteasy</groupId>
             <artifactId>resteasy-jaxrs</artifactId>
             <scope>provided</scope>
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 344e0a0..4027ea3 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 3b0a572..45bef3c 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<Class<? extends Provider>, String> provider = new HashMap<>();
     private volatile Map<Class<? extends Provider>, Map<String, ProviderFactory>> factoriesMap = new HashMap<>();
     protected CopyOnWriteArrayList<ProviderEventListener> 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<Class<? extends Provider>, Map<String, ProviderFactory>> getFactoriesCopy() {
         Map<Class<? extends Provider>, Map<String, ProviderFactory>> 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);
 
-                factories.put(factory.getId(), factory);
+                    if (spi.isInternal() && !isInternal(factory)) {
+                        logger.spiMayChange(factory.getId(), factory.getClass().getName(), spi.getName());
+                    }
 
-                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/transaction/JBossJtaTransactionManagerLookup.java b/services/src/main/java/org/keycloak/transaction/JBossJtaTransactionManagerLookup.java
new file mode 100644
index 0000000..9d0be4a
--- /dev/null
+++ b/services/src/main/java/org/keycloak/transaction/JBossJtaTransactionManagerLookup.java
@@ -0,0 +1,63 @@
+/*
+ * 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.Config;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.services.ServicesLogger;
+
+import javax.naming.InitialContext;
+import javax.naming.NamingException;
+import javax.transaction.TransactionManager;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class JBossJtaTransactionManagerLookup implements JtaTransactionManagerLookup {
+    private static final ServicesLogger logger = ServicesLogger.ROOT_LOGGER;
+    private TransactionManager tm;
+
+    @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");
+            if (tm == null) {
+                logger.debug("Could not locate TransactionManager");
+            }
+        } catch (NamingException e) {
+            logger.debug("Could not load TransactionManager", e);
+        }
+
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+
+    }
+
+    @Override
+    public String getId() {
+        return "jboss";
+    }
+}
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 0000000..ebcf52e
--- /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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface JtaTransactionManagerLookup extends Provider, ProviderFactory<JtaTransactionManagerLookup> {
+    @Override
+    default void close() {
+
+    }
+
+    @Override
+    default JtaTransactionManagerLookup create(KeycloakSession session) {
+        return this;
+    }
+
+    TransactionManager getTransactionManager();
+}
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 0000000..f45d897
--- /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 <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class TransactionManagerLookupSpi implements Spi {
+
+    @Override
+    public boolean isInternal() {
+        return true;
+    }
+
+    @Override
+    public String getName() {
+        return "jta-lookup";
+    }
+
+    @Override
+    public Class<? extends Provider> getProviderClass() {
+        return JtaTransactionManagerLookup.class;
+    }
+
+    @Override
+    public Class<? extends ProviderFactory> getProviderFactoryClass() {
+        return JtaTransactionManagerLookup.class;
+    }
+
+}
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 55b31a0..77cba5e 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 0000000..bc968c4
--- /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/src/test/resources/META-INF/keycloak-server.json b/testsuite/integration/src/test/resources/META-INF/keycloak-server.json
index 16ded24..d3f87c9 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/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 c42d254..99e8614 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/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 aa0c1b2..5a5936f 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));