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 trueif the method was found or
85      * {@literal false} otherwise.
86      *
87      * @param method must not be {@literal null}.
88      * @return {@literal trueif 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