Skip to content

Commit 0332319

Browse files
committed
Make @EnableFeature to handle the case with added provider of currently non-used SPI
closes keycloak#36425 Signed-off-by: mposolda <[email protected]>
1 parent fd9db3a commit 0332319

File tree

6 files changed

+102
-5
lines changed

6 files changed

+102
-5
lines changed

services/src/main/java/org/keycloak/provider/ProviderManager.java

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import org.keycloak.common.util.MultivaluedHashMap;
2121
import org.keycloak.services.DefaultKeycloakSessionFactory;
2222

23+
import java.util.Collection;
2324
import java.util.Collections;
2425
import java.util.HashMap;
2526
import java.util.LinkedList;
@@ -48,8 +49,7 @@ public ProviderManager(KeycloakDeploymentInfo info, ClassLoader baseClassLoader,
4849

4950
logger.debugv("Provider loaders {0}", factories);
5051

51-
loaders.add(new DefaultProviderLoader(info, baseClassLoader));
52-
loaders.add(new DeploymentProviderLoader(info));
52+
addDefaultLoaders(baseClassLoader);
5353

5454
if (resources != null) {
5555
for (String r : resources) {
@@ -71,6 +71,20 @@ public ProviderManager(KeycloakDeploymentInfo info, ClassLoader baseClassLoader,
7171
}
7272
}
7373
}
74+
75+
public ProviderManager(KeycloakDeploymentInfo info, ClassLoader baseClassLoader, Collection<ProviderLoader> additionalProviderLoaders) {
76+
this.info = info;
77+
addDefaultLoaders(baseClassLoader);
78+
if (additionalProviderLoaders != null) {
79+
loaders.addAll(additionalProviderLoaders);
80+
}
81+
}
82+
83+
private void addDefaultLoaders(ClassLoader baseClassLoader) {
84+
loaders.add(new DefaultProviderLoader(info, baseClassLoader));
85+
loaders.add(new DeploymentProviderLoader(info));
86+
}
87+
7488
public synchronized List<Spi> loadSpis() {
7589
// Use a map to prevent duplicates, since the loaders may have overlapping classpaths.
7690
Map<String, Spi> spiMap = new HashMap<>();

services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import java.util.Stack;
3232
import java.util.concurrent.CopyOnWriteArrayList;
3333
import java.util.function.Function;
34+
import java.util.stream.Collectors;
3435
import java.util.stream.Stream;
3536

3637
import org.jboss.logging.Logger;
@@ -181,6 +182,8 @@ protected Map<Class<? extends Provider>, Map<String, ProviderFactory>> getFactor
181182

182183
@Override
183184
public void deploy(ProviderManager pm) {
185+
registerNewSpis(pm);
186+
184187
Map<Class<? extends Provider>, Map<String, ProviderFactory>> copy = getFactoriesCopy();
185188
Map<Class<? extends Provider>, Map<String, ProviderFactory>> newFactories = loadFactories(pm);
186189
Map<Class<? extends Provider>, Map<String, ProviderFactory>> deployed = new HashMap<>();
@@ -223,6 +226,20 @@ public void deploy(ProviderManager pm) {
223226
}
224227
}
225228

229+
// Register SPIs of this providerManager, which are possibly not yet registered in this factory
230+
private void registerNewSpis(ProviderManager pm) {
231+
Set<String> existingSpiNames = this.spis.stream()
232+
.map(spi -> spi.getName())
233+
.collect(Collectors.toSet());
234+
235+
this.spis = new HashSet<>(this.spis);
236+
for (Spi newSpi : pm.loadSpis()) {
237+
if (!existingSpiNames.contains(newSpi.getName())) {
238+
this.spis.add(newSpi);
239+
}
240+
}
241+
}
242+
226243
@Override
227244
public void undeploy(ProviderManager pm) {
228245
logger.debug("undeploy");

testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/util/FeatureDeployerUtil.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.keycloak.provider.Spi;
3131
import org.keycloak.services.DefaultKeycloakSession;
3232

33+
import java.util.Collections;
3334
import java.util.HashMap;
3435
import java.util.List;
3536
import java.util.Map;
@@ -72,7 +73,7 @@ public static void deployFactoriesAfterFeatureEnabled(Profile.Feature feature) {
7273

7374
KeycloakDeploymentInfo di = createDeploymentInfo(factories);
7475

75-
manager = new ProviderManager(di, FeatureDeployerUtil.class.getClassLoader());
76+
manager = new ProviderManager(di, FeatureDeployerUtil.class.getClassLoader(), Collections.singleton(new TestsuiteProviderLoader(di)));
7677
deployersCache.put(feature, manager);
7778
}
7879
ProviderManagerRegistry.SINGLETON.deploy(manager);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Copyright 2025 Red Hat, Inc. and/or its affiliates
3+
* and other contributors as indicated by the @author tags.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
*
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*
18+
*/
19+
20+
package org.keycloak.testsuite.util;
21+
22+
import java.lang.reflect.InvocationTargetException;
23+
import java.util.List;
24+
import java.util.stream.Collectors;
25+
26+
import org.keycloak.provider.KeycloakDeploymentInfo;
27+
import org.keycloak.provider.ProviderFactory;
28+
import org.keycloak.provider.ProviderLoader;
29+
import org.keycloak.provider.Spi;
30+
31+
/**
32+
* Loads additional SPIs from provided KeycloakDeploymentInfo
33+
*
34+
* @author <a href="mailto:[email protected]">Marek Posolda</a>
35+
*/
36+
class TestsuiteProviderLoader implements ProviderLoader {
37+
38+
private final KeycloakDeploymentInfo info;
39+
40+
TestsuiteProviderLoader(KeycloakDeploymentInfo info) {
41+
this.info = info;
42+
}
43+
44+
@Override
45+
public List<Spi> loadSpis() {
46+
return info.getProviders().keySet()
47+
.stream()
48+
.map(this::instantiateSpi)
49+
.collect(Collectors.toList());
50+
}
51+
52+
private Spi instantiateSpi(Class<? extends Spi> clazz) {
53+
try {
54+
return clazz.getDeclaredConstructor().newInstance();
55+
} catch (NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e) {
56+
throw new RuntimeException(e);
57+
}
58+
}
59+
60+
61+
@Override
62+
public List<ProviderFactory> load(Spi spi) {
63+
return List.of();
64+
}
65+
}

testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientTypesTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@
5858
/**
5959
* @author <a href="mailto:[email protected]">Marek Posolda</a>
6060
*/
61-
@EnableFeature(value = CLIENT_TYPES)
61+
@EnableFeature(value = CLIENT_TYPES, skipRestart = true)
6262
public class ClientTypesTest extends AbstractTestRealmKeycloakTest {
6363

6464
@Override

testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@
6262
/**
6363
* Super class for all OID4VC tests. Provides convenience methods to ease the testing.
6464
*/
65-
@EnableFeature(value = Profile.Feature.OID4VC_VCI, skipRestart = false)
65+
@EnableFeature(value = Profile.Feature.OID4VC_VCI, skipRestart = true)
6666
public abstract class OID4VCTest extends AbstractTestRealmKeycloakTest {
6767

6868
private static final Logger LOGGER = Logger.getLogger(OID4VCTest.class);

0 commit comments

Comments
 (0)