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 000000000..4322ff0d7 --- /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 000000000..5abf6fb8b --- /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 000000000..a780c0f34 --- /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 979b0f8e5..c06a5e565 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.NamedQueries; 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 @@ class QueryExecutorMethodInterceptor implements MethodInterceptor { 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 388791a70..6c3a6a98a 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 @@ public class RepositoryComposition { 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 96768c579..561df1ca8 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.Constructor; 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.MethodInterceptor; 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.QueryLookupStrategy.Key; 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 abstract class RepositoryFactorySupport implements BeanClassLoaderAware, repositoryCompositionStep.end(); - validate(information, composition); - StartupStep repositoryTargetStep = onEvent(applicationStartup, "spring.data.repository.target", repositoryInterface); Object target = getTargetRepository(information); @@ -321,6 +321,9 @@ public abstract class RepositoryFactorySupport implements BeanClassLoaderAware, 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 abstract class RepositoryFactorySupport implements BeanClassLoaderAware, 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 @@ public abstract class RepositoryFactorySupport implements BeanClassLoaderAware, */ 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 abstract class RepositoryFactorySupport implements BeanClassLoaderAware, try { return composition.invoke(invocationMulticaster, method, arguments); } catch (Exception e) { - ClassUtils.unwrapReflectionException(e); + org.springframework.data.repository.util.ClassUtils.unwrapReflectionException(e); } throw new IllegalStateException("Should not occur!"); @@ -715,4 +707,94 @@ public abstract class RepositoryFactorySupport implements BeanClassLoaderAware, + 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 000000000..e748d4123 --- /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 468c91da7..f17353687 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}. - * - * @param method */ - private QueryCreationException(String message) { + private QueryCreationException(String message, QueryMethod method) { - super(message); + super(message, method.getMetadata().getRepositoryInterface()); + this.method = method.getMethod(); + } + + /** + * Creates a new {@link QueryCreationException}. + */ + private QueryCreationException(String message, Throwable cause, Class repositoryInterface, Method method) { + + super(message, cause, repositoryInterface); + this.method = method; } /** @@ -45,7 +60,7 @@ public final class QueryCreationException extends RuntimeException { 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 final class QueryCreationException extends RuntimeException { */ 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 final class QueryCreationException extends RuntimeException { * @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 496cc133d..4ce0d2528 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 class QueryMethod { 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 cdc6e904b..08c561edd 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 @@ class RepositoryCompositionUnitTests { 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 3eeb54316..f4e547805 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.PageRequest; 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.RepositoryMetadata; 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 @@ class RepositoryFactorySupportUnitTests { 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 @@ class RepositoryFactorySupportUnitTests { 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(); + + } + }