1 /*
2 * Copyright 2017-2020 the original author or authors.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * https://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package org.springframework.data.repository.core.support;
17
18 import lombok.EqualsAndHashCode;
19 import lombok.NonNull;
20 import lombok.RequiredArgsConstructor;
21
22 import java.lang.reflect.Method;
23 import java.util.Arrays;
24 import java.util.Optional;
25 import java.util.stream.Stream;
26
27 import org.springframework.util.Assert;
28 import org.springframework.util.ClassUtils;
29 import org.springframework.util.ReflectionUtils;
30
31 /**
32 * Value object representing a repository fragment.
33 * <p />
34 * Repository fragments are individual parts that contribute method signatures. They are used to form a
35 * {@link RepositoryComposition}. Fragments can be purely structural or backed with an implementation.
36 * <p/>
37 * {@link #structural(Class) Structural} fragments are not backed by an implementation and are primarily used to
38 * discover the structure of a repository composition and to perform validations.
39 * <p/>
40 * {@link #implemented(Object) Implemented} repository fragments consist of a signature contributor and the implementing
41 * object. A signature contributor may be {@link #implemented(Class, Object) an interface} or
42 * {@link #implemented(Object) just the implementing object} providing the available signatures for a repository.
43 * <p/>
44 * Fragments are immutable.
45 *
46 * @author Mark Paluch
47 * @since 2.0
48 * @see RepositoryComposition
49 */
50 public interface RepositoryFragment<T> {
51
52 /**
53 * Create an implemented {@link RepositoryFragment} backed by the {@code implementation} object.
54 *
55 * @param implementation must not be {@literal null}.
56 * @return
57 */
58 public static <T> RepositoryFragment<T> implemented(T implementation) {
59 return new ImplementedRepositoryFragment<T>(Optional.empty(), implementation);
60 }
61
62 /**
63 * Create an implemented {@link RepositoryFragment} from a {@code interfaceClass} backed by the {@code implementation}
64 * object.
65 *
66 * @param implementation must not be {@literal null}.
67 * @return
68 */
69 public static <T> RepositoryFragment<T> implemented(Class<T> interfaceClass, T implementation) {
70 return new ImplementedRepositoryFragment<>(Optional.of(interfaceClass), implementation);
71 }
72
73 /**
74 * Create a structural {@link RepositoryFragment} given {@code interfaceOrImplementation}.
75 *
76 * @param interfaceOrImplementation must not be {@literal null}.
77 * @return
78 */
79 public static <T> RepositoryFragment<T> structural(Class<T> interfaceOrImplementation) {
80 return new StructuralRepositoryFragment<>(interfaceOrImplementation);
81 }
82
83 /**
84 * Attempt to find the {@link Method} by name and exact parameters. Returns {@literal true} if the method was found or
85 * {@literal false} otherwise.
86 *
87 * @param method must not be {@literal null}.
88 * @return {@literal true} if the method was found or {@literal false} otherwise
89 */
90 default boolean hasMethod(Method method) {
91
92 Assert.notNull(method, "Method must not be null!");
93 return ReflectionUtils.findMethod(getSignatureContributor(), method.getName(), method.getParameterTypes()) != null;
94 }
95
96 /**
97 * @return the optional implementation. Only available for implemented fragments. Structural fragments return always
98 * {@link Optional#empty()}.
99 */
100 default Optional<T> getImplementation() {
101 return Optional.empty();
102 }
103
104 /**
105 * @return a {@link Stream} of methods exposed by this {@link RepositoryFragment}.
106 */
107 default Stream<Method> methods() {
108 return Arrays.stream(getSignatureContributor().getMethods());
109 }
110
111 /**
112 * @return the class/interface providing signatures for this {@link RepositoryFragment}.
113 */
114 Class<?> getSignatureContributor();
115
116 /**
117 * Implement a structural {@link RepositoryFragment} given its {@code implementation} object. Returns an implemented
118 * {@link RepositoryFragment}.
119 *
120 * @param implementation must not be {@literal null}.
121 * @return a new implemented {@link RepositoryFragment} for {@code implementation}.
122 */
123 RepositoryFragment<T> withImplementation(T implementation);
124
125 @RequiredArgsConstructor
126 @EqualsAndHashCode(callSuper = false)
127 static class StructuralRepositoryFragment<T> implements RepositoryFragment<T> {
128
129 private final @NonNull Class<T> interfaceOrImplementation;
130
131 /*
132 * (non-Javadoc)
133 * @see org.springframework.data.repository.core.support.RepositoryFragment#getSignatureContributor()
134 */
135 @Override
136 public Class<?> getSignatureContributor() {
137 return interfaceOrImplementation;
138 }
139
140 /*
141 * (non-Javadoc)
142 * @see org.springframework.data.repository.core.support.RepositoryFragment#withImplementation(java.lang.Object)
143 */
144 @Override
145 public RepositoryFragment<T> withImplementation(T implementation) {
146 return new ImplementedRepositoryFragment<>(Optional.of(interfaceOrImplementation), implementation);
147 }
148
149 /* (non-Javadoc)
150 * @see java.lang.Object#toString()
151 */
152 @Override
153 public String toString() {
154 return String.format("StructuralRepositoryFragment %s", ClassUtils.getShortName(interfaceOrImplementation));
155 }
156 }
157
158 @EqualsAndHashCode(callSuper = false)
159 static class ImplementedRepositoryFragment<T> implements RepositoryFragment<T> {
160
161 private final Optional<Class<T>> interfaceClass;
162 private final T implementation;
163 private final Optional<T> optionalImplementation;
164
165 /**
166 * Creates a new {@link ImplementedRepositoryFragment} for the given interface class and implementation.
167 *
168 * @param interfaceClass must not be {@literal null}.
169 * @param implementation must not be {@literal null}.
170 */
171 public ImplementedRepositoryFragment(Optional<Class<T>> interfaceClass, T implementation) {
172
173 Assert.notNull(interfaceClass, "Interface class must not be null!");
174 Assert.notNull(implementation, "Implementation object must not be null!");
175
176 interfaceClass.ifPresent(it -> {
177
178 Assert.isTrue(ClassUtils.isAssignableValue(it, implementation),
179 () -> String.format("Fragment implementation %s does not implement %s!",
180 ClassUtils.getQualifiedName(implementation.getClass()), ClassUtils.getQualifiedName(it)));
181 });
182
183 this.interfaceClass = interfaceClass;
184 this.implementation = implementation;
185 this.optionalImplementation = Optional.of(implementation);
186 }
187
188 /*
189 * (non-Javadoc)
190 * @see org.springframework.data.repository.core.support.RepositoryFragment#getSignatureContributor()
191 */
192 @SuppressWarnings({ "rawtypes", "unchecked" })
193 public Class<?> getSignatureContributor() {
194 return interfaceClass.orElse((Class) implementation.getClass());
195 }
196
197 /*
198 * (non-Javadoc)
199 * @see org.springframework.data.repository.core.support.RepositoryFragment#getImplementation()
200 */
201 @Override
202 public Optional<T> getImplementation() {
203 return optionalImplementation;
204 }
205
206 /*
207 * (non-Javadoc)
208 * @see org.springframework.data.repository.core.support.RepositoryFragment#withImplementation(java.lang.Object)
209 */
210 @Override
211 public RepositoryFragment<T> withImplementation(T implementation) {
212 return new ImplementedRepositoryFragment<>(interfaceClass, implementation);
213 }
214
215 /*
216 * (non-Javadoc)
217 * @see java.lang.Object#toString()
218 */
219 @Override
220 public String toString() {
221
222 return String.format("ImplementedRepositoryFragment %s%s",
223 interfaceClass.map(ClassUtils::getShortName).map(it -> it + ":").orElse(""),
224 ClassUtils.getShortName(implementation.getClass()));
225 }
226 }
227 }
228