Skip to content

Commit 7fefad9

Browse files
committed
HHH-18937 - Introduce a JavaServiceLoadable extension to contribute "strategy selectors"
1 parent e006427 commit 7fefad9

File tree

9 files changed

+380
-115
lines changed

9 files changed

+380
-115
lines changed

hibernate-core/src/main/java/org/hibernate/boot/registry/selector/internal/StrategySelectorImpl.java

Lines changed: 140 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -19,38 +19,28 @@
1919
import org.hibernate.boot.registry.selector.spi.StrategySelectionException;
2020
import org.hibernate.boot.registry.selector.spi.StrategySelector;
2121

22+
import org.hibernate.boot.registry.selector.spi.NamedStrategyContributions;
23+
import org.hibernate.boot.registry.selector.spi.NamedStrategyContributor;
2224
import org.jboss.logging.Logger;
2325

2426
/**
2527
* Standard implementation of the {@link StrategySelector} contract.
2628
*
29+
* @implNote Supports both {@linkplain #namedStrategyImplementorByStrategyMap normal}
30+
* and {@linkplain #lazyStrategyImplementorByStrategyMap lazy} registration.
31+
*
2732
* @author Steve Ebersole
2833
*/
2934
public class StrategySelectorImpl implements StrategySelector {
30-
3135
private static final Logger log = Logger.getLogger( StrategySelectorImpl.class );
3236

33-
private static final StrategyCreator STANDARD_STRATEGY_CREATOR = strategyClass -> {
34-
try {
35-
return strategyClass.newInstance();
36-
}
37-
catch (Exception e) {
38-
throw new StrategySelectionException(
39-
String.format( "Could not instantiate named strategy class [%s]", strategyClass.getName() ),
40-
e
41-
);
42-
}
43-
};
44-
45-
//Map based approach: most suited for explicit registrations from integrators
46-
private final Map<Class,Map<String,Class>> namedStrategyImplementorByStrategyMap = new ConcurrentHashMap<>();
37+
private static final StrategyCreator<?> STANDARD_STRATEGY_CREATOR = StrategySelectorImpl::create;
4738

48-
//"Lazy" approach: more efficient as we aim to not initialize all implementation classes;
49-
//this is preferable for internal services such as Dialect, as we have a significant amount of them, making
50-
//it worthwhile to try be a bit more efficient about them.
51-
private final Map<Class, LazyServiceResolver> lazyStrategyImplementorByStrategyMap = new ConcurrentHashMap<>();
39+
private final Map<Class<?>,Map<String,Class<?>>> namedStrategyImplementorByStrategyMap = new ConcurrentHashMap<>();
40+
private final Map<Class<?>, LazyServiceResolver<?>> lazyStrategyImplementorByStrategyMap = new ConcurrentHashMap<>();
5241

5342
private final ClassLoaderService classLoaderService;
43+
private final Collection<NamedStrategyContributor> contributors;
5444

5545
/**
5646
* Constructs a StrategySelectorImpl using the given class loader service.
@@ -59,88 +49,29 @@ public class StrategySelectorImpl implements StrategySelector {
5949
*/
6050
public StrategySelectorImpl(ClassLoaderService classLoaderService) {
6151
this.classLoaderService = classLoaderService;
62-
}
63-
64-
public <T> void registerStrategyLazily(Class<T> strategy, LazyServiceResolver<T> resolver) {
65-
LazyServiceResolver previous = lazyStrategyImplementorByStrategyMap.put( strategy, resolver );
66-
if ( previous != null ) {
67-
throw new HibernateException( "Detected a second LazyServiceResolver replacing an existing LazyServiceResolver implementation for strategy " + strategy.getName() );
68-
}
69-
}
70-
71-
@Override
72-
public <T> void registerStrategyImplementor(Class<T> strategy, String name, Class<? extends T> implementation) {
73-
final Map<String,Class> namedStrategyImplementorMap = namedStrategyImplementorByStrategyMap.computeIfAbsent(
74-
strategy,
75-
aClass -> new ConcurrentHashMap<>()
76-
);
77-
78-
final Class old = namedStrategyImplementorMap.put( name, implementation );
79-
if ( old == null ) {
80-
if ( log.isTraceEnabled() ) {
81-
log.trace(
82-
String.format(
83-
"Registering named strategy selector [%s] : [%s] -> [%s]",
84-
strategy.getName(),
85-
name,
86-
implementation.getName()
87-
)
88-
);
89-
}
90-
}
91-
else {
92-
if ( log.isDebugEnabled() ) {
93-
log.debug(
94-
String.format(
95-
"Registering named strategy selector [%s] : [%s] -> [%s] (replacing [%s])",
96-
strategy.getName(),
97-
name,
98-
implementation.getName(),
99-
old.getName()
100-
)
101-
);
102-
}
103-
}
104-
}
105-
106-
@Override
107-
public <T> void unRegisterStrategyImplementor(Class<T> strategy, Class<? extends T> implementation) {
108-
final Map<String,Class> namedStrategyImplementorMap = namedStrategyImplementorByStrategyMap.get( strategy );
109-
if ( namedStrategyImplementorMap == null ) {
110-
log.debug( "Named strategy map did not exist on call to un-register" );
111-
return;
112-
}
113-
114-
final Iterator itr = namedStrategyImplementorMap.values().iterator();
115-
while ( itr.hasNext() ) {
116-
final Class registered = (Class) itr.next();
117-
if ( registered.equals( implementation ) ) {
118-
itr.remove();
119-
}
120-
}
12152

122-
// try to clean up after ourselves...
123-
if ( namedStrategyImplementorMap.isEmpty() ) {
124-
namedStrategyImplementorByStrategyMap.remove( strategy );
53+
this.contributors = classLoaderService.loadJavaServices( NamedStrategyContributor.class );
54+
for ( NamedStrategyContributor contributor : contributors ) {
55+
contributor.contributeStrategyImplementations( new StartupContributions() );
12556
}
12657
}
12758

12859
@Override
12960
@SuppressWarnings("unchecked")
13061
public <T> Class<? extends T> selectStrategyImplementor(Class<T> strategy, String name) {
131-
final Map<String,Class> namedStrategyImplementorMap = namedStrategyImplementorByStrategyMap.get( strategy );
62+
final Map<String,Class<?>> namedStrategyImplementorMap = namedStrategyImplementorByStrategyMap.get( strategy );
13263
if ( namedStrategyImplementorMap != null ) {
133-
final Class registered = namedStrategyImplementorMap.get( name );
64+
final Class<?> registered = namedStrategyImplementorMap.get( name );
13465
if ( registered != null ) {
13566
return (Class<T>) registered;
13667
}
13768
}
13869

139-
LazyServiceResolver lazyServiceResolver = lazyStrategyImplementorByStrategyMap.get( strategy );
70+
final LazyServiceResolver<?> lazyServiceResolver = lazyStrategyImplementorByStrategyMap.get( strategy );
14071
if ( lazyServiceResolver != null ) {
141-
Class resolve = lazyServiceResolver.resolve( name );
72+
final Class<?> resolve = lazyServiceResolver.resolve( name );
14273
if ( resolve != null ) {
143-
return resolve;
74+
return (Class<? extends T>) resolve;
14475
}
14576
}
14677

@@ -175,7 +106,7 @@ public <T> T resolveDefaultableStrategy(
175106
Class<T> strategy,
176107
Object strategyReference,
177108
Callable<T> defaultResolver) {
178-
return (T) resolveStrategy( strategy, strategyReference, defaultResolver, STANDARD_STRATEGY_CREATOR );
109+
return (T) resolveStrategy( strategy, strategyReference, defaultResolver, (StrategyCreator<T>) STANDARD_STRATEGY_CREATOR );
179110
}
180111

181112
@Override
@@ -193,17 +124,17 @@ public <T> T resolveStrategy(
193124
}
194125

195126
@Override
196-
@SuppressWarnings("unchecked")
197-
public Collection getRegisteredStrategyImplementors(Class strategy) {
198-
LazyServiceResolver lazyServiceResolver = lazyStrategyImplementorByStrategyMap.get( strategy );
127+
public <T> Collection<Class<? extends T>> getRegisteredStrategyImplementors(Class<T> strategy) {
128+
final LazyServiceResolver<?> lazyServiceResolver = lazyStrategyImplementorByStrategyMap.get( strategy );
199129
if ( lazyServiceResolver != null ) {
200130
throw new StrategySelectionException( "Can't use this method on for strategy types which are embedded in the core library" );
201131
}
202-
final Map<String, Class> registrations = namedStrategyImplementorByStrategyMap.get( strategy );
132+
final Map<String, Class<?>> registrations = namedStrategyImplementorByStrategyMap.get( strategy );
203133
if ( registrations == null ) {
204134
return Collections.emptySet();
205135
}
206-
return new HashSet( registrations.values() );
136+
//noinspection unchecked,rawtypes
137+
return (Collection) new HashSet<>( registrations.values() );
207138
}
208139

209140
@SuppressWarnings("unchecked")
@@ -244,4 +175,121 @@ public <T> T resolveStrategy(
244175
);
245176
}
246177
}
178+
179+
private static <T> T create(Class<T> strategyClass) {
180+
try {
181+
return strategyClass.getDeclaredConstructor().newInstance();
182+
}
183+
catch (Exception e) {
184+
throw new StrategySelectionException(
185+
String.format( "Could not instantiate named strategy class [%s]", strategyClass.getName() ),
186+
e
187+
);
188+
}
189+
}
190+
191+
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
192+
// Lifecycle
193+
194+
public <T> void registerStrategyLazily(Class<T> strategy, LazyServiceResolver<T> resolver) {
195+
LazyServiceResolver<?> previous = lazyStrategyImplementorByStrategyMap.put( strategy, resolver );
196+
if ( previous != null ) {
197+
throw new HibernateException( "Detected a second LazyServiceResolver replacing an existing LazyServiceResolver implementation for strategy " + strategy.getName() );
198+
}
199+
}
200+
201+
private <T> void contributeImplementation(Class<T> strategy, Class<? extends T> implementation, String... names) {
202+
final Map<String,Class<?>> namedStrategyImplementorMap = namedStrategyImplementorByStrategyMap.computeIfAbsent(
203+
strategy,
204+
aClass -> new ConcurrentHashMap<>()
205+
);
206+
207+
for ( int i = 0; i < names.length; i++ ) {
208+
final String name = names[i];
209+
210+
final Class<?> old = namedStrategyImplementorMap.put( name, implementation );
211+
if ( old == null ) {
212+
if ( log.isTraceEnabled() ) {
213+
log.trace(
214+
String.format(
215+
"Registering named strategy selector [%s] : [%s] -> [%s]",
216+
strategy.getName(),
217+
name,
218+
implementation.getName()
219+
)
220+
);
221+
}
222+
}
223+
else {
224+
if ( log.isDebugEnabled() ) {
225+
log.debug(
226+
String.format(
227+
"Registering named strategy selector [%s] : [%s] -> [%s] (replacing [%s])",
228+
strategy.getName(),
229+
name,
230+
implementation.getName(),
231+
old.getName()
232+
)
233+
);
234+
}
235+
}
236+
}
237+
}
238+
239+
private <T> void removeImplementation(Class<T> strategy, Class<? extends T> implementation) {
240+
final Map<String,Class<?>> namedStrategyImplementorMap = namedStrategyImplementorByStrategyMap.get( strategy );
241+
if ( namedStrategyImplementorMap == null ) {
242+
log.debug( "Named strategy map did not exist on call to un-register" );
243+
return;
244+
}
245+
246+
final Iterator<Class<?>> itr = namedStrategyImplementorMap.values().iterator();
247+
while ( itr.hasNext() ) {
248+
final Class<?> registered = itr.next();
249+
if ( registered.equals( implementation ) ) {
250+
itr.remove();
251+
}
252+
}
253+
254+
// try to clean up after ourselves...
255+
if ( namedStrategyImplementorMap.isEmpty() ) {
256+
namedStrategyImplementorByStrategyMap.remove( strategy );
257+
}
258+
}
259+
260+
@Override
261+
public void stop() {
262+
for ( NamedStrategyContributor contributor : contributors ) {
263+
contributor.clearStrategyImplementations( new ShutdownContributions() );
264+
}
265+
}
266+
267+
private class StartupContributions implements NamedStrategyContributions {
268+
@Override
269+
public <T> void contributeStrategyImplementor(Class<T> strategy, Class<? extends T> implementation, String... names) {
270+
contributeImplementation( strategy, implementation, names );
271+
}
272+
273+
@Override
274+
public <T> void removeStrategyImplementor(Class<T> strategy, Class<? extends T> implementation) {
275+
removeImplementation( strategy, implementation );
276+
}
277+
}
278+
279+
private class ShutdownContributions extends StartupContributions {
280+
@Override
281+
public <T> void contributeStrategyImplementor(Class<T> strategy, Class<? extends T> implementation, String... names) {
282+
throw new IllegalStateException( "Should not register strategies during shutdown" );
283+
}
284+
}
285+
286+
@Override
287+
public <T> void registerStrategyImplementor(Class<T> strategy, String name, Class<? extends T> implementation) {
288+
contributeImplementation( strategy, implementation, name );
289+
}
290+
291+
@Override
292+
public <T> void unRegisterStrategyImplementor(Class<T> strategy, Class<? extends T> implementation) {
293+
removeImplementation( strategy, implementation );
294+
}
247295
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* SPDX-License-Identifier: LGPL-2.1-or-later
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.boot.registry.selector.spi;
6+
7+
/**
8+
* Target for {@linkplain NamedStrategyContributor}
9+
*
10+
* @see StrategySelector
11+
*
12+
* @author Steve Ebersole
13+
*/
14+
public interface NamedStrategyContributions {
15+
/**
16+
* Registers a named implementor of a particular strategy contract.
17+
*
18+
* @param strategy The strategy contract.
19+
* @param implementation The implementation class.
20+
* @param names The registration names.
21+
*
22+
* @param <T> The strategy type.
23+
*/
24+
<T> void contributeStrategyImplementor(Class<T> strategy, Class<? extends T> implementation, String... names);
25+
26+
/**
27+
* Un-registers a named implementor of a particular strategy contract. Un-registers all named registrations
28+
* for the given strategy contract naming the given class.
29+
*
30+
* @param strategy The strategy contract.
31+
* @param implementation The implementation class.
32+
*
33+
* @param <T> The strategy type.
34+
*/
35+
<T> void removeStrategyImplementor(Class<T> strategy, Class<? extends T> implementation);
36+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* SPDX-License-Identifier: LGPL-2.1-or-later
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.boot.registry.selector.spi;
6+
7+
import org.hibernate.service.JavaServiceLoadable;
8+
9+
/**
10+
* Discoverable contributor for named references which are resolvable via StrategySelector.
11+
*
12+
* @see StrategySelector
13+
* @see NamedStrategyContributions
14+
*
15+
* @author Steve Ebersole
16+
*/
17+
@JavaServiceLoadable
18+
public interface NamedStrategyContributor {
19+
/**
20+
* Allows registration of named strategy implementations.
21+
* Called on bootstrap.
22+
*/
23+
void contributeStrategyImplementations(NamedStrategyContributions contributions);
24+
25+
/**
26+
* Allows cleanup of (presumably previously {@linkplain #contributeStrategyImplementations registered}) strategy implementations.
27+
* Called on shutdown.
28+
*/
29+
void clearStrategyImplementations(NamedStrategyContributions contributions);
30+
}

0 commit comments

Comments
 (0)