├── .gitignore ├── .travis.yml ├── LICENSE ├── manage.sh ├── pom.xml ├── readme.md └── src ├── main └── java │ └── com │ └── github │ └── rutledgepaulv │ └── rqe │ ├── adapters │ └── TreeToConditionAdapter.java │ ├── argconverters │ ├── ArgConverter.java │ ├── ConverterChain.java │ ├── EntityFieldTypeConverter.java │ └── OperatorSpecificConverter.java │ ├── contexts │ ├── ArgConversionContext.java │ └── ParseTreeContext.java │ ├── conversions │ ├── SpringConversionServiceConverter.java │ ├── StringToTypeConverter.java │ └── parsers │ │ ├── StringToInstantConverter.java │ │ └── StringToObjectBestEffortConverter.java │ ├── exceptions │ ├── FailedArgumentConversionException.java │ └── UnsupportedQueryOperatorException.java │ ├── operators │ └── QueryOperator.java │ ├── pipes │ ├── DefaultArgumentConversionPipe.java │ ├── DefaultParsingPipe.java │ ├── DefaultQueryBuildingPipe.java │ ├── IdentityPipe.java │ └── QueryConversionPipeline.java │ ├── resolvers │ └── EntityFieldTypeResolver.java │ └── utils │ ├── StreamUtil.java │ └── TriFunction.java └── test └── java └── com └── github └── rutledgepaulv └── rqe ├── conversions └── parsers │ └── StringToObjectBestEffortConverterTest.java ├── pipes ├── DemoCustomConversionPipeline.java ├── FieldCombinationsTest.java ├── MultiValueFieldsWithNestedObjectsTest.java ├── NestedObjectTest.java ├── StandardFieldTest.java └── TestBase.java └── testsupport ├── Address.java ├── Comment.java ├── CommentQuery.java ├── CriteriaSerializer.java ├── State.java ├── User.java └── UserQuery.java /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | target/ 3 | *.class 4 | *.iml 5 | .DS_Store 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - oraclejdk8 4 | after_success: 5 | - mvn clean test jacoco:report coveralls:report 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2016 Paul Rutledge 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /manage.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function unit_tests() { 4 | mvn clean test 5 | } 6 | 7 | function integration_test() { 8 | mvn clean install failsafe:integration-test 9 | } 10 | 11 | function snapshot() { 12 | read -p "This will reset your current working tree to origin/develop, is this okay? " -n 1 -r 13 | echo 14 | if [[ $REPLY =~ ^[Yy]$ ]] 15 | then 16 | git fetch 17 | git reset --hard origin/develop 18 | 19 | echo "Deploying new release artifacts to sonatype repository." 20 | mvn clean deploy -P release 21 | fi 22 | } 23 | 24 | 25 | function release() { 26 | read -p "This will reset your current working tree to origin/develop, is this okay? " -n 1 -r 27 | echo 28 | if [[ $REPLY =~ ^[Yy]$ ]] 29 | then 30 | git fetch 31 | git reset --hard origin/develop 32 | 33 | echo "Creating the release branch" 34 | mvn -Prelease jgitflow:release-start -DpushReleases=true -DautoVersionSubmodules=true 35 | 36 | echo "Merging the release branch into develop & master, pushing changes, and tagging new version off of master" 37 | mvn -Prelease jgitflow:release-finish -DnoReleaseBuild=true -DpushReleases=true -DnoDeploy=true 38 | 39 | echo "Checking out latest version of master." 40 | git fetch 41 | git checkout origin/master 42 | 43 | echo "Deploying new release artifacts to sonatype repository." 44 | mvn clean deploy -P release 45 | fi 46 | } 47 | 48 | function upgrade_dependencies() { 49 | echo "Checking for a newer version of a parent pom.xml file" 50 | mvn versions:update-parent 51 | 52 | echo "Updating all pom.xml files to the latest available from their respective repositories. No changes will be committed." 53 | mvn versions:use-latest-releases 54 | 55 | echo "Checking for properties used to manage dependency versions and updating them as well. No changes will be committed." 56 | mvn versions:update-properties 57 | } 58 | 59 | case "$1" in 60 | integration-test) 61 | echo -n "Starting integration tests..." 62 | integration_test 63 | echo "" 64 | ;; 65 | snapshot) 66 | echo -n "Preparing a new snapshot version of the app..." 67 | snapshot 68 | echo "" 69 | ;; 70 | release) 71 | echo -n "Preparing to release a new version of the app..." 72 | release 73 | echo "" 74 | ;; 75 | unit-test) 76 | echo "Starting unit tests..." 77 | unit_tests 78 | echo "" 79 | ;; 80 | upgrade) 81 | echo -n "Checking for new versions of dependencies..." 82 | upgrade_dependencies 83 | echo "" 84 | ;; 85 | *) 86 | echo "Usage: ./manage.sh integration-test|release|snapshot|unit-test|upgrade" 87 | exit 1 88 | esac 89 | 90 | exit 0 -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 4.0.0 14 | 15 | 16 | com.github.rutledgepaulv 17 | maven 18 | 1.1 19 | 20 | 21 | rest-query-engine 22 | 0.7.2-SNAPSHOT 23 | 24 | rest-query-engine 25 | http://github.com/rutledgepaulv/rest-query-engine 26 | A library for parsing rsql queries into database queries for a REST API. 27 | 28 | 29 | http://github.com/rutledgepaulv/rest-query-engine 30 | scm:git:git@github.com:rutledgepaulv/rest-query-engine.git 31 | 32 | 33 | 34 | travis 35 | https://travis-ci.org/rutledgepaulv/rest-query-engine 36 | 37 | 38 | 39 | github 40 | https://github.com/rutledgepaulv/rest-query-engine/issues 41 | 42 | 43 | 44 | 45 | 46 | cz.jirutka.rsql 47 | rsql-parser 48 | 2.1.0 49 | 50 | 51 | 52 | com.github.rutledgepaulv 53 | q-builders 54 | 1.5 55 | 56 | 57 | 58 | org.springframework 59 | spring-core 60 | 4.3.1.RELEASE 61 | 62 | 63 | 67 | 68 | org.springframework.data 69 | spring-data-mongodb 70 | 1.9.2.RELEASE 71 | test 72 | 73 | 74 | 75 | org.elasticsearch 76 | elasticsearch 77 | 2.3.4 78 | test 79 | 80 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/RutledgePaulV/rest-query-engine.svg)](https://travis-ci.org/RutledgePaulV/rest-query-engine) 2 | [![Coverage Status](https://coveralls.io/repos/github/RutledgePaulV/rest-query-engine/badge.svg?branch=develop)](https://coveralls.io/github/RutledgePaulV/rest-query-engine?branch=develop) 3 | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.github.rutledgepaulv/rest-query-engine/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.github.rutledgepaulv/rest-query-engine) 4 | 5 | 6 | ### Rest Query Engine 7 | A library for adding arbitrarily flexible querying to any java API. Almost all APIs deal with some set of predefined models 8 | which they expose via various endpoints. You shouldn't have to create your own mechanisms for querying against collections 9 | of these models and then figuring out how to make that query work against wherever you're storing it. 10 | 11 | This common problem is where RQE comes in. Using RQE you send your queries across HTTP as a simple query parameter 12 | using the so-simple-you-feel-stupid query language [rsql](https://github.com/jirutka/rsql-parser). This library handles 13 | everything necessary to parse that incoming query, convert the various arguments in the query into the type that will 14 | actually appear in the database, and build an intermediate query tree that can be visited to produce a query for any 15 | number of backends. 16 | 17 | 18 | ### Modular 19 | Pretty much all of the components involved to parse, traverse, and convert all use pluggable implementations. As this 20 | library matures I'll document all the extension points and why you might use them. 21 | 22 | 23 | ### Backends 24 | This library builds queries into the intermediate form understood by [q-builders](https://github.com/rutledgepaulv/q-builders). 25 | This means that any backend which is supported via q-builders is also supported by this library. Currently those include: 26 | 27 | * Java Predicate 28 | * RSQL Query String 29 | * Elasticsearch QueryBuilder 30 | * Spring Data Mongodb Criteria 31 | 32 | 33 | ### Client Side 34 | Do you provide a Java-based SDK for your API? Or do you send real rest requests in your test suite? Then you'll probably 35 | be interested in using [q-builders](https://github.com/rutledgepaulv/q-builders) on the client side too. Since RSQL is a 36 | supported target of the intermediate representation you can use it to construct your queries for the API in a type and typo 37 | safe way. 38 | 39 | 40 | ### Usage 41 | ```java 42 | private QueryConversionPipeline pipeline = QueryConversionPipeline.defaultPipeline(); 43 | 44 | 45 | @Test 46 | public void mongo() { 47 | 48 | Condition condition = pipeline.apply("firstName==Paul;age==30", User.class); 49 | Criteria query = condition.query(new MongoVisitor()); 50 | 51 | } 52 | 53 | 54 | @Test 55 | public void elasticsearch() { 56 | 57 | Condition condition = pipeline.apply("firstName==Paul;age==30", User.class); 58 | QueryBuilder query = condition.query(new ElasticsearchVisitor()); 59 | 60 | } 61 | 62 | 63 | @Test 64 | public void predicate() { 65 | 66 | Condition condition = pipeline.apply("firstName==Paul;age==30", User.class); 67 | Predicate predicate = condition.query(new PredicateVisitor<>()); 68 | 69 | } 70 | 71 | ``` 72 | 73 | ### Installation 74 | 75 | #### Release Versions 76 | ```xml 77 | 78 | 79 | com.github.rutledgepaulv 80 | rest-query-engine 81 | 0.7.1 82 | 83 | 84 | ``` 85 | 86 | #### Snapshot Version 87 | ```xml 88 | 89 | 90 | com.github.rutledgepaulv 91 | rest-query-engine 92 | 0.7.2-SNAPSHOT 93 | 94 | 95 | 96 | 97 | 98 | 99 | ossrh 100 | Repository for snapshots 101 | https://oss.sonatype.org/content/repositories/snapshots 102 | 103 | true 104 | 105 | 106 | 107 | ``` 108 | 109 | 110 | #### Optional dependencies 111 | ```xml 112 | 113 | 114 | 115 | 116 | org.springframework.data 117 | spring-data-mongodb 118 | 1.9.2.RELEASE 119 | 120 | 121 | 122 | 123 | org.elasticsearch 124 | elasticsearch 125 | 2.3.4 126 | 127 | 128 | 129 | ``` 130 | 131 | ### License 132 | 133 | This project is licensed under [MIT license](http://opensource.org/licenses/MIT). 134 | -------------------------------------------------------------------------------- /src/main/java/com/github/rutledgepaulv/rqe/adapters/TreeToConditionAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * com.github.rutledgepaulv.rqe.adapters.TreeToConditionAdapter 3 | * * 4 | * * Copyright (C) 2016 Paul Rutledge 5 | * * 6 | * * This software may be modified and distributed under the terms 7 | * * of the MIT license. See the LICENSE file for details. 8 | * 9 | */ 10 | 11 | package com.github.rutledgepaulv.rqe.adapters; 12 | 13 | import com.github.rutledgepaulv.qbuilders.builders.GeneralQueryBuilder; 14 | import com.github.rutledgepaulv.qbuilders.conditions.Condition; 15 | import com.github.rutledgepaulv.qbuilders.nodes.AbstractNode; 16 | import com.github.rutledgepaulv.qbuilders.nodes.AndNode; 17 | import com.github.rutledgepaulv.qbuilders.nodes.ComparisonNode; 18 | import com.github.rutledgepaulv.qbuilders.nodes.OrNode; 19 | import com.github.rutledgepaulv.qbuilders.visitors.AbstractVoidContextNodeVisitor; 20 | 21 | import java.util.function.Function; 22 | 23 | import static java.util.stream.Collectors.toList; 24 | 25 | /** 26 | * This adapter allows for converting between a general tree with logical and leaf nodes into a 27 | * query builder instance. 28 | */ 29 | public class TreeToConditionAdapter implements Function> { 30 | 31 | @Override 32 | public Condition apply(AbstractNode tree) { 33 | return tree.visit(new QBuilderVisitor()); 34 | } 35 | 36 | 37 | private class QBuilderVisitor extends AbstractVoidContextNodeVisitor> { 38 | 39 | @Override 40 | protected Condition visit(AndNode node) { 41 | return new GeneralQueryBuilder().and(node.getChildren().stream().map(this::visitAny).collect(toList())); 42 | } 43 | 44 | @Override 45 | protected Condition visit(OrNode node) { 46 | return new GeneralQueryBuilder().or(node.getChildren().stream().map(this::visitAny).collect(toList())); 47 | } 48 | 49 | @Override 50 | protected Condition visit(ComparisonNode node) { 51 | return new GeneralQueryBuilder().passThrough(node); 52 | } 53 | 54 | } 55 | 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/com/github/rutledgepaulv/rqe/argconverters/ArgConverter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * com.github.rutledgepaulv.rqe.argconverters.ArgConverter 3 | * * 4 | * * Copyright (C) 2016 Paul Rutledge 5 | * * 6 | * * This software may be modified and distributed under the terms 7 | * * of the MIT license. See the LICENSE file for details. 8 | * 9 | */ 10 | 11 | package com.github.rutledgepaulv.rqe.argconverters; 12 | 13 | import com.github.rutledgepaulv.rqe.contexts.ArgConversionContext; 14 | 15 | import java.util.List; 16 | import java.util.function.Function; 17 | 18 | /** 19 | * A converter for taking a set of string arguments as part of a query value and converting 20 | * them into the necessary types to be used in queries against some backend. 21 | */ 22 | public interface ArgConverter extends Function> { 23 | 24 | /** 25 | * Returns true if this converter provides the ability to convert the 26 | * provided arguments. 27 | * 28 | * @param context The conversion context. 29 | * 30 | * @return a boolean value indicating if this converter provides the ability to convert the arguments. 31 | */ 32 | boolean supports(ArgConversionContext context); 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/github/rutledgepaulv/rqe/argconverters/ConverterChain.java: -------------------------------------------------------------------------------- 1 | /* 2 | * com.github.rutledgepaulv.rqe.argconverters.ConverterChain 3 | * * 4 | * * Copyright (C) 2016 Paul Rutledge 5 | * * 6 | * * This software may be modified and distributed under the terms 7 | * * of the MIT license. See the LICENSE file for details. 8 | * 9 | */ 10 | 11 | package com.github.rutledgepaulv.rqe.argconverters; 12 | 13 | import com.github.rutledgepaulv.rqe.contexts.ArgConversionContext; 14 | import com.github.rutledgepaulv.rqe.exceptions.FailedArgumentConversionException; 15 | import com.github.rutledgepaulv.rqe.utils.StreamUtil; 16 | 17 | import java.util.*; 18 | 19 | public class ConverterChain implements Iterable, ArgConverter { 20 | 21 | private Map, Boolean> switchBoard = new HashMap<>(); 22 | private List converters = new LinkedList<>(); 23 | 24 | public ConverterChain() {} 25 | 26 | public ConverterChain(Iterable converters) { 27 | converters.forEach(this::appendInternal); 28 | } 29 | 30 | public ConverterChain(ConverterChain clone) { 31 | clone.converters.forEach(this::appendInternal); 32 | clone.switchBoard.entrySet().stream().forEach(entry -> 33 | switchBoard.put(entry.getKey(), entry.getValue())); 34 | } 35 | 36 | public ConverterChain disable(Class clazz) { 37 | ConverterChain result = new ConverterChain(this); 38 | result.switchBoard.put(clazz, false); 39 | return result; 40 | } 41 | 42 | public ConverterChain enable(Class clazz) { 43 | ConverterChain result = new ConverterChain(this); 44 | result.switchBoard.put(clazz, true); 45 | return result; 46 | } 47 | 48 | public ConverterChain append(ArgConverter converter) { 49 | ConverterChain result = new ConverterChain(this); 50 | 51 | if(!result.switchBoard.containsKey(converter.getClass())) { 52 | result.switchBoard.put(converter.getClass(), true); 53 | } 54 | 55 | result.converters.add(converter); 56 | 57 | return result; 58 | } 59 | 60 | public ConverterChain prepend(ArgConverter converter) { 61 | ConverterChain result = new ConverterChain(this); 62 | 63 | if(!result.switchBoard.containsKey(converter.getClass())) { 64 | result.switchBoard.put(converter.getClass(), true); 65 | } 66 | 67 | result.converters.add(0, converter); 68 | 69 | return result; 70 | } 71 | 72 | private void appendInternal(ArgConverter converter) { 73 | if(!switchBoard.containsKey(converter.getClass())) { 74 | switchBoard.put(converter.getClass(), true); 75 | } 76 | 77 | converters.add(converter); 78 | } 79 | 80 | public Iterator iterator() { 81 | return converters.stream() 82 | .filter(converter -> switchBoard.getOrDefault(converter.getClass(), false)) 83 | .iterator(); 84 | } 85 | 86 | @Override 87 | public boolean supports(ArgConversionContext context) { 88 | return StreamUtil.fromIterator(iterator()).anyMatch(converter -> converter.supports(context)); 89 | } 90 | 91 | @Override 92 | public List apply(ArgConversionContext context) { 93 | return StreamUtil.fromIterator(iterator()).filter(converter -> 94 | converter.supports(context)).findFirst() 95 | .map(converter -> converter.apply(context)) 96 | .orElseThrow(FailedArgumentConversionException::new); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/com/github/rutledgepaulv/rqe/argconverters/EntityFieldTypeConverter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * com.github.rutledgepaulv.rqe.argconverters.EntityFieldTypeConverter 3 | * * 4 | * * Copyright (C) 2016 Paul Rutledge 5 | * * 6 | * * This software may be modified and distributed under the terms 7 | * * of the MIT license. See the LICENSE file for details. 8 | * 9 | */ 10 | 11 | package com.github.rutledgepaulv.rqe.argconverters; 12 | 13 | import com.github.rutledgepaulv.qbuilders.structures.FieldPath; 14 | import com.github.rutledgepaulv.rqe.contexts.ArgConversionContext; 15 | import com.github.rutledgepaulv.rqe.conversions.StringToTypeConverter; 16 | 17 | import java.util.List; 18 | import java.util.function.BiFunction; 19 | 20 | import static java.util.stream.Collectors.toList; 21 | 22 | public class EntityFieldTypeConverter implements ArgConverter { 23 | 24 | private BiFunction, Class> fieldTypeResolver; 25 | private StringToTypeConverter converter; 26 | 27 | public EntityFieldTypeConverter(BiFunction, Class> fieldTypeResolver, StringToTypeConverter converter) { 28 | this.fieldTypeResolver = fieldTypeResolver; 29 | this.converter = converter; 30 | } 31 | 32 | @Override 33 | public boolean supports(ArgConversionContext context) { 34 | return isNotAnOperatorSpecificArgument(context) && pathResolvesToConvertibleType(context); 35 | } 36 | 37 | @Override 38 | public List apply(ArgConversionContext context) { 39 | Class clazz = fieldTypeResolver.apply(context.getPropertyPath(), context.getEntityType()); 40 | return context.getValues().stream().map(val -> converter.apply(val, clazz)).collect(toList()); 41 | } 42 | 43 | private boolean isNotAnOperatorSpecificArgument(ArgConversionContext context) { 44 | return !context.getQueryOperator().doesOperatorDetermineValueType(); 45 | } 46 | 47 | private boolean pathResolvesToConvertibleType(ArgConversionContext context) { 48 | Class clazz = fieldTypeResolver.apply(context.getPropertyPath(), context.getEntityType()); 49 | return converter.supports(clazz); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/github/rutledgepaulv/rqe/argconverters/OperatorSpecificConverter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * com.github.rutledgepaulv.rqe.argconverters.OperatorSpecificConverter 3 | * * 4 | * * Copyright (C) 2016 Paul Rutledge 5 | * * 6 | * * This software may be modified and distributed under the terms 7 | * * of the MIT license. See the LICENSE file for details. 8 | * 9 | */ 10 | 11 | package com.github.rutledgepaulv.rqe.argconverters; 12 | 13 | import com.github.rutledgepaulv.qbuilders.nodes.AbstractNode; 14 | import com.github.rutledgepaulv.qbuilders.structures.FieldPath; 15 | import com.github.rutledgepaulv.rqe.contexts.ArgConversionContext; 16 | import com.github.rutledgepaulv.rqe.contexts.ParseTreeContext; 17 | import com.github.rutledgepaulv.rqe.exceptions.UnsupportedQueryOperatorException; 18 | import com.github.rutledgepaulv.rqe.utils.TriFunction; 19 | 20 | import java.util.List; 21 | import java.util.function.BiFunction; 22 | 23 | import static java.util.Collections.singletonList; 24 | import static java.util.stream.Collectors.toList; 25 | 26 | public class OperatorSpecificConverter implements ArgConverter { 27 | 28 | private TriFunction, ParseTreeContext, AbstractNode> subqueryPipeline; 29 | private BiFunction, Class> resolver; 30 | 31 | public OperatorSpecificConverter(TriFunction, ParseTreeContext, AbstractNode> subqueryPipeline, 32 | BiFunction, Class> resolver) { 33 | this.subqueryPipeline = subqueryPipeline; 34 | this.resolver = resolver; 35 | } 36 | 37 | @Override 38 | public boolean supports(ArgConversionContext context) { 39 | return context.getQueryOperator().doesOperatorDetermineValueType(); 40 | } 41 | 42 | @Override 43 | public List apply(ArgConversionContext context) { 44 | switch(context.getQueryOperator()) { 45 | case REGEX: 46 | return context.getValues().stream().limit(1).map(Object::toString).collect(toList()); 47 | case EXISTS: 48 | return context.getValues().stream().map(Boolean::valueOf).collect(toList()); 49 | case SUBQUERY_ANY: 50 | return singletonList(parse(context)); 51 | default: 52 | throw new UnsupportedQueryOperatorException("This converter cannot handle the operator " + context.getQueryOperator()); 53 | } 54 | } 55 | 56 | private AbstractNode parse(ArgConversionContext context) { 57 | ParseTreeContext subqueryContext = new ParseTreeContext(); 58 | subqueryContext.setParentPath(context.getPropertyPath()); 59 | 60 | return subqueryPipeline.apply(context.getValues().iterator().next(), 61 | subType(context.getPropertyPath(), context.getEntityType()), subqueryContext); 62 | } 63 | 64 | private Class subType(FieldPath propertyPath, Class entityType) { 65 | return resolver.apply(propertyPath, entityType); 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/com/github/rutledgepaulv/rqe/contexts/ArgConversionContext.java: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * com.github.rutledgepaulv.rqe.contexts.ArgConversionContext 4 | * * 5 | * * Copyright (C) 2016 Paul Rutledge 6 | * * 7 | * * This software may be modified and distributed under the terms 8 | * * of the MIT license. See the LICENSE file for details. 9 | * 10 | */ 11 | 12 | package com.github.rutledgepaulv.rqe.contexts; 13 | 14 | import com.github.rutledgepaulv.qbuilders.structures.FieldPath; 15 | import com.github.rutledgepaulv.rqe.argconverters.ConverterChain; 16 | import com.github.rutledgepaulv.rqe.operators.QueryOperator; 17 | 18 | import java.util.*; 19 | 20 | public class ArgConversionContext { 21 | 22 | private ConverterChain chain; 23 | private Class entityType; 24 | private FieldPath propertyPath; 25 | private List values; 26 | private QueryOperator queryOperator; 27 | private Map additionalInformation = new HashMap<>(); 28 | 29 | 30 | public ArgConversionContext() {} 31 | 32 | public ArgConversionContext(ArgConversionContext clone) { 33 | this.entityType = clone.entityType; 34 | this.propertyPath = clone.propertyPath; 35 | this.values = new LinkedList<>(clone.values); 36 | this.queryOperator = clone.queryOperator; 37 | this.additionalInformation = new HashMap<>(clone.additionalInformation); 38 | this.chain = new ConverterChain(clone.chain); 39 | } 40 | 41 | public List getValues() { 42 | return values; 43 | } 44 | 45 | public ArgConversionContext setValues(List values) { 46 | this.values = values; 47 | return this; 48 | } 49 | 50 | public Class getEntityType() { 51 | return entityType; 52 | } 53 | 54 | public ArgConversionContext setEntityType(Class entityType) { 55 | this.entityType = entityType; 56 | return this; 57 | } 58 | 59 | public FieldPath getPropertyPath() { 60 | return propertyPath; 61 | } 62 | 63 | public ArgConversionContext setPropertyPath(FieldPath propertyPath) { 64 | this.propertyPath = propertyPath; 65 | return this; 66 | } 67 | 68 | public Map getAdditionalInformation() { 69 | return additionalInformation; 70 | } 71 | 72 | public ArgConversionContext setAdditionalInformation(Map additionalInformation) { 73 | this.additionalInformation = additionalInformation; 74 | return this; 75 | } 76 | 77 | 78 | public QueryOperator getQueryOperator() { 79 | return queryOperator; 80 | } 81 | 82 | public ArgConversionContext setQueryOperator(QueryOperator queryOperator) { 83 | this.queryOperator = queryOperator; 84 | return this; 85 | } 86 | 87 | public ConverterChain getChain() { 88 | return chain; 89 | } 90 | 91 | public ArgConversionContext setChain(ConverterChain chain) { 92 | this.chain = chain; 93 | return this; 94 | } 95 | 96 | 97 | @Override 98 | public boolean equals(Object o) { 99 | if (this == o) { 100 | return true; 101 | } 102 | if (!(o instanceof ArgConversionContext)) { 103 | return false; 104 | } 105 | ArgConversionContext that = (ArgConversionContext) o; 106 | return Objects.equals(chain, that.chain) && 107 | Objects.equals(entityType, that.entityType) && 108 | Objects.equals(propertyPath, that.propertyPath) && 109 | Objects.equals(values, that.values) && 110 | queryOperator == that.queryOperator && 111 | Objects.equals(additionalInformation, that.additionalInformation); 112 | } 113 | 114 | @Override 115 | public int hashCode() { 116 | return Objects.hash(chain, entityType, propertyPath, values, queryOperator, additionalInformation); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/main/java/com/github/rutledgepaulv/rqe/contexts/ParseTreeContext.java: -------------------------------------------------------------------------------- 1 | /* 2 | * com.github.rutledgepaulv.rqe.contexts.ParseTreeContext 3 | * * 4 | * * Copyright (C) 2016 Paul Rutledge 5 | * * 6 | * * This software may be modified and distributed under the terms 7 | * * of the MIT license. See the LICENSE file for details. 8 | * 9 | */ 10 | 11 | package com.github.rutledgepaulv.rqe.contexts; 12 | 13 | import com.github.rutledgepaulv.qbuilders.nodes.LogicalNode; 14 | import com.github.rutledgepaulv.qbuilders.structures.FieldPath; 15 | 16 | import java.util.HashMap; 17 | import java.util.Map; 18 | import java.util.Objects; 19 | import java.util.Optional; 20 | 21 | public class ParseTreeContext { 22 | 23 | private LogicalNode parent; 24 | private FieldPath parentPath; 25 | private Map additionalInformation = new HashMap<>(); 26 | 27 | public LogicalNode getParent() { 28 | return parent; 29 | } 30 | 31 | public void setParent(LogicalNode parent) { 32 | this.parent = parent; 33 | } 34 | 35 | public Map getAdditionalInformation() { 36 | return additionalInformation; 37 | } 38 | 39 | public void setAdditionalInformation(Map additionalInformation) { 40 | this.additionalInformation = additionalInformation; 41 | } 42 | 43 | public Optional getParentPath() { 44 | return Optional.ofNullable(parentPath); 45 | } 46 | 47 | public ParseTreeContext setParentPath(FieldPath parentPath) { 48 | this.parentPath = parentPath; 49 | return this; 50 | } 51 | 52 | @Override 53 | public boolean equals(Object o) { 54 | if (this == o) { 55 | return true; 56 | } 57 | if (!(o instanceof ParseTreeContext)) { 58 | return false; 59 | } 60 | ParseTreeContext that = (ParseTreeContext) o; 61 | return Objects.equals(parent, that.parent) && 62 | Objects.equals(parentPath, that.parentPath) && 63 | Objects.equals(additionalInformation, that.additionalInformation); 64 | } 65 | 66 | @Override 67 | public int hashCode() { 68 | return Objects.hash(parent, parentPath, additionalInformation); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/com/github/rutledgepaulv/rqe/conversions/SpringConversionServiceConverter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * com.github.rutledgepaulv.rqe.conversions.SpringConversionServiceConverter 3 | * * 4 | * * Copyright (C) 2016 Paul Rutledge 5 | * * 6 | * * This software may be modified and distributed under the terms 7 | * * of the MIT license. See the LICENSE file for details. 8 | * 9 | */ 10 | 11 | package com.github.rutledgepaulv.rqe.conversions; 12 | 13 | import com.github.rutledgepaulv.rqe.conversions.parsers.StringToInstantConverter; 14 | import com.github.rutledgepaulv.rqe.conversions.parsers.StringToObjectBestEffortConverter; 15 | import org.springframework.core.convert.ConversionService; 16 | import org.springframework.core.convert.support.DefaultConversionService; 17 | 18 | public class SpringConversionServiceConverter implements StringToTypeConverter { 19 | 20 | private ConversionService conversionService; 21 | 22 | public SpringConversionServiceConverter() { 23 | DefaultConversionService conversions = new DefaultConversionService(); 24 | conversions.addConverter(new StringToInstantConverter()); 25 | conversions.addConverter(new StringToObjectBestEffortConverter()); 26 | this.conversionService = conversions; 27 | } 28 | 29 | public SpringConversionServiceConverter(ConversionService conversionService) { 30 | this.conversionService = conversionService; 31 | } 32 | 33 | @Override 34 | public boolean supports(Class clazz) { 35 | return conversionService.canConvert(String.class, clazz); 36 | } 37 | 38 | @Override 39 | public Object apply(String s, Class aClass) { 40 | return conversionService.convert(s, aClass); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/github/rutledgepaulv/rqe/conversions/StringToTypeConverter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * com.github.rutledgepaulv.rqe.conversions.StringToTypeConverter 3 | * * 4 | * * Copyright (C) 2016 Paul Rutledge 5 | * * 6 | * * This software may be modified and distributed under the terms 7 | * * of the MIT license. See the LICENSE file for details. 8 | * 9 | */ 10 | 11 | package com.github.rutledgepaulv.rqe.conversions; 12 | 13 | import java.util.function.BiFunction; 14 | 15 | public interface StringToTypeConverter extends BiFunction, Object> { 16 | 17 | boolean supports(Class clazz); 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/github/rutledgepaulv/rqe/conversions/parsers/StringToInstantConverter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * com.github.rutledgepaulv.rqe.conversions.parsers.StringToInstantConverter 3 | * * 4 | * * Copyright (C) 2016 Paul Rutledge 5 | * * 6 | * * This software may be modified and distributed under the terms 7 | * * of the MIT license. See the LICENSE file for details. 8 | * 9 | */ 10 | 11 | package com.github.rutledgepaulv.rqe.conversions.parsers; 12 | 13 | import org.springframework.core.convert.converter.Converter; 14 | 15 | import java.time.Instant; 16 | import java.time.format.DateTimeFormatter; 17 | 18 | public class StringToInstantConverter implements Converter { 19 | 20 | private static final DateTimeFormatter PARSER = DateTimeFormatter.ISO_DATE_TIME; 21 | 22 | @Override 23 | public Instant convert(String source) { 24 | return Instant.from(PARSER.parse(source)); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/github/rutledgepaulv/rqe/conversions/parsers/StringToObjectBestEffortConverter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * com.github.rutledgepaulv.rqe.conversions.parsers.StringToObjectBestEffortConverter 3 | * * 4 | * * Copyright (C) 2016 Paul Rutledge 5 | * * 6 | * * This software may be modified and distributed under the terms 7 | * * of the MIT license. See the LICENSE file for details. 8 | * 9 | */ 10 | 11 | package com.github.rutledgepaulv.rqe.conversions.parsers; 12 | 13 | import org.springframework.core.convert.converter.Converter; 14 | 15 | import java.time.Instant; 16 | import java.time.format.DateTimeFormatter; 17 | import java.util.Objects; 18 | import java.util.Optional; 19 | 20 | public class StringToObjectBestEffortConverter implements Converter { 21 | 22 | private static final DateTimeFormatter PARSER = DateTimeFormatter.ISO_DATE_TIME; 23 | 24 | 25 | @Override 26 | public Object convert(String source) { 27 | return tryParseBoolean(source) 28 | .orElseGet(() -> tryParseDate(source) 29 | .orElseGet(() -> tryParseNumber(source) 30 | .orElse(source))); 31 | } 32 | 33 | 34 | private static Optional tryParseBoolean(String value) { 35 | String normalized = Objects.toString(value); 36 | if(Boolean.TRUE.toString().equals(normalized)){ 37 | return Optional.of(true); 38 | } else if (Boolean.FALSE.toString().equals(normalized)) { 39 | return Optional.of(false); 40 | } else { 41 | return Optional.empty(); 42 | } 43 | } 44 | 45 | 46 | private static Optional tryParseDate(String value) { 47 | try { 48 | return Optional.of(Instant.from(PARSER.parse(value))); 49 | }catch(Exception e) { 50 | return Optional.empty(); 51 | } 52 | } 53 | 54 | 55 | private static Optional tryParseNumber(String value) { 56 | try { 57 | return Optional.of(Long.parseLong(value)); 58 | } catch (NumberFormatException e) { 59 | try { 60 | return Optional.of(Double.parseDouble(value)); 61 | } catch (NumberFormatException e2) { 62 | return Optional.empty(); 63 | } 64 | } 65 | } 66 | 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/com/github/rutledgepaulv/rqe/exceptions/FailedArgumentConversionException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * com.github.rutledgepaulv.rqe.exceptions.FailedArgumentConversionException 3 | * * 4 | * * Copyright (C) 2016 Paul Rutledge 5 | * * 6 | * * This software may be modified and distributed under the terms 7 | * * of the MIT license. See the LICENSE file for details. 8 | * 9 | */ 10 | 11 | package com.github.rutledgepaulv.rqe.exceptions; 12 | 13 | public class FailedArgumentConversionException extends RuntimeException { 14 | public FailedArgumentConversionException() { 15 | } 16 | 17 | public FailedArgumentConversionException(String message) { 18 | super(message); 19 | } 20 | 21 | public FailedArgumentConversionException(String message, Throwable cause) { 22 | super(message, cause); 23 | } 24 | 25 | public FailedArgumentConversionException(Throwable cause) { 26 | super(cause); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/github/rutledgepaulv/rqe/exceptions/UnsupportedQueryOperatorException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * com.github.rutledgepaulv.rqe.exceptions.UnsupportedQueryOperatorException 3 | * * 4 | * * Copyright (C) 2016 Paul Rutledge 5 | * * 6 | * * This software may be modified and distributed under the terms 7 | * * of the MIT license. See the LICENSE file for details. 8 | * 9 | */ 10 | 11 | package com.github.rutledgepaulv.rqe.exceptions; 12 | 13 | public class UnsupportedQueryOperatorException extends RuntimeException { 14 | 15 | public UnsupportedQueryOperatorException() { 16 | } 17 | 18 | public UnsupportedQueryOperatorException(String message) { 19 | super(message); 20 | } 21 | 22 | public UnsupportedQueryOperatorException(String message, Throwable cause) { 23 | super(message, cause); 24 | } 25 | 26 | public UnsupportedQueryOperatorException(Throwable cause) { 27 | super(cause); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/github/rutledgepaulv/rqe/operators/QueryOperator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * com.github.rutledgepaulv.rqe.operators.QueryOperator 3 | * * 4 | * * Copyright (C) 2016 Paul Rutledge 5 | * * 6 | * * This software may be modified and distributed under the terms 7 | * * of the MIT license. See the LICENSE file for details. 8 | * 9 | */ 10 | 11 | package com.github.rutledgepaulv.rqe.operators; 12 | 13 | import cz.jirutka.rsql.parser.ast.ComparisonOperator; 14 | import cz.jirutka.rsql.parser.ast.RSQLOperators; 15 | 16 | import java.util.Arrays; 17 | 18 | public enum QueryOperator { 19 | 20 | EQUAL(RSQLOperators.EQUAL, com.github.rutledgepaulv.qbuilders.operators.ComparisonOperator.EQ), 21 | NOT_EQUAL(RSQLOperators.NOT_EQUAL, com.github.rutledgepaulv.qbuilders.operators.ComparisonOperator.NE), 22 | LESS_THAN(RSQLOperators.LESS_THAN, com.github.rutledgepaulv.qbuilders.operators.ComparisonOperator.LT), 23 | LESS_THAN_OR_EQUAL(RSQLOperators.LESS_THAN_OR_EQUAL, com.github.rutledgepaulv.qbuilders.operators.ComparisonOperator.LTE), 24 | GREATER_THAN(RSQLOperators.GREATER_THAN, com.github.rutledgepaulv.qbuilders.operators.ComparisonOperator.GT), 25 | GREATER_THAN_OR_EQUAL(RSQLOperators.GREATER_THAN_OR_EQUAL, com.github.rutledgepaulv.qbuilders.operators.ComparisonOperator.GTE), 26 | IN(RSQLOperators.IN, com.github.rutledgepaulv.qbuilders.operators.ComparisonOperator.IN), 27 | NOT_IN(RSQLOperators.NOT_IN, com.github.rutledgepaulv.qbuilders.operators.ComparisonOperator.NIN), 28 | EXISTS(new ComparisonOperator("=ex="), com.github.rutledgepaulv.qbuilders.operators.ComparisonOperator.EX), 29 | REGEX(new ComparisonOperator("=re="), com.github.rutledgepaulv.qbuilders.operators.ComparisonOperator.RE), 30 | SUBQUERY_ANY(new ComparisonOperator("=q="), com.github.rutledgepaulv.qbuilders.operators.ComparisonOperator.SUB_CONDITION_ANY); 31 | 32 | 33 | private ComparisonOperator parserOperator; 34 | private com.github.rutledgepaulv.qbuilders.operators.ComparisonOperator qbuilderOperator; 35 | 36 | QueryOperator(ComparisonOperator parserOperator, 37 | com.github.rutledgepaulv.qbuilders.operators.ComparisonOperator qbuilderOperator) { 38 | 39 | this.parserOperator = parserOperator; 40 | this.qbuilderOperator = qbuilderOperator; 41 | } 42 | 43 | 44 | public ComparisonOperator parserOperator() { 45 | return parserOperator; 46 | } 47 | 48 | public com.github.rutledgepaulv.qbuilders.operators.ComparisonOperator qbuilderOperator() { 49 | return qbuilderOperator; 50 | } 51 | 52 | public boolean doesOperatorDetermineValueType() { 53 | return this == EXISTS || this == SUBQUERY_ANY; 54 | } 55 | 56 | public static QueryOperator fromParserOperator(ComparisonOperator op) { 57 | return Arrays.stream(values()).filter(el -> el.parserOperator.equals(op)).findFirst().orElse(null); 58 | } 59 | 60 | public static QueryOperator fromQBuilderOperator(com.github.rutledgepaulv.qbuilders.operators.ComparisonOperator op) { 61 | return Arrays.stream(values()).filter(el -> el.qbuilderOperator.equals(op)).findFirst().orElse(null); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/github/rutledgepaulv/rqe/pipes/DefaultArgumentConversionPipe.java: -------------------------------------------------------------------------------- 1 | /* 2 | * com.github.rutledgepaulv.rqe.pipes.DefaultArgumentConversionPipe 3 | * * 4 | * * Copyright (C) 2016 Paul Rutledge 5 | * * 6 | * * This software may be modified and distributed under the terms 7 | * * of the MIT license. See the LICENSE file for details. 8 | * 9 | */ 10 | 11 | package com.github.rutledgepaulv.rqe.pipes; 12 | 13 | import com.github.rutledgepaulv.qbuilders.nodes.AbstractNode; 14 | import com.github.rutledgepaulv.qbuilders.structures.FieldPath; 15 | import com.github.rutledgepaulv.rqe.argconverters.ArgConverter; 16 | import com.github.rutledgepaulv.rqe.argconverters.ConverterChain; 17 | import com.github.rutledgepaulv.rqe.argconverters.EntityFieldTypeConverter; 18 | import com.github.rutledgepaulv.rqe.argconverters.OperatorSpecificConverter; 19 | import com.github.rutledgepaulv.rqe.contexts.ArgConversionContext; 20 | import com.github.rutledgepaulv.rqe.contexts.ParseTreeContext; 21 | import com.github.rutledgepaulv.rqe.conversions.SpringConversionServiceConverter; 22 | import com.github.rutledgepaulv.rqe.conversions.StringToTypeConverter; 23 | import com.github.rutledgepaulv.rqe.operators.QueryOperator; 24 | import com.github.rutledgepaulv.rqe.resolvers.EntityFieldTypeResolver; 25 | import com.github.rutledgepaulv.rqe.utils.TriFunction; 26 | import cz.jirutka.rsql.parser.ast.*; 27 | 28 | import java.util.Collection; 29 | import java.util.LinkedList; 30 | import java.util.List; 31 | import java.util.Objects; 32 | import java.util.function.BiFunction; 33 | import java.util.function.Function; 34 | 35 | import static java.util.stream.Collectors.toList; 36 | 37 | public class DefaultArgumentConversionPipe implements BiFunction, AbstractNode>, 38 | TriFunction, ParseTreeContext, AbstractNode> { 39 | 40 | public static class DefaultArgumentConversionPipeBuilder { 41 | 42 | private Function parsingPipe = new DefaultParsingPipe(); 43 | private StringToTypeConverter stringToTypeConverter = new SpringConversionServiceConverter(); 44 | private BiFunction, Class> fieldResolver = new EntityFieldTypeResolver(); 45 | private List customConverters = new LinkedList<>(); 46 | 47 | public DefaultArgumentConversionPipeBuilder useNonDefaultParsingPipe(Function parsingPipe) { 48 | this.parsingPipe = parsingPipe; 49 | return this; 50 | } 51 | 52 | public DefaultArgumentConversionPipeBuilder useNonDefaultStringToTypeConverter( 53 | StringToTypeConverter stringToTypeConverter) { 54 | this.stringToTypeConverter = stringToTypeConverter; 55 | return this; 56 | } 57 | 58 | public DefaultArgumentConversionPipeBuilder useNonDefaultFieldResolver( 59 | BiFunction, Class> fieldResolver) { 60 | this.fieldResolver = fieldResolver; 61 | return this; 62 | } 63 | 64 | public DefaultArgumentConversionPipeBuilder addCustomArgumentConverter(ArgConverter fieldResolver) { 65 | this.customConverters.add(fieldResolver); 66 | return this; 67 | } 68 | 69 | public DefaultArgumentConversionPipe build() { 70 | return new DefaultArgumentConversionPipe(this); 71 | } 72 | } 73 | 74 | public static DefaultArgumentConversionPipe defaults() { 75 | return new DefaultArgumentConversionPipeBuilder().build(); 76 | } 77 | 78 | public static DefaultArgumentConversionPipeBuilder builder() { 79 | return new DefaultArgumentConversionPipeBuilder(); 80 | } 81 | 82 | private Function parsingPipe; 83 | private StringToTypeConverter stringToTypeConverter; 84 | private BiFunction, Class> fieldResolver; 85 | private Collection customConverters = new LinkedList<>(); 86 | 87 | 88 | private DefaultArgumentConversionPipe(DefaultArgumentConversionPipeBuilder builder) { 89 | this.parsingPipe = Objects.requireNonNull(builder.parsingPipe); 90 | this.stringToTypeConverter = Objects.requireNonNull(builder.stringToTypeConverter); 91 | this.fieldResolver = Objects.requireNonNull(builder.fieldResolver); 92 | this.customConverters.addAll(builder.customConverters); 93 | } 94 | 95 | 96 | @Override 97 | public AbstractNode apply(Node node, Class entityClass) { 98 | return apply(node, entityClass, new ParseTreeContext()); 99 | } 100 | 101 | @Override 102 | public AbstractNode apply(Node node, Class entityClass, ParseTreeContext parseTreeContext) { 103 | ConverterChain chain = new ConverterChain(); 104 | 105 | for(ArgConverter converter : customConverters) { 106 | chain = chain.append(converter); 107 | } 108 | 109 | chain = chain.append(new OperatorSpecificConverter(subqueryPipeline(this), fieldResolver)); 110 | chain = chain.append(new EntityFieldTypeConverter(fieldResolver, stringToTypeConverter)); 111 | 112 | return node.accept(new ConvertingVisitor(entityClass, chain), parseTreeContext); 113 | } 114 | 115 | 116 | private TriFunction, ParseTreeContext, AbstractNode> subqueryPipeline(TriFunction, ParseTreeContext, AbstractNode> pipe) { 117 | return (rsql, clazz, parseTreeContext) -> parsingPipe.andThen(node -> 118 | pipe.apply(node, clazz, parseTreeContext)).apply(rsql); 119 | } 120 | 121 | 122 | private class ConvertingVisitor implements RSQLVisitor { 123 | 124 | private ConverterChain converterChain; 125 | private Class entityClass; 126 | 127 | public ConvertingVisitor(Class entityClass, ConverterChain chain) { 128 | this.entityClass = entityClass; 129 | this.converterChain = chain; 130 | } 131 | 132 | @Override 133 | public AbstractNode visit(AndNode node, ParseTreeContext param) { 134 | List children = new LinkedList<>(); 135 | 136 | com.github.rutledgepaulv.qbuilders.nodes.AndNode parent = 137 | new com.github.rutledgepaulv.qbuilders.nodes.AndNode(param.getParent(), children); 138 | 139 | param.setParent(parent); 140 | children.addAll(visitChildren(node, param)); 141 | 142 | return parent; 143 | } 144 | 145 | @Override 146 | public AbstractNode visit(OrNode node, ParseTreeContext param) { 147 | List children = new LinkedList<>(); 148 | 149 | com.github.rutledgepaulv.qbuilders.nodes.OrNode parent = 150 | new com.github.rutledgepaulv.qbuilders.nodes.OrNode(param.getParent(), children); 151 | 152 | param.setParent(parent); 153 | children.addAll(visitChildren(node, param)); 154 | 155 | return parent; 156 | } 157 | 158 | @Override 159 | public AbstractNode visit(ComparisonNode node, ParseTreeContext param) { 160 | 161 | QueryOperator operator = QueryOperator.fromParserOperator(node.getOperator()); 162 | 163 | FieldPath path = new FieldPath(node.getSelector()); 164 | 165 | if(param.getParentPath().isPresent()) { 166 | path = path.prepend(param.getParentPath().get()); 167 | } 168 | 169 | ArgConversionContext context = new ArgConversionContext() 170 | .setChain(converterChain) 171 | .setEntityType(entityClass) 172 | .setValues(node.getArguments()) 173 | .setPropertyPath(path) 174 | .setQueryOperator(operator); 175 | 176 | com.github.rutledgepaulv.qbuilders.nodes.ComparisonNode leaf = 177 | new com.github.rutledgepaulv.qbuilders.nodes.ComparisonNode(param.getParent()); 178 | 179 | leaf.setField(path); 180 | leaf.setOperator(operator.qbuilderOperator()); 181 | leaf.setValues(converterChain.apply(context)); 182 | 183 | return leaf; 184 | } 185 | 186 | 187 | private Collection visitChildren(LogicalNode node, ParseTreeContext param) { 188 | return node.getChildren().stream().map(child -> { 189 | if(child instanceof AndNode) { 190 | return this.visit((AndNode)child, param); 191 | } else if (child instanceof OrNode) { 192 | return this.visit((OrNode)child, param); 193 | } else { 194 | return this.visit((ComparisonNode)child, param); 195 | } 196 | }).collect(toList()); 197 | } 198 | 199 | } 200 | 201 | 202 | 203 | } 204 | -------------------------------------------------------------------------------- /src/main/java/com/github/rutledgepaulv/rqe/pipes/DefaultParsingPipe.java: -------------------------------------------------------------------------------- 1 | /* 2 | * com.github.rutledgepaulv.rqe.pipes.DefaultParsingPipe 3 | * * 4 | * * Copyright (C) 2016 Paul Rutledge 5 | * * 6 | * * This software may be modified and distributed under the terms 7 | * * of the MIT license. See the LICENSE file for details. 8 | * 9 | */ 10 | 11 | package com.github.rutledgepaulv.rqe.pipes; 12 | 13 | import com.github.rutledgepaulv.rqe.operators.QueryOperator; 14 | import cz.jirutka.rsql.parser.RSQLParser; 15 | import cz.jirutka.rsql.parser.ast.Node; 16 | 17 | import java.util.Arrays; 18 | import java.util.function.Function; 19 | 20 | import static java.util.stream.Collectors.toSet; 21 | 22 | public class DefaultParsingPipe implements Function { 23 | 24 | 25 | @Override 26 | public Node apply(String rsql) { 27 | return new RSQLParser(Arrays.stream(QueryOperator.values()) 28 | .map(QueryOperator::parserOperator).collect(toSet())) 29 | .parse(rsql); 30 | } 31 | 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/github/rutledgepaulv/rqe/pipes/DefaultQueryBuildingPipe.java: -------------------------------------------------------------------------------- 1 | /* 2 | * com.github.rutledgepaulv.rqe.pipes.DefaultQueryBuildingPipe 3 | * * 4 | * * Copyright (C) 2016 Paul Rutledge 5 | * * 6 | * * This software may be modified and distributed under the terms 7 | * * of the MIT license. See the LICENSE file for details. 8 | * 9 | */ 10 | 11 | package com.github.rutledgepaulv.rqe.pipes; 12 | 13 | import com.github.rutledgepaulv.qbuilders.builders.GeneralQueryBuilder; 14 | import com.github.rutledgepaulv.qbuilders.conditions.Condition; 15 | import com.github.rutledgepaulv.qbuilders.nodes.AbstractNode; 16 | import com.github.rutledgepaulv.rqe.adapters.TreeToConditionAdapter; 17 | 18 | import java.util.function.Function; 19 | 20 | public class DefaultQueryBuildingPipe implements Function> { 21 | 22 | @Override 23 | public Condition apply(AbstractNode abstractNode) { 24 | return new TreeToConditionAdapter().apply(abstractNode); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/github/rutledgepaulv/rqe/pipes/IdentityPipe.java: -------------------------------------------------------------------------------- 1 | /* 2 | * com.github.rutledgepaulv.rqe.pipes.IdentityPipe 3 | * * 4 | * * Copyright (C) 2016 Paul Rutledge 5 | * * 6 | * * This software may be modified and distributed under the terms 7 | * * of the MIT license. See the LICENSE file for details. 8 | * 9 | */ 10 | 11 | package com.github.rutledgepaulv.rqe.pipes; 12 | 13 | import java.util.function.Function; 14 | 15 | public class IdentityPipe implements Function { 16 | 17 | @Override 18 | public T apply(T t) { 19 | return t; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/github/rutledgepaulv/rqe/pipes/QueryConversionPipeline.java: -------------------------------------------------------------------------------- 1 | /* 2 | * com.github.rutledgepaulv.rqe.pipes.QueryConversionPipeline 3 | * * 4 | * * Copyright (C) 2016 Paul Rutledge 5 | * * 6 | * * This software may be modified and distributed under the terms 7 | * * of the MIT license. See the LICENSE file for details. 8 | * 9 | */ 10 | 11 | package com.github.rutledgepaulv.rqe.pipes; 12 | 13 | import com.github.rutledgepaulv.qbuilders.builders.GeneralQueryBuilder; 14 | import com.github.rutledgepaulv.qbuilders.conditions.Condition; 15 | import com.github.rutledgepaulv.qbuilders.nodes.AbstractNode; 16 | import cz.jirutka.rsql.parser.ast.Node; 17 | 18 | import java.util.function.BiFunction; 19 | import java.util.function.Function; 20 | 21 | public class QueryConversionPipeline implements BiFunction, Condition> { 22 | 23 | public static class QueryConversionPipelineBuilder { 24 | private QueryConversionPipelineBuilder(){} 25 | 26 | private Function parsingPipe = new DefaultParsingPipe(); 27 | private Function preConversionTransformer = new IdentityPipe<>(); 28 | private Function postConversionTransformer = new IdentityPipe<>(); 29 | private Function> queryBuildingPipe = new DefaultQueryBuildingPipe(); 30 | private BiFunction, AbstractNode> argumentConversionPipe = DefaultArgumentConversionPipe.defaults(); 31 | 32 | 33 | public QueryConversionPipelineBuilder useNonDefaultParsingPipe(Function parsingPipe) { 34 | this.parsingPipe = parsingPipe; 35 | return this; 36 | } 37 | 38 | public QueryConversionPipelineBuilder useNonDefaultPreConversionTransformer( 39 | Function preConversionTransformer) { 40 | this.preConversionTransformer = preConversionTransformer; 41 | return this; 42 | } 43 | 44 | public QueryConversionPipelineBuilder useNonDefaultArgumentConversionPipe( 45 | BiFunction, AbstractNode> argumentConversionPipe) { 46 | this.argumentConversionPipe = argumentConversionPipe; 47 | return this; 48 | } 49 | 50 | public QueryConversionPipelineBuilder useNonDefaultPostConversionTransformer( 51 | Function postConversionTransformer) { 52 | this.postConversionTransformer = postConversionTransformer; 53 | return this; 54 | } 55 | 56 | public QueryConversionPipelineBuilder useNonDefaultQueryBuildingPipe( 57 | Function> queryBuildingPipe) { 58 | this.queryBuildingPipe = queryBuildingPipe; 59 | return this; 60 | } 61 | 62 | public QueryConversionPipeline build() { 63 | return new QueryConversionPipeline(parsingPipe, preConversionTransformer, argumentConversionPipe, postConversionTransformer, queryBuildingPipe); 64 | } 65 | } 66 | 67 | 68 | public static QueryConversionPipelineBuilder builder() { 69 | return new QueryConversionPipelineBuilder(); 70 | } 71 | 72 | public static QueryConversionPipeline defaultPipeline() { 73 | return QueryConversionPipeline.builder().build(); 74 | } 75 | 76 | 77 | private Function parsingPipe; 78 | private Function preConversionTransformer; 79 | private BiFunction, AbstractNode> argumentConversionPipe; 80 | private Function postConversionTransformer; 81 | private Function> queryBuildingPipe; 82 | 83 | 84 | private QueryConversionPipeline( 85 | Function parsingPipe, 86 | Function preConversionTransformer, 87 | BiFunction, AbstractNode> argumentConversionPipe, 88 | Function postConversionTransformer, 89 | Function> queryBuildingPipe) { 90 | 91 | this.parsingPipe = parsingPipe; 92 | this.preConversionTransformer = preConversionTransformer; 93 | this.argumentConversionPipe = argumentConversionPipe; 94 | this.postConversionTransformer = postConversionTransformer; 95 | this.queryBuildingPipe = queryBuildingPipe; 96 | } 97 | 98 | @Override 99 | public Condition apply(String rsql, Class targetEntity) { 100 | return reducedPipeline().apply(rsql, targetEntity); 101 | } 102 | 103 | private BiFunction, Condition> reducedPipeline() { 104 | return (String string, Class clazz) -> parsingPipe 105 | .andThen(preConversionTransformer) 106 | .andThen(node -> argumentConversionPipe.apply(node, clazz)) 107 | .andThen(postConversionTransformer) 108 | .andThen(queryBuildingPipe).apply(string); 109 | } 110 | 111 | 112 | } 113 | -------------------------------------------------------------------------------- /src/main/java/com/github/rutledgepaulv/rqe/resolvers/EntityFieldTypeResolver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * com.github.rutledgepaulv.rqe.resolvers.EntityFieldTypeResolver 3 | * * 4 | * * Copyright (C) 2016 Paul Rutledge 5 | * * 6 | * * This software may be modified and distributed under the terms 7 | * * of the MIT license. See the LICENSE file for details. 8 | * 9 | */ 10 | 11 | package com.github.rutledgepaulv.rqe.resolvers; 12 | 13 | import com.github.rutledgepaulv.qbuilders.structures.FieldPath; 14 | 15 | import java.lang.reflect.Field; 16 | import java.lang.reflect.ParameterizedType; 17 | import java.util.Collection; 18 | import java.util.function.BiFunction; 19 | 20 | import static org.apache.commons.lang3.reflect.FieldUtils.getField; 21 | 22 | public class EntityFieldTypeResolver implements BiFunction, Class> { 23 | 24 | @Override 25 | public Class apply(FieldPath path, Class root) { 26 | String[] splitField = path.asKey().split("\\.", 2); 27 | if(splitField.length == 1) { 28 | return normalize(getField(root, splitField[0], true)); 29 | } else { 30 | return apply(new FieldPath(splitField[1]), normalize(getField(root, splitField[0], true))); 31 | } 32 | } 33 | 34 | private static Class normalize(Field field) { 35 | if(Collection.class.isAssignableFrom(field.getType())) { 36 | return getFirstTypeParameterOf(field); 37 | } else if(field.getType().isArray()) { 38 | return field.getType().getComponentType(); 39 | } else { 40 | return field.getType(); 41 | } 42 | } 43 | 44 | private static Class getFirstTypeParameterOf(Field field) { 45 | return (Class)((ParameterizedType)field.getGenericType()).getActualTypeArguments()[0]; 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/github/rutledgepaulv/rqe/utils/StreamUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * com.github.rutledgepaulv.rqe.utils.StreamUtil 3 | * * 4 | * * Copyright (C) 2016 Paul Rutledge 5 | * * 6 | * * This software may be modified and distributed under the terms 7 | * * of the MIT license. See the LICENSE file for details. 8 | * 9 | */ 10 | 11 | package com.github.rutledgepaulv.rqe.utils; 12 | 13 | import java.util.Iterator; 14 | import java.util.stream.Stream; 15 | import java.util.stream.StreamSupport; 16 | 17 | public class StreamUtil { 18 | 19 | public static Stream fromIterator(Iterator iter) { 20 | Iterable iterable = () -> iter; 21 | return StreamSupport.stream(iterable.spliterator(), false); 22 | } 23 | 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/github/rutledgepaulv/rqe/utils/TriFunction.java: -------------------------------------------------------------------------------- 1 | /* 2 | * com.github.rutledgepaulv.rqe.utils.TriFunction 3 | * * 4 | * * Copyright (C) 2016 Paul Rutledge 5 | * * 6 | * * This software may be modified and distributed under the terms 7 | * * of the MIT license. See the LICENSE file for details. 8 | * 9 | */ 10 | 11 | package com.github.rutledgepaulv.rqe.utils; 12 | 13 | import java.util.Objects; 14 | import java.util.function.Function; 15 | 16 | /** 17 | * Represents a function that accepts three arguments and produces a result. 18 | * This is the three-arity specialization of {@link Function}. 19 | * 20 | *

