1 /*
2  * Copyright 2013-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.jpa.repository.query;
17
18 import java.util.regex.Pattern;
19
20 import org.springframework.data.repository.core.EntityMetadata;
21 import org.springframework.expression.Expression;
22 import org.springframework.expression.ParserContext;
23 import org.springframework.expression.spel.standard.SpelExpressionParser;
24 import org.springframework.expression.spel.support.StandardEvaluationContext;
25 import org.springframework.util.Assert;
26
27 /**
28  * Extension of {@link StringQuery} that evaluates the given query string as a SpEL template-expression.
29  * <p>
30  * Currently the following template variables are available:
31  * <ol>
32  * <li>{@code #entityName} - the simple class name of the given entity</li>
33  * <ol>
34  *
35  * @author Thomas Darimont
36  * @author Oliver Gierke
37  * @author Tom Hombergs
38  */

39 class ExpressionBasedStringQuery extends StringQuery {
40
41     private static final String EXPRESSION_PARAMETER = "?#{";
42     private static final String QUOTED_EXPRESSION_PARAMETER = "?__HASH__{";
43
44     private static final Pattern EXPRESSION_PARAMETER_QUOTING = Pattern.compile(Pattern.quote(EXPRESSION_PARAMETER));
45     private static final Pattern EXPRESSION_PARAMETER_UNQUOTING = Pattern.compile(Pattern
46             .quote(QUOTED_EXPRESSION_PARAMETER));
47
48     private static final String ENTITY_NAME = "entityName";
49     private static final String ENTITY_NAME_VARIABLE = "#" + ENTITY_NAME;
50     private static final String ENTITY_NAME_VARIABLE_EXPRESSION = "#{" + ENTITY_NAME_VARIABLE + "}";
51
52     /**
53      * Creates a new {@link ExpressionBasedStringQuery} for the given query and {@link EntityMetadata}.
54      *
55      * @param query must not be {@literal null} or empty.
56      * @param metadata must not be {@literal null}.
57      * @param parser must not be {@literal null}.
58      */

59     public ExpressionBasedStringQuery(String query, JpaEntityMetadata<?> metadata, SpelExpressionParser parser) {
60         super(renderQueryIfExpressionOrReturnQuery(query, metadata, parser));
61     }
62
63     /**
64      * Creates an {@link ExpressionBasedStringQuery} from a given {@link DeclaredQuery}.
65      * 
66      * @param query the original query. Must not be {@literal null}.
67      * @param metadata the {@link JpaEntityMetadata} for the given entity. Must not be {@literal null}.
68      * @param parser Parser for resolving SpEL expressions. Must not be {@literal null}.
69      * @return A query supporting SpEL expressions.
70      */

71     static ExpressionBasedStringQuery from(DeclaredQuery query, JpaEntityMetadata metadata,
72             SpelExpressionParser parser) {
73         return new ExpressionBasedStringQuery(query.getQueryString(), metadata, parser);
74     }
75
76     /**
77      * @param query, the query expression potentially containing a SpEL expression. Must not be {@literal null}.}
78      * @param metadata the {@link JpaEntityMetadata} for the given entity. Must not be {@literal null}.
79      * @param parser Must not be {@literal null}.
80      * @return
81      */

82     private static String renderQueryIfExpressionOrReturnQuery(String query, JpaEntityMetadata<?> metadata,
83             SpelExpressionParser parser) {
84
85         Assert.notNull(query, "query must not be null!");
86         Assert.notNull(metadata, "metadata must not be null!");
87         Assert.notNull(parser, "parser must not be null!");
88
89         if (!containsExpression(query)) {
90             return query;
91         }
92
93         StandardEvaluationContext evalContext = new StandardEvaluationContext();
94         evalContext.setVariable(ENTITY_NAME, metadata.getEntityName());
95
96         query = potentiallyQuoteExpressionsParameter(query);
97
98         Expression expr = parser.parseExpression(query, ParserContext.TEMPLATE_EXPRESSION);
99
100         String result = expr.getValue(evalContext, String.class);
101
102         if (result == null) {
103             return query;
104         }
105
106         return potentiallyUnquoteParameterExpressions(result);
107     }
108
109     private static String potentiallyUnquoteParameterExpressions(String result) {
110         return EXPRESSION_PARAMETER_UNQUOTING.matcher(result).replaceAll(EXPRESSION_PARAMETER);
111     }
112
113     private static String potentiallyQuoteExpressionsParameter(String query) {
114         return EXPRESSION_PARAMETER_QUOTING.matcher(query).replaceAll(QUOTED_EXPRESSION_PARAMETER);
115     }
116
117     private static boolean containsExpression(String query) {
118         return query.contains(ENTITY_NAME_VARIABLE_EXPRESSION);
119     }
120 }
121