KEYCLOAK-6519 Theme resource provider
This commit is contained in:
parent
e8de4655ac
commit
505cf5b251
30 changed files with 587 additions and 218 deletions
|
@ -43,6 +43,7 @@
|
||||||
<module name="org.jboss.as.web" optional="true"/>
|
<module name="org.jboss.as.web" optional="true"/>
|
||||||
<module name="org.jboss.as.version" optional="true"/>
|
<module name="org.jboss.as.version" optional="true"/>
|
||||||
<module name="org.keycloak.keycloak-services"/>
|
<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.keycloak.keycloak-wildfly-adapter" optional="true"/>
|
||||||
<module name="org.jboss.metadata"/>
|
<module name="org.jboss.metadata"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,6 +24,6 @@ public interface ProviderLoaderFactory {
|
||||||
|
|
||||||
boolean supports(String type);
|
boolean supports(String type);
|
||||||
|
|
||||||
ProviderLoader create(ClassLoader baseClassLoader, String resource);
|
ProviderLoader create(KeycloakDeploymentInfo info, ClassLoader baseClassLoader, String resource);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,7 +48,6 @@ org.keycloak.email.EmailSenderSpi
|
||||||
org.keycloak.email.EmailTemplateSpi
|
org.keycloak.email.EmailTemplateSpi
|
||||||
org.keycloak.executors.ExecutorsSpi
|
org.keycloak.executors.ExecutorsSpi
|
||||||
org.keycloak.theme.ThemeSpi
|
org.keycloak.theme.ThemeSpi
|
||||||
org.keycloak.theme.ThemeSelectorSpi
|
|
||||||
org.keycloak.truststore.TruststoreSpi
|
org.keycloak.truststore.TruststoreSpi
|
||||||
org.keycloak.connections.httpclient.HttpClientSpi
|
org.keycloak.connections.httpclient.HttpClientSpi
|
||||||
org.keycloak.models.cache.CacheRealmProviderSpi
|
org.keycloak.models.cache.CacheRealmProviderSpi
|
||||||
|
|
|
@ -40,10 +40,6 @@ public interface Theme {
|
||||||
|
|
||||||
URL getTemplate(String name) throws IOException;
|
URL getTemplate(String name) throws IOException;
|
||||||
|
|
||||||
InputStream getTemplateAsStream(String name) throws IOException;
|
|
||||||
|
|
||||||
URL getResource(String path) throws IOException;
|
|
||||||
|
|
||||||
InputStream getResourceAsStream(String path) throws IOException;
|
InputStream getResourceAsStream(String path) throws IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
56
server-spi/src/main/java/org/keycloak/theme/ThemeResourceProvider.java
Executable file
56
server-spi/src/main/java/org/keycloak/theme/ThemeResourceProvider.java
Executable file
|
@ -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;
|
||||||
|
|
||||||
|
}
|
|
@ -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> {
|
||||||
|
}
|
48
server-spi/src/main/java/org/keycloak/theme/ThemeResourceSpi.java
Executable file
48
server-spi/src/main/java/org/keycloak/theme/ThemeResourceSpi.java
Executable file
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -32,4 +32,6 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
|
||||||
org.keycloak.storage.UserStorageProviderSpi
|
org.keycloak.storage.UserStorageProviderSpi
|
||||||
|
org.keycloak.theme.ThemeResourceSpi
|
||||||
|
org.keycloak.theme.ThemeSelectorSpi
|
|
@ -17,6 +17,12 @@
|
||||||
|
|
||||||
package org.keycloak.provider;
|
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.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.ServiceLoader;
|
import java.util.ServiceLoader;
|
||||||
|
@ -26,27 +32,46 @@ import java.util.ServiceLoader;
|
||||||
*/
|
*/
|
||||||
public class DefaultProviderLoader implements ProviderLoader {
|
public class DefaultProviderLoader implements ProviderLoader {
|
||||||
|
|
||||||
|
private KeycloakDeploymentInfo info;
|
||||||
private ClassLoader classLoader;
|
private ClassLoader classLoader;
|
||||||
|
|
||||||
public DefaultProviderLoader(ClassLoader classLoader) {
|
public DefaultProviderLoader(KeycloakDeploymentInfo info, ClassLoader classLoader) {
|
||||||
|
this.info = info;
|
||||||
this.classLoader = classLoader;
|
this.classLoader = classLoader;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Spi> loadSpis() {
|
public List<Spi> loadSpis() {
|
||||||
LinkedList<Spi> list = new LinkedList<>();
|
if (info.hasServices()) {
|
||||||
for (Spi spi : ServiceLoader.load(Spi.class, classLoader)) {
|
LinkedList<Spi> list = new LinkedList<>();
|
||||||
list.add(spi);
|
for (Spi spi : ServiceLoader.load(Spi.class, classLoader)) {
|
||||||
|
list.add(spi);
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
} else {
|
||||||
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
return list;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<ProviderFactory> load(Spi spi) {
|
public List<ProviderFactory> load(Spi spi) {
|
||||||
LinkedList<ProviderFactory> list = new LinkedList<ProviderFactory>();
|
List<ProviderFactory> list = new LinkedList<>();
|
||||||
for (ProviderFactory f : ServiceLoader.load(spi.getProviderFactoryClass(), classLoader)) {
|
if (info.hasServices()) {
|
||||||
list.add(f);
|
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;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,8 +28,8 @@ public class DefaultProviderLoaderFactory implements ProviderLoaderFactory {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ProviderLoader create(ClassLoader baseClassLoader, String resource) {
|
public ProviderLoader create(KeycloakDeploymentInfo info, ClassLoader baseClassLoader, String resource) {
|
||||||
return new DefaultProviderLoader(baseClassLoader);
|
return new DefaultProviderLoader(info, baseClassLoader);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,8 +38,8 @@ public class FileSystemProviderLoaderFactory implements ProviderLoaderFactory {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ProviderLoader create(ClassLoader baseClassLoader, String resource) {
|
public ProviderLoader create(KeycloakDeploymentInfo info, ClassLoader baseClassLoader, String resource) {
|
||||||
return new DefaultProviderLoader(createClassLoader(baseClassLoader, resource.split(";")));
|
return new DefaultProviderLoader(info, createClassLoader(baseClassLoader, resource.split(";")));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static URLClassLoader createClassLoader(ClassLoader parent, String... files) {
|
private static URLClassLoader createClassLoader(ClassLoader parent, String... files) {
|
||||||
|
|
|
@ -21,11 +21,13 @@ import org.keycloak.common.util.MultivaluedHashMap;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.IdentityHashMap;
|
import java.util.IdentityHashMap;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.ServiceLoader;
|
import java.util.ServiceLoader;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @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<>();
|
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>();
|
List<ProviderLoaderFactory> factories = new LinkedList<ProviderLoaderFactory>();
|
||||||
for (ProviderLoaderFactory f : ServiceLoader.load(ProviderLoaderFactory.class, getClass().getClassLoader())) {
|
for (ProviderLoaderFactory f : ServiceLoader.load(ProviderLoaderFactory.class, getClass().getClassLoader())) {
|
||||||
factories.add(f);
|
factories.add(f);
|
||||||
|
@ -46,7 +48,7 @@ public class ProviderManager {
|
||||||
|
|
||||||
logger.debugv("Provider loaders {0}", factories);
|
logger.debugv("Provider loaders {0}", factories);
|
||||||
|
|
||||||
loaders.add(new DefaultProviderLoader(baseClassLoader));
|
loaders.add(new DefaultProviderLoader(info, baseClassLoader));
|
||||||
|
|
||||||
if (resources != null) {
|
if (resources != null) {
|
||||||
for (String r : resources) {
|
for (String r : resources) {
|
||||||
|
@ -56,7 +58,8 @@ public class ProviderManager {
|
||||||
boolean found = false;
|
boolean found = false;
|
||||||
for (ProviderLoaderFactory f : factories) {
|
for (ProviderLoaderFactory f : factories) {
|
||||||
if (f.supports(type)) {
|
if (f.supports(type)) {
|
||||||
loaders.add(f.create(baseClassLoader, resource));
|
KeycloakDeploymentInfo resourceInfo = KeycloakDeploymentInfo.create().services();
|
||||||
|
loaders.add(f.create(resourceInfo, baseClassLoader, resource));
|
||||||
found = true;
|
found = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -67,11 +70,6 @@ public class ProviderManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ProviderManager(ClassLoader baseClassLoader) {
|
|
||||||
loaders.add(new DefaultProviderLoader(baseClassLoader));
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized List<Spi> loadSpis() {
|
public synchronized List<Spi> loadSpis() {
|
||||||
// Use a map to prevent duplicates, since the loaders may have overlapping classpaths.
|
// Use a map to prevent duplicates, since the loaders may have overlapping classpaths.
|
||||||
Map<String, Spi> spiMap = new HashMap<>();
|
Map<String, Spi> spiMap = new HashMap<>();
|
||||||
|
@ -88,15 +86,16 @@ public class ProviderManager {
|
||||||
|
|
||||||
public synchronized List<ProviderFactory> load(Spi spi) {
|
public synchronized List<ProviderFactory> load(Spi spi) {
|
||||||
if (!cache.containsKey(spi.getProviderClass())) {
|
if (!cache.containsKey(spi.getProviderClass())) {
|
||||||
IdentityHashMap factoryClasses = new IdentityHashMap();
|
|
||||||
|
Set<String> loaded = new HashSet<>();
|
||||||
for (ProviderLoader loader : loaders) {
|
for (ProviderLoader loader : loaders) {
|
||||||
List<ProviderFactory> f = loader.load(spi);
|
List<ProviderFactory> f = loader.load(spi);
|
||||||
if (f != null) {
|
if (f != null) {
|
||||||
for (ProviderFactory pf: f) {
|
for (ProviderFactory pf: f) {
|
||||||
// make sure there are no duplicates
|
String uniqueId = spi.getName() + "-" + pf.getId();
|
||||||
if (!factoryClasses.containsKey(pf.getClass())) {
|
if (!loaded.contains(uniqueId)) {
|
||||||
cache.add(spi.getProviderClass(), pf);
|
cache.add(spi.getProviderClass(), pf);
|
||||||
factoryClasses.put(pf.getClass(), pf);
|
loaded.add(uniqueId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ import org.keycloak.common.util.MultivaluedHashMap;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
import org.keycloak.provider.EnvironmentDependentProviderFactory;
|
import org.keycloak.provider.EnvironmentDependentProviderFactory;
|
||||||
|
import org.keycloak.provider.KeycloakDeploymentInfo;
|
||||||
import org.keycloak.provider.Provider;
|
import org.keycloak.provider.Provider;
|
||||||
import org.keycloak.provider.ProviderEvent;
|
import org.keycloak.provider.ProviderEvent;
|
||||||
import org.keycloak.provider.ProviderEventListener;
|
import org.keycloak.provider.ProviderEventListener;
|
||||||
|
@ -72,7 +73,7 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory, Pr
|
||||||
public void init() {
|
public void init() {
|
||||||
serverStartupTimestamp = System.currentTimeMillis();
|
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());
|
spis.addAll(pm.loadSpis());
|
||||||
factoriesMap = loadFactories(pm);
|
factoriesMap = loadFactories(pm);
|
||||||
for (ProviderManager manager : ProviderManagerRegistry.SINGLETON.getPreBoot()) {
|
for (ProviderManager manager : ProviderManagerRegistry.SINGLETON.getPreBoot()) {
|
||||||
|
|
|
@ -104,16 +104,6 @@ public class ClassLoaderTheme implements Theme {
|
||||||
return classLoader.getResource(templateRoot + name);
|
return classLoader.getResource(templateRoot + name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public InputStream getTemplateAsStream(String name) {
|
|
||||||
return classLoader.getResourceAsStream(templateRoot + name);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public URL getResource(String path) {
|
|
||||||
return classLoader.getResource(resourceRoot + path);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public InputStream getResourceAsStream(String path) {
|
public InputStream getResourceAsStream(String path) {
|
||||||
return classLoader.getResourceAsStream(resourceRoot + path);
|
return classLoader.getResourceAsStream(resourceRoot + path);
|
||||||
|
|
|
@ -25,11 +25,11 @@ import java.util.Set;
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
*/
|
*/
|
||||||
public class JarThemeProvider implements ThemeProvider {
|
public class ClasspathThemeProvider implements ThemeProvider {
|
||||||
|
|
||||||
private Map<Theme.Type, Map<String, ClassLoaderTheme>> themes;
|
private Map<Theme.Type, Map<String, ClassLoaderTheme>> themes;
|
||||||
|
|
||||||
public JarThemeProvider(Map<Theme.Type, Map<String, ClassLoaderTheme>> themes) {
|
public ClasspathThemeProvider(Map<Theme.Type, Map<String, ClassLoaderTheme>> themes) {
|
||||||
this.themes = themes;
|
this.themes = themes;
|
||||||
}
|
}
|
||||||
|
|
121
services/src/main/java/org/keycloak/theme/ClasspathThemeProviderFactory.java
Executable file
121
services/src/main/java/org/keycloak/theme/ClasspathThemeProviderFactory.java
Executable file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -111,31 +111,27 @@ public class ExtendingThemeManager implements ThemeProvider {
|
||||||
|
|
||||||
private Theme loadTheme(String name, Theme.Type type) throws IOException {
|
private Theme loadTheme(String name, Theme.Type type) throws IOException {
|
||||||
Theme theme = findTheme(name, type);
|
Theme theme = findTheme(name, type);
|
||||||
if (theme != null && (theme.getParentName() != null || theme.getImportName() != null)) {
|
List<Theme> themes = new LinkedList<>();
|
||||||
List<Theme> themes = new LinkedList<>();
|
themes.add(theme);
|
||||||
themes.add(theme);
|
|
||||||
|
|
||||||
if (theme.getImportName() != null) {
|
if (theme.getImportName() != null) {
|
||||||
String[] s = theme.getImportName().split("/");
|
String[] s = theme.getImportName().split("/");
|
||||||
themes.add(findTheme(s[1], Theme.Type.valueOf(s[0].toUpperCase())));
|
themes.add(findTheme(s[1], Theme.Type.valueOf(s[0].toUpperCase())));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (theme.getParentName() != null) {
|
if (theme.getParentName() != null) {
|
||||||
for (String parentName = theme.getParentName(); parentName != null; parentName = theme.getParentName()) {
|
for (String parentName = theme.getParentName(); parentName != null; parentName = theme.getParentName()) {
|
||||||
theme = findTheme(parentName, type);
|
theme = findTheme(parentName, type);
|
||||||
themes.add(theme);
|
themes.add(theme);
|
||||||
|
|
||||||
if (theme.getImportName() != null) {
|
if (theme.getImportName() != null) {
|
||||||
String[] s = theme.getImportName().split("/");
|
String[] s = theme.getImportName().split("/");
|
||||||
themes.add(findTheme(s[1], Theme.Type.valueOf(s[0].toUpperCase())));
|
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
|
@Override
|
||||||
|
@ -178,13 +174,15 @@ public class ExtendingThemeManager implements ThemeProvider {
|
||||||
public static class ExtendingTheme implements Theme {
|
public static class ExtendingTheme implements Theme {
|
||||||
|
|
||||||
private List<Theme> themes;
|
private List<Theme> themes;
|
||||||
|
private Set<ThemeResourceProvider> themeResourceProviders;
|
||||||
|
|
||||||
private Properties properties;
|
private Properties properties;
|
||||||
|
|
||||||
private ConcurrentHashMap<String, ConcurrentHashMap<Locale, Properties>> messages = new ConcurrentHashMap<>();
|
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.themes = themes;
|
||||||
|
this.themeResourceProviders = themeResourceProviders;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -215,29 +213,14 @@ public class ExtendingThemeManager implements ThemeProvider {
|
||||||
return template;
|
return template;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
for (ThemeResourceProvider t : themeResourceProviders) {
|
||||||
public InputStream getTemplateAsStream(String name) throws IOException {
|
URL template = t.getTemplate(name);
|
||||||
for (Theme t : themes) {
|
|
||||||
InputStream template = t.getTemplateAsStream(name);
|
|
||||||
if (template != null) {
|
if (template != null) {
|
||||||
return template;
|
return template;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public URL getResource(String path) throws IOException {
|
|
||||||
for (Theme t : themes) {
|
|
||||||
URL resource = t.getResource(path);
|
|
||||||
if (resource != null) {
|
|
||||||
return resource;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -249,6 +232,14 @@ public class ExtendingThemeManager implements ThemeProvider {
|
||||||
return resource;
|
return resource;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (ThemeResourceProvider t : themeResourceProviders) {
|
||||||
|
InputStream resource = t.getResourceAsStream(path);
|
||||||
|
if (resource != null) {
|
||||||
|
return resource;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -87,13 +87,7 @@ public class FolderTheme implements Theme {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public InputStream getTemplateAsStream(String name) throws IOException {
|
public InputStream getResourceAsStream(String path) throws IOException {
|
||||||
URL url = getTemplate(name);
|
|
||||||
return url != null ? url.openStream() : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public URL getResource(String path) throws IOException {
|
|
||||||
if (File.separatorChar != '/') {
|
if (File.separatorChar != '/') {
|
||||||
path = path.replace('/', File.separatorChar);
|
path = path.replace('/', File.separatorChar);
|
||||||
}
|
}
|
||||||
|
@ -102,16 +96,10 @@ public class FolderTheme implements Theme {
|
||||||
if (!file.isFile() || !file.getCanonicalPath().startsWith(resourcesDir.getCanonicalPath())) {
|
if (!file.isFile() || !file.getCanonicalPath().startsWith(resourcesDir.getCanonicalPath())) {
|
||||||
return null;
|
return null;
|
||||||
} else {
|
} 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
|
@Override
|
||||||
public Properties getMessages(Locale locale) throws IOException {
|
public Properties getMessages(Locale locale) throws IOException {
|
||||||
return getMessages("messages", locale);
|
return getMessages("messages", locale);
|
||||||
|
|
|
@ -32,47 +32,15 @@ import java.util.Map;
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @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";
|
public JarThemeProviderFactory() {
|
||||||
protected static Map<Theme.Type, Map<String, ClassLoaderTheme>> themes = new HashMap<>();
|
super("jar");
|
||||||
|
|
||||||
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
|
@Override
|
||||||
public ThemeProvider create(KeycloakSession session) {
|
public ThemeProvider create(KeycloakSession session) {
|
||||||
return new JarThemeProvider(themes);
|
return new ClasspathThemeProvider(themes);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
org.keycloak.testsuite.theme.TestThemeResourceProvider
|
|
@ -0,0 +1 @@
|
||||||
|
console.debug('hello');
|
|
@ -0,0 +1 @@
|
||||||
|
<html><body>Hello!</body></html>
|
|
@ -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());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -21,6 +21,7 @@ import org.jboss.modules.Module;
|
||||||
import org.jboss.modules.ModuleClassLoader;
|
import org.jboss.modules.ModuleClassLoader;
|
||||||
import org.jboss.modules.ModuleIdentifier;
|
import org.jboss.modules.ModuleIdentifier;
|
||||||
import org.keycloak.provider.DefaultProviderLoader;
|
import org.keycloak.provider.DefaultProviderLoader;
|
||||||
|
import org.keycloak.provider.KeycloakDeploymentInfo;
|
||||||
import org.keycloak.provider.ProviderLoader;
|
import org.keycloak.provider.ProviderLoader;
|
||||||
import org.keycloak.provider.ProviderLoaderFactory;
|
import org.keycloak.provider.ProviderLoaderFactory;
|
||||||
|
|
||||||
|
@ -35,11 +36,11 @@ public class ModuleProviderLoaderFactory implements ProviderLoaderFactory {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ProviderLoader create(ClassLoader baseClassLoader, String resource) {
|
public ProviderLoader create(KeycloakDeploymentInfo info, ClassLoader baseClassLoader, String resource) {
|
||||||
try {
|
try {
|
||||||
Module module = Module.getContextModuleLoader().loadModule(ModuleIdentifier.fromString(resource));
|
Module module = Module.getContextModuleLoader().loadModule(ModuleIdentifier.fromString(resource));
|
||||||
ModuleClassLoader classLoader = module.getClassLoader();
|
ModuleClassLoader classLoader = module.getClassLoader();
|
||||||
return new DefaultProviderLoader(classLoader);
|
return new DefaultProviderLoader(info, classLoader);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,12 +21,17 @@ import org.jboss.modules.Module;
|
||||||
import org.jboss.modules.ModuleClassLoader;
|
import org.jboss.modules.ModuleClassLoader;
|
||||||
import org.jboss.modules.ModuleIdentifier;
|
import org.jboss.modules.ModuleIdentifier;
|
||||||
import org.keycloak.Config;
|
import org.keycloak.Config;
|
||||||
|
import org.keycloak.theme.ClasspathThemeProviderFactory;
|
||||||
import org.keycloak.theme.JarThemeProviderFactory;
|
import org.keycloak.theme.JarThemeProviderFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @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
|
@Override
|
||||||
public void init(Config.Scope config) {
|
public void init(Config.Scope config) {
|
||||||
|
@ -44,9 +49,4 @@ public class ModuleThemeProviderFactory extends JarThemeProviderFactory {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getId() {
|
|
||||||
return "module";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.ModuleDependency;
|
||||||
import org.jboss.as.server.deployment.module.ModuleSpecification;
|
import org.jboss.as.server.deployment.module.ModuleSpecification;
|
||||||
import org.jboss.as.server.deployment.module.ResourceRoot;
|
import org.jboss.as.server.deployment.module.ResourceRoot;
|
||||||
import org.jboss.logging.Logger;
|
|
||||||
import org.jboss.modules.Module;
|
import org.jboss.modules.Module;
|
||||||
import org.jboss.modules.ModuleIdentifier;
|
import org.jboss.modules.ModuleIdentifier;
|
||||||
import org.jboss.modules.ModuleLoader;
|
import org.jboss.modules.ModuleLoader;
|
||||||
import org.jboss.vfs.VirtualFile;
|
import org.jboss.vfs.VirtualFile;
|
||||||
import org.jboss.vfs.util.AbstractVirtualFileFilterWithAttributes;
|
import org.jboss.vfs.util.AbstractVirtualFileFilterWithAttributes;
|
||||||
|
import org.keycloak.provider.KeycloakDeploymentInfo;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
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 RESTEASY = ModuleIdentifier.create("org.jboss.resteasy.resteasy-jaxrs");
|
||||||
private static final ModuleIdentifier APACHE = ModuleIdentifier.create("org.apache.httpcomponents");
|
private static final ModuleIdentifier APACHE = ModuleIdentifier.create("org.apache.httpcomponents");
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(KeycloakProviderDependencyProcessor.class);
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {
|
public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {
|
||||||
DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
|
DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
|
||||||
|
@ -60,53 +58,66 @@ public class KeycloakProviderDependencyProcessor implements DeploymentUnitProces
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isKeycloakProviderDeployment(deploymentUnit)) return;
|
KeycloakDeploymentInfo info = getKeycloakProviderDeploymentInfo(deploymentUnit);
|
||||||
|
if (info.hasServices()) {
|
||||||
final ModuleSpecification moduleSpecification = deploymentUnit.getAttachment(Attachments.MODULE_SPECIFICATION);
|
final ModuleSpecification moduleSpecification = deploymentUnit.getAttachment(Attachments.MODULE_SPECIFICATION);
|
||||||
final ModuleLoader moduleLoader = Module.getBootModuleLoader();
|
final ModuleLoader moduleLoader = Module.getBootModuleLoader();
|
||||||
moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_COMMON, false, false, false, false));
|
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_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, false, false, false, false));
|
||||||
moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_SERVER_SPI_PRIVATE, 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, JAXRS, false, false, false, false));
|
||||||
moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, RESTEASY, 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, APACHE, false, false, false, false));
|
||||||
moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_JPA, false, false, false, false));
|
moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_JPA, false, false, false, false));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public KeycloakProviderDependencyProcessor() {
|
public KeycloakProviderDependencyProcessor() {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isKeycloakProviderDeployment(DeploymentUnit du) {
|
public static KeycloakDeploymentInfo getKeycloakProviderDeploymentInfo(DeploymentUnit du) {
|
||||||
final ResourceRoot resourceRoot = du.getAttachment(Attachments.DEPLOYMENT_ROOT);
|
KeycloakDeploymentInfo info = KeycloakDeploymentInfo.create();
|
||||||
if (resourceRoot == null) {
|
info.name(du.getName());
|
||||||
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");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return !archives.isEmpty();
|
|
||||||
} catch (IOException e) {
|
|
||||||
|
|
||||||
|
final ResourceRoot resourceRoot = du.getAttachment(Attachments.DEPLOYMENT_ROOT);
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
@Override
|
||||||
public void undeploy(DeploymentUnit context) {
|
public void undeploy(DeploymentUnit context) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ import org.jboss.as.server.deployment.DeploymentUnitProcessingException;
|
||||||
import org.jboss.as.server.deployment.DeploymentUnitProcessor;
|
import org.jboss.as.server.deployment.DeploymentUnitProcessor;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.jboss.modules.Module;
|
import org.jboss.modules.Module;
|
||||||
|
import org.keycloak.provider.KeycloakDeploymentInfo;
|
||||||
import org.keycloak.provider.ProviderManager;
|
import org.keycloak.provider.ProviderManager;
|
||||||
import org.keycloak.provider.ProviderManagerRegistry;
|
import org.keycloak.provider.ProviderManagerRegistry;
|
||||||
|
|
||||||
|
@ -46,16 +47,14 @@ public class KeycloakProviderDeploymentProcessor implements DeploymentUnitProces
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!KeycloakProviderDependencyProcessor.isKeycloakProviderDeployment(deploymentUnit)) return;
|
KeycloakDeploymentInfo info = KeycloakProviderDependencyProcessor.getKeycloakProviderDeploymentInfo(deploymentUnit);
|
||||||
|
if (info.isProvider()) {
|
||||||
logger.infov("Deploying Keycloak provider: {0}", deploymentUnit.getName());
|
logger.infov("Deploying Keycloak provider: {0}", deploymentUnit.getName());
|
||||||
final Module module = deploymentUnit.getAttachment(Attachments.MODULE);
|
final Module module = deploymentUnit.getAttachment(Attachments.MODULE);
|
||||||
ProviderManager pm = new ProviderManager(module.getClassLoader());
|
ProviderManager pm = new ProviderManager(info, module.getClassLoader());
|
||||||
ProviderManagerRegistry.SINGLETON.deploy(pm);
|
ProviderManagerRegistry.SINGLETON.deploy(pm);
|
||||||
deploymentUnit.putAttachment(ATTACHMENT_KEY, pm);
|
deploymentUnit.putAttachment(ATTACHMENT_KEY, pm);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public KeycloakProviderDeploymentProcessor() {
|
public KeycloakProviderDeploymentProcessor() {
|
||||||
|
|
Loading…
Reference in a new issue