1 /*
2  * Copyright 2008-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.query.parser;
17
18 import java.util.Collections;
19 import java.util.Iterator;
20 import java.util.Optional;
21
22 import org.springframework.data.domain.Sort;
23 import org.springframework.data.repository.query.ParameterAccessor;
24 import org.springframework.data.repository.query.ParametersParameterAccessor;
25 import org.springframework.data.repository.query.parser.PartTree.OrPart;
26 import org.springframework.lang.Nullable;
27 import org.springframework.util.Assert;
28
29 /**
30  * Base class for query creators that create criteria based queries from a {@link PartTree}.
31  *
32  * @param <T> the actual query type to be created
33  * @param <S> the intermediate criteria type
34  * @author Oliver Gierke
35  * @author Mark Paluch
36  * @author Christoph Strobl
37  */

38 public abstract class AbstractQueryCreator<T, S> {
39
40     private final Optional<ParameterAccessor> parameters;
41     private final PartTree tree;
42
43     /**
44      * Creates a new {@link AbstractQueryCreator} for the given {@link PartTree}. This will cause {@literal null} be
45      * handed for the {@link Iterator} in the callback methods.
46      *
47      * @param tree must not be {@literal null}.
48      * @since 2.0
49      */

50     public AbstractQueryCreator(PartTree tree) {
51         this(tree, Optional.empty());
52     }
53
54     /**
55      * Creates a new {@link AbstractQueryCreator} for the given {@link PartTree} and {@link ParametersParameterAccessor}.
56      * The latter is used to hand actual parameter values into the callback methods as well as to apply dynamic sorting
57      * via a {@link Sort} parameter.
58      *
59      * @param tree must not be {@literal null}.
60      * @param parameters must not be {@literal null}.
61      */

62     public AbstractQueryCreator(PartTree tree, ParameterAccessor parameters) {
63         this(tree, Optional.of(parameters));
64     }
65
66     private AbstractQueryCreator(PartTree tree, Optional<ParameterAccessor> parameters) {
67
68         Assert.notNull(tree, "PartTree must not be null");
69         Assert.notNull(parameters, "ParameterAccessor must not be null");
70
71         this.tree = tree;
72         this.parameters = parameters;
73     }
74
75     /**
76      * Creates the actual query object.
77      *
78      * @return
79      */

80     public T createQuery() {
81         return createQuery(parameters.map(ParameterAccessor::getSort) //
82                 .orElse(Sort.unsorted()));
83     }
84
85     /**
86      * Creates the actual query object applying the given {@link Sort} parameter. Use this method in case you haven't
87      * provided a {@link ParameterAccessor} in the first place but want to apply dynamic sorting nevertheless.
88      *
89      * @param dynamicSort must not be {@literal null}.
90      * @return
91      */

92     public T createQuery(Sort dynamicSort) {
93
94         Assert.notNull(dynamicSort, "DynamicSort must not be null!");
95         return complete(createCriteria(tree), tree.getSort().and(dynamicSort));
96     }
97
98     /**
99      * Actual query building logic. Traverses the {@link PartTree} and invokes callback methods to delegate actual
100      * criteria creation and concatenation.
101      *
102      * @param tree must not be {@literal null}.
103      * @return
104      */

105     @Nullable
106     private S createCriteria(PartTree tree) {
107
108         S base = null;
109         Iterator<Object> iterator = parameters.map(ParameterAccessor::iterator).orElse(Collections.emptyIterator());
110
111         for (OrPart node : tree) {
112
113             Iterator<Part> parts = node.iterator();
114
115             if (!parts.hasNext()) {
116                 throw new IllegalStateException(String.format("No part found in PartTree %s!", tree));
117             }
118
119             S criteria = create(parts.next(), iterator);
120
121             while (parts.hasNext()) {
122                 criteria = and(parts.next(), criteria, iterator);
123             }
124
125             base = base == null ? criteria : or(base, criteria);
126         }
127
128         return base;
129     }
130
131     /**
132      * Creates a new atomic instance of the criteria object.
133      *
134      * @param part must not be {@literal null}.
135      * @param iterator must not be {@literal null}.
136      * @return
137      */

138     protected abstract S create(Part part, Iterator<Object> iterator);
139
140     /**
141      * Creates a new criteria object from the given part and and-concatenates it to the given base criteria.
142      *
143      * @param part must not be {@literal null}.
144      * @param base will never be {@literal null}.
145      * @param iterator must not be {@literal null}.
146      * @return
147      */

148     protected abstract S and(Part part, S base, Iterator<Object> iterator);
149
150     /**
151      * Or-concatenates the given base criteria to the given new criteria.
152      *
153      * @param base must not be {@literal null}.
154      * @param criteria must not be {@literal null}.
155      * @return
156      */

157     protected abstract S or(S base, S criteria);
158
159     /**
160      * Actually creates the query object applying the given criteria object and {@link Sort} definition.
161      *
162      * @param criteria can be {@literal null}.
163      * @param sort must not be {@literal null}.
164      * @return
165      */

166     protected abstract T complete(@Nullable S criteria, Sort sort);
167 }
168