This is a functional interface 21 | * whose functional method is {@link #apply(Object, Object, Object)}. 22 | * 23 | * @param the type of the first argument to the function 24 | * @param the type of the second argument to the function 25 | * @param the type of the third argument to the function 26 | * @param the type of the result of the function 27 | * 28 | * @see Function 29 | */ 30 | @FunctionalInterface 31 | public interface TriFunction { 32 | 33 | /** 34 | * Applies this function to the given arguments. 35 | * 36 | * @param t the first function argument 37 | * @param u the second function argument 38 | * @param v the third function argument 39 | * @return the function result 40 | */ 41 | R apply(T t, U u, V v); 42 | 43 | /** 44 | * Returns a composed function that first applies this function to 45 | * its input, and then applies the {@code after} function to the result. 46 | * If evaluation of either function throws an exception, it is relayed to 47 | * the caller of the composed function. 48 | * 49 | * @param the type of output of the {@code after} function, and of the 50 | * composed function 51 | * @param after the function to apply after this function is applied 52 | * @return a composed function that first applies this function and then 53 | * applies the {@code after} function 54 | * @throws NullPointerException if after is null 55 | */ 56 | default TriFunction then(Function after) { 57 | Objects.requireNonNull(after); 58 | return (T a, U b, V c) -> after.apply(apply(a, b, c)); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/test/java/com/github/rutledgepaulv/rqe/conversions/parsers/StringToObjectBestEffortConverterTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * com.github.rutledgepaulv.rqe.conversions.parsers.StringToObjectBestEffortConverterTest 3 | * * 4 | * * Copyright (C) 2016 Paul Rutledge 5 | * * 6 | * * This software may be modified and distributed under the terms 7 | * * of the MIT license. See the LICENSE file for details. 8 | * 9 | */ 10 | 11 | package com.github.rutledgepaulv.rqe.conversions.parsers; 12 | 13 | import org.junit.Test; 14 | 15 | import java.time.Instant; 16 | import java.time.temporal.ChronoUnit; 17 | 18 | import static org.junit.Assert.assertEquals; 19 | 20 | public class StringToObjectBestEffortConverterTest { 21 | 22 | private static final StringToObjectBestEffortConverter it = new StringToObjectBestEffortConverter(); 23 | 24 | @Test 25 | public void itConvertsBooleans() throws Exception { 26 | assertEquals(true, it.convert("true")); 27 | assertEquals(false, it.convert("false")); 28 | } 29 | 30 | @Test 31 | public void itConvertsLongs() throws Exception { 32 | assertEquals(1L, it.convert("1")); 33 | assertEquals(9999L, it.convert("9999")); 34 | assertEquals(-30L, it.convert("-30")); 35 | } 36 | 37 | @Test 38 | public void itConvertsDoubles() throws Exception { 39 | assertEquals(30.5, it.convert("30.5")); 40 | assertEquals(3.1415926, it.convert("3.1415926")); 41 | assertEquals(-9993333.25980, it.convert("-9993333.25980")); 42 | } 43 | 44 | @Test 45 | public void itConvertsDatesInIsoFormat() throws Exception { 46 | Instant expected1 = Instant.EPOCH.plus(35, ChronoUnit.HOURS).plus(3, ChronoUnit.DAYS); 47 | Instant expected2 = Instant.EPOCH.plus(35, ChronoUnit.MINUTES).plus(3, ChronoUnit.HALF_DAYS); 48 | Instant now = Instant.now(); 49 | assertEquals(expected1, it.convert("1970-01-05T11:00:00Z")); 50 | assertEquals(expected2, it.convert("1970-01-02T12:35:00Z")); 51 | assertEquals(now, it.convert(now.toString())); 52 | } 53 | 54 | @Test 55 | public void ifItCantConvertToSomethingElseItJustReturnsTheString() throws Exception { 56 | assertEquals("badgers", it.convert("badgers")); 57 | assertEquals("chevron", it.convert("chevron")); 58 | assertEquals("buggy", it.convert("buggy")); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/test/java/com/github/rutledgepaulv/rqe/pipes/DemoCustomConversionPipeline.java: -------------------------------------------------------------------------------- 1 | /* 2 | * com.github.rutledgepaulv.rqe.pipes.DemoCustomConversionPipeline 3 | * * 4 | * * Copyright (C) 2016 Paul Rutledge 5 | * * 6 | * * This software may be modified and distributed under the terms 7 | * * of the MIT license. See the LICENSE file for details. 8 | * 9 | */ 10 | 11 | package com.github.rutledgepaulv.rqe.pipes; 12 | 13 | import com.github.rutledgepaulv.qbuilders.visitors.ElasticsearchVisitor; 14 | import com.github.rutledgepaulv.qbuilders.visitors.MongoVisitor; 15 | import org.elasticsearch.index.query.QueryBuilder; 16 | import org.junit.Test; 17 | import org.springframework.data.mongodb.core.query.Criteria; 18 | 19 | import java.util.LinkedList; 20 | import java.util.List; 21 | 22 | import static org.junit.Assert.assertEquals; 23 | 24 | /** 25 | * {@see https://github.com/RutledgePaulV/rest-query-engine/issues/3} 26 | */ 27 | public class DemoCustomConversionPipeline { 28 | 29 | 30 | public static class Spectra { 31 | private List compounds = new LinkedList<>(); 32 | } 33 | 34 | public static class ChemicalCompound { 35 | private String name; 36 | private List metaData = new LinkedList<>(); 37 | } 38 | 39 | 40 | public static class Score {} 41 | 42 | public static class MetaData { 43 | private String category; 44 | private boolean computed; 45 | private boolean deleted; 46 | private boolean hidden; 47 | private String name; 48 | private Score score; 49 | private String unit; 50 | private String url; 51 | private Object value; 52 | private List metaData = new LinkedList<>(); 53 | } 54 | 55 | 56 | private QueryConversionPipeline pipeline = QueryConversionPipeline.builder().build(); 57 | 58 | 59 | @Test 60 | public void doubleNestedQuery() { 61 | 62 | String rsql = "compounds=q='name==\"Test\";metaData=q=\"(category==Acid;hidden==false)\"'"; 63 | 64 | Criteria criteria = pipeline.apply(rsql, Spectra.class).query(new MongoVisitor()); 65 | assertEquals("{ \"compounds\" : { \"$elemMatch\" : { \"$and\" : " + 66 | "[ { \"name\" : \"Test\"} , { \"metaData\" : { \"$elemMatch\" :" + 67 | " { \"$and\" : [ { \"category\" : \"Acid\"} ," + 68 | " { \"hidden\" : false}]}}}]}}}", criteria.getCriteriaObject().toString()); 69 | 70 | 71 | QueryBuilder builder = pipeline.apply(rsql, Spectra.class) 72 | .query(new ElasticsearchVisitor(), new ElasticsearchVisitor.Context()); 73 | 74 | assertEquals("{\n" + 75 | " \"nested\" : {\n" + 76 | " \"query\" : {\n" + 77 | " \"bool\" : {\n" + 78 | " \"must\" : [ {\n" + 79 | " \"term\" : {\n" + 80 | " \"compounds.name\" : \"Test\"\n" + 81 | " }\n" + 82 | " }, {\n" + 83 | " \"nested\" : {\n" + 84 | " \"query\" : {\n" + 85 | " \"bool\" : {\n" + 86 | " \"must\" : [ {\n" + 87 | " \"term\" : {\n" + 88 | " \"compounds.metaData.category\" : \"Acid\"\n" + 89 | " }\n" + 90 | " }, {\n" + 91 | " \"term\" : {\n" + 92 | " \"compounds.metaData.hidden\" : false\n" + 93 | " }\n" + 94 | " } ]\n" + 95 | " }\n" + 96 | " },\n" + 97 | " \"path\" : \"compounds.metaData\"\n" + 98 | " }\n" + 99 | " } ]\n" + 100 | " }\n" + 101 | " },\n" + 102 | " \"path\" : \"compounds\"\n" + 103 | " }\n" + 104 | "}", builder.toString()); 105 | 106 | } 107 | 108 | 109 | @Test 110 | public void testNestedQueries() { 111 | 112 | Criteria criteria; 113 | String rsql; 114 | 115 | 116 | rsql = "compounds.metaData=q='name==\"total exact mass\" and value=gt=411.31 and value=lt=411.4'"; 117 | criteria = pipeline.apply(rsql, Spectra.class).query(new MongoVisitor()); 118 | assertEquals("{ \"compounds.metaData\" : { \"$elemMatch\" : { \"$and\" :" + 119 | " [ { \"name\" : \"total exact mass\"} , { \"value\" : { \"$gt\" : 411.31}} ," + 120 | " { \"value\" : { \"$lt\" : 411.4}}]}}}", criteria.getCriteriaObject().toString()); 121 | 122 | 123 | rsql = "compounds.metaData=q='name==\"total exact mass\" and value=gt=1 and value=lt=5'"; 124 | criteria = pipeline.apply(rsql, Spectra.class).query(new MongoVisitor()); 125 | assertEquals("{ \"compounds.metaData\" : { \"$elemMatch\" : { \"$and\" :" + 126 | " [ { \"name\" : \"total exact mass\"} , { \"value\" : { \"$gt\" : 1}} ," + 127 | " { \"value\" : { \"$lt\" : 5}}]}}}", criteria.getCriteriaObject().toString()); 128 | 129 | 130 | rsql = "compounds.metaData=q='name==\"total exact mass\" and value==true'"; 131 | criteria = pipeline.apply(rsql, Spectra.class).query(new MongoVisitor()); 132 | assertEquals("{ \"compounds.metaData\" : { \"$elemMatch\" : { \"$and\" :" + 133 | " [ { \"name\" : \"total exact mass\"} , { \"value\" : true}]}}}", 134 | criteria.getCriteriaObject().toString()); 135 | 136 | 137 | rsql = "compounds.metaData=q='name==\"total exact mass\" and value==false'"; 138 | criteria = pipeline.apply(rsql, Spectra.class).query(new MongoVisitor()); 139 | assertEquals("{ \"compounds.metaData\" : { \"$elemMatch\" : { \"$and\" :" + 140 | " [ { \"name\" : \"total exact mass\"} , { \"value\" : false}]}}}", 141 | criteria.getCriteriaObject().toString()); 142 | 143 | 144 | } 145 | 146 | 147 | @Test 148 | public void nestedQueryAgainstElasticsearchMaintainsFieldPathNameOnNestedElements() { 149 | 150 | String rsql = "metaData=q='name==\"license\" and value==\"CC BY-SA\"'"; 151 | QueryBuilder builder = pipeline.apply(rsql, ChemicalCompound.class).query(new ElasticsearchVisitor(), new ElasticsearchVisitor.Context()); 152 | 153 | 154 | assertEquals("{\n" + 155 | " \"nested\" : {\n" + 156 | " \"query\" : {\n" + 157 | " \"bool\" : {\n" + 158 | " \"must\" : [ {\n" + 159 | " \"term\" : {\n" + 160 | " \"metaData.name\" : \"license\"\n" + 161 | " }\n" + 162 | " }, {\n" + 163 | " \"term\" : {\n" + 164 | " \"metaData.value\" : \"CC BY-SA\"\n" + 165 | " }\n" + 166 | " } ]\n" + 167 | " }\n" + 168 | " },\n" + 169 | " \"path\" : \"metaData\"\n" + 170 | " }\n" + 171 | "}",builder.toString()); 172 | } 173 | 174 | @Test 175 | public void multipleDepthsOfNested() { 176 | 177 | String rsql = "metaData=q='name==\"license\" and value==\"CC BY-SA\" and metaData=q=\"name==notLicense\"'"; 178 | 179 | QueryBuilder builder = pipeline.apply(rsql, ChemicalCompound.class) 180 | .query(new ElasticsearchVisitor(), new ElasticsearchVisitor.Context()); 181 | 182 | 183 | assertEquals("{\n" + 184 | " \"nested\" : {\n" + 185 | " \"query\" : {\n" + 186 | " \"bool\" : {\n" + 187 | " \"must\" : [ {\n" + 188 | " \"term\" : {\n" + 189 | " \"metaData.name\" : \"license\"\n" + 190 | " }\n" + 191 | " }, {\n" + 192 | " \"term\" : {\n" + 193 | " \"metaData.value\" : \"CC BY-SA\"\n" + 194 | " }\n" + 195 | " }, {\n" + 196 | " \"nested\" : {\n" + 197 | " \"query\" : {\n" + 198 | " \"term\" : {\n" + 199 | " \"metaData.metaData.name\" : \"notLicense\"\n" + 200 | " }\n" + 201 | " },\n" + 202 | " \"path\" : \"metaData.metaData\"\n" + 203 | " }\n" + 204 | " } ]\n" + 205 | " }\n" + 206 | " },\n" + 207 | " \"path\" : \"metaData\"\n" + 208 | " }\n" + 209 | "}",builder.toString()); 210 | } 211 | 212 | } 213 | -------------------------------------------------------------------------------- /src/test/java/com/github/rutledgepaulv/rqe/pipes/FieldCombinationsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * com.github.rutledgepaulv.rqe.pipes.FieldCombinationsTest 3 | * * 4 | * * Copyright (C) 2016 Paul Rutledge 5 | * * 6 | * * This software may be modified and distributed under the terms 7 | * * of the MIT license. See the LICENSE file for details. 8 | * 9 | */ 10 | 11 | package com.github.rutledgepaulv.rqe.pipes; 12 | 13 | import com.github.rutledgepaulv.qbuilders.builders.GeneralQueryBuilder; 14 | import com.github.rutledgepaulv.qbuilders.conditions.Condition; 15 | import com.github.rutledgepaulv.qbuilders.visitors.ElasticsearchVisitor; 16 | import com.github.rutledgepaulv.qbuilders.visitors.MongoVisitor; 17 | import com.github.rutledgepaulv.qbuilders.visitors.RSQLVisitor; 18 | import com.github.rutledgepaulv.rqe.testsupport.CommentQuery; 19 | import com.github.rutledgepaulv.rqe.testsupport.User; 20 | import com.github.rutledgepaulv.rqe.testsupport.UserQuery; 21 | import org.junit.Test; 22 | 23 | import static java.time.Instant.EPOCH; 24 | import static org.junit.Assert.assertEquals; 25 | 26 | public class FieldCombinationsTest extends TestBase { 27 | 28 | static { 29 | MODE = RunMode.TEST; 30 | } 31 | 32 | 33 | private QueryConversionPipeline pipeline = QueryConversionPipeline.defaultPipeline(); 34 | 35 | @Test 36 | public void andTwoTopLevelProperties() { 37 | 38 | Condition query = new UserQuery().age().eq(23).and().firstName().eq("Paul"); 39 | 40 | String rsql = query.query(new RSQLVisitor()); 41 | assertEquals("age==\"23\";firstName==\"Paul\"", rsql); 42 | 43 | Condition parsed = pipeline.apply(rsql, User.class); 44 | 45 | assertEquals("(age==\"23\";firstName==\"Paul\")", parsed.query(new RSQLVisitor())); 46 | assertEquals(query.query(new MongoVisitor()), parsed.query(new MongoVisitor())); 47 | assertEquals(query.query(new ElasticsearchVisitor(), new ElasticsearchVisitor.Context()).toString(), parsed.query(new ElasticsearchVisitor(), new ElasticsearchVisitor.Context()).toString()); 48 | 49 | assertMongo(parsed, "{ \"$and\" : [ { \"age\" : 23} , { \"firstName\" : \"Paul\"}]}"); 50 | 51 | assertElasticsearch(parsed, "{\n" + 52 | " \"bool\" : {\n" + 53 | " \"must\" : [ {\n" + 54 | " \"term\" : {\n" + 55 | " \"age\" : 23\n" + 56 | " }\n" + 57 | " }, {\n" + 58 | " \"term\" : {\n" + 59 | " \"firstName\" : \"Paul\"\n" + 60 | " }\n" + 61 | " } ]\n" + 62 | " }\n" + 63 | "}"); 64 | 65 | } 66 | 67 | @Test 68 | public void orTwoTopLevelProperties() { 69 | 70 | Condition query = new UserQuery().age().eq(23).or().firstName().eq("Paul"); 71 | 72 | String rsql = query.query(new RSQLVisitor()); 73 | assertEquals("age==\"23\",firstName==\"Paul\"", rsql); 74 | 75 | Condition parsed = pipeline.apply(rsql, User.class); 76 | 77 | assertEquals("(age==\"23\",firstName==\"Paul\")", parsed.query(new RSQLVisitor())); 78 | assertEquals(query.query(new MongoVisitor()), parsed.query(new MongoVisitor())); 79 | assertEquals(query.query(new ElasticsearchVisitor(), new ElasticsearchVisitor.Context()).toString(), parsed.query(new ElasticsearchVisitor(), new ElasticsearchVisitor.Context()).toString()); 80 | 81 | assertMongo(parsed, "{ \"$or\" : [ { \"age\" : 23} , { \"firstName\" : \"Paul\"}]}"); 82 | 83 | assertElasticsearch(parsed, "{\n" + 84 | " \"bool\" : {\n" + 85 | " \"should\" : [ {\n" + 86 | " \"term\" : {\n" + 87 | " \"age\" : 23\n" + 88 | " }\n" + 89 | " }, {\n" + 90 | " \"term\" : {\n" + 91 | " \"firstName\" : \"Paul\"\n" + 92 | " }\n" + 93 | " } ]\n" + 94 | " }\n" + 95 | "}"); 96 | 97 | } 98 | 99 | @Test 100 | public void andTopLevelWithORedNestedQuery() { 101 | 102 | Condition topLevel = new UserQuery().age().eq(23).and().firstName().eq("Paul"); 103 | Condition nested = new UserQuery().comments().any(new CommentQuery().comment().eq("Test").or().timestamp().eq(EPOCH)); 104 | Condition root = new UserQuery().or(topLevel, nested); 105 | 106 | String rsql = root.query(new RSQLVisitor()); 107 | 108 | assertEquals("((age==\"23\";firstName==\"Paul\"),comments=q='comment==\"Test\",timestamp==\"1970-01-01T00:00:00Z\"')", rsql); 109 | 110 | Condition parsed = pipeline.apply(rsql, User.class); 111 | 112 | assertEquals(rsql, parsed.query(new RSQLVisitor())); 113 | 114 | assertMongo(parsed, "{ \"$or\" : [ { \"$and\" : [ { \"age\" : 23} , " + 115 | "{ \"firstName\" : \"Paul\"}]} , { \"comments\" : { \"$elemMatch\" :" + 116 | " { \"$or\" : [ { \"comment\" : \"Test\"} , { \"timestamp\" : " + 117 | "{ \"$date\" : \"1970-01-01T00:00:00.000Z\"}}]}}}]}"); 118 | 119 | assertElasticsearch(root, "{\n" + 120 | " \"bool\" : {\n" + 121 | " \"should\" : [ {\n" + 122 | " \"bool\" : {\n" + 123 | " \"must\" : [ {\n" + 124 | " \"term\" : {\n" + 125 | " \"age\" : 23\n" + 126 | " }\n" + 127 | " }, {\n" + 128 | " \"term\" : {\n" + 129 | " \"firstName\" : \"Paul\"\n" + 130 | " }\n" + 131 | " } ]\n" + 132 | " }\n" + 133 | " }, {\n" + 134 | " \"nested\" : {\n" + 135 | " \"query\" : {\n" + 136 | " \"bool\" : {\n" + 137 | " \"should\" : [ {\n" + 138 | " \"term\" : {\n" + 139 | " \"comments.comment\" : \"Test\"\n" + 140 | " }\n" + 141 | " }, {\n" + 142 | " \"term\" : {\n" + 143 | " \"comments.timestamp\" : \"1970-01-01T00:00:00Z\"\n" + 144 | " }\n" + 145 | " } ]\n" + 146 | " }\n" + 147 | " },\n" + 148 | " \"path\" : \"comments\"\n" + 149 | " }\n" + 150 | " } ]\n" + 151 | " }\n" + 152 | "}"); 153 | 154 | } 155 | 156 | @Test 157 | public void orTopLevelWithAndedNestedQuery() { 158 | 159 | Condition topLevel = new UserQuery().age().eq(23).and().firstName().eq("Paul"); 160 | Condition nested = new UserQuery().comments().any(new CommentQuery().comment().eq("Test").and().timestamp().eq(EPOCH)); 161 | Condition root = new UserQuery().and(topLevel, nested); 162 | 163 | String rsql = root.query(new RSQLVisitor()); 164 | 165 | assertEquals("((age==\"23\";firstName==\"Paul\");comments=q='comment==\"Test\";timestamp==\"1970-01-01T00:00:00Z\"')", rsql); 166 | 167 | Condition parsed = pipeline.apply(rsql, User.class); 168 | 169 | assertEquals(rsql, parsed.query(new RSQLVisitor())); 170 | 171 | assertMongo(parsed, "{ \"$and\" : [ { \"$and\" : [ { \"age\" : 23} , " + 172 | "{ \"firstName\" : \"Paul\"}]} , { \"comments\" : { \"$elemMatch\" :" + 173 | " { \"$and\" : [ { \"comment\" : \"Test\"} , { \"timestamp\" : " + 174 | "{ \"$date\" : \"1970-01-01T00:00:00.000Z\"}}]}}}]}"); 175 | 176 | assertElasticsearch(root, "{\n" + 177 | " \"bool\" : {\n" + 178 | " \"must\" : [ {\n" + 179 | " \"bool\" : {\n" + 180 | " \"must\" : [ {\n" + 181 | " \"term\" : {\n" + 182 | " \"age\" : 23\n" + 183 | " }\n" + 184 | " }, {\n" + 185 | " \"term\" : {\n" + 186 | " \"firstName\" : \"Paul\"\n" + 187 | " }\n" + 188 | " } ]\n" + 189 | " }\n" + 190 | " }, {\n" + 191 | " \"nested\" : {\n" + 192 | " \"query\" : {\n" + 193 | " \"bool\" : {\n" + 194 | " \"must\" : [ {\n" + 195 | " \"term\" : {\n" + 196 | " \"comments.comment\" : \"Test\"\n" + 197 | " }\n" + 198 | " }, {\n" + 199 | " \"term\" : {\n" + 200 | " \"comments.timestamp\" : \"1970-01-01T00:00:00Z\"\n" + 201 | " }\n" + 202 | " } ]\n" + 203 | " }\n" + 204 | " },\n" + 205 | " \"path\" : \"comments\"\n" + 206 | " }\n" + 207 | " } ]\n" + 208 | " }\n" + 209 | "}"); 210 | 211 | } 212 | 213 | } 214 | -------------------------------------------------------------------------------- /src/test/java/com/github/rutledgepaulv/rqe/pipes/MultiValueFieldsWithNestedObjectsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * com.github.rutledgepaulv.rqe.pipes.MultiValueFieldsWithNestedObjectsTest 3 | * * 4 | * * Copyright (C) 2016 Paul Rutledge 5 | * * 6 | * * This software may be modified and distributed under the terms 7 | * * of the MIT license. See the LICENSE file for details. 8 | * 9 | */ 10 | 11 | package com.github.rutledgepaulv.rqe.pipes; 12 | 13 | import com.github.rutledgepaulv.qbuilders.builders.GeneralQueryBuilder; 14 | import com.github.rutledgepaulv.qbuilders.conditions.Condition; 15 | import com.github.rutledgepaulv.rqe.testsupport.Comment; 16 | import com.github.rutledgepaulv.rqe.testsupport.User; 17 | import org.junit.Test; 18 | 19 | public class MultiValueFieldsWithNestedObjectsTest extends TestBase { 20 | 21 | static { 22 | MODE = RunMode.TEST; 23 | } 24 | 25 | private QueryConversionPipeline pipeline = QueryConversionPipeline.defaultPipeline(); 26 | 27 | 28 | @Test 29 | public void propertiesOnObjectsInMultiValueField_notSubquery() { 30 | 31 | Condition condition = pipeline.apply("comments.comment=='This is my first comment'", User.class); 32 | 33 | assertMongo(condition, "{ \"comments.comment\" : \"This is my first comment\"}"); 34 | 35 | assertElasticsearch(condition, "{\n" + 36 | " \"term\" : {\n" + 37 | " \"comments.comment\" : \"This is my first comment\"\n" + 38 | " }\n" + 39 | "}"); 40 | 41 | } 42 | 43 | @Test 44 | public void propertiesOnObjectsInMultiValueField_predicate_notSubquery() { 45 | 46 | Condition condition = pipeline.apply("comments.comment=='This is my first comment'", User.class); 47 | 48 | User user = new User(); 49 | Comment comment1 = new Comment(); 50 | comment1.setComment("This is my first comment"); 51 | 52 | Comment comment2 = new Comment(); 53 | comment2.setComment("This is my second comment"); 54 | 55 | user.getComments().add(comment1); 56 | user.getComments().add(comment2); 57 | 58 | assertPredicate(condition, user); 59 | 60 | comment1.setComment("Something else"); 61 | assertNotPredicate(condition, user); 62 | 63 | } 64 | 65 | 66 | @Test 67 | public void nestedQuery() { 68 | 69 | Condition condition = pipeline.apply("comments=q=\"comment=='This is my first comment';timestamp=ex=true\"", User.class); 70 | 71 | assertMongo(condition, "{ \"comments\" : { \"$elemMatch\" : { \"$and\" : [ { \"comment\" : \"This is my first comment\"} , { \"timestamp\" : { \"$exists\" : true}}]}}}"); 72 | 73 | assertElasticsearch(condition, "{\n" + 74 | " \"nested\" : {\n" + 75 | " \"query\" : {\n" + 76 | " \"bool\" : {\n" + 77 | " \"must\" : [ {\n" + 78 | " \"term\" : {\n" + 79 | " \"comments.comment\" : \"This is my first comment\"\n" + 80 | " }\n" + 81 | " }, {\n" + 82 | " \"exists\" : {\n" + 83 | " \"field\" : \"comments.timestamp\"\n" + 84 | " }\n" + 85 | " } ]\n" + 86 | " }\n" + 87 | " },\n" + 88 | " \"path\" : \"comments\"\n" + 89 | " }\n" + 90 | "}"); 91 | 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /src/test/java/com/github/rutledgepaulv/rqe/pipes/NestedObjectTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * com.github.rutledgepaulv.rqe.pipes.NestedObjectTest 3 | * * 4 | * * Copyright (C) 2016 Paul Rutledge 5 | * * 6 | * * This software may be modified and distributed under the terms 7 | * * of the MIT license. See the LICENSE file for details. 8 | * 9 | */ 10 | 11 | package com.github.rutledgepaulv.rqe.pipes; 12 | 13 | import com.github.rutledgepaulv.qbuilders.builders.GeneralQueryBuilder; 14 | import com.github.rutledgepaulv.qbuilders.conditions.Condition; 15 | import com.github.rutledgepaulv.rqe.testsupport.State; 16 | import com.github.rutledgepaulv.rqe.testsupport.User; 17 | import org.junit.Test; 18 | 19 | public class NestedObjectTest extends TestBase { 20 | 21 | static { 22 | MODE = RunMode.TEST; 23 | } 24 | 25 | private QueryConversionPipeline pipeline = QueryConversionPipeline.defaultPipeline(); 26 | 27 | @Test 28 | public void stringPropertyOnNestedObject() { 29 | 30 | Condition condition = pipeline.apply("address.street=='1 Michigan Ave'", User.class); 31 | 32 | User user = new User(); 33 | 34 | user.getAddress().setStreet("1 Michigan Ave"); 35 | assertPredicate(condition, user); 36 | 37 | user.getAddress().setStreet("Something else"); 38 | assertNotPredicate(condition, user); 39 | 40 | assertMongo(condition, "{ \"address.street\" : \"1 Michigan Ave\"}"); 41 | 42 | assertElasticsearch(condition, "{\n" + 43 | " \"term\" : {\n" + 44 | " \"address.street\" : \"1 Michigan Ave\"\n" + 45 | " }\n" + 46 | "}"); 47 | 48 | } 49 | 50 | @Test 51 | public void numberPropertyOnNestedObject() { 52 | 53 | Condition condition = pipeline.apply("address.unit==100", User.class); 54 | 55 | User user = new User(); 56 | 57 | user.getAddress().setUnit(100); 58 | assertPredicate(condition, user); 59 | 60 | user.getAddress().setUnit(400); 61 | assertNotPredicate(condition, user); 62 | 63 | assertMongo(condition, "{ \"address.unit\" : 100}"); 64 | 65 | assertElasticsearch(condition, "{\n" + 66 | " \"term\" : {\n" + 67 | " \"address.unit\" : 100\n" + 68 | " }\n" + 69 | "}"); 70 | 71 | } 72 | 73 | @Test 74 | public void booleanPropertyOnNestedObject() { 75 | 76 | Condition condition = pipeline.apply("address.hasCat==false", User.class); 77 | 78 | User user = new User(); 79 | 80 | user.getAddress().setHasCat(false); 81 | assertPredicate(condition, user); 82 | 83 | user.getAddress().setHasCat(true); 84 | assertNotPredicate(condition, user); 85 | 86 | assertMongo(condition, "{ \"address.hasCat\" : false}"); 87 | 88 | assertElasticsearch(condition, "{\n" + 89 | " \"term\" : {\n" + 90 | " \"address.hasCat\" : false\n" + 91 | " }\n" + 92 | "}"); 93 | 94 | } 95 | 96 | 97 | @Test 98 | public void enumPropertyOnNestedObject_mongodbAndElasticsearch() { 99 | Condition condition = pipeline.apply("address.state==ILLINOIS", User.class); 100 | 101 | assertMongo(condition, "{ \"address.state\" : \"ILLINOIS\"}"); 102 | 103 | assertElasticsearch(condition, "{\n" + 104 | " \"term\" : {\n" + 105 | " \"address.state\" : \"ILLINOIS\"\n" + 106 | " }\n" + 107 | "}"); 108 | } 109 | 110 | 111 | @Test 112 | public void enumPropertyOnNestedObject_predicate() { 113 | Condition condition = pipeline.apply("address.state==ILLINOIS", User.class); 114 | 115 | User user = new User(); 116 | 117 | user.getAddress().setState(State.ILLINOIS); 118 | assertPredicate(condition, user); 119 | 120 | user.getAddress().setState(State.MINNESOTA); 121 | assertNotPredicate(condition, user); 122 | } 123 | 124 | } 125 | -------------------------------------------------------------------------------- /src/test/java/com/github/rutledgepaulv/rqe/pipes/StandardFieldTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * com.github.rutledgepaulv.rqe.pipes.StandardFieldTest 3 | * * 4 | * * Copyright (C) 2016 Paul Rutledge 5 | * * 6 | * * This software may be modified and distributed under the terms 7 | * * of the MIT license. See the LICENSE file for details. 8 | * 9 | */ 10 | 11 | package com.github.rutledgepaulv.rqe.pipes; 12 | 13 | import com.github.rutledgepaulv.qbuilders.builders.GeneralQueryBuilder; 14 | import com.github.rutledgepaulv.qbuilders.conditions.Condition; 15 | import com.github.rutledgepaulv.rqe.testsupport.User; 16 | import org.junit.Test; 17 | 18 | public class StandardFieldTest extends TestBase { 19 | 20 | static { 21 | MODE = RunMode.TEST; 22 | } 23 | 24 | private QueryConversionPipeline pipeline = QueryConversionPipeline.defaultPipeline(); 25 | 26 | @Test 27 | public void stringPropertyOnRootObject() { 28 | 29 | Condition condition = pipeline.apply("firstName==Paul", User.class); 30 | 31 | assertPredicate(condition, new User().setFirstName("Paul")); 32 | 33 | assertNotPredicate(condition, new User().setFirstName("Joe")); 34 | 35 | assertMongo(condition, "{ \"firstName\" : \"Paul\"}"); 36 | 37 | assertElasticsearch(condition, "{\n" + 38 | " \"term\" : {\n" + 39 | " \"firstName\" : \"Paul\"\n" + 40 | " }\n" + 41 | "}"); 42 | } 43 | 44 | 45 | @Test 46 | public void stringPropertyOnRootObjectRegex() { 47 | 48 | Condition condition1 = pipeline.apply("firstName=re=.*Paul$", User.class); 49 | 50 | assertPredicate(condition1, new User().setFirstName("Paul")); 51 | 52 | assertNotPredicate(condition1, new User().setFirstName("Joe")); 53 | 54 | assertMongo(condition1, "{ \"firstName\" : { \"$regex\" : \".*Paul$\"}}"); 55 | 56 | assertElasticsearch(condition1, "{\n" + 57 | " \"regexp\" : {\n" + 58 | " \"firstName\" : {\n" + 59 | " \"value\" : \".*Paul$\",\n" + 60 | " \"flags_value\" : 65535\n" + 61 | " }\n" + 62 | " }\n" + 63 | "}"); 64 | } 65 | 66 | @Test 67 | public void numberPropertyOnRootObject() { 68 | 69 | Condition condition = pipeline.apply("age==23", User.class); 70 | 71 | assertPredicate(condition, new User().setAge(23)); 72 | 73 | assertNotPredicate(condition, new User().setAge(24)); 74 | 75 | assertMongo(condition, "{ \"age\" : 23}"); 76 | 77 | assertElasticsearch(condition, "{\n" + 78 | " \"term\" : {\n" + 79 | " \"age\" : 23\n" + 80 | " }\n" + 81 | "}"); 82 | 83 | } 84 | 85 | @Test 86 | public void booleanPropertyOnRootObject() { 87 | 88 | Condition condition = pipeline.apply("enabled==true", User.class); 89 | 90 | assertPredicate(condition, new User().setEnabled(true)); 91 | 92 | assertNotPredicate(condition, new User().setEnabled(false)); 93 | 94 | assertMongo(condition, "{ \"enabled\" : true}"); 95 | 96 | assertElasticsearch(condition, "{\n" + 97 | " \"term\" : {\n" + 98 | " \"enabled\" : true\n" + 99 | " }\n" + 100 | "}"); 101 | 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /src/test/java/com/github/rutledgepaulv/rqe/pipes/TestBase.java: -------------------------------------------------------------------------------- 1 | /* 2 | * com.github.rutledgepaulv.rqe.pipes.TestBase 3 | * * 4 | * * Copyright (C) 2016 Paul Rutledge 5 | * * 6 | * * This software may be modified and distributed under the terms 7 | * * of the MIT license. See the LICENSE file for details. 8 | * 9 | */ 10 | 11 | package com.github.rutledgepaulv.rqe.pipes; 12 | 13 | import com.github.rutledgepaulv.qbuilders.conditions.Condition; 14 | import com.github.rutledgepaulv.qbuilders.visitors.ElasticsearchVisitor; 15 | import com.github.rutledgepaulv.qbuilders.visitors.MongoVisitor; 16 | import com.github.rutledgepaulv.qbuilders.visitors.PredicateVisitor; 17 | import com.github.rutledgepaulv.rqe.testsupport.CriteriaSerializer; 18 | import org.elasticsearch.index.query.QueryBuilder; 19 | import org.springframework.data.mongodb.core.query.Criteria; 20 | 21 | import java.util.function.Predicate; 22 | 23 | import static org.junit.Assert.*; 24 | 25 | public abstract class TestBase { 26 | 27 | public static RunMode MODE = RunMode.PRINT; 28 | 29 | public enum RunMode { 30 | TEST, 31 | PRINT 32 | } 33 | 34 | public void assertElasticsearch(Condition condition, String expected) { 35 | QueryBuilder criteria = condition.query(new ElasticsearchVisitor(), new ElasticsearchVisitor.Context()); 36 | String actual = criteria.toString(); 37 | doOrPrint(() -> assertEquals(expected, actual), actual); 38 | } 39 | 40 | public void assertMongo(Condition condition, String expected) { 41 | Criteria criteria = condition.query(new MongoVisitor()); 42 | String actual = new CriteriaSerializer().apply(criteria); 43 | doOrPrint(() -> assertEquals(expected, actual), actual); 44 | } 45 | 46 | public void assertPredicate(Condition condition, Object object) { 47 | Predicate pred = condition.query(new PredicateVisitor<>()); 48 | assertTrue(pred.test(object)); 49 | } 50 | 51 | public void assertNotPredicate(Condition condition, Object object) { 52 | Predicate pred = condition.query(new PredicateVisitor<>()); 53 | assertFalse(pred.test(object)); 54 | } 55 | 56 | public void doOrPrint(Runnable doMe, String printMe) { 57 | if(MODE == RunMode.TEST) { 58 | doMe.run(); 59 | } else { 60 | System.out.println(printMe); 61 | } 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/test/java/com/github/rutledgepaulv/rqe/testsupport/Address.java: -------------------------------------------------------------------------------- 1 | /* 2 | * com.github.rutledgepaulv.rqe.testsupport.Address 3 | * * 4 | * * Copyright (C) 2016 Paul Rutledge 5 | * * 6 | * * This software may be modified and distributed under the terms 7 | * * of the MIT license. See the LICENSE file for details. 8 | * 9 | */ 10 | 11 | package com.github.rutledgepaulv.rqe.testsupport; 12 | 13 | public class Address { 14 | 15 | private String street; 16 | private String city; 17 | private State state; 18 | private int unit; 19 | private boolean hasCat; 20 | 21 | public int getUnit() { 22 | return unit; 23 | } 24 | 25 | public Address setUnit(int unit) { 26 | this.unit = unit; 27 | return this; 28 | } 29 | 30 | public State getState() { 31 | return state; 32 | } 33 | 34 | public Address setState(State state) { 35 | this.state = state; 36 | return this; 37 | } 38 | 39 | public String getCity() { 40 | return city; 41 | } 42 | 43 | public Address setCity(String city) { 44 | this.city = city; 45 | return this; 46 | } 47 | 48 | public String getStreet() { 49 | return street; 50 | } 51 | 52 | public Address setStreet(String street) { 53 | this.street = street; 54 | return this; 55 | } 56 | 57 | public boolean isHasCat() { 58 | return hasCat; 59 | } 60 | 61 | public Address setHasCat(boolean hasCat) { 62 | this.hasCat = hasCat; 63 | return this; 64 | } 65 | 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/test/java/com/github/rutledgepaulv/rqe/testsupport/Comment.java: -------------------------------------------------------------------------------- 1 | /* 2 | * com.github.rutledgepaulv.rqe.testsupport.Comment 3 | * * 4 | * * Copyright (C) 2016 Paul Rutledge 5 | * * 6 | * * This software may be modified and distributed under the terms 7 | * * of the MIT license. See the LICENSE file for details. 8 | * 9 | */ 10 | 11 | package com.github.rutledgepaulv.rqe.testsupport; 12 | 13 | import java.time.Instant; 14 | import java.util.Objects; 15 | 16 | public class Comment { 17 | 18 | private String comment; 19 | private Instant timestamp; 20 | 21 | public String getComment() { 22 | return comment; 23 | } 24 | 25 | public Comment setComment(String comment) { 26 | this.comment = comment; 27 | return this; 28 | } 29 | 30 | public Instant getTimestamp() { 31 | return timestamp; 32 | } 33 | 34 | public Comment setTimestamp(Instant timestamp) { 35 | this.timestamp = timestamp; 36 | return this; 37 | } 38 | 39 | @Override 40 | public boolean equals(Object o) { 41 | if (this == o) { 42 | return true; 43 | } 44 | if (!(o instanceof Comment)) { 45 | return false; 46 | } 47 | Comment comment1 = (Comment) o; 48 | return Objects.equals(comment, comment1.comment) && Objects.equals(timestamp, comment1.timestamp); 49 | } 50 | 51 | @Override 52 | public int hashCode() { 53 | return Objects.hash(comment, timestamp); 54 | } 55 | 56 | @Override 57 | public String toString() { 58 | return "Comment{" + 59 | "comment='" + comment + '\'' + 60 | ", timestamp=" + timestamp + 61 | '}'; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/test/java/com/github/rutledgepaulv/rqe/testsupport/CommentQuery.java: -------------------------------------------------------------------------------- 1 | /* 2 | * com.github.rutledgepaulv.rqe.testsupport.CommentQuery 3 | * * 4 | * * Copyright (C) 2016 Paul Rutledge 5 | * * 6 | * * This software may be modified and distributed under the terms 7 | * * of the MIT license. See the LICENSE file for details. 8 | * 9 | */ 10 | 11 | package com.github.rutledgepaulv.rqe.testsupport; 12 | 13 | import com.github.rutledgepaulv.qbuilders.builders.QBuilder; 14 | import com.github.rutledgepaulv.qbuilders.properties.concrete.InstantProperty; 15 | import com.github.rutledgepaulv.qbuilders.properties.concrete.StringProperty; 16 | 17 | public class CommentQuery extends QBuilder { 18 | 19 | public StringProperty comment() { 20 | return string("comment"); 21 | } 22 | 23 | public InstantProperty timestamp() { 24 | return instant("timestamp"); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/test/java/com/github/rutledgepaulv/rqe/testsupport/CriteriaSerializer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * com.github.rutledgepaulv.rqe.testsupport.CriteriaSerializer 3 | * * 4 | * * Copyright (C) 2016 Paul Rutledge 5 | * * 6 | * * This software may be modified and distributed under the terms 7 | * * of the MIT license. See the LICENSE file for details. 8 | * 9 | */ 10 | 11 | package com.github.rutledgepaulv.rqe.testsupport; 12 | 13 | import com.mongodb.DBObject; 14 | import org.springframework.data.mongodb.core.query.Criteria; 15 | 16 | import java.util.Collection; 17 | import java.util.List; 18 | import java.util.Objects; 19 | import java.util.function.Function; 20 | import java.util.stream.Collectors; 21 | 22 | /** 23 | * Spring doesn't know how to serialize enums on a toString call 24 | */ 25 | public class CriteriaSerializer implements Function { 26 | 27 | @Override 28 | public String apply(Criteria criteria) { 29 | return applyInternal(criteria.getCriteriaObject()).toString(); 30 | } 31 | 32 | private DBObject applyInternal(DBObject object) { 33 | object.keySet().stream().forEach(key -> 34 | object.put(key, object.get(key) instanceof Enum ? 35 | object.get(key).toString() : object.get(key) instanceof DBObject ? 36 | applyInternal((DBObject) object.get(key)) : object.get(key) instanceof Collection ? 37 | applyList((Collection)object.get(key)) : object.get(key))); 38 | 39 | return object; 40 | } 41 | 42 | 43 | private List applyList(Collection items) { 44 | return items.stream().map(item -> { 45 | if (item instanceof Enum) { 46 | return Objects.toString(item); 47 | } else { 48 | return item; 49 | }}).collect(Collectors.toList()); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/test/java/com/github/rutledgepaulv/rqe/testsupport/State.java: -------------------------------------------------------------------------------- 1 | /* 2 | * com.github.rutledgepaulv.rqe.testsupport.State 3 | * * 4 | * * Copyright (C) 2016 Paul Rutledge 5 | * * 6 | * * This software may be modified and distributed under the terms 7 | * * of the MIT license. See the LICENSE file for details. 8 | * 9 | */ 10 | 11 | package com.github.rutledgepaulv.rqe.testsupport; 12 | 13 | public enum State { 14 | MINNESOTA, 15 | ILLINOIS, 16 | IOWA, 17 | MICHIGAN, 18 | WISCONSIN, 19 | SOUTH_DAKOTA, 20 | NORTH_DAKOTA, 21 | NEBRASKA 22 | } 23 | -------------------------------------------------------------------------------- /src/test/java/com/github/rutledgepaulv/rqe/testsupport/User.java: -------------------------------------------------------------------------------- 1 | /* 2 | * com.github.rutledgepaulv.rqe.testsupport.User 3 | * * 4 | * * Copyright (C) 2016 Paul Rutledge 5 | * * 6 | * * This software may be modified and distributed under the terms 7 | * * of the MIT license. See the LICENSE file for details. 8 | * 9 | */ 10 | 11 | package com.github.rutledgepaulv.rqe.testsupport; 12 | 13 | import java.util.LinkedList; 14 | import java.util.List; 15 | 16 | public class User { 17 | 18 | private int age; 19 | private String id; 20 | private String firstName; 21 | private boolean enabled; 22 | private Address address = new Address(); 23 | private List comments = new LinkedList<>(); 24 | 25 | 26 | public String getId() { 27 | return id; 28 | } 29 | 30 | public User setId(String id) { 31 | this.id = id; 32 | return this; 33 | } 34 | 35 | public String getFirstName() { 36 | return firstName; 37 | } 38 | 39 | public User setFirstName(String firstName) { 40 | this.firstName = firstName; 41 | return this; 42 | } 43 | 44 | public int getAge() { 45 | return age; 46 | } 47 | 48 | public User setAge(int age) { 49 | this.age = age; 50 | return this; 51 | } 52 | 53 | public Address getAddress() { 54 | return address; 55 | } 56 | 57 | public User setAddress(Address address) { 58 | this.address = address; 59 | return this; 60 | } 61 | 62 | public List getComments() { 63 | return comments; 64 | } 65 | 66 | public User setComments(List comments) { 67 | this.comments = comments; 68 | return this; 69 | } 70 | 71 | public boolean isEnabled() { 72 | return enabled; 73 | } 74 | 75 | public User setEnabled(boolean enabled) { 76 | this.enabled = enabled; 77 | return this; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/test/java/com/github/rutledgepaulv/rqe/testsupport/UserQuery.java: -------------------------------------------------------------------------------- 1 | /* 2 | * com.github.rutledgepaulv.rqe.testsupport.UserQuery 3 | * * 4 | * * Copyright (C) 2016 Paul Rutledge 5 | * * 6 | * * This software may be modified and distributed under the terms 7 | * * of the MIT license. See the LICENSE file for details. 8 | * 9 | */ 10 | 11 | package com.github.rutledgepaulv.rqe.testsupport; 12 | 13 | import com.github.rutledgepaulv.qbuilders.builders.QBuilder; 14 | import com.github.rutledgepaulv.qbuilders.properties.concrete.ConditionProperty; 15 | import com.github.rutledgepaulv.qbuilders.properties.concrete.IntegerProperty; 16 | import com.github.rutledgepaulv.qbuilders.properties.concrete.StringProperty; 17 | 18 | public class UserQuery extends QBuilder { 19 | 20 | public StringProperty firstName() { 21 | return string("firstName"); 22 | } 23 | 24 | public IntegerProperty age() { 25 | return intNum("age"); 26 | } 27 | 28 | public ConditionProperty comments() { 29 | return condition("comments"); 30 | } 31 | 32 | } 33 | --------------------------------------------------------------------------------