keycloak-aplcache

Changes

Details

diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-wildfly-server-subsystem/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-wildfly-server-subsystem/main/module.xml
index d82e1a9..d038c7b 100644
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-wildfly-server-subsystem/main/module.xml
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-wildfly-server-subsystem/main/module.xml
@@ -43,6 +43,7 @@
         <module name="org.jboss.as.web" optional="true"/>
         <module name="org.jboss.as.version" optional="true"/>
         <module name="org.keycloak.keycloak-services"/>
+        <module name="org.keycloak.keycloak-server-spi-private"/>
         <module name="org.keycloak.keycloak-wildfly-adapter" optional="true"/>
         <module name="org.jboss.metadata"/>
     </dependencies>
diff --git a/server-spi/src/main/java/org/keycloak/theme/Theme.java b/server-spi/src/main/java/org/keycloak/theme/Theme.java
index 660029e..c37b7e7 100755
--- a/server-spi/src/main/java/org/keycloak/theme/Theme.java
+++ b/server-spi/src/main/java/org/keycloak/theme/Theme.java
@@ -40,10 +40,6 @@ public interface Theme {
 
     URL getTemplate(String name) throws IOException;
 
-    InputStream getTemplateAsStream(String name) throws IOException;
-
-    URL getResource(String path) throws IOException;
-
     InputStream getResourceAsStream(String path) throws IOException;
 
     /**
diff --git a/server-spi/src/main/java/org/keycloak/theme/ThemeResourceProvider.java b/server-spi/src/main/java/org/keycloak/theme/ThemeResourceProvider.java
new file mode 100755
index 0000000..d647f54
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/theme/ThemeResourceProvider.java
@@ -0,0 +1,56 @@
+/*
+ * 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.theme;
+
+import org.keycloak.provider.Provider;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Set;
+
+/**
+ * A theme resource provider can be used to load additional templates and resources. An example use of this would be
+ * a custom authenticator that requires an additional template and a JavaScript file.
+ *
+ * The theme is searched for templates and resources first. Theme resource providers are only searched if the template
+ * or resource is not found. This allows overriding templates and resources from theme resource providers in the theme.
+ *
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface ThemeResourceProvider extends Provider {
+
+    /**
+     * Load the template for the specific name
+     *
+     * @param name the template name
+     * @return the URL of the template, or null if the template is unknown
+     * @throws IOException
+     */
+    URL getTemplate(String name) throws IOException;
+
+    /**
+     * Load the resource for the specific path
+     *
+     * @param path the resource path
+     * @return an InputStream to read the resource, or null if the resource is unknown
+     * @throws IOException
+     */
+    InputStream getResourceAsStream(String path) throws IOException;
+
+}
diff --git a/server-spi/src/main/java/org/keycloak/theme/ThemeResourceProviderFactory.java b/server-spi/src/main/java/org/keycloak/theme/ThemeResourceProviderFactory.java
new file mode 100644
index 0000000..9acccb3
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/theme/ThemeResourceProviderFactory.java
@@ -0,0 +1,26 @@
+/*
+ * 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.theme;
+
+import org.keycloak.provider.ProviderFactory;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface ThemeResourceProviderFactory extends ProviderFactory<ThemeResourceProvider> {
+}
diff --git a/server-spi/src/main/java/org/keycloak/theme/ThemeResourceSpi.java b/server-spi/src/main/java/org/keycloak/theme/ThemeResourceSpi.java
new file mode 100755
index 0000000..c0ecd82
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/theme/ThemeResourceSpi.java
@@ -0,0 +1,48 @@
+/*
+ * 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.theme;
+
+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 ThemeResourceSpi implements Spi {
+
+    @Override
+    public boolean isInternal() {
+        return false;
+    }
+
+    @Override
+    public String getName() {
+        return "themeResource";
+    }
+
+    @Override
+    public Class<? extends Provider> getProviderClass() {
+        return ThemeResourceProvider.class;
+    }
+
+    @Override
+    public Class<? extends ProviderFactory> getProviderFactoryClass() {
+        return ThemeResourceProviderFactory.class;
+    }
+}
diff --git a/server-spi/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/server-spi/src/main/resources/META-INF/services/org.keycloak.provider.Spi
index c7ee486..86539b5 100755
--- a/server-spi/src/main/resources/META-INF/services/org.keycloak.provider.Spi
+++ b/server-spi/src/main/resources/META-INF/services/org.keycloak.provider.Spi
@@ -32,4 +32,6 @@
 # limitations under the License.
 #
 
-org.keycloak.storage.UserStorageProviderSpi
\ No newline at end of file
+org.keycloak.storage.UserStorageProviderSpi
+org.keycloak.theme.ThemeResourceSpi
+org.keycloak.theme.ThemeSelectorSpi
\ No newline at end of file
diff --git a/server-spi-private/src/main/java/org/keycloak/provider/KeycloakDeploymentInfo.java b/server-spi-private/src/main/java/org/keycloak/provider/KeycloakDeploymentInfo.java
new file mode 100644
index 0000000..8b6c99a
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/provider/KeycloakDeploymentInfo.java
@@ -0,0 +1,56 @@
+package org.keycloak.provider;
+
+public class KeycloakDeploymentInfo {
+
+    private String name;
+    private boolean services;
+    private boolean themes;
+    private boolean themeResources;
+
+    public boolean isProvider() {
+        return services || themes || themeResources;
+    }
+
+    public boolean hasServices() {
+        return services;
+    }
+
+    public static KeycloakDeploymentInfo create() {
+        return new KeycloakDeploymentInfo();
+    }
+
+    private KeycloakDeploymentInfo() {
+    }
+
+    public KeycloakDeploymentInfo name(String name) {
+        this.name = name;
+        return this;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public KeycloakDeploymentInfo services() {
+        this.services = true;
+        return this;
+    }
+
+    public boolean hasThemes() {
+        return themes;
+    }
+
+    public KeycloakDeploymentInfo themes() {
+        this.themes = true;
+        return this;
+    }
+
+    public boolean hasThemeResources() {
+        return themeResources;
+    }
+
+    public KeycloakDeploymentInfo themeResources() {
+        themeResources = true;
+        return this;
+    }
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/provider/ProviderLoaderFactory.java b/server-spi-private/src/main/java/org/keycloak/provider/ProviderLoaderFactory.java
index 85a5e17..7f4cded 100644
--- a/server-spi-private/src/main/java/org/keycloak/provider/ProviderLoaderFactory.java
+++ b/server-spi-private/src/main/java/org/keycloak/provider/ProviderLoaderFactory.java
@@ -24,6 +24,6 @@ public interface ProviderLoaderFactory {
 
     boolean supports(String type);
 
-    ProviderLoader create(ClassLoader baseClassLoader, String resource);
+    ProviderLoader create(KeycloakDeploymentInfo info, ClassLoader baseClassLoader, String resource);
 
 }
diff --git a/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi
index a63b206..9fae0fd 100755
--- a/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi
+++ b/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi
@@ -48,7 +48,6 @@ org.keycloak.email.EmailSenderSpi
 org.keycloak.email.EmailTemplateSpi
 org.keycloak.executors.ExecutorsSpi
 org.keycloak.theme.ThemeSpi
-org.keycloak.theme.ThemeSelectorSpi
 org.keycloak.truststore.TruststoreSpi
 org.keycloak.connections.httpclient.HttpClientSpi
 org.keycloak.models.cache.CacheRealmProviderSpi
diff --git a/services/src/main/java/org/keycloak/provider/DefaultProviderLoader.java b/services/src/main/java/org/keycloak/provider/DefaultProviderLoader.java
index 5f4b19d..ced747a 100644
--- a/services/src/main/java/org/keycloak/provider/DefaultProviderLoader.java
+++ b/services/src/main/java/org/keycloak/provider/DefaultProviderLoader.java
@@ -17,6 +17,12 @@
 
 package org.keycloak.provider;
 
+import org.keycloak.theme.ClasspathThemeProviderFactory;
+import org.keycloak.theme.ClasspathThemeResourceProviderFactory;
+import org.keycloak.theme.ThemeResourceSpi;
+import org.keycloak.theme.ThemeSpi;
+
+import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.ServiceLoader;
@@ -26,27 +32,46 @@ import java.util.ServiceLoader;
  */
 public class DefaultProviderLoader implements ProviderLoader {
 
+    private KeycloakDeploymentInfo info;
     private ClassLoader classLoader;
 
-    public DefaultProviderLoader(ClassLoader classLoader) {
+    public DefaultProviderLoader(KeycloakDeploymentInfo info, ClassLoader classLoader) {
+        this.info = info;
         this.classLoader = classLoader;
     }
 
     @Override
     public List<Spi> loadSpis() {
-        LinkedList<Spi> list = new LinkedList<>();
-        for (Spi spi : ServiceLoader.load(Spi.class, classLoader)) {
-            list.add(spi);
+        if (info.hasServices()) {
+            LinkedList<Spi> list = new LinkedList<>();
+            for (Spi spi : ServiceLoader.load(Spi.class, classLoader)) {
+                list.add(spi);
+            }
+            return list;
+        } else {
+            return Collections.emptyList();
         }
-        return list;
     }
 
     @Override
     public List<ProviderFactory> load(Spi spi) {
-        LinkedList<ProviderFactory> list = new LinkedList<ProviderFactory>();
-        for (ProviderFactory f : ServiceLoader.load(spi.getProviderFactoryClass(), classLoader)) {
-            list.add(f);
+        List<ProviderFactory> list = new LinkedList<>();
+        if (info.hasServices()) {
+            for (ProviderFactory f : ServiceLoader.load(spi.getProviderFactoryClass(), classLoader)) {
+                list.add(f);
+            }
+        }
+
+        if (spi.getClass().equals(ThemeResourceSpi.class) && info.hasThemeResources()) {
+            ClasspathThemeResourceProviderFactory resourceProviderFactory = new ClasspathThemeResourceProviderFactory(info.getName(), classLoader);
+            list.add(resourceProviderFactory);
         }
+
+        if (spi.getClass().equals(ThemeSpi.class) && info.hasThemes()) {
+            ClasspathThemeProviderFactory themeProviderFactory = new ClasspathThemeProviderFactory(info.getName(), classLoader);
+            list.add(themeProviderFactory);
+        }
+
         return list;
     }
 
diff --git a/services/src/main/java/org/keycloak/provider/DefaultProviderLoaderFactory.java b/services/src/main/java/org/keycloak/provider/DefaultProviderLoaderFactory.java
index 0062a61..1786370 100644
--- a/services/src/main/java/org/keycloak/provider/DefaultProviderLoaderFactory.java
+++ b/services/src/main/java/org/keycloak/provider/DefaultProviderLoaderFactory.java
@@ -28,8 +28,8 @@ public class DefaultProviderLoaderFactory implements ProviderLoaderFactory {
     }
 
     @Override
-    public ProviderLoader create(ClassLoader baseClassLoader, String resource) {
-        return new DefaultProviderLoader(baseClassLoader);
+    public ProviderLoader create(KeycloakDeploymentInfo info, ClassLoader baseClassLoader, String resource) {
+        return new DefaultProviderLoader(info, baseClassLoader);
     }
 
 }
diff --git a/services/src/main/java/org/keycloak/provider/FileSystemProviderLoaderFactory.java b/services/src/main/java/org/keycloak/provider/FileSystemProviderLoaderFactory.java
index 414bea8..f7c10e2 100644
--- a/services/src/main/java/org/keycloak/provider/FileSystemProviderLoaderFactory.java
+++ b/services/src/main/java/org/keycloak/provider/FileSystemProviderLoaderFactory.java
@@ -38,8 +38,8 @@ public class FileSystemProviderLoaderFactory implements ProviderLoaderFactory {
     }
 
     @Override
-    public ProviderLoader create(ClassLoader baseClassLoader, String resource) {
-        return new DefaultProviderLoader(createClassLoader(baseClassLoader, resource.split(";")));
+    public ProviderLoader create(KeycloakDeploymentInfo info, ClassLoader baseClassLoader, String resource) {
+        return new DefaultProviderLoader(info, createClassLoader(baseClassLoader, resource.split(";")));
     }
 
     private static URLClassLoader createClassLoader(ClassLoader parent, String... files) {
diff --git a/services/src/main/java/org/keycloak/provider/ProviderManager.java b/services/src/main/java/org/keycloak/provider/ProviderManager.java
index 9db3181..e7659a4 100644
--- a/services/src/main/java/org/keycloak/provider/ProviderManager.java
+++ b/services/src/main/java/org/keycloak/provider/ProviderManager.java
@@ -21,11 +21,13 @@ import org.keycloak.common.util.MultivaluedHashMap;
 
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.IdentityHashMap;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.ServiceLoader;
+import java.util.Set;
 
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -38,7 +40,7 @@ public class ProviderManager {
     private MultivaluedHashMap<Class<? extends Provider>, ProviderFactory> cache = new MultivaluedHashMap<>();
 
 
-    public ProviderManager(ClassLoader baseClassLoader, String... resources) {
+    public ProviderManager(KeycloakDeploymentInfo info, ClassLoader baseClassLoader, String... resources) {
         List<ProviderLoaderFactory> factories = new LinkedList<ProviderLoaderFactory>();
         for (ProviderLoaderFactory f : ServiceLoader.load(ProviderLoaderFactory.class, getClass().getClassLoader())) {
             factories.add(f);
@@ -46,7 +48,7 @@ public class ProviderManager {
 
         logger.debugv("Provider loaders {0}", factories);
 
-        loaders.add(new DefaultProviderLoader(baseClassLoader));
+        loaders.add(new DefaultProviderLoader(info, baseClassLoader));
 
         if (resources != null) {
             for (String r : resources) {
@@ -56,7 +58,8 @@ public class ProviderManager {
                 boolean found = false;
                 for (ProviderLoaderFactory f : factories) {
                     if (f.supports(type)) {
-                        loaders.add(f.create(baseClassLoader, resource));
+                        KeycloakDeploymentInfo resourceInfo = KeycloakDeploymentInfo.create().services();
+                        loaders.add(f.create(resourceInfo, baseClassLoader, resource));
                         found = true;
                         break;
                     }
@@ -67,11 +70,6 @@ public class ProviderManager {
             }
         }
     }
-
-    public ProviderManager(ClassLoader baseClassLoader) {
-        loaders.add(new DefaultProviderLoader(baseClassLoader));
-    }
-
     public synchronized List<Spi> loadSpis() {
         // Use a map to prevent duplicates, since the loaders may have overlapping classpaths.
         Map<String, Spi> spiMap = new HashMap<>();
@@ -88,15 +86,16 @@ public class ProviderManager {
 
     public synchronized List<ProviderFactory> load(Spi spi) {
         if (!cache.containsKey(spi.getProviderClass())) {
-            IdentityHashMap factoryClasses = new IdentityHashMap();
+
+            Set<String> loaded = new HashSet<>();
             for (ProviderLoader loader : loaders) {
                 List<ProviderFactory> f = loader.load(spi);
                 if (f != null) {
                     for (ProviderFactory pf: f) {
-                        // make sure there are no duplicates
-                        if (!factoryClasses.containsKey(pf.getClass())) {
+                        String uniqueId = spi.getName() + "-" + pf.getId();
+                        if (!loaded.contains(uniqueId)) {
                             cache.add(spi.getProviderClass(), pf);
-                            factoryClasses.put(pf.getClass(), pf);
+                            loaded.add(uniqueId);
                         }
                     }
                 }
diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java
index 21da694..78738b3 100755
--- a/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java
+++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java
@@ -22,6 +22,7 @@ import org.keycloak.common.util.MultivaluedHashMap;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.KeycloakSessionFactory;
 import org.keycloak.provider.EnvironmentDependentProviderFactory;
+import org.keycloak.provider.KeycloakDeploymentInfo;
 import org.keycloak.provider.Provider;
 import org.keycloak.provider.ProviderEvent;
 import org.keycloak.provider.ProviderEventListener;
@@ -72,7 +73,7 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory, Pr
     public void init() {
         serverStartupTimestamp = System.currentTimeMillis();
 
-        ProviderManager pm = new ProviderManager(getClass().getClassLoader(), Config.scope().getArray("providers"));
+        ProviderManager pm = new ProviderManager(KeycloakDeploymentInfo.create().services(), getClass().getClassLoader(), Config.scope().getArray("providers"));
         spis.addAll(pm.loadSpis());
         factoriesMap = loadFactories(pm);
         for (ProviderManager manager : ProviderManagerRegistry.SINGLETON.getPreBoot()) {
diff --git a/services/src/main/java/org/keycloak/theme/ClassLoaderTheme.java b/services/src/main/java/org/keycloak/theme/ClassLoaderTheme.java
index 24f215a..82ad64a 100644
--- a/services/src/main/java/org/keycloak/theme/ClassLoaderTheme.java
+++ b/services/src/main/java/org/keycloak/theme/ClassLoaderTheme.java
@@ -105,16 +105,6 @@ public class ClassLoaderTheme implements Theme {
     }
 
     @Override
-    public InputStream getTemplateAsStream(String name) {
-        return classLoader.getResourceAsStream(templateRoot + name);
-    }
-
-    @Override
-    public URL getResource(String path) {
-        return classLoader.getResource(resourceRoot + path);
-    }
-
-    @Override
     public InputStream getResourceAsStream(String path) {
         return classLoader.getResourceAsStream(resourceRoot + path);
     }
diff --git a/services/src/main/java/org/keycloak/theme/ClasspathThemeProviderFactory.java b/services/src/main/java/org/keycloak/theme/ClasspathThemeProviderFactory.java
new file mode 100755
index 0000000..0bd0891
--- /dev/null
+++ b/services/src/main/java/org/keycloak/theme/ClasspathThemeProviderFactory.java
@@ -0,0 +1,121 @@
+/*
+ * 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.theme;
+
+import org.keycloak.Config;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.util.JsonSerialization;
+
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ClasspathThemeProviderFactory implements ThemeProviderFactory {
+
+    public static final String KEYCLOAK_THEMES_JSON = "META-INF/keycloak-themes.json";
+    protected static Map<Theme.Type, Map<String, ClassLoaderTheme>> themes = new HashMap<>();
+
+    private String id;
+
+    public ClasspathThemeProviderFactory(String id) {
+        this.id = id;
+    }
+
+    public ClasspathThemeProviderFactory(String id, ClassLoader classLoader) {
+        this.id = id;
+        loadThemes(classLoader, classLoader.getResourceAsStream(KEYCLOAK_THEMES_JSON));
+    }
+
+    public static class ThemeRepresentation {
+        private String name;
+        private String[] types;
+
+        public String getName() {
+            return name;
+        }
+
+        public void setName(String name) {
+            this.name = name;
+        }
+
+        public String[] getTypes() {
+            return types;
+        }
+
+        public void setTypes(String[] types) {
+            this.types = types;
+        }
+    }
+
+    public static class ThemesRepresentation {
+        private ThemeRepresentation[] themes;
+
+        public ThemeRepresentation[] getThemes() {
+            return themes;
+        }
+
+        public void setThemes(ThemeRepresentation[] themes) {
+            this.themes = themes;
+        }
+    }
+
+    @Override
+    public ThemeProvider create(KeycloakSession session) {
+        return new ClasspathThemeProvider(themes);
+    }
+
+    @Override
+    public void init(Config.Scope config) {
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+    }
+
+    @Override
+    public void close() {
+    }
+
+    @Override
+    public String getId() {
+        return id;
+    }
+
+    protected void loadThemes(ClassLoader classLoader, InputStream themesInputStream) {
+        try {
+            ThemesRepresentation themesRep = JsonSerialization.readValue(themesInputStream, ThemesRepresentation.class);
+
+            for (ThemeRepresentation themeRep : themesRep.getThemes()) {
+                for (String t : themeRep.getTypes()) {
+                    Theme.Type type = Theme.Type.valueOf(t.toUpperCase());
+                    if (!themes.containsKey(type)) {
+                        themes.put(type, new HashMap<>());
+                    }
+                    themes.get(type).put(themeRep.getName(), new ClassLoaderTheme(themeRep.getName(), type, classLoader));
+                }
+            }
+        } catch (Exception e) {
+            throw new RuntimeException("Failed to load themes", e);
+        }
+    }
+
+}
diff --git a/services/src/main/java/org/keycloak/theme/ClasspathThemeResourceProviderFactory.java b/services/src/main/java/org/keycloak/theme/ClasspathThemeResourceProviderFactory.java
new file mode 100644
index 0000000..760bbac
--- /dev/null
+++ b/services/src/main/java/org/keycloak/theme/ClasspathThemeResourceProviderFactory.java
@@ -0,0 +1,55 @@
+package org.keycloak.theme;
+
+import org.keycloak.Config;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+
+public class ClasspathThemeResourceProviderFactory implements ThemeResourceProviderFactory, ThemeResourceProvider {
+
+    public static final String THEME_RESOURCES_TEMPLATES = "theme-resources/templates/";
+    public static final String THEME_RESOURCES_RESOURCES = "theme-resources/resources/";
+    private final String id;
+    private final ClassLoader classLoader;
+
+    public ClasspathThemeResourceProviderFactory(String id, ClassLoader classLoader) {
+        this.id = id;
+        this.classLoader = classLoader;
+    }
+
+    @Override
+    public ThemeResourceProvider create(KeycloakSession session) {
+        return this;
+    }
+
+    @Override
+    public URL getTemplate(String name) throws IOException {
+        return classLoader.getResource(THEME_RESOURCES_TEMPLATES + name);
+    }
+
+    @Override
+    public InputStream getResourceAsStream(String path) throws IOException {
+        return classLoader.getResourceAsStream(THEME_RESOURCES_RESOURCES + path);
+    }
+
+    @Override
+    public String getId() {
+        return id;
+    }
+
+    @Override
+    public void init(Config.Scope config) {
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+    }
+
+    @Override
+    public void close() {
+    }
+
+}
diff --git a/services/src/main/java/org/keycloak/theme/ExtendingThemeManager.java b/services/src/main/java/org/keycloak/theme/ExtendingThemeManager.java
index 3aa6cdc..b580a4a 100755
--- a/services/src/main/java/org/keycloak/theme/ExtendingThemeManager.java
+++ b/services/src/main/java/org/keycloak/theme/ExtendingThemeManager.java
@@ -111,31 +111,27 @@ public class ExtendingThemeManager implements ThemeProvider {
 
     private Theme loadTheme(String name, Theme.Type type) throws IOException {
         Theme theme = findTheme(name, type);
-        if (theme != null && (theme.getParentName() != null || theme.getImportName() != null)) {
-            List<Theme> themes = new LinkedList<>();
-            themes.add(theme);
+        List<Theme> themes = new LinkedList<>();
+        themes.add(theme);
 
-            if (theme.getImportName() != null) {
-                String[] s = theme.getImportName().split("/");
-                themes.add(findTheme(s[1], Theme.Type.valueOf(s[0].toUpperCase())));
-            }
+        if (theme.getImportName() != null) {
+            String[] s = theme.getImportName().split("/");
+            themes.add(findTheme(s[1], Theme.Type.valueOf(s[0].toUpperCase())));
+        }
 
-            if (theme.getParentName() != null) {
-                for (String parentName = theme.getParentName(); parentName != null; parentName = theme.getParentName()) {
-                    theme = findTheme(parentName, type);
-                    themes.add(theme);
+        if (theme.getParentName() != null) {
+            for (String parentName = theme.getParentName(); parentName != null; parentName = theme.getParentName()) {
+                theme = findTheme(parentName, type);
+                themes.add(theme);
 
-                    if (theme.getImportName() != null) {
-                        String[] s = theme.getImportName().split("/");
-                        themes.add(findTheme(s[1], Theme.Type.valueOf(s[0].toUpperCase())));
-                    }
+                if (theme.getImportName() != null) {
+                    String[] s = theme.getImportName().split("/");
+                    themes.add(findTheme(s[1], Theme.Type.valueOf(s[0].toUpperCase())));
                 }
             }
-
-            return new ExtendingTheme(themes);
-        } else {
-            return theme;
         }
+
+        return new ExtendingTheme(themes, session.getAllProviders(ThemeResourceProvider.class));
     }
 
     @Override
@@ -178,13 +174,15 @@ public class ExtendingThemeManager implements ThemeProvider {
     public static class ExtendingTheme implements Theme {
 
         private List<Theme> themes;
+        private Set<ThemeResourceProvider> themeResourceProviders;
 
         private Properties properties;
 
         private ConcurrentHashMap<String, ConcurrentHashMap<Locale, Properties>> messages = new ConcurrentHashMap<>();
 
-        public ExtendingTheme(List<Theme> themes) {
+        public ExtendingTheme(List<Theme> themes, Set<ThemeResourceProvider> themeResourceProviders) {
             this.themes = themes;
+            this.themeResourceProviders = themeResourceProviders;
         }
 
         @Override
@@ -215,40 +213,33 @@ public class ExtendingThemeManager implements ThemeProvider {
                     return template;
                 }
             }
-            return null;
-        }
 
-        @Override
-        public InputStream getTemplateAsStream(String name) throws IOException {
-            for (Theme t : themes) {
-                InputStream template = t.getTemplateAsStream(name);
+            for (ThemeResourceProvider t : themeResourceProviders) {
+                URL template = t.getTemplate(name);
                 if (template != null) {
                     return template;
                 }
             }
+
             return null;
         }
 
-
         @Override
-        public URL getResource(String path) throws IOException {
+        public InputStream getResourceAsStream(String path) throws IOException {
             for (Theme t : themes) {
-                URL resource = t.getResource(path);
+                InputStream resource = t.getResourceAsStream(path);
                 if (resource != null) {
                     return resource;
                 }
             }
-            return null;
-        }
 
-        @Override
-        public InputStream getResourceAsStream(String path) throws IOException {
-            for (Theme t : themes) {
+            for (ThemeResourceProvider t : themeResourceProviders) {
                 InputStream resource = t.getResourceAsStream(path);
                 if (resource != null) {
                     return resource;
                 }
             }
+
             return null;
         }
 
diff --git a/services/src/main/java/org/keycloak/theme/FolderTheme.java b/services/src/main/java/org/keycloak/theme/FolderTheme.java
index 04621d7..f15bddb 100644
--- a/services/src/main/java/org/keycloak/theme/FolderTheme.java
+++ b/services/src/main/java/org/keycloak/theme/FolderTheme.java
@@ -87,13 +87,7 @@ public class FolderTheme implements Theme {
     }
 
     @Override
-    public InputStream getTemplateAsStream(String name) throws IOException {
-        URL url = getTemplate(name);
-        return url != null ? url.openStream() : null;
-    }
-
-    @Override
-    public URL getResource(String path) throws IOException {
+    public InputStream getResourceAsStream(String path) throws IOException {
         if (File.separatorChar != '/') {
             path = path.replace('/', File.separatorChar);
         }
@@ -102,17 +96,11 @@ public class FolderTheme implements Theme {
         if (!file.isFile() || !file.getCanonicalPath().startsWith(resourcesDir.getCanonicalPath())) {
             return null;
         } else {
-            return file.toURI().toURL();
+            return file.toURI().toURL().openStream();
         }
     }
 
     @Override
-    public InputStream getResourceAsStream(String path) throws IOException {
-        URL url = getResource(path);
-        return url != null ? url.openStream() : null;
-    }
-
-    @Override
     public Properties getMessages(Locale locale) throws IOException {
         return getMessages("messages", locale);
     }
diff --git a/services/src/main/java/org/keycloak/theme/JarThemeProviderFactory.java b/services/src/main/java/org/keycloak/theme/JarThemeProviderFactory.java
index 4a2bb26..83cbb58 100755
--- a/services/src/main/java/org/keycloak/theme/JarThemeProviderFactory.java
+++ b/services/src/main/java/org/keycloak/theme/JarThemeProviderFactory.java
@@ -32,47 +32,15 @@ import java.util.Map;
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
-public class JarThemeProviderFactory implements ThemeProviderFactory {
+public class JarThemeProviderFactory extends ClasspathThemeProviderFactory {
 
-    protected static final String KEYCLOAK_THEMES_JSON = "META-INF/keycloak-themes.json";
-    protected static Map<Theme.Type, Map<String, ClassLoaderTheme>> themes = new HashMap<>();
-
-    public static class ThemeRepresentation {
-        private String name;
-        private String[] types;
-
-        public String getName() {
-            return name;
-        }
-
-        public void setName(String name) {
-            this.name = name;
-        }
-
-        public String[] getTypes() {
-            return types;
-        }
-
-        public void setTypes(String[] types) {
-            this.types = types;
-        }
-    }
-
-    public static class ThemesRepresentation {
-        private ThemeRepresentation[] themes;
-
-        public ThemeRepresentation[] getThemes() {
-            return themes;
-        }
-
-        public void setThemes(ThemeRepresentation[] themes) {
-            this.themes = themes;
-        }
+    public JarThemeProviderFactory() {
+        super("jar");
     }
 
     @Override
     public ThemeProvider create(KeycloakSession session) {
-        return new JarThemeProvider(themes);
+        return new ClasspathThemeProvider(themes);
     }
 
     @Override
@@ -88,35 +56,4 @@ public class JarThemeProviderFactory implements ThemeProviderFactory {
         }
     }
 
-    @Override
-    public void postInit(KeycloakSessionFactory factory) {
-    }
-
-    @Override
-    public void close() {
-    }
-
-    @Override
-    public String getId() {
-        return "jar";
-    }
-
-    protected void loadThemes(ClassLoader classLoader, InputStream themesInputStream) {
-        try {
-            ThemesRepresentation themesRep = JsonSerialization.readValue(themesInputStream, ThemesRepresentation.class);
-
-            for (ThemeRepresentation themeRep : themesRep.getThemes()) {
-                for (String t : themeRep.getTypes()) {
-                    Theme.Type type = Theme.Type.valueOf(t.toUpperCase());
-                    if (!themes.containsKey(type)) {
-                        themes.put(type, new HashMap<String, ClassLoaderTheme>());
-                    }
-                    themes.get(type).put(themeRep.getName(), new ClassLoaderTheme(themeRep.getName(), type, classLoader));
-                }
-            }
-        } catch (Exception e) {
-            throw new RuntimeException("Failed to load themes", e);
-        }
-    }
-
 }
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/theme/TestThemeResourceProvider.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/theme/TestThemeResourceProvider.java
new file mode 100644
index 0000000..e621b82
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/theme/TestThemeResourceProvider.java
@@ -0,0 +1,11 @@
+package org.keycloak.testsuite.theme;
+
+import org.keycloak.theme.ClasspathThemeResourceProviderFactory;
+
+public class TestThemeResourceProvider extends ClasspathThemeResourceProviderFactory {
+
+    public TestThemeResourceProvider() {
+        super("test-resources", TestThemeResourceProvider.class.getClassLoader());
+    }
+
+}
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/META-INF/services/org.keycloak.theme.ThemeResourceProviderFactory b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/META-INF/services/org.keycloak.theme.ThemeResourceProviderFactory
new file mode 100644
index 0000000..4da0a77
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/META-INF/services/org.keycloak.theme.ThemeResourceProviderFactory
@@ -0,0 +1 @@
+org.keycloak.testsuite.theme.TestThemeResourceProvider
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme-resources/resources/test.js b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme-resources/resources/test.js
new file mode 100644
index 0000000..b5d5243
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme-resources/resources/test.js
@@ -0,0 +1 @@
+console.debug('hello');
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme-resources/templates/test.ftl b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme-resources/templates/test.ftl
new file mode 100644
index 0000000..5124d86
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme-resources/templates/test.ftl
@@ -0,0 +1 @@
+<html><body>Hello!</body></html>
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/theme/ThemeResourceProviderTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/theme/ThemeResourceProviderTest.java
new file mode 100644
index 0000000..5373cb0
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/theme/ThemeResourceProviderTest.java
@@ -0,0 +1,53 @@
+package org.keycloak.testsuite.theme;
+
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.Assert;
+import org.junit.Test;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
+import org.keycloak.testsuite.runonserver.RunOnServerDeployment;
+import org.keycloak.theme.Theme;
+import org.keycloak.theme.ThemeProvider;
+
+import java.io.IOException;
+
+public class ThemeResourceProviderTest extends AbstractTestRealmKeycloakTest {
+
+    @Deployment
+    public static WebArchive deploy() {
+        return RunOnServerDeployment.create(ThemeResourceProviderTest.class, AbstractTestRealmKeycloakTest.class);
+    }
+
+    @Override
+    public void configureTestRealm(RealmRepresentation testRealm) {
+
+    }
+
+    @Test
+    public void getTheme() {
+        testingClient.server().run(session -> {
+            try {
+                ThemeProvider extending = session.getProvider(ThemeProvider.class, "extending");
+                Theme theme = extending.getTheme("base", Theme.Type.LOGIN);
+                Assert.assertNotNull(theme.getTemplate("test.ftl"));
+            } catch (IOException e) {
+                Assert.fail(e.getMessage());
+            }
+        });
+    }
+
+    @Test
+    public void getResourceAsStream() {
+        testingClient.server().run(session -> {
+            try {
+                ThemeProvider extending = session.getProvider(ThemeProvider.class, "extending");
+                Theme theme = extending.getTheme("base", Theme.Type.LOGIN);
+                Assert.assertNotNull(theme.getResourceAsStream("test.js"));
+            } catch (IOException e) {
+                Assert.fail(e.getMessage());
+            }
+        });
+    }
+
+}
diff --git a/wildfly/extensions/src/main/java/org/keycloak/provider/wildfly/ModuleProviderLoaderFactory.java b/wildfly/extensions/src/main/java/org/keycloak/provider/wildfly/ModuleProviderLoaderFactory.java
index b0d66c2..32a45b8 100755
--- a/wildfly/extensions/src/main/java/org/keycloak/provider/wildfly/ModuleProviderLoaderFactory.java
+++ b/wildfly/extensions/src/main/java/org/keycloak/provider/wildfly/ModuleProviderLoaderFactory.java
@@ -21,6 +21,7 @@ import org.jboss.modules.Module;
 import org.jboss.modules.ModuleClassLoader;
 import org.jboss.modules.ModuleIdentifier;
 import org.keycloak.provider.DefaultProviderLoader;
+import org.keycloak.provider.KeycloakDeploymentInfo;
 import org.keycloak.provider.ProviderLoader;
 import org.keycloak.provider.ProviderLoaderFactory;
 
@@ -35,11 +36,11 @@ public class ModuleProviderLoaderFactory implements ProviderLoaderFactory {
     }
 
     @Override
-    public ProviderLoader create(ClassLoader baseClassLoader, String resource) {
+    public ProviderLoader create(KeycloakDeploymentInfo info, ClassLoader baseClassLoader, String resource) {
         try {
             Module module = Module.getContextModuleLoader().loadModule(ModuleIdentifier.fromString(resource));
             ModuleClassLoader classLoader = module.getClassLoader();
-            return new DefaultProviderLoader(classLoader);
+            return new DefaultProviderLoader(info, classLoader);
         } catch (Exception e) {
             throw new RuntimeException(e);
         }
diff --git a/wildfly/extensions/src/main/java/org/keycloak/provider/wildfly/ModuleThemeProviderFactory.java b/wildfly/extensions/src/main/java/org/keycloak/provider/wildfly/ModuleThemeProviderFactory.java
index 72d2e1d..d1e6ce5 100644
--- a/wildfly/extensions/src/main/java/org/keycloak/provider/wildfly/ModuleThemeProviderFactory.java
+++ b/wildfly/extensions/src/main/java/org/keycloak/provider/wildfly/ModuleThemeProviderFactory.java
@@ -21,12 +21,17 @@ import org.jboss.modules.Module;
 import org.jboss.modules.ModuleClassLoader;
 import org.jboss.modules.ModuleIdentifier;
 import org.keycloak.Config;
+import org.keycloak.theme.ClasspathThemeProviderFactory;
 import org.keycloak.theme.JarThemeProviderFactory;
 
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
-public class ModuleThemeProviderFactory extends JarThemeProviderFactory {
+public class ModuleThemeProviderFactory extends ClasspathThemeProviderFactory {
+
+    public ModuleThemeProviderFactory() {
+        super("module");
+    }
 
     @Override
     public void init(Config.Scope config) {
@@ -44,9 +49,4 @@ public class ModuleThemeProviderFactory extends JarThemeProviderFactory {
         }
     }
 
-    @Override
-    public String getId() {
-        return "module";
-    }
-
 }
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 7def4d1..3d6e367 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
@@ -24,12 +24,12 @@ import org.jboss.as.server.deployment.DeploymentUnitProcessor;
 import org.jboss.as.server.deployment.module.ModuleDependency;
 import org.jboss.as.server.deployment.module.ModuleSpecification;
 import org.jboss.as.server.deployment.module.ResourceRoot;
-import org.jboss.logging.Logger;
 import org.jboss.modules.Module;
 import org.jboss.modules.ModuleIdentifier;
 import org.jboss.modules.ModuleLoader;
 import org.jboss.vfs.VirtualFile;
 import org.jboss.vfs.util.AbstractVirtualFileFilterWithAttributes;
+import org.keycloak.provider.KeycloakDeploymentInfo;
 
 import java.io.IOException;
 import java.util.List;
@@ -48,8 +48,6 @@ public class KeycloakProviderDependencyProcessor implements DeploymentUnitProces
     private static final ModuleIdentifier RESTEASY = ModuleIdentifier.create("org.jboss.resteasy.resteasy-jaxrs");
     private static final ModuleIdentifier APACHE = ModuleIdentifier.create("org.apache.httpcomponents");
 
-    private static final Logger logger = Logger.getLogger(KeycloakProviderDependencyProcessor.class);
-
     @Override
     public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {
         DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
@@ -60,53 +58,66 @@ public class KeycloakProviderDependencyProcessor implements DeploymentUnitProces
             return;
         }
 
-        if (!isKeycloakProviderDeployment(deploymentUnit)) return;
-
-        final ModuleSpecification moduleSpecification = deploymentUnit.getAttachment(Attachments.MODULE_SPECIFICATION);
-        final ModuleLoader moduleLoader = Module.getBootModuleLoader();
-        moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_COMMON, false, false, false, false));
-        moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_CORE, false, false, false, false));
-        moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_SERVER_SPI, false, false, false, false));
-        moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_SERVER_SPI_PRIVATE, false, false, false, false));
-        moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, JAXRS, false, false, false, false));
-        moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, RESTEASY, false, false, false, false));
-        moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, APACHE, false, false, false, false));
-        moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_JPA, false, false, false, false));
-
-
+        KeycloakDeploymentInfo info = getKeycloakProviderDeploymentInfo(deploymentUnit);
+        if (info.hasServices()) {
+            final ModuleSpecification moduleSpecification = deploymentUnit.getAttachment(Attachments.MODULE_SPECIFICATION);
+            final ModuleLoader moduleLoader = Module.getBootModuleLoader();
+            moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_COMMON, false, false, false, false));
+            moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_CORE, false, false, false, false));
+            moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_SERVER_SPI, false, false, false, false));
+            moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_SERVER_SPI_PRIVATE, false, false, false, false));
+            moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, JAXRS, false, false, false, false));
+            moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, RESTEASY, false, false, false, false));
+            moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, APACHE, false, false, false, false));
+            moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_JPA, false, false, false, false));
+        }
     }
 
     public KeycloakProviderDependencyProcessor() {
         super();
     }
 
-    public static boolean isKeycloakProviderDeployment(DeploymentUnit du) {
+    public static KeycloakDeploymentInfo getKeycloakProviderDeploymentInfo(DeploymentUnit du) {
+        KeycloakDeploymentInfo info = KeycloakDeploymentInfo.create();
+        info.name(du.getName());
+
         final ResourceRoot resourceRoot = du.getAttachment(Attachments.DEPLOYMENT_ROOT);
-        if (resourceRoot == null) {
-            return false;
-        }
-        final VirtualFile deploymentRoot = resourceRoot.getRoot();
-        if (deploymentRoot == null || !deploymentRoot.exists()) {
-            return false;
-        }
-        VirtualFile services = deploymentRoot.getChild("META-INF/services");
-        if (!services.exists()) return false;
-        try {
-            List<VirtualFile> archives = services.getChildren(new AbstractVirtualFileFilterWithAttributes(){
-                @Override
-                public boolean accepts(VirtualFile file) {
-                    return file.getName().startsWith("org.keycloak");
+        if (resourceRoot != null) {
+            final VirtualFile deploymentRoot = resourceRoot.getRoot();
+            if (deploymentRoot != null && deploymentRoot.exists()) {
+                if (deploymentRoot.getChild("META-INF/keycloak-themes.json").exists() && deploymentRoot.getChild("theme").exists()) {
+                    info.themes();
+                }
+
+                if (deploymentRoot.getChild("theme-resources").exists()) {
+                    info.themeResources();
                 }
-            });
-            return !archives.isEmpty();
-        } catch (IOException e) {
 
+                VirtualFile services = deploymentRoot.getChild("META-INF/services");
+                if(services.exists()) {
+                    try {
+                        List<VirtualFile> archives = services.getChildren(new AbstractVirtualFileFilterWithAttributes() {
+                            @Override
+                            public boolean accepts(VirtualFile file) {
+                                return file.getName().startsWith("org.keycloak");
+                            }
+                        });
+                        if (!archives.isEmpty()) {
+                            info.services();
+                        }
+                    } catch (IOException e) {
+                        e.printStackTrace();
+                    }
+                }
+            }
         }
-        return false;
+
+        return info;
     }
 
     @Override
     public void undeploy(DeploymentUnit context) {
 
     }
+
 }
diff --git a/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/KeycloakProviderDeploymentProcessor.java b/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/KeycloakProviderDeploymentProcessor.java
index fcd1b46..bf74732 100644
--- a/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/KeycloakProviderDeploymentProcessor.java
+++ b/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/KeycloakProviderDeploymentProcessor.java
@@ -24,6 +24,7 @@ import org.jboss.as.server.deployment.DeploymentUnitProcessingException;
 import org.jboss.as.server.deployment.DeploymentUnitProcessor;
 import org.jboss.logging.Logger;
 import org.jboss.modules.Module;
+import org.keycloak.provider.KeycloakDeploymentInfo;
 import org.keycloak.provider.ProviderManager;
 import org.keycloak.provider.ProviderManagerRegistry;
 
@@ -46,16 +47,14 @@ public class KeycloakProviderDeploymentProcessor implements DeploymentUnitProces
             return;
         }
 
-        if (!KeycloakProviderDependencyProcessor.isKeycloakProviderDeployment(deploymentUnit)) return;
-
-        logger.infov("Deploying Keycloak provider: {0}", deploymentUnit.getName());
-        final Module module = deploymentUnit.getAttachment(Attachments.MODULE);
-        ProviderManager pm = new ProviderManager(module.getClassLoader());
-        ProviderManagerRegistry.SINGLETON.deploy(pm);
-        deploymentUnit.putAttachment(ATTACHMENT_KEY, pm);
-
-
-
+        KeycloakDeploymentInfo info = KeycloakProviderDependencyProcessor.getKeycloakProviderDeploymentInfo(deploymentUnit);
+        if (info.isProvider()) {
+            logger.infov("Deploying Keycloak provider: {0}", deploymentUnit.getName());
+            final Module module = deploymentUnit.getAttachment(Attachments.MODULE);
+            ProviderManager pm = new ProviderManager(info, module.getClassLoader());
+            ProviderManagerRegistry.SINGLETON.deploy(pm);
+            deploymentUnit.putAttachment(ATTACHMENT_KEY, pm);
+        }
     }
 
     public KeycloakProviderDeploymentProcessor() {