|
19 | 19 | import java.lang.reflect.Method;
|
20 | 20 | import java.util.ArrayList;
|
21 | 21 | import java.util.Arrays;
|
| 22 | +import java.util.HashMap; |
22 | 23 | import java.util.List;
|
23 | 24 | import java.util.Map;
|
24 | 25 | import java.util.Optional;
|
|
28 | 29 | import org.aopalliance.intercept.MethodInvocation;
|
29 | 30 | import org.apache.commons.logging.Log;
|
30 | 31 | import org.apache.commons.logging.LogFactory;
|
| 32 | + |
31 | 33 | import org.springframework.aop.framework.ProxyFactory;
|
32 | 34 | import org.springframework.aop.interceptor.ExposeInvocationInterceptor;
|
33 | 35 | import org.springframework.beans.BeanUtils;
|
|
57 | 59 | import org.springframework.data.repository.query.QueryMethod;
|
58 | 60 | import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
|
59 | 61 | import org.springframework.data.repository.query.RepositoryQuery;
|
60 |
| -import org.springframework.data.repository.util.ClassUtils; |
61 | 62 | import org.springframework.data.repository.util.QueryExecutionConverters;
|
62 | 63 | import org.springframework.data.util.ReflectionUtils;
|
63 | 64 | import org.springframework.lang.Nullable;
|
64 | 65 | import org.springframework.transaction.interceptor.TransactionalProxy;
|
65 | 66 | import org.springframework.util.Assert;
|
| 67 | +import org.springframework.util.ClassUtils; |
66 | 68 | import org.springframework.util.ConcurrentReferenceHashMap;
|
67 | 69 | import org.springframework.util.ConcurrentReferenceHashMap.ReferenceType;
|
68 | 70 | import org.springframework.util.ObjectUtils;
|
@@ -312,15 +314,16 @@ public <T> T getRepository(Class<T> repositoryInterface, RepositoryFragments fra
|
312 | 314 |
|
313 | 315 | repositoryCompositionStep.end();
|
314 | 316 |
|
315 |
| - validate(information, composition); |
316 |
| - |
317 | 317 | StartupStep repositoryTargetStep = onEvent(applicationStartup, "spring.data.repository.target",
|
318 | 318 | repositoryInterface);
|
319 | 319 | Object target = getTargetRepository(information);
|
320 | 320 |
|
321 | 321 | repositoryTargetStep.tag("target", target.getClass().getName());
|
322 | 322 | repositoryTargetStep.end();
|
323 | 323 |
|
| 324 | + RepositoryComposition compositionToUse = composition.append(RepositoryFragment.implemented(target)); |
| 325 | + validate(information, compositionToUse); |
| 326 | + |
324 | 327 | // Create proxy
|
325 | 328 | StartupStep repositoryProxyStep = onEvent(applicationStartup, "spring.data.repository.proxy", repositoryInterface);
|
326 | 329 | ProxyFactory result = new ProxyFactory();
|
@@ -357,7 +360,6 @@ public <T> T getRepository(Class<T> repositoryInterface, RepositoryFragments fra
|
357 | 360 | result.addAdvice(new QueryExecutorMethodInterceptor(information, projectionFactory, queryLookupStrategy,
|
358 | 361 | namedQueries, queryPostProcessors, methodInvocationListeners));
|
359 | 362 |
|
360 |
| - RepositoryComposition compositionToUse = composition.append(RepositoryFragment.implemented(target)); |
361 | 363 | result.addAdvice(
|
362 | 364 | new ImplementationMethodExecutionInterceptor(information, compositionToUse, methodInvocationListeners));
|
363 | 365 |
|
@@ -502,17 +504,7 @@ protected Optional<QueryLookupStrategy> getQueryLookupStrategy(@Nullable Key key
|
502 | 504 | */
|
503 | 505 | private void validate(RepositoryInformation repositoryInformation, RepositoryComposition composition) {
|
504 | 506 |
|
505 |
| - if (repositoryInformation.hasCustomMethod()) { |
506 |
| - |
507 |
| - if (composition.isEmpty()) { |
508 |
| - |
509 |
| - throw new IllegalArgumentException( |
510 |
| - String.format("You have custom methods in %s but have not provided a custom implementation!", |
511 |
| - repositoryInformation.getRepositoryInterface())); |
512 |
| - } |
513 |
| - |
514 |
| - composition.validateImplementation(); |
515 |
| - } |
| 507 | + RepositoryValidator.validate(composition, getClass(), repositoryInformation); |
516 | 508 |
|
517 | 509 | validate(repositoryInformation);
|
518 | 510 | }
|
@@ -606,7 +598,7 @@ public Object invoke(@SuppressWarnings("null") MethodInvocation invocation) thro
|
606 | 598 | try {
|
607 | 599 | return composition.invoke(invocationMulticaster, method, arguments);
|
608 | 600 | } catch (Exception e) {
|
609 |
| - ClassUtils.unwrapReflectionException(e); |
| 601 | + org.springframework.util.ReflectionUtils.handleReflectionException(e); |
610 | 602 | }
|
611 | 603 |
|
612 | 604 | throw new IllegalStateException("Should not occur!");
|
@@ -715,4 +707,94 @@ public String toString() {
|
715 | 707 | + this.getRepositoryInterfaceName() + ", compositionHash=" + this.getCompositionHash() + ")";
|
716 | 708 | }
|
717 | 709 | }
|
| 710 | + |
| 711 | + /** |
| 712 | + * Validator utility to catch common mismatches with a proper error message instead of letting the query mechanism |
| 713 | + * attempt implementing a query method and fail with a less specific message. |
| 714 | + */ |
| 715 | + static class RepositoryValidator { |
| 716 | + |
| 717 | + static Map<Class<?>, String> WELL_KNOWN_EXECUTORS = new HashMap<>(); |
| 718 | + |
| 719 | + static { |
| 720 | + |
| 721 | + org.springframework.data.repository.util.ClassUtils.ifPresent( |
| 722 | + "org.springframework.data.querydsl.QuerydslPredicateExecutor", RepositoryValidator.class.getClassLoader(), |
| 723 | + it -> { |
| 724 | + WELL_KNOWN_EXECUTORS.put(it, "Querydsl"); |
| 725 | + }); |
| 726 | + |
| 727 | + org.springframework.data.repository.util.ClassUtils.ifPresent( |
| 728 | + "org.springframework.data.querydsl.ReactiveQuerydslPredicateExecutor", |
| 729 | + RepositoryValidator.class.getClassLoader(), it -> { |
| 730 | + WELL_KNOWN_EXECUTORS.put(it, "Reactive Querydsl"); |
| 731 | + }); |
| 732 | + |
| 733 | + org.springframework.data.repository.util.ClassUtils.ifPresent( |
| 734 | + "org.springframework.data.repository.query.QueryByExampleExecutor", |
| 735 | + RepositoryValidator.class.getClassLoader(), it -> { |
| 736 | + WELL_KNOWN_EXECUTORS.put(it, "Query by Example"); |
| 737 | + }); |
| 738 | + |
| 739 | + org.springframework.data.repository.util.ClassUtils.ifPresent( |
| 740 | + "org.springframework.data.repository.query.ReactiveQueryByExampleExecutor", |
| 741 | + RepositoryValidator.class.getClassLoader(), it -> { |
| 742 | + WELL_KNOWN_EXECUTORS.put(it, "Reactive Query by Example"); |
| 743 | + }); |
| 744 | + } |
| 745 | + |
| 746 | + /** |
| 747 | + * Validate the {@link RepositoryComposition} for custom implementations and well-known executors. |
| 748 | + * |
| 749 | + * @param composition |
| 750 | + * @param source |
| 751 | + * @param repositoryInformation |
| 752 | + */ |
| 753 | + public static void validate(RepositoryComposition composition, Class<?> source, |
| 754 | + RepositoryInformation repositoryInformation) { |
| 755 | + |
| 756 | + Class<?> repositoryInterface = repositoryInformation.getRepositoryInterface(); |
| 757 | + if (repositoryInformation.hasCustomMethod()) { |
| 758 | + |
| 759 | + if (composition.isEmpty()) { |
| 760 | + |
| 761 | + throw new MissingFragmentException( |
| 762 | + String.format("You have custom methods in %s but have not provided a custom implementation!", |
| 763 | + org.springframework.util.ClassUtils.getQualifiedName(repositoryInterface)), |
| 764 | + repositoryInterface); |
| 765 | + } |
| 766 | + |
| 767 | + composition.validateImplementation(); |
| 768 | + } |
| 769 | + |
| 770 | + for (Map.Entry<Class<?>, String> entry : WELL_KNOWN_EXECUTORS.entrySet()) { |
| 771 | + |
| 772 | + Class<?> executorInterface = entry.getKey(); |
| 773 | + if (!executorInterface.isAssignableFrom(repositoryInterface)) { |
| 774 | + continue; |
| 775 | + } |
| 776 | + |
| 777 | + if (!containsFragmentImplementation(composition, executorInterface)) { |
| 778 | + throw new UnsupportedFragmentException( |
| 779 | + String.format("Repository %s implements %s but %s does not support %s!", |
| 780 | + ClassUtils.getQualifiedName(repositoryInterface), ClassUtils.getQualifiedName(executorInterface), |
| 781 | + ClassUtils.getShortName(source), entry.getValue()), |
| 782 | + repositoryInterface, executorInterface); |
| 783 | + } |
| 784 | + } |
| 785 | + } |
| 786 | + |
| 787 | + private static boolean containsFragmentImplementation(RepositoryComposition composition, |
| 788 | + Class<?> executorInterface) { |
| 789 | + |
| 790 | + for (RepositoryFragment<?> fragment : composition.getFragments()) { |
| 791 | + |
| 792 | + if (fragment.getImplementation().filter(executorInterface::isInstance).isPresent()) { |
| 793 | + return true; |
| 794 | + } |
| 795 | + } |
| 796 | + |
| 797 | + return false; |
| 798 | + } |
| 799 | + } |
718 | 800 | }
|
0 commit comments