/* * Copyright 2014-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.data.keyvalue.repository.query; import java.util.Iterator; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.domain.Sort; import org.springframework.data.keyvalue.core.query.KeyValueQuery; import org.springframework.data.repository.query.ParameterAccessor; import org.springframework.data.repository.query.parser.AbstractQueryCreator; import org.springframework.data.repository.query.parser.Part; import org.springframework.data.repository.query.parser.Part.IgnoreCaseType; import org.springframework.data.repository.query.parser.Part.Type; import org.springframework.data.repository.query.parser.PartTree; import org.springframework.data.repository.query.parser.PartTree.OrPart; import org.springframework.expression.spel.standard.SpelExpression; import org.springframework.expression.spel.standard.SpelExpressionParser; /** * {@link AbstractQueryCreator} to create {@link SpelExpression} based {@link KeyValueQuery}s. * * @author Christoph Strobl * @author Oliver Gierke */ public class SpelQueryCreator extends AbstractQueryCreator<KeyValueQuery<SpelExpression>, String> { private static final SpelExpressionParser PARSER = new SpelExpressionParser(); private final SpelExpression expression; /** * Creates a new {@link SpelQueryCreator} for the given {@link PartTree} and {@link ParameterAccessor}. * * @param tree must not be {@literal null}. * @param parameters must not be {@literal null}. */ public SpelQueryCreator(PartTree tree, ParameterAccessor parameters) { super(tree, parameters); this.expression = toPredicateExpression(tree); } /* * (non-Javadoc) * @see org.springframework.data.repository.query.parser.AbstractQueryCreator#create(org.springframework.data.repository.query.parser.Part, java.util.Iterator) */ @Override protected String create(Part part, Iterator<Object> iterator) { return ""; } /* * (non-Javadoc) * @see org.springframework.data.repository.query.parser.AbstractQueryCreator#and(org.springframework.data.repository.query.parser.Part, java.lang.Object, java.util.Iterator) */ @Override protected String and(Part part, String base, Iterator<Object> iterator) { return ""; } /* * (non-Javadoc) * @see org.springframework.data.repository.query.parser.AbstractQueryCreator#or(java.lang.Object, java.lang.Object) */ @Override protected String or(String base, String criteria) { return ""; } @Override protected KeyValueQuery<SpelExpression> complete(String criteria, Sort sort) { KeyValueQuery<SpelExpression> query = new KeyValueQuery<>(this.expression); if (sort != null) { query.orderBy(sort); } return query; } protected SpelExpression toPredicateExpression(PartTree tree) { int parameterIndex = 0; StringBuilder sb = new StringBuilder(); for (Iterator<OrPart> orPartIter = tree.iterator(); orPartIter.hasNext();) { int partCnt = 0; StringBuilder partBuilder = new StringBuilder(); OrPart orPart = orPartIter.next(); for (Iterator<Part> partIter = orPart.iterator(); partIter.hasNext();) { Part part = partIter.next(); if(!requiresInverseLookup(part)) { partBuilder.append("#it?."); partBuilder.append(part.getProperty().toDotPath().replace(".", "?.")); } // TODO: check if we can have caseinsensitive search if (!part.shouldIgnoreCase().equals(IgnoreCaseType.NEVER)) { throw new InvalidDataAccessApiUsageException("Ignore case not supported!"); } switch (part.getType()) { case TRUE: partBuilder.append("?.equals(true)"); break; case FALSE: partBuilder.append("?.equals(false)"); break; case SIMPLE_PROPERTY: partBuilder.append("?.equals(").append("[").append(parameterIndex++).append("])"); break; case IS_NULL: partBuilder.append(" == null"); break; case IS_NOT_NULL: partBuilder.append(" != null"); break; case LIKE: partBuilder.append("?.contains(").append("[").append(parameterIndex++).append("])"); break; case STARTING_WITH: partBuilder.append("?.startsWith(").append("[").append(parameterIndex++).append("])"); break; case AFTER: case GREATER_THAN: partBuilder.append(">").append("[").append(parameterIndex++).append("]"); break; case GREATER_THAN_EQUAL: partBuilder.append(">=").append("[").append(parameterIndex++).append("]"); break; case BEFORE: case LESS_THAN: partBuilder.append("<").append("[").append(parameterIndex++).append("]"); break; case LESS_THAN_EQUAL: partBuilder.append("<=").append("[").append(parameterIndex++).append("]"); break; case ENDING_WITH: partBuilder.append("?.endsWith(").append("[").append(parameterIndex++).append("])"); break; case BETWEEN: int index = partBuilder.lastIndexOf("#it?."); partBuilder.insert(index, "("); partBuilder.append(">").append("[").append(parameterIndex++).append("]"); partBuilder.append("&&"); partBuilder.append("#it?."); partBuilder.append(part.getProperty().toDotPath().replace(".", "?.")); partBuilder.append("<").append("[").append(parameterIndex++).append("]"); partBuilder.append(")"); break; case REGEX: partBuilder.append(" matches ").append("[").append(parameterIndex++).append("]"); break; case IN: partBuilder.append("[").append(parameterIndex++).append("].contains("); partBuilder.append("#it?."); partBuilder.append(part.getProperty().toDotPath().replace(".", "?.")); partBuilder.append(")"); break; case CONTAINING: case NOT_CONTAINING: case NEGATING_SIMPLE_PROPERTY: case EXISTS: default: throw new InvalidDataAccessApiUsageException(String.format("Found invalid part '%s' in query", part.getType())); } if (partIter.hasNext()) { partBuilder.append("&&"); } partCnt++; } if (partCnt > 1) { sb.append("(").append(partBuilder).append(")"); } else { sb.append(partBuilder); } if (orPartIter.hasNext()) { sb.append("||"); } } return PARSER.parseRaw(sb.toString()); } private static boolean requiresInverseLookup(Part part) { return part.getType() == Type.IN; } }