1919
2020import java .io .IOException ;
2121import java .util .ArrayList ;
22+ import java .util .Collections ;
2223import java .util .List ;
2324import java .util .Locale ;
25+ import java .util .Map ;
2426import java .util .Optional ;
2527import java .util .stream .Collectors ;
2628import java .util .stream .Stream ;
2729
2830import org .apache .commons .logging .Log ;
2931import org .apache .commons .logging .LogFactory ;
32+
33+ import org .springframework .beans .factory .annotation .AnnotatedGenericBeanDefinition ;
3034import org .springframework .beans .factory .config .BeanDefinition ;
3135import org .springframework .beans .factory .config .RuntimeBeanReference ;
3236import org .springframework .beans .factory .support .AbstractBeanDefinition ;
3539import org .springframework .beans .factory .support .BeanDefinitionRegistry ;
3640import org .springframework .core .env .Environment ;
3741import org .springframework .core .io .ResourceLoader ;
42+ import org .springframework .core .io .support .SpringFactoriesLoader ;
3843import org .springframework .core .type .classreading .CachingMetadataReaderFactory ;
44+ import org .springframework .core .type .classreading .MetadataReader ;
3945import org .springframework .core .type .classreading .MetadataReaderFactory ;
4046import org .springframework .data .config .ParsingUtils ;
4147import org .springframework .data .repository .core .support .RepositoryFragment ;
4248import org .springframework .data .repository .core .support .RepositoryFragmentsFactoryBean ;
4349import org .springframework .data .util .Optionals ;
50+ import org .springframework .lang .Nullable ;
4451import org .springframework .util .Assert ;
4552import org .springframework .util .ClassUtils ;
53+ import org .springframework .util .StringUtils ;
4654
4755/**
4856 * Builder to create {@link BeanDefinitionBuilder} instance to eventually create Spring Data repository instances.
@@ -63,6 +71,7 @@ class RepositoryBeanDefinitionBuilder {
6371 private final MetadataReaderFactory metadataReaderFactory ;
6472 private final FragmentMetadata fragmentMetadata ;
6573 private final CustomRepositoryImplementationDetector implementationDetector ;
74+ private final RepositoryFactoriesLoader factoriesLoader ;
6675
6776 /**
6877 * Creates a new {@link RepositoryBeanDefinitionBuilder} from the given {@link BeanDefinitionRegistry},
@@ -83,7 +92,7 @@ public RepositoryBeanDefinitionBuilder(BeanDefinitionRegistry registry, Reposito
8392 this .registry = registry ;
8493 this .extension = extension ;
8594 this .resourceLoader = resourceLoader ;
86-
95+ this . factoriesLoader = RepositoryFactoriesLoader . forDefaultResourceLocation ( resourceLoader . getClassLoader ());
8796 this .metadataReaderFactory = new CachingMetadataReaderFactory (resourceLoader );
8897
8998 this .fragmentMetadata = new FragmentMetadata (metadataReaderFactory );
@@ -139,6 +148,7 @@ public BeanDefinitionBuilder build(RepositoryConfiguration<?> configuration) {
139148 }
140149
141150 // TODO: merge that with the one that creates the BD
151+ // TODO: Add support for fragments discovered from spring.factories
142152 RepositoryConfigurationAdapter <?> buildMetadata (RepositoryConfiguration <?> configuration ) {
143153
144154 ImplementationDetectionConfiguration config = configuration
@@ -223,21 +233,71 @@ private Stream<RepositoryFragmentConfiguration> registerRepositoryFragmentsImple
223233 ImplementationDetectionConfiguration config = configuration
224234 .toImplementationDetectionConfiguration (metadataReaderFactory );
225235
226- return fragmentMetadata .getFragmentInterfaces (configuration .getRepositoryInterface ()) //
227- .map (it -> detectRepositoryFragmentConfiguration (it , config , configuration )) //
228- .flatMap (Optionals ::toStream ) //
236+ Stream <RepositoryFragmentConfiguration > discovered = discoverFragments (configuration , config );
237+ Stream <RepositoryFragmentConfiguration > loaded = loadFragments (configuration );
238+
239+ return Stream .concat (discovered , loaded ) //
229240 .peek (it -> potentiallyRegisterFragmentImplementation (configuration , it )) //
230241 .peek (it -> potentiallyRegisterRepositoryFragment (configuration , it ));
231242 }
232243
244+ private Stream <RepositoryFragmentConfiguration > discoverFragments (RepositoryConfiguration <?> configuration ,
245+ ImplementationDetectionConfiguration config ) {
246+ return fragmentMetadata .getFragmentInterfaces (configuration .getRepositoryInterface ())
247+ .map (it -> detectRepositoryFragmentConfiguration (it , config , configuration )) //
248+ .flatMap (Optionals ::toStream );
249+ }
250+
251+ private Stream <RepositoryFragmentConfiguration > loadFragments (RepositoryConfiguration <?> configuration ) {
252+
253+ List <String > names = factoriesLoader .loadFactoryNames (configuration .getRepositoryInterface ());
254+
255+ if (names .isEmpty ()) {
256+ return Stream .empty ();
257+ }
258+
259+ return names .stream ().map (it -> createFragmentConfiguration (null , configuration , it ));
260+ }
261+
233262 private Optional <RepositoryFragmentConfiguration > detectRepositoryFragmentConfiguration (String fragmentInterface ,
234263 ImplementationDetectionConfiguration config , RepositoryConfiguration <?> configuration ) {
235264
236- ImplementationLookupConfiguration lookup = config .forFragment (fragmentInterface );
237- Optional <AbstractBeanDefinition > beanDefinition = implementationDetector .detectCustomImplementation (lookup );
265+ List <String > names = factoriesLoader .loadFactoryNames (fragmentInterface );
266+
267+ if (names .isEmpty ()) {
268+
269+ ImplementationLookupConfiguration lookup = config .forFragment (fragmentInterface );
270+ Optional <AbstractBeanDefinition > beanDefinition = implementationDetector .detectCustomImplementation (lookup );
271+
272+ return beanDefinition .map (bd -> createFragmentConfiguration (fragmentInterface , configuration , bd ));
273+ }
274+
275+ if (names .size () > 1 ) {
276+ logger .debug (String .format ("Multiple fragment implementations %s registered for fragment interface %s" , names ,
277+ fragmentInterface ));
278+ }
279+
280+ return Optional .of (createFragmentConfiguration (fragmentInterface , configuration , names .get (0 )));
281+ }
282+
283+ private RepositoryFragmentConfiguration createFragmentConfiguration (@ Nullable String fragmentInterface ,
284+ RepositoryConfiguration <?> configuration , String className ) {
285+
286+ try {
238287
239- return beanDefinition .map (bd -> new RepositoryFragmentConfiguration (fragmentInterface , bd ,
240- configuration .getConfigurationSource ().generateBeanName (bd )));
288+ MetadataReader metadataReader = metadataReaderFactory .getMetadataReader (className );
289+ AnnotatedGenericBeanDefinition bd = new AnnotatedGenericBeanDefinition (metadataReader .getAnnotationMetadata ());
290+ return createFragmentConfiguration (fragmentInterface , configuration , bd );
291+ } catch (IOException e ) {
292+ throw new IllegalStateException (e );
293+ }
294+ }
295+
296+ private static RepositoryFragmentConfiguration createFragmentConfiguration (@ Nullable String fragmentInterface ,
297+ RepositoryConfiguration <?> configuration , AbstractBeanDefinition beanDefinition ) {
298+
299+ return new RepositoryFragmentConfiguration (fragmentInterface , beanDefinition ,
300+ configuration .getConfigurationSource ().generateBeanName (beanDefinition ));
241301 }
242302
243303 private String potentiallyRegisterRepositoryImplementation (RepositoryConfiguration <?> configuration ,
@@ -314,10 +374,47 @@ private void potentiallyRegisterRepositoryFragment(RepositoryConfiguration<?> co
314374 BeanDefinitionBuilder fragmentBuilder = BeanDefinitionBuilder .rootBeanDefinition (RepositoryFragment .class ,
315375 "implemented" );
316376
317- fragmentBuilder .addConstructorArgValue (fragmentConfiguration .getInterfaceName ());
377+ if (StringUtils .hasText (fragmentConfiguration .getInterfaceName ())) {
378+ fragmentBuilder .addConstructorArgValue (fragmentConfiguration .getInterfaceName ());
379+ }
318380 fragmentBuilder .addConstructorArgReference (fragmentConfiguration .getImplementationBeanName ());
319381
320382 registry .registerBeanDefinition (beanName ,
321383 ParsingUtils .getSourceBeanDefinition (fragmentBuilder , configuration .getSource ()));
322384 }
385+
386+ static class RepositoryFactoriesLoader extends SpringFactoriesLoader {
387+
388+ private final Map <String , List <String >> factories ;
389+
390+ /**
391+ * Create a new {@link SpringFactoriesLoader} instance.
392+ *
393+ * @param classLoader the classloader used to instantiate the factories
394+ * @param factories a map of factory class name to implementation class names
395+ */
396+ protected RepositoryFactoriesLoader (@ Nullable ClassLoader classLoader , Map <String , List <String >> factories ) {
397+ super (classLoader , factories );
398+ this .factories = factories ;
399+ }
400+
401+ /**
402+ * Create a {@link RepositoryFactoriesLoader} instance that will load and instantiate the factory implementations
403+ * from the default location, using the given class loader.
404+ *
405+ * @param classLoader the ClassLoader to use for loading resources; can be {@code null} to use the default
406+ * @return a {@link RepositoryFactoriesLoader} instance
407+ * @see #forResourceLocation(String)
408+ */
409+ public static RepositoryFactoriesLoader forDefaultResourceLocation (@ Nullable ClassLoader classLoader ) {
410+ ClassLoader resourceClassLoader = (classLoader != null ? classLoader
411+ : SpringFactoriesLoader .class .getClassLoader ());
412+ return new RepositoryFactoriesLoader (classLoader ,
413+ loadFactoriesResource (resourceClassLoader , FACTORIES_RESOURCE_LOCATION ));
414+ }
415+
416+ List <String > loadFactoryNames (String factoryType ) {
417+ return this .factories .getOrDefault (factoryType , Collections .emptyList ());
418+ }
419+ }
323420}
0 commit comments