Browse Source

DATAJDBC-293 - Polishing.

Removed most usage of the Stream API.
Improved error messages.
Added tests.
Code formatting.

Original pull request: #102.
pull/106/head
Jens Schauder 7 years ago
parent
commit
b7da4f412f
  1. 137
      spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/JdbcRepositoryConfigExtension.java
  2. 148
      spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/config/JdbcRepositoryConfigExtensionUnitTests.java
  3. 4
      spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/DbActionExecutionExceptionUnitTests.java

137
spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/JdbcRepositoryConfigExtension.java

@ -15,20 +15,16 @@
*/ */
package org.springframework.data.jdbc.repository.config; package org.springframework.data.jdbc.repository.config;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection;
import java.util.function.Function;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional; import java.util.Optional;
import java.util.function.Supplier; 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.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException; 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.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry; 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.RepositoryConfigurationExtensionSupport;
import org.springframework.data.repository.config.RepositoryConfigurationSource; import org.springframework.data.repository.config.RepositoryConfigurationSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; 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.ObjectUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
@ -49,7 +47,8 @@ import org.springframework.util.StringUtils;
*/ */
public class JdbcRepositoryConfigExtension extends RepositoryConfigurationExtensionSupport { public class JdbcRepositoryConfigExtension extends RepositoryConfigurationExtensionSupport {
private ConfigurableListableBeanFactory listableBeanFactory; private ListableBeanFactory beanFactory;
/* /*
* (non-Javadoc) * (non-Javadoc)
* @see org.springframework.data.repository.config.RepositoryConfigurationExtension#getModuleName() * @see org.springframework.data.repository.config.RepositoryConfigurationExtension#getModuleName()
@ -58,7 +57,6 @@ public class JdbcRepositoryConfigExtension extends RepositoryConfigurationExtens
public String getModuleName() { public String getModuleName() {
return "JDBC"; return "JDBC";
} }
/* /*
* (non-Javadoc) * (non-Javadoc)
@ -82,83 +80,110 @@ public class JdbcRepositoryConfigExtension extends RepositoryConfigurationExtens
* (non-Javadoc) * (non-Javadoc)
* @see org.springframework.data.repository.config.RepositoryConfigurationExtension#registerBeansForRoot(org.springframework.beans.factory.support.BeanDefinitionRegistry, org.springframework.data.repository.config.RepositoryConfigurationSource) * @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, public void registerBeansForRoot(BeanDefinitionRegistry registry, RepositoryConfigurationSource configurationSource) {
RepositoryConfigurationSource configurationSource) {
if (registry instanceof ConfigurableListableBeanFactory) { if (registry instanceof ListableBeanFactory) {
this.listableBeanFactory = (ConfigurableListableBeanFactory) registry; this.beanFactory = (ListableBeanFactory) registry;
} }
} }
/* /*
* (non-Javadoc) * (non-Javadoc)
* @see org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport#postProcess(org.springframework.beans.factory.support.BeanDefinitionBuilder, org.springframework.data.repository.config.RepositoryConfigurationSource) * @see org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport#postProcess(org.springframework.beans.factory.support.BeanDefinitionBuilder, org.springframework.data.repository.config.RepositoryConfigurationSource)
*/ */
@Override @Override
public void postProcess(BeanDefinitionBuilder builder, RepositoryConfigurationSource source) { public void postProcess(BeanDefinitionBuilder builder, RepositoryConfigurationSource source) {
resolveReference(builder, source, "jdbcOperationsRef", "jdbcOperations", NamedParameterJdbcOperations.class,
true); resolveReference(builder, source, "jdbcOperationsRef", "jdbcOperations", NamedParameterJdbcOperations.class, true);
resolveReference(builder, source, "dataAccessStrategyRef", "dataAccessStrategy", DataAccessStrategy.class, resolveReference(builder, source, "dataAccessStrategyRef", "dataAccessStrategy", DataAccessStrategy.class, false);
false);
} }
private void resolveReference(BeanDefinitionBuilder builder, RepositoryConfigurationSource source, private void resolveReference(BeanDefinitionBuilder builder, RepositoryConfigurationSource source,
String attributeName, String propertyName, Class<?> classRef, boolean required) { String attributeName, String propertyName, Class<?> classRef, boolean required) {
Optional<String> beanNameRef = source.getAttribute(attributeName).filter(StringUtils::hasText); Optional<String> beanNameRef = source.getAttribute(attributeName).filter(StringUtils::hasText);
String beanName = beanNameRef.orElseGet(() -> { String beanName = beanNameRef.orElseGet(() -> determineMatchingBeanName(propertyName, classRef, required));
if (this.listableBeanFactory != null) {
List<String> beanNames = Arrays.asList(listableBeanFactory.getBeanNamesForType(classRef)); if (beanName != null) {
Map<String, BeanDefinition> bdMap = beanNames.stream() builder.addPropertyReference(propertyName, beanName);
.collect(Collectors.toMap(Function.identity(), listableBeanFactory::getBeanDefinition)); } else {
Assert.isTrue(!required,
"The beanName must not be null when requested as 'required'. Please report this as a bug.");
}
}
@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.size() > 1) { if (beanNames.isEmpty()) {
// determine primary return nullOrThrowException(required,
() -> new NoSuchBeanDefinitionException(classRef, String.format("No bean of type %s available", classRef)));
}
Map<String, BeanDefinition> primaryBdMap = bdMap.entrySet().stream() if (beanNames.size() == 1) {
.filter(e -> e.getValue().isPrimary()) return beanNames.get(0);
.collect(Collectors.toMap(Entry::getKey, Entry::getValue)); }
Optional<String> primaryBeanName = getSingleBeanName(primaryBdMap.keySet(), classRef, if (!(beanFactory instanceof ConfigurableListableBeanFactory)) {
() -> "more than one 'primary' bean found among candidates: " + primaryBdMap.keySet());
// In Java 11 should use Optional.or() return nullOrThrowException(required,
if (primaryBeanName.isPresent()) { () -> new NoSuchBeanDefinitionException(String.format(
return primaryBeanName.get(); "BeanFactory does not implement ConfigurableListableBeanFactory when trying to find bean of type %s.",
} classRef)));
}
// determine matchesBeanName List<String> primaryBeanNames = getPrimaryBeanDefinitions(beanNames, (ConfigurableListableBeanFactory) beanFactory);
Optional<String> matchesBeanName = beanNames.stream() if (primaryBeanNames.size() == 1) {
.filter(name -> propertyName.equals(name) return primaryBeanNames.get(0);
|| ObjectUtils.containsElement(listableBeanFactory.getAliases(name), propertyName)) }
.findFirst();
if (matchesBeanName.isPresent()) { if (primaryBeanNames.size() > 1) {
return matchesBeanName.get(); throw new NoUniqueBeanDefinitionException(classRef, primaryBeanNames.size(),
} "more than one 'primary' bean found among candidates: " + primaryBeanNames);
}
} for (String beanName : beanNames) {
if (beanNames.size() == 1) { if (propertyName.equals(beanName)
return beanNames.get(0); || ObjectUtils.containsElement(beanFactory.getAliases(beanName), propertyName)) {
} return beanName;
} }
return null;
});
if (beanName != null) {
builder.addPropertyReference(propertyName, beanName);
} else if (required) {
throw new NoSuchBeanDefinitionException(classRef);
} }
return nullOrThrowException(required,
() -> new NoSuchBeanDefinitionException(String.format("No bean of name %s found.", propertyName)));
} }
private Optional<String> getSingleBeanName(Collection<String> beanNames, Class<?> classRef, private static List<String> getPrimaryBeanDefinitions(List<String> beanNames,
Supplier<String> errorMessage) { ConfigurableListableBeanFactory beanFactory) {
if (beanNames.size() > 1) {
throw new NoUniqueBeanDefinitionException(classRef, beanNames.size(), errorMessage.get()); ArrayList<String> primaryBeanNames = new ArrayList<>();
for (String name : beanNames) {
if (beanFactory.getBeanDefinition(name).isPrimary()) {
primaryBeanNames.add(name);
}
} }
return primaryBeanNames;
}
return beanNames.stream().findFirst(); @Nullable
private static String nullOrThrowException(boolean required, Supplier<RuntimeException> exception) {
if (required) {
throw exception.get();
}
return null;
} }
} }

148
spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/config/JdbcRepositoryConfigExtensionUnitTests.java

@ -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());
}
}

4
spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/DbActionExecutionExceptionTest.java → spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/DbActionExecutionExceptionUnitTests.java

@ -20,9 +20,11 @@ import static org.mockito.Mockito.*;
import org.junit.Test; import org.junit.Test;
/** /**
* Unit test for {@link DbActionExecutionException}.
*
* @author Jens Schauder * @author Jens Schauder
*/ */
public class DbActionExecutionExceptionTest { public class DbActionExecutionExceptionUnitTests {
@Test // DATAJDBC-162 @Test // DATAJDBC-162
public void constructorWorksWithNullPropertyPath() { public void constructorWorksWithNullPropertyPath() {
Loading…
Cancel
Save