DATAJDBC-293 - Polishing.
Removed most usage of the Stream API. Improved error messages. Added tests. Code formatting. Original pull request: #102.
This commit is contained in:
@@ -15,20 +15,16 @@
|
||||
*/
|
||||
package org.springframework.data.jdbc.repository.config;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.function.Function;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.beans.factory.ListableBeanFactory;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
@@ -37,6 +33,8 @@ import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactoryBea
|
||||
import org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport;
|
||||
import org.springframework.data.repository.config.RepositoryConfigurationSource;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@@ -49,7 +47,8 @@ import org.springframework.util.StringUtils;
|
||||
*/
|
||||
public class JdbcRepositoryConfigExtension extends RepositoryConfigurationExtensionSupport {
|
||||
|
||||
private ConfigurableListableBeanFactory listableBeanFactory;
|
||||
private ListableBeanFactory beanFactory;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.config.RepositoryConfigurationExtension#getModuleName()
|
||||
@@ -58,7 +57,6 @@ public class JdbcRepositoryConfigExtension extends RepositoryConfigurationExtens
|
||||
public String getModuleName() {
|
||||
return "JDBC";
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
@@ -82,83 +80,110 @@ public class JdbcRepositoryConfigExtension extends RepositoryConfigurationExtens
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.config.RepositoryConfigurationExtension#registerBeansForRoot(org.springframework.beans.factory.support.BeanDefinitionRegistry, org.springframework.data.repository.config.RepositoryConfigurationSource)
|
||||
*/
|
||||
public void registerBeansForRoot(BeanDefinitionRegistry registry,
|
||||
RepositoryConfigurationSource configurationSource) {
|
||||
if (registry instanceof ConfigurableListableBeanFactory) {
|
||||
this.listableBeanFactory = (ConfigurableListableBeanFactory) registry;
|
||||
public void registerBeansForRoot(BeanDefinitionRegistry registry, RepositoryConfigurationSource configurationSource) {
|
||||
|
||||
if (registry instanceof ListableBeanFactory) {
|
||||
this.beanFactory = (ListableBeanFactory) registry;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport#postProcess(org.springframework.beans.factory.support.BeanDefinitionBuilder, org.springframework.data.repository.config.RepositoryConfigurationSource)
|
||||
*/
|
||||
@Override
|
||||
public void postProcess(BeanDefinitionBuilder builder, RepositoryConfigurationSource source) {
|
||||
resolveReference(builder, source, "jdbcOperationsRef", "jdbcOperations", NamedParameterJdbcOperations.class,
|
||||
true);
|
||||
resolveReference(builder, source, "dataAccessStrategyRef", "dataAccessStrategy", DataAccessStrategy.class,
|
||||
false);
|
||||
|
||||
resolveReference(builder, source, "jdbcOperationsRef", "jdbcOperations", NamedParameterJdbcOperations.class, true);
|
||||
resolveReference(builder, source, "dataAccessStrategyRef", "dataAccessStrategy", DataAccessStrategy.class, false);
|
||||
}
|
||||
|
||||
private void resolveReference(BeanDefinitionBuilder builder, RepositoryConfigurationSource source,
|
||||
String attributeName, String propertyName, Class<?> classRef, boolean required) {
|
||||
|
||||
Optional<String> beanNameRef = source.getAttribute(attributeName).filter(StringUtils::hasText);
|
||||
|
||||
String beanName = beanNameRef.orElseGet(() -> {
|
||||
if (this.listableBeanFactory != null) {
|
||||
List<String> beanNames = Arrays.asList(listableBeanFactory.getBeanNamesForType(classRef));
|
||||
Map<String, BeanDefinition> bdMap = beanNames.stream()
|
||||
.collect(Collectors.toMap(Function.identity(), listableBeanFactory::getBeanDefinition));
|
||||
String beanName = beanNameRef.orElseGet(() -> determineMatchingBeanName(propertyName, classRef, required));
|
||||
|
||||
if (beanNames.size() > 1) {
|
||||
// determine primary
|
||||
|
||||
Map<String, BeanDefinition> primaryBdMap = bdMap.entrySet().stream()
|
||||
.filter(e -> e.getValue().isPrimary())
|
||||
.collect(Collectors.toMap(Entry::getKey, Entry::getValue));
|
||||
|
||||
Optional<String> primaryBeanName = getSingleBeanName(primaryBdMap.keySet(), classRef,
|
||||
() -> "more than one 'primary' bean found among candidates: " + primaryBdMap.keySet());
|
||||
|
||||
// In Java 11 should use Optional.or()
|
||||
if (primaryBeanName.isPresent()) {
|
||||
return primaryBeanName.get();
|
||||
}
|
||||
|
||||
// determine matchesBeanName
|
||||
|
||||
Optional<String> matchesBeanName = beanNames.stream()
|
||||
.filter(name -> propertyName.equals(name)
|
||||
|| ObjectUtils.containsElement(listableBeanFactory.getAliases(name), propertyName))
|
||||
.findFirst();
|
||||
|
||||
if (matchesBeanName.isPresent()) {
|
||||
return matchesBeanName.get();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (beanNames.size() == 1) {
|
||||
return beanNames.get(0);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
});
|
||||
if (beanName != null) {
|
||||
builder.addPropertyReference(propertyName, beanName);
|
||||
} else if (required) {
|
||||
throw new NoSuchBeanDefinitionException(classRef);
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<String> getSingleBeanName(Collection<String> beanNames, Class<?> classRef,
|
||||
Supplier<String> errorMessage) {
|
||||
if (beanNames.size() > 1) {
|
||||
throw new NoUniqueBeanDefinitionException(classRef, beanNames.size(), errorMessage.get());
|
||||
} else {
|
||||
Assert.isTrue(!required,
|
||||
"The beanName must not be null when requested as 'required'. Please report this as a bug.");
|
||||
}
|
||||
|
||||
return beanNames.stream().findFirst();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String determineMatchingBeanName(String propertyName, Class<?> classRef, boolean required) {
|
||||
|
||||
if (this.beanFactory == null) {
|
||||
return nullOrThrowException(required,
|
||||
() -> new NoSuchBeanDefinitionException(classRef, "No BeanFactory available."));
|
||||
}
|
||||
|
||||
List<String> beanNames = Arrays.asList(beanFactory.getBeanNamesForType(classRef));
|
||||
|
||||
if (beanNames.isEmpty()) {
|
||||
return nullOrThrowException(required,
|
||||
() -> new NoSuchBeanDefinitionException(classRef, String.format("No bean of type %s available", classRef)));
|
||||
}
|
||||
|
||||
if (beanNames.size() == 1) {
|
||||
return beanNames.get(0);
|
||||
}
|
||||
|
||||
if (!(beanFactory instanceof ConfigurableListableBeanFactory)) {
|
||||
|
||||
return nullOrThrowException(required,
|
||||
() -> new NoSuchBeanDefinitionException(String.format(
|
||||
"BeanFactory does not implement ConfigurableListableBeanFactory when trying to find bean of type %s.",
|
||||
classRef)));
|
||||
}
|
||||
|
||||
List<String> primaryBeanNames = getPrimaryBeanDefinitions(beanNames, (ConfigurableListableBeanFactory) beanFactory);
|
||||
|
||||
if (primaryBeanNames.size() == 1) {
|
||||
return primaryBeanNames.get(0);
|
||||
}
|
||||
|
||||
if (primaryBeanNames.size() > 1) {
|
||||
throw new NoUniqueBeanDefinitionException(classRef, primaryBeanNames.size(),
|
||||
"more than one 'primary' bean found among candidates: " + primaryBeanNames);
|
||||
}
|
||||
|
||||
for (String beanName : beanNames) {
|
||||
|
||||
if (propertyName.equals(beanName)
|
||||
|| ObjectUtils.containsElement(beanFactory.getAliases(beanName), propertyName)) {
|
||||
return beanName;
|
||||
}
|
||||
}
|
||||
|
||||
return nullOrThrowException(required,
|
||||
() -> new NoSuchBeanDefinitionException(String.format("No bean of name %s found.", propertyName)));
|
||||
}
|
||||
|
||||
private static List<String> getPrimaryBeanDefinitions(List<String> beanNames,
|
||||
ConfigurableListableBeanFactory beanFactory) {
|
||||
|
||||
ArrayList<String> primaryBeanNames = new ArrayList<>();
|
||||
for (String name : beanNames) {
|
||||
|
||||
if (beanFactory.getBeanDefinition(name).isPrimary()) {
|
||||
primaryBeanNames.add(name);
|
||||
}
|
||||
}
|
||||
return primaryBeanNames;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static String nullOrThrowException(boolean required, Supplier<RuntimeException> exception) {
|
||||
|
||||
if (required) {
|
||||
throw exception.get();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* Copyright 2018 the original author or authors.
|
||||
*
|
||||
* 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.springframework.data.jdbc.repository.config;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.beans.factory.config.RuntimeBeanReference;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.context.support.GenericApplicationContext;
|
||||
import org.springframework.data.repository.config.RepositoryConfigurationSource;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
|
||||
|
||||
/**
|
||||
* @author Jens Schauder
|
||||
*/
|
||||
public class JdbcRepositoryConfigExtensionUnitTests {
|
||||
|
||||
BeanDefinitionBuilder definitionBuilder = BeanDefinitionBuilder.genericBeanDefinition();
|
||||
RepositoryConfigurationSource configSource = mock(RepositoryConfigurationSource.class);
|
||||
DefaultListableBeanFactory listableBeanFactory = new DefaultListableBeanFactory();
|
||||
|
||||
JdbcRepositoryConfigExtension extension = new JdbcRepositoryConfigExtension();
|
||||
|
||||
@Test // DATAJDBC-293
|
||||
public void exceptionIsThrownOnPostProcessIfNoBeanFactoryIsAvailable() {
|
||||
|
||||
assertThatThrownBy( //
|
||||
() -> extension.postProcess(definitionBuilder, configSource)) //
|
||||
.isInstanceOf(NoSuchBeanDefinitionException.class) //
|
||||
.hasMessageContaining("No BeanFactory");
|
||||
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-293
|
||||
public void exceptionIsThrownOnPostProcessIfNoJdbcOperationsBeanIsAvailable() {
|
||||
|
||||
extension.registerBeansForRoot(listableBeanFactory, null);
|
||||
|
||||
assertThatThrownBy( //
|
||||
() -> extension.postProcess(definitionBuilder, configSource)) //
|
||||
.isInstanceOf(NoSuchBeanDefinitionException.class) //
|
||||
.hasMessageContaining("NamedParameterJdbcOperations"); //
|
||||
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-293
|
||||
public void exceptionIsThrownOnPostProcessIfMultipleJdbcOperationsBeansAreAvailableAndNoConfigurableBeanFactoryAvailable() {
|
||||
|
||||
GenericApplicationContext applicationContext = new GenericApplicationContext();
|
||||
|
||||
applicationContext.registerBean( //
|
||||
"one", //
|
||||
NamedParameterJdbcOperations.class, //
|
||||
() -> mock(NamedParameterJdbcOperations.class));
|
||||
applicationContext.registerBean( //
|
||||
"two", //
|
||||
NamedParameterJdbcOperations.class, //
|
||||
() -> mock(NamedParameterJdbcOperations.class));
|
||||
|
||||
applicationContext.refresh();
|
||||
|
||||
extension.registerBeansForRoot(applicationContext, null);
|
||||
|
||||
assertThatThrownBy( //
|
||||
() -> extension.postProcess(definitionBuilder, configSource)) //
|
||||
.isInstanceOf(NoSuchBeanDefinitionException.class) //
|
||||
.hasMessageContaining("NamedParameterJdbcOperations"); //
|
||||
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-293
|
||||
public void exceptionIsThrownOnPostProcessIfMultiplePrimaryNoJdbcOperationsBeansAreAvailable() {
|
||||
|
||||
registerJdbcOperations("one", true);
|
||||
registerJdbcOperations("two", true);
|
||||
|
||||
extension.registerBeansForRoot(listableBeanFactory, null);
|
||||
|
||||
assertThatThrownBy( //
|
||||
() -> extension.postProcess(definitionBuilder, configSource)) //
|
||||
.isInstanceOf(NoSuchBeanDefinitionException.class) //
|
||||
.hasMessageContaining("NamedParameterJdbcOperations"); //
|
||||
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-293
|
||||
public void uniquePrimaryBeanIsUsedOfNamedParameterJdbcOperations() {
|
||||
|
||||
registerJdbcOperations("one", false);
|
||||
registerJdbcOperations("two", true);
|
||||
|
||||
extension.registerBeansForRoot(listableBeanFactory, null);
|
||||
|
||||
extension.postProcess(definitionBuilder, configSource);
|
||||
|
||||
Object jdbcOperations = definitionBuilder.getBeanDefinition().getPropertyValues().get("jdbcOperations");
|
||||
|
||||
assertThat(jdbcOperations) //
|
||||
.isInstanceOf(RuntimeBeanReference.class) //
|
||||
.extracting(rbr -> ((RuntimeBeanReference) rbr).getBeanName()).contains("two");
|
||||
|
||||
System.out.println(jdbcOperations);
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-293
|
||||
public void matchesByNameAsLastResort() {
|
||||
|
||||
registerJdbcOperations("jdbcOperations", false);
|
||||
registerJdbcOperations("two", false);
|
||||
|
||||
extension.registerBeansForRoot(listableBeanFactory, null);
|
||||
|
||||
extension.postProcess(definitionBuilder, configSource);
|
||||
|
||||
Object jdbcOperations = definitionBuilder.getBeanDefinition().getPropertyValues().get("jdbcOperations");
|
||||
|
||||
assertThat(jdbcOperations) //
|
||||
.isInstanceOf(RuntimeBeanReference.class) //
|
||||
.extracting(rbr -> ((RuntimeBeanReference) rbr).getBeanName()).contains("jdbcOperations");
|
||||
|
||||
}
|
||||
|
||||
private void registerJdbcOperations(String name, boolean primary) {
|
||||
|
||||
listableBeanFactory.registerBeanDefinition(name, BeanDefinitionBuilder.genericBeanDefinition( //
|
||||
NamedParameterJdbcOperations.class, //
|
||||
() -> mock(NamedParameterJdbcOperations.class)) //
|
||||
.applyCustomizers(bd -> bd.setPrimary(primary)) //
|
||||
.getBeanDefinition());
|
||||
}
|
||||
}
|
||||
@@ -20,9 +20,11 @@ import static org.mockito.Mockito.*;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Unit test for {@link DbActionExecutionException}.
|
||||
*
|
||||
* @author Jens Schauder
|
||||
*/
|
||||
public class DbActionExecutionExceptionTest {
|
||||
public class DbActionExecutionExceptionUnitTests {
|
||||
|
||||
@Test // DATAJDBC-162
|
||||
public void constructorWorksWithNullPropertyPath() {
|
||||
Reference in New Issue
Block a user