|
16 | 16 | package org.springframework.data.neo4j.repository.query;
|
17 | 17 |
|
18 | 18 | import java.util.LinkedHashMap;
|
| 19 | +import java.util.Locale; |
19 | 20 | import java.util.Map;
|
| 21 | +import java.util.regex.Matcher; |
| 22 | +import java.util.regex.Pattern; |
| 23 | +import java.util.stream.Collectors; |
20 | 24 |
|
21 | 25 | import org.apiguardian.api.API;
|
22 | 26 | import org.springframework.data.domain.Pageable;
|
23 | 27 | import org.springframework.data.domain.Sort;
|
24 | 28 | import org.springframework.data.neo4j.core.mapping.CypherGenerator;
|
| 29 | +import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext; |
| 30 | +import org.springframework.data.neo4j.core.mapping.Neo4jPersistentEntity; |
| 31 | +import org.springframework.data.repository.core.EntityMetadata; |
| 32 | +import org.springframework.expression.Expression; |
| 33 | +import org.springframework.expression.ParserContext; |
| 34 | +import org.springframework.expression.spel.standard.SpelExpressionParser; |
| 35 | +import org.springframework.expression.spel.support.StandardEvaluationContext; |
25 | 36 | import org.springframework.lang.Nullable;
|
| 37 | +import org.springframework.util.Assert; |
26 | 38 |
|
27 | 39 | /**
|
28 |
| - * This class provides a couple of extensions to the Spring Data Neo4j SpEL support and is registered by |
29 |
| - * the appropriate repository factories as a root bean. |
| 40 | + * This class provides a couple of extensions to the Spring Data Neo4j SpEL support. It's static functions are registered |
| 41 | + * inside an {@link org.springframework.data.spel.spi.EvaluationContextExtension} that in turn will be provided as a root bean. |
30 | 42 | *
|
31 | 43 | * @author Michael J. Simons
|
32 | 44 | * @soundtrack Red Hot Chili Peppers - Californication
|
@@ -138,4 +150,68 @@ public Target getTarget() {
|
138 | 150 | }
|
139 | 151 | }
|
140 | 152 |
|
| 153 | + private static final Pattern LABEL_AND_TYPE_QUOTATION = Pattern.compile("`"); |
| 154 | + private static final String EXPRESSION_PARAMETER = "$1#{"; |
| 155 | + private static final String QUOTED_EXPRESSION_PARAMETER = "$1__HASH__{"; |
| 156 | + |
| 157 | + private static final String ENTITY_NAME = "staticLabels"; |
| 158 | + private static final String ENTITY_NAME_VARIABLE = "#" + ENTITY_NAME; |
| 159 | + private static final String ENTITY_NAME_VARIABLE_EXPRESSION = "#{" + ENTITY_NAME_VARIABLE + "}"; |
| 160 | + |
| 161 | + private static final Pattern EXPRESSION_PARAMETER_QUOTING = Pattern.compile("([:?])#\\{(?!" + ENTITY_NAME_VARIABLE + ")"); |
| 162 | + private static final Pattern EXPRESSION_PARAMETER_UNQUOTING = Pattern.compile("([:?])__HASH__\\{"); |
| 163 | + |
| 164 | + /** |
| 165 | + * @param query the query expression potentially containing a SpEL expression. Must not be {@literal null}. |
| 166 | + * @param metadata the {@link Neo4jPersistentEntity} for the given entity. Must not be {@literal null}. |
| 167 | + * @param parser Must not be {@literal null}. |
| 168 | + * @return A query in which some SpEL expression have been replaced with the result of evaluating the expression |
| 169 | + */ |
| 170 | + public static String renderQueryIfExpressionOrReturnQuery(String query, Neo4jMappingContext mappingContext, EntityMetadata<?> metadata, |
| 171 | + SpelExpressionParser parser) { |
| 172 | + |
| 173 | + Assert.notNull(query, "query must not be null!"); |
| 174 | + Assert.notNull(metadata, "metadata must not be null!"); |
| 175 | + Assert.notNull(parser, "parser must not be null!"); |
| 176 | + |
| 177 | + if (!containsExpression(query)) { |
| 178 | + return query; |
| 179 | + } |
| 180 | + |
| 181 | + StandardEvaluationContext evalContext = new StandardEvaluationContext(); |
| 182 | + Neo4jPersistentEntity<?> requiredPersistentEntity = mappingContext |
| 183 | + .getRequiredPersistentEntity(metadata.getJavaType()); |
| 184 | + evalContext.setVariable(ENTITY_NAME, requiredPersistentEntity.getStaticLabels() |
| 185 | + .stream() |
| 186 | + .map(l -> { |
| 187 | + Matcher matcher = LABEL_AND_TYPE_QUOTATION.matcher(l); |
| 188 | + return String.format(Locale.ENGLISH, "`%s`", matcher.replaceAll("``")); |
| 189 | + }) |
| 190 | + .collect(Collectors.joining(":"))); |
| 191 | + |
| 192 | + query = potentiallyQuoteExpressionsParameter(query); |
| 193 | + |
| 194 | + Expression expr = parser.parseExpression(query, ParserContext.TEMPLATE_EXPRESSION); |
| 195 | + |
| 196 | + String result = expr.getValue(evalContext, String.class); |
| 197 | + |
| 198 | + if (result == null) { |
| 199 | + return query; |
| 200 | + } |
| 201 | + |
| 202 | + return potentiallyUnquoteParameterExpressions(result); |
| 203 | + } |
| 204 | + |
| 205 | + static String potentiallyUnquoteParameterExpressions(String result) { |
| 206 | + return EXPRESSION_PARAMETER_UNQUOTING.matcher(result).replaceAll(EXPRESSION_PARAMETER); |
| 207 | + } |
| 208 | + |
| 209 | + static String potentiallyQuoteExpressionsParameter(String query) { |
| 210 | + return EXPRESSION_PARAMETER_QUOTING.matcher(query).replaceAll(QUOTED_EXPRESSION_PARAMETER); |
| 211 | + } |
| 212 | + |
| 213 | + |
| 214 | + private static boolean containsExpression(String query) { |
| 215 | + return query.contains(ENTITY_NAME_VARIABLE_EXPRESSION); |
| 216 | + } |
141 | 217 | }
|
0 commit comments