diff --git a/pom.xml b/pom.xml index f0f583b51e..ad5120a38f 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-commons - 2.5.0-SNAPSHOT + 2.5.0-GH-2341-SNAPSHOT Spring Data Core @@ -339,7 +339,7 @@ 0.1.4 test - + org.jmolecules.integrations jmolecules-spring diff --git a/src/main/java/org/springframework/data/repository/core/RepositoryCreationException.java b/src/main/java/org/springframework/data/repository/core/RepositoryCreationException.java new file mode 100644 index 0000000000..4322ff0d76 --- /dev/null +++ b/src/main/java/org/springframework/data/repository/core/RepositoryCreationException.java @@ -0,0 +1,57 @@ +/* + * Copyright 2021 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 + * + * https://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.repository.core; + +import org.springframework.dao.InvalidDataAccessApiUsageException; + +/** + * Exception thrown in the context of repository creation. + * + * @author Mark Paluch + * @since 2.5 + */ +@SuppressWarnings("serial") +public class RepositoryCreationException extends InvalidDataAccessApiUsageException { + + private final Class repositoryInterface; + + /** + * Constructor for RepositoryCreationException. + * + * @param msg the detail message. + * @param repositoryInterface the repository interface. + */ + public RepositoryCreationException(String msg, Class repositoryInterface) { + super(msg); + this.repositoryInterface = repositoryInterface; + } + + /** + * Constructor for RepositoryException. + * + * @param msg the detail message. + * @param cause the root cause from the data access API in use. + * @param repositoryInterface the repository interface. + */ + public RepositoryCreationException(String msg, Throwable cause, Class repositoryInterface) { + super(msg, cause); + this.repositoryInterface = repositoryInterface; + } + + public Class getRepositoryInterface() { + return repositoryInterface; + } +} diff --git a/src/main/java/org/springframework/data/repository/core/support/FragmentNotImplementedException.java b/src/main/java/org/springframework/data/repository/core/support/FragmentNotImplementedException.java new file mode 100644 index 0000000000..5abf6fb8b4 --- /dev/null +++ b/src/main/java/org/springframework/data/repository/core/support/FragmentNotImplementedException.java @@ -0,0 +1,47 @@ +/* + * Copyright 2021 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 + * + * https://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.repository.core.support; + +import org.springframework.data.repository.core.RepositoryCreationException; + +/** + * Exception thrown during repository creation or repository method invocation when invoking a repository method on a + * fragment without an implementation. + * + * @author Mark Paluch + * @since 2.5 + */ +@SuppressWarnings("serial") +public class FragmentNotImplementedException extends RepositoryCreationException { + + private final RepositoryFragment fragment; + + /** + * Constructor for FragmentNotImplementedException. + * + * @param msg the detail message. + * @param repositoryInterface the repository interface. + * @param fragment the offending repository fragment. + */ + public FragmentNotImplementedException(String msg, Class repositoryInterface, RepositoryFragment fragment) { + super(msg, repositoryInterface); + this.fragment = fragment; + } + + public RepositoryFragment getFragment() { + return fragment; + } +} diff --git a/src/main/java/org/springframework/data/repository/core/support/IncompleteRepositoryCompositionException.java b/src/main/java/org/springframework/data/repository/core/support/IncompleteRepositoryCompositionException.java new file mode 100644 index 0000000000..a780c0f34f --- /dev/null +++ b/src/main/java/org/springframework/data/repository/core/support/IncompleteRepositoryCompositionException.java @@ -0,0 +1,39 @@ +/* + * Copyright 2021 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 + * + * https://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.repository.core.support; + +import org.springframework.data.repository.core.RepositoryCreationException; + +/** + * Exception thrown during repository creation when a the repository has custom methods that are not backed by a + * fragment or if no fragment could be found for a repository method invocation. + * + * @author Mark Paluch + * @since 2.5 + */ +@SuppressWarnings("serial") +public class IncompleteRepositoryCompositionException extends RepositoryCreationException { + + /** + * Constructor for IncompleteRepositoryCompositionException. + * + * @param msg the detail message. + * @param repositoryInterface the repository interface. + */ + public IncompleteRepositoryCompositionException(String msg, Class repositoryInterface) { + super(msg, repositoryInterface); + } +} diff --git a/src/main/java/org/springframework/data/repository/core/support/QueryExecutorMethodInterceptor.java b/src/main/java/org/springframework/data/repository/core/support/QueryExecutorMethodInterceptor.java index 979b0f8e5f..c06a5e5652 100644 --- a/src/main/java/org/springframework/data/repository/core/support/QueryExecutorMethodInterceptor.java +++ b/src/main/java/org/springframework/data/repository/core/support/QueryExecutorMethodInterceptor.java @@ -29,6 +29,7 @@ import org.springframework.data.repository.core.RepositoryInformation; import org.springframework.data.repository.core.support.RepositoryInvocationMulticaster.DefaultRepositoryInvocationMulticaster; import org.springframework.data.repository.core.support.RepositoryInvocationMulticaster.NoOpRepositoryInvocationMulticaster; +import org.springframework.data.repository.query.QueryCreationException; import org.springframework.data.repository.query.QueryLookupStrategy; import org.springframework.data.repository.query.QueryMethod; import org.springframework.data.repository.query.RepositoryQuery; @@ -97,7 +98,13 @@ private Map mapMethodsToQuery(RepositoryInformation rep private Pair lookupQuery(Method method, RepositoryInformation information, QueryLookupStrategy strategy, ProjectionFactory projectionFactory) { - return Pair.of(method, strategy.resolveQuery(method, information, projectionFactory, namedQueries)); + try { + return Pair.of(method, strategy.resolveQuery(method, information, projectionFactory, namedQueries)); + } catch (QueryCreationException e) { + throw e; + } catch (RuntimeException e) { + throw QueryCreationException.create(e.getMessage(), e, information.getRepositoryInterface(), method); + } } @SuppressWarnings({ "rawtypes", "unchecked" }) diff --git a/src/main/java/org/springframework/data/repository/core/support/RepositoryComposition.java b/src/main/java/org/springframework/data/repository/core/support/RepositoryComposition.java index 388791a70c..6c3a6a98a8 100644 --- a/src/main/java/org/springframework/data/repository/core/support/RepositoryComposition.java +++ b/src/main/java/org/springframework/data/repository/core/support/RepositoryComposition.java @@ -316,8 +316,12 @@ Method getMethod(Method method) { public void validateImplementation() { fragments.stream().forEach(it -> it.getImplementation() // - .orElseThrow(() -> new IllegalStateException(String.format("Fragment %s has no implementation.", - ClassUtils.getQualifiedName(it.getSignatureContributor()))))); + .orElseThrow(() -> { + Class repositoryInterface = metadata != null ? metadata.getRepositoryInterface() : Object.class; + return new FragmentNotImplementedException(String.format("Fragment %s used in %s has no implementation.", + ClassUtils.getQualifiedName(it.getSignatureContributor()), + ClassUtils.getQualifiedName(repositoryInterface)), repositoryInterface, it); + })); } /* diff --git a/src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java b/src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java index 96768c5796..c9d761e8ca 100644 --- a/src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java +++ b/src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java @@ -19,6 +19,7 @@ import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; @@ -28,6 +29,7 @@ import org.aopalliance.intercept.MethodInvocation; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; + import org.springframework.aop.framework.ProxyFactory; import org.springframework.aop.interceptor.ExposeInvocationInterceptor; import org.springframework.beans.BeanUtils; @@ -57,12 +59,12 @@ import org.springframework.data.repository.query.QueryMethod; import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; import org.springframework.data.repository.query.RepositoryQuery; -import org.springframework.data.repository.util.ClassUtils; import org.springframework.data.repository.util.QueryExecutionConverters; import org.springframework.data.util.ReflectionUtils; import org.springframework.lang.Nullable; import org.springframework.transaction.interceptor.TransactionalProxy; import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; import org.springframework.util.ConcurrentReferenceHashMap; import org.springframework.util.ConcurrentReferenceHashMap.ReferenceType; import org.springframework.util.ObjectUtils; @@ -312,8 +314,6 @@ public T getRepository(Class repositoryInterface, RepositoryFragments fra repositoryCompositionStep.end(); - validate(information, composition); - StartupStep repositoryTargetStep = onEvent(applicationStartup, "spring.data.repository.target", repositoryInterface); Object target = getTargetRepository(information); @@ -321,6 +321,9 @@ public T getRepository(Class repositoryInterface, RepositoryFragments fra repositoryTargetStep.tag("target", target.getClass().getName()); repositoryTargetStep.end(); + RepositoryComposition compositionToUse = composition.append(RepositoryFragment.implemented(target)); + validate(information, compositionToUse); + // Create proxy StartupStep repositoryProxyStep = onEvent(applicationStartup, "spring.data.repository.proxy", repositoryInterface); ProxyFactory result = new ProxyFactory(); @@ -357,7 +360,6 @@ public T getRepository(Class repositoryInterface, RepositoryFragments fra result.addAdvice(new QueryExecutorMethodInterceptor(information, projectionFactory, queryLookupStrategy, namedQueries, queryPostProcessors, methodInvocationListeners)); - RepositoryComposition compositionToUse = composition.append(RepositoryFragment.implemented(target)); result.addAdvice( new ImplementationMethodExecutionInterceptor(information, compositionToUse, methodInvocationListeners)); @@ -502,17 +504,7 @@ protected Optional getQueryLookupStrategy(@Nullable Key key */ private void validate(RepositoryInformation repositoryInformation, RepositoryComposition composition) { - if (repositoryInformation.hasCustomMethod()) { - - if (composition.isEmpty()) { - - throw new IllegalArgumentException( - String.format("You have custom methods in %s but have not provided a custom implementation!", - repositoryInformation.getRepositoryInterface())); - } - - composition.validateImplementation(); - } + RepositoryValidator.validate(composition, getClass(), repositoryInformation); validate(repositoryInformation); } @@ -606,7 +598,7 @@ public Object invoke(@SuppressWarnings("null") MethodInvocation invocation) thro try { return composition.invoke(invocationMulticaster, method, arguments); } catch (Exception e) { - ClassUtils.unwrapReflectionException(e); + org.springframework.util.ReflectionUtils.handleReflectionException(e); } throw new IllegalStateException("Should not occur!"); @@ -715,4 +707,94 @@ public String toString() { + this.getRepositoryInterfaceName() + ", compositionHash=" + this.getCompositionHash() + ")"; } } + + /** + * Validator utility to catch common mismatches with a proper error message instead of letting the query mechanism + * attempt implementing a query method and fail with a less specific message. + */ + static class RepositoryValidator { + + static Map, String> WELL_KNOWN_EXECUTORS = new HashMap<>(); + + static { + + org.springframework.data.repository.util.ClassUtils.ifPresent( + "org.springframework.data.querydsl.QuerydslPredicateExecutor", RepositoryValidator.class.getClassLoader(), + it -> { + WELL_KNOWN_EXECUTORS.put(it, "Querydsl"); + }); + + org.springframework.data.repository.util.ClassUtils.ifPresent( + "org.springframework.data.querydsl.ReactiveQuerydslPredicateExecutor", + RepositoryValidator.class.getClassLoader(), it -> { + WELL_KNOWN_EXECUTORS.put(it, "Reactive Querydsl"); + }); + + org.springframework.data.repository.util.ClassUtils.ifPresent( + "org.springframework.data.repository.query.QueryByExampleExecutor", + RepositoryValidator.class.getClassLoader(), it -> { + WELL_KNOWN_EXECUTORS.put(it, "Query by Example"); + }); + + org.springframework.data.repository.util.ClassUtils.ifPresent( + "org.springframework.data.repository.query.ReactiveQueryByExampleExecutor", + RepositoryValidator.class.getClassLoader(), it -> { + WELL_KNOWN_EXECUTORS.put(it, "Reactive Query by Example"); + }); + } + + /** + * Validate the {@link RepositoryComposition} for custom implementations and well-known executors. + * + * @param composition + * @param source + * @param repositoryInformation + */ + public static void validate(RepositoryComposition composition, Class source, + RepositoryInformation repositoryInformation) { + + Class repositoryInterface = repositoryInformation.getRepositoryInterface(); + if (repositoryInformation.hasCustomMethod()) { + + if (composition.isEmpty()) { + + throw new IncompleteRepositoryCompositionException( + String.format("You have custom methods in %s but have not provided a custom implementation!", + org.springframework.util.ClassUtils.getQualifiedName(repositoryInterface)), + repositoryInterface); + } + + composition.validateImplementation(); + } + + for (Map.Entry, String> entry : WELL_KNOWN_EXECUTORS.entrySet()) { + + Class executorInterface = entry.getKey(); + if (!executorInterface.isAssignableFrom(repositoryInterface)) { + continue; + } + + if (!containsFragmentImplementation(composition, executorInterface)) { + throw new UnsupportedFragmentException( + String.format("Repository %s implements %s but %s does not support %s!", + ClassUtils.getQualifiedName(repositoryInterface), ClassUtils.getQualifiedName(executorInterface), + ClassUtils.getShortName(source), entry.getValue()), + repositoryInterface, executorInterface); + } + } + } + + private static boolean containsFragmentImplementation(RepositoryComposition composition, + Class executorInterface) { + + for (RepositoryFragment fragment : composition.getFragments()) { + + if (fragment.getImplementation().filter(executorInterface::isInstance).isPresent()) { + return true; + } + } + + return false; + } + } } diff --git a/src/main/java/org/springframework/data/repository/core/support/UnsupportedFragmentException.java b/src/main/java/org/springframework/data/repository/core/support/UnsupportedFragmentException.java new file mode 100644 index 0000000000..e748d4123b --- /dev/null +++ b/src/main/java/org/springframework/data/repository/core/support/UnsupportedFragmentException.java @@ -0,0 +1,47 @@ +/* + * Copyright 2021 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 + * + * https://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.repository.core.support; + +import org.springframework.data.repository.core.RepositoryCreationException; + +/** + * Exception thrown during repository creation when a well-known fragment interface is not supported by the repository + * factory. + * + * @author Mark Paluch + * @since 2.5 + */ +@SuppressWarnings("serial") +public class UnsupportedFragmentException extends RepositoryCreationException { + + private final Class fragmentInterface; + + /** + * Constructor for UnsupportedFragmentException. + * + * @param msg the detail message. + * @param repositoryInterface the repository interface. + * @param fragmentInterface the offending fragment interface. + */ + public UnsupportedFragmentException(String msg, Class repositoryInterface, Class fragmentInterface) { + super(msg, repositoryInterface); + this.fragmentInterface = fragmentInterface; + } + + public Class getFragmentInterface() { + return fragmentInterface; + } +} diff --git a/src/main/java/org/springframework/data/repository/query/QueryCreationException.java b/src/main/java/org/springframework/data/repository/query/QueryCreationException.java index 468c91da73..f17353687f 100644 --- a/src/main/java/org/springframework/data/repository/query/QueryCreationException.java +++ b/src/main/java/org/springframework/data/repository/query/QueryCreationException.java @@ -15,24 +15,39 @@ */ package org.springframework.data.repository.query; +import java.lang.reflect.Method; + +import org.springframework.data.repository.core.RepositoryCreationException; + /** - * Exception to be thrown if a query cannot be created from a {@link QueryMethod}. + * Exception to be thrown if a query cannot be created from a {@link Method}. * * @author Oliver Gierke + * @author Mark Paluch */ -public final class QueryCreationException extends RuntimeException { +public final class QueryCreationException extends RepositoryCreationException { private static final long serialVersionUID = -1238456123580L; private static final String MESSAGE_TEMPLATE = "Could not create query for method %s! Could not find property %s on domain class %s."; + private final Method method; + + /** + * Creates a new {@link QueryCreationException}. + */ + private QueryCreationException(String message, QueryMethod method) { + + super(message, method.getMetadata().getRepositoryInterface()); + this.method = method.getMethod(); + } + /** * Creates a new {@link QueryCreationException}. - * - * @param method */ - private QueryCreationException(String message) { + private QueryCreationException(String message, Throwable cause, Class repositoryInterface, Method method) { - super(message); + super(message, cause, repositoryInterface); + this.method = method; } /** @@ -45,7 +60,7 @@ private QueryCreationException(String message) { public static QueryCreationException invalidProperty(QueryMethod method, String propertyName) { return new QueryCreationException(String.format(MESSAGE_TEMPLATE, method, propertyName, method.getDomainClass() - .getName())); + .getName()), method); } /** @@ -57,7 +72,8 @@ public static QueryCreationException invalidProperty(QueryMethod method, String */ public static QueryCreationException create(QueryMethod method, String message) { - return new QueryCreationException(String.format("Could not create query for %s! Reason: %s", method, message)); + return new QueryCreationException(String.format("Could not create query for %s! Reason: %s", method, message), + method); } /** @@ -68,7 +84,29 @@ public static QueryCreationException create(QueryMethod method, String message) * @return */ public static QueryCreationException create(QueryMethod method, Throwable cause) { + return new QueryCreationException(cause.getMessage(), cause, method.getMetadata().getRepositoryInterface(), + method.getMethod()); + } - return create(method, cause.getMessage()); + /** + * Creates a new {@link QueryCreationException} for the given {@link QueryMethod} and {@link Throwable} as cause. + * + * @param method + * @param cause + * @return + * @since 2.5 + */ + public static QueryCreationException create(String message, Throwable cause, Class repositoryInterface, + Method method) { + return new QueryCreationException(String.format("Could not create query for %s! Reason: %s", method, message), + cause, repositoryInterface, method); + } + + /** + * @return + * @since 2.5 + */ + public Method getMethod() { + return method; } } diff --git a/src/main/java/org/springframework/data/repository/query/QueryMethod.java b/src/main/java/org/springframework/data/repository/query/QueryMethod.java index 496cc133dc..4ce0d25283 100644 --- a/src/main/java/org/springframework/data/repository/query/QueryMethod.java +++ b/src/main/java/org/springframework/data/repository/query/QueryMethod.java @@ -241,6 +241,14 @@ public ResultProcessor getResultProcessor() { return resultProcessor; } + RepositoryMetadata getMetadata() { + return metadata; + } + + Method getMethod() { + return method; + } + /* * (non-Javadoc) * @see java.lang.Object#toString() diff --git a/src/test/java/org/springframework/data/repository/core/support/RepositoryCompositionUnitTests.java b/src/test/java/org/springframework/data/repository/core/support/RepositoryCompositionUnitTests.java index cdc6e904bb..08c561eddc 100644 --- a/src/test/java/org/springframework/data/repository/core/support/RepositoryCompositionUnitTests.java +++ b/src/test/java/org/springframework/data/repository/core/support/RepositoryCompositionUnitTests.java @@ -136,16 +136,17 @@ void shouldCallMethodsInOrder() throws Throwable { assertThat(barFoo.invoke(barFoo.findMethod(getString).get())).isEqualTo("bar"); } - @Test // DATACMNS-102 + @Test // DATACMNS-102, GH-2341 void shouldValidateStructuralFragments() { RepositoryComposition mixed = RepositoryComposition.of(RepositoryFragment.structural(QueryByExampleExecutor.class), RepositoryFragment.implemented(backingRepo)); - assertThatIllegalStateException() // + assertThatExceptionOfType(FragmentNotImplementedException.class) // .isThrownBy(mixed::validateImplementation) // .withMessageContaining( - "Fragment org.springframework.data.repository.query.QueryByExampleExecutor has no implementation."); + "Fragment org.springframework.data.repository.query.QueryByExampleExecutor") + .withMessageContaining("has no implementation"); } @Test // DATACMNS-102 diff --git a/src/test/java/org/springframework/data/repository/core/support/RepositoryFactorySupportUnitTests.java b/src/test/java/org/springframework/data/repository/core/support/RepositoryFactorySupportUnitTests.java index 3eeb54316d..f4e547805f 100755 --- a/src/test/java/org/springframework/data/repository/core/support/RepositoryFactorySupportUnitTests.java +++ b/src/test/java/org/springframework/data/repository/core/support/RepositoryFactorySupportUnitTests.java @@ -51,6 +51,7 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.projection.ProjectionFactory; +import org.springframework.data.querydsl.QuerydslPredicateExecutor; import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.repository.Repository; import org.springframework.data.repository.RepositoryDefinition; @@ -61,7 +62,13 @@ import org.springframework.data.repository.core.support.RepositoryComposition.RepositoryFragments; import org.springframework.data.repository.core.support.RepositoryMethodInvocationListener.RepositoryMethodInvocation; import org.springframework.data.repository.core.support.RepositoryMethodInvocationListener.RepositoryMethodInvocationResult.State; +import org.springframework.data.repository.query.QueryByExampleExecutor; +import org.springframework.data.repository.query.QueryCreationException; +import org.springframework.data.repository.query.QueryLookupStrategy; +import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; +import org.springframework.data.repository.query.ReactiveQueryByExampleExecutor; import org.springframework.data.repository.query.RepositoryQuery; +import org.springframework.data.repository.query.parser.PartTree; import org.springframework.data.repository.sample.User; import org.springframework.lang.Nullable; import org.springframework.scheduling.annotation.Async; @@ -422,6 +429,49 @@ void callsApplicationStartupOnRepositoryInitialization() { orderedInvocation.verify(startup).start("spring.data.repository.proxy"); } + @Test // GH-2341 + void dummyRepositoryShouldsupportQuerydsl() { + factory.getRepository(WithQuerydsl.class, backingRepo); + } + + @Test // GH-2341 + void dummyRepositoryNotSupportingReactiveQuerydslShouldRaiseException() { + assertThatThrownBy(() -> factory.getRepository(WithReactiveQuerydsl.class, backingRepo)) + .isInstanceOf(UnsupportedFragmentException.class).hasMessage( + "Repository org.springframework.data.repository.core.support.RepositoryFactorySupportUnitTests$WithReactiveQuerydsl implements org.springframework.data.repository.query.ReactiveQueryByExampleExecutor but DummyRepositoryFactory does not support Reactive Query by Example!"); + } + + @Test // GH-2341 + void dummyRepositoryNotSupportingQbeShouldRaiseException() { + assertThatThrownBy(() -> factory.getRepository(WithQbe.class, backingRepo)) + .hasMessageContaining("does not support Query by Example"); + } + + @Test // GH-2341 + void dummyRepositoryNotSupportingReactiveQbeShouldRaiseException() { + assertThatThrownBy(() -> factory.getRepository(WithReactiveQbe.class, backingRepo)) + .hasMessageContaining("does not support Reactive Query by Example"); + } + + @Test // GH-2341 + void derivedQueryMethodCannotBeImplemented() { + + DummyRepositoryFactory factory = new DummyRepositoryFactory(backingRepo) { + @Override + protected Optional getQueryLookupStrategy(QueryLookupStrategy.Key key, + QueryMethodEvaluationContextProvider evaluationContextProvider) { + return Optional.of((method, metadata, factory, namedQueries) -> { + new PartTree(method.getName(), method.getReturnType()); + return null; + }); + } + }; + + assertThatThrownBy(() -> factory.getRepository(WithQueryMethodUsingInvalidProperty.class)) + .isInstanceOf(QueryCreationException.class).hasMessageContaining("findAllByName") + .hasMessageContaining("No property name found for type Object"); + } + private ConvertingRepository prepareConvertingRepository(final Object expectedValue) { when(factory.queryOne.execute(any(Object[].class))).then(invocation -> { @@ -543,4 +593,27 @@ static class CustomRepositoryBaseClass { CustomRepositoryBaseClass(EntityInformation information) {} } + + interface WithQuerydsl extends Repository, QuerydslPredicateExecutor { + + } + + interface WithReactiveQuerydsl extends Repository, ReactiveQueryByExampleExecutor { + + } + + interface WithQbe extends Repository, QueryByExampleExecutor { + + } + + interface WithReactiveQbe extends Repository, ReactiveQueryByExampleExecutor { + + } + + interface WithQueryMethodUsingInvalidProperty extends Repository { + + Object findAllByName(); + + } + }