From b41479bf7f22e54049e3a0f1ea4b71dc79a38f7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=CC=88rg=20Prante?= Date: Fri, 25 Nov 2016 00:06:17 +0100 Subject: [PATCH] initial commit --- .gitignore | 12 + .travis.yml | 7 + LICENSE.txt | 202 +++++++ README.adoc | 8 + build.gradle | 65 +++ config/checkstyle/checkstyle.xml | 323 ++++++++++++ gradle/ext.gradle | 8 + gradle/publish.gradle | 66 +++ gradle/sonarqube.gradle | 41 ++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 53556 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 164 ++++++ gradlew.bat | 90 ++++ settings.gradle | 1 + src/main/jacc/org/xbib/cql/CQL.jacc | 1 + src/main/java/org/xbib/cql/AbstractNode.java | 26 + src/main/java/org/xbib/cql/BooleanGroup.java | 38 ++ .../java/org/xbib/cql/BooleanOperator.java | 80 +++ src/main/java/org/xbib/cql/CQLGenerator.java | 224 ++++++++ src/main/java/org/xbib/cql/Comparitor.java | 80 +++ src/main/java/org/xbib/cql/Identifier.java | 33 ++ src/main/java/org/xbib/cql/Index.java | 51 ++ src/main/java/org/xbib/cql/Modifier.java | 45 ++ src/main/java/org/xbib/cql/ModifierList.java | 39 ++ src/main/java/org/xbib/cql/Node.java | 14 + .../java/org/xbib/cql/PrefixAssignment.java | 38 ++ src/main/java/org/xbib/cql/Query.java | 57 ++ src/main/java/org/xbib/cql/QueryFacet.java | 21 + src/main/java/org/xbib/cql/QueryFilter.java | 7 + src/main/java/org/xbib/cql/QueryOption.java | 16 + src/main/java/org/xbib/cql/Relation.java | 39 ++ src/main/java/org/xbib/cql/ScopedClause.java | 50 ++ src/main/java/org/xbib/cql/SearchClause.java | 62 +++ src/main/java/org/xbib/cql/SimpleName.java | 28 + src/main/java/org/xbib/cql/SingleSpec.java | 37 ++ src/main/java/org/xbib/cql/SortSpec.java | 38 ++ src/main/java/org/xbib/cql/SortedQuery.java | 39 ++ .../java/org/xbib/cql/SyntaxException.java | 25 + src/main/java/org/xbib/cql/Term.java | 147 ++++++ src/main/java/org/xbib/cql/Visitor.java | 38 ++ .../ElasticsearchFilterGenerator.java | 349 +++++++++++++ .../ElasticsearchQueryGenerator.java | 492 ++++++++++++++++++ .../cql/elasticsearch/FacetsGenerator.java | 177 +++++++ .../cql/elasticsearch/FilterGenerator.java | 338 ++++++++++++ .../cql/elasticsearch/QueryGenerator.java | 381 ++++++++++++++ .../xbib/cql/elasticsearch/SortGenerator.java | 109 ++++ .../cql/elasticsearch/SourceGenerator.java | 43 ++ .../org/xbib/cql/elasticsearch/Visitor.java | 24 + .../cql/elasticsearch/ast/Expression.java | 110 ++++ .../xbib/cql/elasticsearch/ast/Modifier.java | 49 ++ .../org/xbib/cql/elasticsearch/ast/Name.java | 52 ++ .../org/xbib/cql/elasticsearch/ast/Node.java | 16 + .../xbib/cql/elasticsearch/ast/Operator.java | 62 +++ .../org/xbib/cql/elasticsearch/ast/Token.java | 213 ++++++++ .../xbib/cql/elasticsearch/ast/TokenType.java | 9 + .../cql/elasticsearch/ast/package-info.java | 4 + .../model/ElasticsearchFacet.java | 93 ++++ .../model/ElasticsearchFilter.java | 53 ++ .../model/ElasticsearchQueryModel.java | 200 +++++++ .../cql/elasticsearch/model/package-info.java | 4 + .../xbib/cql/elasticsearch/package-info.java | 4 + .../org/xbib/cql/model/CQLQueryModel.java | 230 ++++++++ src/main/java/org/xbib/cql/model/Facet.java | 70 +++ src/main/java/org/xbib/cql/model/Filter.java | 68 +++ src/main/java/org/xbib/cql/model/Option.java | 48 ++ .../breadcrumb/FacetBreadcrumbTrail.java | 32 ++ .../breadcrumb/FilterBreadcrumbTrail.java | 44 ++ .../breadcrumb/OptionBreadcrumbTrail.java | 39 ++ .../cql/model/breadcrumb/package-info.java | 4 + .../java/org/xbib/cql/model/package-info.java | 4 + src/main/java/org/xbib/cql/package-info.java | 4 + src/main/java/org/xbib/cql/util/DateUtil.java | 316 +++++++++++ .../org/xbib/cql/util/QueryStringDecoder.java | 363 +++++++++++++ .../xbib/cql/util/QuotedStringTokenizer.java | 233 +++++++++ .../UnterminatedQuotedStringException.java | 11 + .../java/org/xbib/cql/util/package-info.java | 4 + src/main/jflex/org/xbib/cql/CQL.jflex | 194 +++++++ src/test/java/org/xbib/cql/QueryTest.java | 60 +++ .../elasticsearch/ElasticsearchQueryTest.java | 128 +++++ .../xbib/cql/elasticsearch/package-info.java | 4 + src/test/java/org/xbib/cql/package-info.java | 4 + .../cql/util/QuotedStringTokenizerTest.java | 22 + .../java/org/xbib/cql/util/package-info.java | 4 + src/test/resources/log4j2.xml | 13 + .../org/xbib/cql/elasticsearch/queries.txt | 123 +++++ src/test/resources/org/xbib/cql/queries.txt | 141 +++++ 86 files changed, 7211 insertions(+) create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 LICENSE.txt create mode 100644 README.adoc create mode 100644 build.gradle create mode 100644 config/checkstyle/checkstyle.xml create mode 100644 gradle/ext.gradle create mode 100644 gradle/publish.gradle create mode 100644 gradle/sonarqube.gradle create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle create mode 100644 src/main/jacc/org/xbib/cql/CQL.jacc create mode 100644 src/main/java/org/xbib/cql/AbstractNode.java create mode 100644 src/main/java/org/xbib/cql/BooleanGroup.java create mode 100644 src/main/java/org/xbib/cql/BooleanOperator.java create mode 100644 src/main/java/org/xbib/cql/CQLGenerator.java create mode 100644 src/main/java/org/xbib/cql/Comparitor.java create mode 100644 src/main/java/org/xbib/cql/Identifier.java create mode 100644 src/main/java/org/xbib/cql/Index.java create mode 100644 src/main/java/org/xbib/cql/Modifier.java create mode 100644 src/main/java/org/xbib/cql/ModifierList.java create mode 100644 src/main/java/org/xbib/cql/Node.java create mode 100644 src/main/java/org/xbib/cql/PrefixAssignment.java create mode 100644 src/main/java/org/xbib/cql/Query.java create mode 100644 src/main/java/org/xbib/cql/QueryFacet.java create mode 100644 src/main/java/org/xbib/cql/QueryFilter.java create mode 100644 src/main/java/org/xbib/cql/QueryOption.java create mode 100644 src/main/java/org/xbib/cql/Relation.java create mode 100644 src/main/java/org/xbib/cql/ScopedClause.java create mode 100644 src/main/java/org/xbib/cql/SearchClause.java create mode 100644 src/main/java/org/xbib/cql/SimpleName.java create mode 100644 src/main/java/org/xbib/cql/SingleSpec.java create mode 100644 src/main/java/org/xbib/cql/SortSpec.java create mode 100644 src/main/java/org/xbib/cql/SortedQuery.java create mode 100644 src/main/java/org/xbib/cql/SyntaxException.java create mode 100644 src/main/java/org/xbib/cql/Term.java create mode 100644 src/main/java/org/xbib/cql/Visitor.java create mode 100644 src/main/java/org/xbib/cql/elasticsearch/ElasticsearchFilterGenerator.java create mode 100644 src/main/java/org/xbib/cql/elasticsearch/ElasticsearchQueryGenerator.java create mode 100644 src/main/java/org/xbib/cql/elasticsearch/FacetsGenerator.java create mode 100644 src/main/java/org/xbib/cql/elasticsearch/FilterGenerator.java create mode 100644 src/main/java/org/xbib/cql/elasticsearch/QueryGenerator.java create mode 100644 src/main/java/org/xbib/cql/elasticsearch/SortGenerator.java create mode 100644 src/main/java/org/xbib/cql/elasticsearch/SourceGenerator.java create mode 100644 src/main/java/org/xbib/cql/elasticsearch/Visitor.java create mode 100644 src/main/java/org/xbib/cql/elasticsearch/ast/Expression.java create mode 100644 src/main/java/org/xbib/cql/elasticsearch/ast/Modifier.java create mode 100644 src/main/java/org/xbib/cql/elasticsearch/ast/Name.java create mode 100644 src/main/java/org/xbib/cql/elasticsearch/ast/Node.java create mode 100644 src/main/java/org/xbib/cql/elasticsearch/ast/Operator.java create mode 100644 src/main/java/org/xbib/cql/elasticsearch/ast/Token.java create mode 100644 src/main/java/org/xbib/cql/elasticsearch/ast/TokenType.java create mode 100644 src/main/java/org/xbib/cql/elasticsearch/ast/package-info.java create mode 100644 src/main/java/org/xbib/cql/elasticsearch/model/ElasticsearchFacet.java create mode 100644 src/main/java/org/xbib/cql/elasticsearch/model/ElasticsearchFilter.java create mode 100644 src/main/java/org/xbib/cql/elasticsearch/model/ElasticsearchQueryModel.java create mode 100644 src/main/java/org/xbib/cql/elasticsearch/model/package-info.java create mode 100644 src/main/java/org/xbib/cql/elasticsearch/package-info.java create mode 100644 src/main/java/org/xbib/cql/model/CQLQueryModel.java create mode 100644 src/main/java/org/xbib/cql/model/Facet.java create mode 100644 src/main/java/org/xbib/cql/model/Filter.java create mode 100644 src/main/java/org/xbib/cql/model/Option.java create mode 100644 src/main/java/org/xbib/cql/model/breadcrumb/FacetBreadcrumbTrail.java create mode 100644 src/main/java/org/xbib/cql/model/breadcrumb/FilterBreadcrumbTrail.java create mode 100644 src/main/java/org/xbib/cql/model/breadcrumb/OptionBreadcrumbTrail.java create mode 100644 src/main/java/org/xbib/cql/model/breadcrumb/package-info.java create mode 100644 src/main/java/org/xbib/cql/model/package-info.java create mode 100644 src/main/java/org/xbib/cql/package-info.java create mode 100644 src/main/java/org/xbib/cql/util/DateUtil.java create mode 100644 src/main/java/org/xbib/cql/util/QueryStringDecoder.java create mode 100644 src/main/java/org/xbib/cql/util/QuotedStringTokenizer.java create mode 100644 src/main/java/org/xbib/cql/util/UnterminatedQuotedStringException.java create mode 100644 src/main/java/org/xbib/cql/util/package-info.java create mode 100644 src/main/jflex/org/xbib/cql/CQL.jflex create mode 100644 src/test/java/org/xbib/cql/QueryTest.java create mode 100644 src/test/java/org/xbib/cql/elasticsearch/ElasticsearchQueryTest.java create mode 100644 src/test/java/org/xbib/cql/elasticsearch/package-info.java create mode 100644 src/test/java/org/xbib/cql/package-info.java create mode 100644 src/test/java/org/xbib/cql/util/QuotedStringTokenizerTest.java create mode 100644 src/test/java/org/xbib/cql/util/package-info.java create mode 100644 src/test/resources/log4j2.xml create mode 100644 src/test/resources/org/xbib/cql/elasticsearch/queries.txt create mode 100644 src/test/resources/org/xbib/cql/queries.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3e42bcc --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +/data +/work +/logs +/.idea +/target +.DS_Store +*.iml +/.settings +/.classpath +/.project +/.gradle +/build \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..57000f7 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,7 @@ +sudo: false +language: java +jdk: + - oraclejdk8 +cache: + directories: + - $HOME/.m2 diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.adoc b/README.adoc new file mode 100644 index 0000000..951580c --- /dev/null +++ b/README.adoc @@ -0,0 +1,8 @@ +# xbib Contextual Query Language Compiler + +image:https://api.travis-ci.org/xbib/cql.svg[title="Build status", link="https://travis-ci.org/xbib/cql/"] +image:https://img.shields.io/sonar/http/nemo.sonarqube.com/org.xbib%3Acql/coverage.svg?style=flat-square[title="Coverage", link="https://sonarqube.com/dashboard/index?id=org.xbib%3Acql"] +image:https://maven-badges.herokuapp.com/maven-central/org.xbib/cql/badge.svg[title="Maven Central", link="http://search.maven.org/#search%7Cga%7C1%7Cxbib%20cql"] +image:https://img.shields.io/badge/License-Apache%202.0-blue.svg[title="Apache License 2.0", link="https://opensource.org/licenses/Apache-2.0"] +image:https://img.shields.io/twitter/url/https/twitter.com/xbib.svg?style=social&label=Follow%20%40xbib[title="Twitter", link="https://twitter.com/xbib"] +image:https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif[title="PayPal", link="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=GVHFQYZ9WZ8HG"] diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..436e3e6 --- /dev/null +++ b/build.gradle @@ -0,0 +1,65 @@ +plugins { + id 'org.xbib.gradle.plugin.jflex' version '1.1.0' + id 'org.xbib.gradle.plugin.jacc' version '1.1.3' + id "org.sonarqube" version '2.2' +} + +group = 'org.xbib' +version = '1.0.0' + +apply plugin: 'java' +apply plugin: 'maven' +apply plugin: 'signing' +apply plugin: 'findbugs' +apply plugin: 'pmd' +apply plugin: 'checkstyle' +apply plugin: 'jacoco' + +repositories { + mavenCentral() +} + +configurations { + wagon +} + +dependencies { + compile 'org.xbib:content-core:1.0.6' + testCompile 'junit:junit:4.12' + wagon 'org.apache.maven.wagon:wagon-ssh-external:2.10' +} + +sourceCompatibility = JavaVersion.VERSION_1_8 +targetCompatibility = JavaVersion.VERSION_1_8 + +[compileJava, compileTestJava]*.options*.encoding = 'UTF-8' +tasks.withType(JavaCompile) { + options.compilerArgs << "-Xlint:all" << "-profile" << "compact1" +} + +test { + testLogging { + showStandardStreams = false + exceptionFormat = 'full' + } +} + +task sourcesJar(type: Jar, dependsOn: classes) { + classifier 'sources' + from sourceSets.main.allSource +} +task javadocJar(type: Jar, dependsOn: javadoc) { + classifier 'javadoc' +} +artifacts { + archives sourcesJar, javadocJar +} +if (project.hasProperty('signing.keyId')) { + signing { + sign configurations.archives + } +} + +apply from: 'gradle/ext.gradle' +apply from: 'gradle/publish.gradle' +apply from: 'gradle/sonarqube.gradle' diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml new file mode 100644 index 0000000..52fe33c --- /dev/null +++ b/config/checkstyle/checkstyle.xml @@ -0,0 +1,323 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gradle/ext.gradle b/gradle/ext.gradle new file mode 100644 index 0000000..9b3403e --- /dev/null +++ b/gradle/ext.gradle @@ -0,0 +1,8 @@ +ext { + user = 'xbib' + projectName = 'cql' + projectDescription = 'Contextual Query Language compiler for Java' + scmUrl = 'https://github.com/xbib/cql' + scmConnection = 'scm:git:git://github.com/xbib/cql.git' + scmDeveloperConnection = 'scm:git:git://github.com/xbib/cql.git' +} diff --git a/gradle/publish.gradle b/gradle/publish.gradle new file mode 100644 index 0000000..caf0531 --- /dev/null +++ b/gradle/publish.gradle @@ -0,0 +1,66 @@ + +task xbibUpload(type: Upload, dependsOn: build) { + configuration = configurations.archives + uploadDescriptor = true + repositories { + if (project.hasProperty('xbibUsername')) { + mavenDeployer { + configuration = configurations.wagon + repository(url: uri('scpexe://xbib.org/repository')) { + authentication(userName: xbibUsername, privateKey: xbibPrivateKey) + } + } + } + } +} + +task sonatypeUpload(type: Upload, dependsOn: build) { + configuration = configurations.archives + uploadDescriptor = true + repositories { + if (project.hasProperty('ossrhUsername')) { + mavenDeployer { + beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } + repository(url: uri(ossrhReleaseUrl)) { + authentication(userName: ossrhUsername, password: ossrhPassword) + } + snapshotRepository(url: uri(ossrhSnapshotUrl)) { + authentication(userName: ossrhUsername, password: ossrhPassword) + } + pom.project { + groupId project.group + artifactId project.name + version project.version + name project.name + description projectDescription + packaging 'jar' + inceptionYear '2016' + url scmUrl + organization { + name 'xbib' + url 'http://xbib.org' + } + developers { + developer { + id user + name 'Jörg Prante' + email 'joergprante@gmail.com' + url 'https://github.com/jprante' + } + } + scm { + url scmUrl + connection scmConnection + developerConnection scmDeveloperConnection + } + licenses { + license { + name 'The Apache License, Version 2.0' + url 'http://www.apache.org/licenses/LICENSE-2.0.txt' + } + } + } + } + } + } +} diff --git a/gradle/sonarqube.gradle b/gradle/sonarqube.gradle new file mode 100644 index 0000000..6d4c3fa --- /dev/null +++ b/gradle/sonarqube.gradle @@ -0,0 +1,41 @@ +tasks.withType(FindBugs) { + ignoreFailures = true + reports { + xml.enabled = true + html.enabled = false + } +} +tasks.withType(Pmd) { + ignoreFailures = true + reports { + xml.enabled = true + html.enabled = true + } +} +tasks.withType(Checkstyle) { + ignoreFailures = true + reports { + xml.enabled = true + html.enabled = true + } +} + +jacocoTestReport { + reports { + xml.enabled true + csv.enabled false + xml.destination "${buildDir}/reports/jacoco-xml" + html.destination "${buildDir}/reports/jacoco-html" + } +} + +sonarqube { + properties { + property "sonar.projectName", "${project.group} ${project.name}" + property "sonar.sourceEncoding", "UTF-8" + property "sonar.tests", "src/test/java" + property "sonar.scm.provider", "git" + property "sonar.java.coveragePlugin", "jacoco" + property "sonar.junit.reportsPath", "build/test-results/test/" + } +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..ca78035ef0501d802d4fc55381ef2d5c3ce0ec6e GIT binary patch literal 53556 zcmafaW3XsJ(%7|a+qP}nwr$(CZQFj=wr$(@UA(+xH(#=wO)^z|&iv@9neOWDX^nz3 zFbEU?00abpJ7cBo`loO)|22l7HMDRNfRDr(;s(%6He@B!R zl#>(_RaT*s6?>AMo|2KKrCWfNrlp#lo@-WOSZ3Zod7P#lmzMGa(ZwA{NHx8{)|HLtOGBmL<{ePk& z|0}Aylc9rysnh?l#3IPVtoSeL%3mP<&r3w?-R*4b4NXWG>5Od*ot=GSWT6Hb5JLAX zShc9#=!2lw!t#FMI}pFJc zw6Uj8`Bst|cD2?nsG(d*ZG#%NF?Y80v0PGQSJPsUg@n3BQIkW_dR~d>N{{*bSH}Pd zIWdTJ#iH#>%S&)$tqoH6b*V7fLp<>(xL_ji`jq2`%oD)~iD7`@hsO@Vy3*qM{u`G^ zc0*TD{z`zuUlxn}e`r+pbapYdRdBNZ%Pbd5Q|G@k4^Kf?7YkE67fWM97kj6FFrif0 z)*eX^!4Hihd~D&c(x5hVbJa`bB+7ol01GlU5|UB2N>+y7))3gd&fUa5@v;6n+Lq-3 z{Jl7)Ss;}F5czIs_L}Eunuojl?dWXn4q(#5iYPV+5*ifPnsS@1F)kK`O<80078hB& z!Uu$#cM=e$$6FUI2Uys(|$Fxqmy zG@_F97OGMH;TUgxma36@BQi`!B{e(ZeayiDo z;os4R9{50YQVC-ThdC9S{Ee)4ikHa8|X*ach%>dfECip|EPi!8S zDh{J&bjYD?EYtrlYx3Xq_Uu~2x$3X9ZT$tJ|15Qq|5LU8AycBUzy2x~OxU04i>D z9w@yRqlcbqC}2T_XT5eNHYx5)7rtz8{DE*J?o>>OiS)0JC!ZaB0JL-Ob1w)8zanZ< zR(Xiz3$ioy*%XQmL-bJnNfvE$rI2P~LX90G#gt4nb9mku*6S{mqFw`_kt{LAkj!x21fSFo(-^4px?_hH9-@XW8zqNrs(RYSX5R zn7kQuX>YGYLyM(G>^wtn&><_Q!~W27r537fQwZIqYL965<@&T|=xUF6c$g=5 z9B|kBeu>}r8R@-o3b!=}4_HG6sot1tgjjbmglPS~q)5GX6CU&gxsD0v9llaw7Bh7W zG`o>aya0{@c}L+Gw`1PRqcl6e6}@o3Bcd#mP)9H<2a|Wi{ZWqCzX%93IfRpvQ5Gba z7lEPC4fM4WC?*W3IpV-cRPh5Sc}Q>vS@2qu<+V(nS%!Sm&*^W!gSj)# z5h9&o{KIKp2kov&g`CP%-CqAqA#o0Mw?;q#0Dk{<4VeG4n2LHB+qgPgx|xbu+L#I& z8=E>i%Np7lnw$R9>ZhtnJ0P3l{ISg3VawG!KBZ_pvN2DYtK&W!-f06 z`*U{p=QkVw&*us(0Q^xhL0e%n5Ms&j;)%FBf*#J>kq82xOVpI4<0WK)`n9DXCuv$A zfn4!kd?3Iqh$3+WD+l&4vj>}m@*Jom+}vj&2m=KQGoVRm7M2KY7**ns0|M5px)Deh zez6~hUk1`@NgO%XoGXd)&6$_Hs|(2|X^7HUDkEtbwHV#1wRTpbb)rHlLu^njhFg9S zx+)}U8(USDXm>S%pp;a_Y<5>3i_Hp_vWwtzt5uj8ewqTFEE)E15)Wjvv?x}}8HMiX z;^3-OH85AzcV_0O-Exhrj`RpUZ;j$qjmZ|L#+*_US5`JV%8wqakxhD&XCpyuWo{N- z+bNS}p+afKlpHI>3VBBeq|G8boGeUaC)(Ru3u`YLW30>~)5=GL=sUjLgu65%VcPGs}PA z2_OLv=2)9Xm11f*FTt*o*yc8FG>4G~q{mOUX#}$!=u>KSGyX(=*}&rI;2K(U?Koxp z7F-pc*}}pO@m;7sff=FGTE4TA9ZNTRx%XWeaa|lx9o$qjHByj0HxuO5TvpM}CwTW> z#R=1vZp)76kO?#z;(>6Mu&gCwrlvRCVG_g8sMl;^DrH)&-*)v5ZHl3IWWpPi!|ZNQ z4&vdL!lWNaYH)lo!KJkFQfoCqF_@w-in(c2pNkpCKo6my8_yVs_Uj=zGVLKUT#^z^ z-)|f>)fuk#(@A>3(o0VqQ1$4+z_E9HCQ7R^ z30tu-(OIxDiiOEkGpXw&zReM}VP+C}bFAvU5%L?0cQ@?`fBSwH7!4o)d`OImPc+X< zrwk1#`^<8L8#>HOQb0pxt)HxXg%o|3x3nsPjSioaPqZ^lnSNOaJHg}1zqdDur0PoP zRVh{xV61JsNFuq`Xd6MtK*HtXN?NH20{)o}s_-I*YU7#=qn8b)kV`MS%A%ewrx<5I zY9{WpWlK^G^SP=5nvS-WEy+2%2}G?;#q01CSQ@%UJgw>}sHVEQip4`tToFyKHmwTV z-vWa!(`#8lj^drh)TLYVZLU!F!ak3OPw(qUajt(mO&u~ANUN%r3KUzV%k%|1=7Iat z5Pt`rL>P6u2G|qX<$)j~A0r2ZdE%y2n!@s>8}^KzEQEj6Kc?A%>r0ye>xB@wj|1Ob47`2EH4(rA(O{ zU}u2kj}N3&2?^3EQ{aT{?2g=~RLM;{)T7k%gI$^7qr`&%?-K{7Z|xhUKgd+!`-Yie zuE4Z_s?8kT>|npn6{66?E4$Pc2K(`?YTz3q(aigbu-ShRhKK|(f0cCh1&Q1?!Rr=v&a!K}wA-|$Gr{J~k~ z7@gS_x|i#V?>C5h_S4>+&Y9UC;Z@h2@kZgiJ|M%c)C38h@es^Y`p#a9|M_8mi3pR( z6*QJ0&b&7q+!3NCbBMs(x}XlEUyQp~0K9id;Wx1KycVf%ae(I8KJgjc!$0vE-NSwS zEu2^31P|2W6P)+j90blNtRJ5=DmAN?R}TD4!&z=N=@IeHhDTl-!_-e0hc?;+-;cCJ zm~zCBdd&GjPVt9?QcvkJQtf#Mv5mGLq7;pHYUils+`Yo8=kJB06UOcuYC;cMU2)oG zMH>rDE_p-R8=u3n)w%~+lE$>My@gq^RU(c_#Yk|`!Sjm$ug=Rfte#lnU+3im?EmV# zsQ)8&61KN9vov>gGIX)DxBI8_l58uFEQm1nXX|V=m@g=xsEFu>FsERj84_NVQ56PN z!biByA&vMXZd;f2LD`as@gWp{0NymGSG%BQYnYw6nfWRI`$p&Ub8b!_;Pjp%TsmXI zfGrv)2Ikh0e{6<_{jJk;U`7Zl+LFg){?(TM{#uQ_K{wp6!O_Bx33d!Brgr9~942)4 zchrS8Old{AF_&$zBx^bCTQ74ka9H84%F{rOzJ`rkJjSB_^^pZqe9`VQ^HyUpX_!ZA z+f0In>sw`>{d(L>oA+{4&zo5_^6t%TX0Gj0^M@u0@~^-f=4Gt9HMY&X&b`K%xjauF z8_!X>V|CrL;+a6gp zKd)6{;@wH+A{&U6?dAu>etSxBD)@5z;S~6%oQqH(uVW(Ajr>Dy{pPKUlD+ zFbjJ6c69Zum)+VkzfW(gW7%C{gU6X+a{LH?s2^BS64n$B%cf()0AWRUIbQPhQ|q|& z55=zLH=!8-f5HKjA|4`9M&54<=^^w{`bc~@pMec>@~;_k-6-b93So0uesmwYOL zmrx9lp%heN8h0j@P=!rO5=@h9UIZ^85wMay-2UO?xo>XOHLK<6Q|uyT6%*f4V!dYTC-$swh8fk{pCMlf5hw+9jV|?GlEBEAx zj#np5nqD`peZ6m5`&-xKetv((^8@xo*!!N3lmt=YUou<_xyn#yJp3Y#wf`tEP?IB4 z>Mq>31$Blx^|cr*L09CYlW3$Ek;PY`k@ToRobo6~q}E71Oxr##L$~JJ9_?1@As_if z`YlL&yDtoy733P&wytI4>Gd;vxHw2O@+@KgbPa)>3z8mMkyAS%Fna#8Sg!uWhMEubF;n{i3Ae4j{$p>dYj-^9?1ysjK~i0Q(4XUQE? zq8WLEcE@FsQ%hrS`3O$YbyPGkF6o;%&dxfHG?_n@Z&K4vR@ieBC{}cst~pIc4R0u& zj`QUL>5UQF@PgvVoBbRAtoQ_wyeeA9wsSN9mXX-dN^aFG=EB_B_b{U`BenI&D=;Fj zT!n`sy{aPu9YibsEpvrQ^0t(q&Inj%Pca%Yu&!K1ORT4wD6j-dc+{?5(JAouXgIy8 z%-H6Fbhd6%S=KCeIm`}PC!@`F>UKx&(#(Exk?s77w@&*`_tZ&sgzQ!_QK=DBnare8 z;)ocuEeZw)R1@{BuzGzIj$Z6EqM#s17Zv{q88!cq88!bXFpB=ZG^k$1C)OSWOnz4h zh&DA{Lx8q4*47TCo_gzx?MlHD(Bx{$87ha%T$XB*_{8uv@LhK>VV`UY=tPjwOandObAG0 z65^99S$7U)%^i%0Rnv*|IFjxg{!=`YHMJK^XV#j)p>*^S8FcuGV-BAwAU)a(e+)Wj z<=0$&0zB{usg@89sQBDI-|(HM1iz{8?zwn?5-k8jfM6Uf#vp^D4ozQhw#0tB@N(_V z5G#8|@Ta&(7#{whu<-X6VG66*t5~?Wlg0j8JGkpMEo%Sg1fExMxWXFTg2;1a+bNC~ zMiFaxTcU3ZKjv)V5kM}`LLzVunn%c$N*BoJj-NZ6`Q{g=3;*E#!f_{#*C?+ad~5zZ z=keRIuK5M;04KWI+Ycv(7YzExxp+b(xFaY3Z^kf3mPKNCd{OQbO%F%7nd8P(nBNon z_?lN|<`FF*oN)KZYNm_512Er;<8GEqpFWsK<1M&j{|B zo5C*08{%HJJyGfROq44Q!PMdxq^&J+j?ahYI=`%GLh<*U*BGQ36lvssxuhS-weUq^_|F7sRH2KqhQ2}MFKYfgn|}o{=of1QHP+(v0l0HYK}G+OiNO_D__5DAvd@{ul69am-m8ERsfZLSCNp9cTU% zmH*GrZ`geV`DBTGGoW+_>cFiEGR0sT5#0!Gq3u)$0>Q+2gNXQYFn7##$e~T?O6@UKnaPmHYrr;IL66 zpHCH6FCU(hv{CKW&}j6$b_zL?RWjo+BMls3=9G<#5Tzqzb=To%u9RQYw&j~}FJ@T0 zwqYi7d0bfhOvCF+KQ?e8GFX^6Wr;#sLd>z=9rOo+Sn!Gx#S!8{JZOiICy=>JL!*Db z?0=i<6a%%-Qb$_VMK#jDzwycH@RdM&ODTf(BM+(VE<)*OfvATsOZ?;*Z|+KHl#LYV zwB(~69*ivMM^es;_qv2a`F=yr7hG(h9F_QsJdxq1W);`Gg)XvElwdAOhjO9z zZr>li{sH_~k(_n9ib4ek0I-7t03iF%BB@~LVj<}4Y-(%tUl(nv+J`Z=I^xgjDynBP zN0jq=Yp@Y{EX@X*q%wsh^8JcPZT)X5xy=r1Yhrts;iZ@>npp;KAbS=u^ z7C^t_c%Z%wUF|lirC0D?_B+enX?Etl?DjuDbKmTMIivlD98rUKIU`CqV0Ocly#&IF zVJ8$a8*L_yNF&jX!-@&G+9c#)>ZeLLirXnS+DtWKjc8+nJ|uDRlm6xpN-+4*hewV+ zK>0BT%8ou*`H3UuqFuNnXC^;BIAixsF!~XP(TYBlVf14Qq4mS}s)|2ZF#71(dk7cV zj6Tw*_G9cDz}0~ zXB=I`eTPx>~gi%8(4o7@g1GNnp$hJ_%Mg1`VLZDvLJeHGr+zT1&yk_ z)dbBKq?T{~APy~$Nlig_@z&C!xIWPDo3m~uxHe!qrNb26;xt|ht-7c7np#s+cje~J zZ~taj5)DfMbEaGGQw!+3dN0G2S=fRaa3rl z7Osx|l1jjjIOhCoaPxPQt1`ZxtLxIkA`VmUHN|vTlJRWNz<2C9m^>k4usuSUG})b%|D<wP^rU?JNVjdb*1yWsZBE8HZC}Q5va#I zsBwfZp;FX)RpB3EoWZyd4Bs{TNmbQ{0Kzz-0SgBPl2=f6IWi{9_QZu%rTT_|l31Q_ zycR4qyR5Il(L|CofDAL(ez5(KmRFo@U&>^{qK1eq^QMA`FZE_d6`2iXL�H$uJM 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 \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..f6d5974 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..a6041cf --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'cql' diff --git a/src/main/jacc/org/xbib/cql/CQL.jacc b/src/main/jacc/org/xbib/cql/CQL.jacc new file mode 100644 index 0000000..205d6f2 --- /dev/null +++ b/src/main/jacc/org/xbib/cql/CQL.jacc @@ -0,0 +1 @@ +%{ import java.io.StringReader; %} %class CQLParser %interface CQLTokens %package org.xbib.cql %token NL %token LPAR RPAR SLASH %token AND OR NOT PROX %token SORTBY %token GE LE NE EXACT LT GT EQ NAMEDCOMPARITORS %token SIMPLESTRING QUOTEDSTRING %token INTEGER %token FLOAT %left OR %left AND PROX %left NOT %type sortedQuery %type cqlQuery %type sortSpec %type singleSpec %type prefixAssignment %type scopedClause %type booleanGroup %type searchClause %type comparitor %type relation %type modifier %type modifierList %type index %type term %type identifier %type simpleName %type quotedString %start cql %% /* CQL 1.2 */ cql: sortedQuery { this.cql = $1; $$ = this.cql; } ; /* sortedQuery ::= prefixAssignment sortedQuery | scopedClause 'sortby' sortSpec | scopedClause --> sortedQuey ::= cqlQuery 'sortby' sortSpec | cqlQuery */ sortedQuery: cqlQuery SORTBY sortSpec { $$ = new SortedQuery($1, $3); } | cqlQuery { $$ = new SortedQuery($1); } ; /* sortSpec ::= sortSpec singleSpec | singleSpec */ sortSpec: sortSpec singleSpec { $$ = new SortSpec($1, $2); } | singleSpec { $$ = new SortSpec($1); } ; /* singleSpec ::= index modifierList | index */ singleSpec: index modifierList { $$ = new SingleSpec($1, $2); } | index { $$ = new SingleSpec($1); } ; /* cqlQuery ::= prefixAssignment cqlQuery | scopedClause */ cqlQuery: prefixAssignment cqlQuery { $$ = new Query($1, $2); } | scopedClause { $$ = new Query($1); } ; /* prefixAssignment ::= '>' prefix '=' uri | '>' uri */ prefixAssignment: GT term EQ term { $$ = new PrefixAssignment($2, $4); } | GT term { $$ = new PrefixAssignment($2); } ; /* scopedClause ::= scopedClause booleanGroup searchClause | searchClause */ scopedClause: scopedClause booleanGroup searchClause { $$ = new ScopedClause($1, $2, $3 ); } | searchClause { $$ = new ScopedClause($1); } ; /* booleanGroup ::= boolean modifierList | boolean */ booleanGroup: boolean modifierList { $$ = new BooleanGroup(BooleanOperator.forToken($1), $2); } | boolean { $$ = new BooleanGroup(BooleanOperator.forToken($1)); } ; /* boolean ::= 'and' | 'or' | 'not' | 'prox' */ boolean: AND | OR | NOT | PROX ; /* searchClause ::= '(' cqlQuery ')' | index relation searchTerm | searchTerm */ searchClause: LPAR cqlQuery RPAR { $$ = new SearchClause($2); } | index relation term { $$ = new SearchClause($1, $2, $3); } | term { $$ = new SearchClause($1); } ; /* relation ::= comparitor modifierList | comparitor */ relation: comparitor modifierList { $$ = new Relation($1, $2); } | comparitor { $$ = new Relation($1); } ; /* comparitor ::= comparitorSymbol | namedComparitor */ comparitor: comparitorSymbol { $$ = Comparitor.forToken($1); } | namedComparitor { $$ = Comparitor.forToken($1); } ; comparitorSymbol: EQ | LT | GT | GE | LE | NE | EXACT ; namedComparitor: NAMEDCOMPARITORS ; /* modifierList ::= modifierList modifier | modifier */ modifierList: modifierList modifier { $$ = new ModifierList($1,$2); } | modifier { $$ = new ModifierList($1); } ; /* modifier ::= '/' modifierName [comparitorSymbol modifierValue] */ modifier: SLASH simpleName comparitorSymbol term { $$ = new Modifier($2, Comparitor.forToken($3), $4); } | SLASH simpleName { $$ = new Modifier($2); } ; index: simpleName { $$ = new Index($1); } ; /* term ::= identifier | 'and' | 'or' | 'not' | 'prox' */ term: identifier { $$ = new Term($1); } | boolean { $$ = new Term(BooleanOperator.forToken($1).getToken()); } | INTEGER { $$ = new Term($1); } | FLOAT { $$ = new Term($1); } ; /* identifier ::= simpleName | quotedString */ identifier: simpleName { $$ = new Identifier($1); } | quotedString { $$ = new Identifier($1); } ; simpleName: SIMPLESTRING { $$ = new SimpleName($1); } ; quotedString: QUOTEDSTRING { $$ = $1; } ; %% private CQLLexer lexer; private String input; private SortedQuery cql; public CQLParser(String input) { this.input = input; this.lexer = new CQLLexer(new StringReader(input)); lexer.nextToken(); } public void yyerror (String error) { throw new SyntaxException("CQL syntax error at " + "[" + lexer.getLine() + "," + lexer.getColumn() + "] in\"" + input + "\": " + (yyerrno >= 0 ? yyerrmsgs[yyerrno] : error) + ": " + lexer.getSemantic()); } public SortedQuery getCQLQuery() { return cql; } \ No newline at end of file diff --git a/src/main/java/org/xbib/cql/AbstractNode.java b/src/main/java/org/xbib/cql/AbstractNode.java new file mode 100644 index 0000000..715d662 --- /dev/null +++ b/src/main/java/org/xbib/cql/AbstractNode.java @@ -0,0 +1,26 @@ +package org.xbib.cql; + +/** + * This abstract node class is the base class for the CQL abstract syntax tree. + */ +public abstract class AbstractNode implements Node { + + /** + * Try to accept this node by a visitor. + * + * @param visitor the visitor + */ + @Override + public abstract void accept(Visitor visitor); + + /** + * Compare this node to another node. + */ + @Override + public int compareTo(Node object) { + if (this == object) { + return 0; + } + return toString().compareTo(object.toString()); + } +} diff --git a/src/main/java/org/xbib/cql/BooleanGroup.java b/src/main/java/org/xbib/cql/BooleanGroup.java new file mode 100644 index 0000000..b8bfe62 --- /dev/null +++ b/src/main/java/org/xbib/cql/BooleanGroup.java @@ -0,0 +1,38 @@ +package org.xbib.cql; + +/** + * Abstract syntax tree of CQL - Boolean Group. + */ +public class BooleanGroup extends AbstractNode { + + private BooleanOperator op; + private ModifierList modifiers; + + BooleanGroup(BooleanOperator op, ModifierList modifiers) { + this.op = op; + this.modifiers = modifiers; + } + + BooleanGroup(BooleanOperator op) { + this.op = op; + } + + public BooleanOperator getOperator() { + return op; + } + + public ModifierList getModifierList() { + return modifiers; + } + + @Override + public void accept(Visitor visitor) { + visitor.visit(this); + } + + @Override + public String toString() { + return op != null && modifiers != null ? op + modifiers.toString() + : op != null ? op.toString() : null; + } +} diff --git a/src/main/java/org/xbib/cql/BooleanOperator.java b/src/main/java/org/xbib/cql/BooleanOperator.java new file mode 100644 index 0000000..2585161 --- /dev/null +++ b/src/main/java/org/xbib/cql/BooleanOperator.java @@ -0,0 +1,80 @@ +package org.xbib.cql; + +import java.util.HashMap; +import java.util.Map; + +/** + * Abstract syntax tree of CQL - boolean operator enumeration. + */ +public enum BooleanOperator { + + AND("and"), + OR("or"), + NOT("not"), + PROX("prox"); + /** + * Token/operator map. + */ + private static Map tokenMap; + /** + * Operator/token map. + */ + private static Map opMap; + private String token; + + /** + * Creates a new Operator object. + * + * @param token the operator token + */ + BooleanOperator(String token) { + this.token = token; + map(token, this); + } + + /** + * Map token to operator. + * + * @param token the token + * @param op the operator + */ + private static void map(String token, BooleanOperator op) { + if (tokenMap == null) { + tokenMap = new HashMap<>(); + } + tokenMap.put(token, op); + if (opMap == null) { + opMap = new HashMap<>(); + } + opMap.put(op, token); + } + + /** + * Get token. + * + * @return the token + */ + public String getToken() { + return token; + } + + /** + * Get operator for token. + * + * @param token the token + * @return the operator + */ + static BooleanOperator forToken(Object token) { + return tokenMap.get(token.toString().toLowerCase()); + } + + /** + * Write operator representation. + * + * @return the operator token + */ + @Override + public String toString() { + return token; + } +} diff --git a/src/main/java/org/xbib/cql/CQLGenerator.java b/src/main/java/org/xbib/cql/CQLGenerator.java new file mode 100644 index 0000000..de0c477 --- /dev/null +++ b/src/main/java/org/xbib/cql/CQLGenerator.java @@ -0,0 +1,224 @@ +package org.xbib.cql; + +import org.xbib.cql.model.CQLQueryModel; +import org.xbib.cql.model.Facet; +import org.xbib.cql.model.Filter; +import org.xbib.cql.model.Option; + +/** + * This is a CQL abstract syntax tree generator useful for normalizing CQL queries. + */ +public final class CQLGenerator implements Visitor { + + /** + * helper for managing our CQL query model (facet/filter/option contexts, breadcrumb trails etc.). + */ + private CQLQueryModel model; + + /** + * A replacement string. + */ + private String replacementString; + + /** + * String to be replaced. + */ + private String stringToBeReplaced; + + public CQLGenerator() { + this.replacementString = null; + this.stringToBeReplaced = null; + this.model = new CQLQueryModel(); + } + + public CQLGenerator model(CQLQueryModel model) { + this.model = model; + return this; + } + + public CQLQueryModel getModel() { + return model; + } + + public String getResult() { + return model.getQuery(); + } + + @Override + public void visit(SortedQuery node) { + if (node.getSortSpec() != null) { + node.getSortSpec().accept(this); + } + if (node.getQuery() != null) { + node.getQuery().accept(this); + } + model.setQuery(node.toString()); + } + + @Override + public void visit(Query node) { + if (node.getPrefixAssignments() != null) { + for (PrefixAssignment assignment : node.getPrefixAssignments()) { + assignment.accept(this); + } + } + if (node.getQuery() != null) { + node.getQuery().accept(this); + } + if (node.getScopedClause() != null) { + node.getScopedClause().accept(this); + } + } + + @Override + public void visit(SortSpec node) { + if (node.getSingleSpec() != null) { + node.getSingleSpec().accept(this); + } + if (node.getSortSpec() != null) { + node.getSortSpec().accept(this); + } + } + + @Override + public void visit(SingleSpec node) { + if (node.getIndex() != null) { + node.getIndex().accept(this); + } + if (node.getModifierList() != null) { + node.getModifierList().accept(this); + } + } + + @Override + public void visit(PrefixAssignment node) { + node.getPrefix().accept(this); + node.getURI().accept(this); + } + + @Override + public void visit(ScopedClause node) { + if (node.getScopedClause() != null) { + node.getScopedClause().accept(this); + } + node.getSearchClause().accept(this); + if (node.getBooleanGroup() != null) { + node.getBooleanGroup().accept(this); + BooleanOperator op = node.getBooleanGroup().getOperator(); + checkFilter(op, node); + checkFilter(op, node.getScopedClause()); + } + } + + @Override + public void visit(BooleanGroup node) { + if (node.getModifierList() != null) { + node.getModifierList().accept(this); + } + } + + @Override + public void visit(SearchClause node) { + if (node.getQuery() != null) { + node.getQuery().accept(this); + } + if (node.getTerm() != null) { + node.getTerm().accept(this); + } + if (node.getIndex() != null) { + node.getIndex().accept(this); + String context = node.getIndex().getContext(); + if (CQLQueryModel.FACET_INDEX_NAME.equals(context)) { + Facet facet = new Facet<>(node.getIndex().getName()); + facet.setValue(node.getTerm()); + model.addFacet(facet); + } else if (CQLQueryModel.OPTION_INDEX_NAME.equals(context)) { + Option option = new Option<>(); + option.setName(node.getIndex().getName()); + option.setValue(node.getTerm()); + model.addOption(option); + } + } + if (node.getRelation() != null) { + node.getRelation().accept(this); + } + } + + @Override + public void visit(Relation node) { + if (node.getModifierList() != null) { + node.getModifierList().accept(this); + } + } + + @Override + public void visit(Modifier node) { + if (node.getTerm() != null) { + node.getTerm().accept(this); + } + if (node.getName() != null) { + node.getName().accept(this); + } + } + + @Override + public void visit(ModifierList node) { + for (Modifier modifier : node.getModifierList()) { + modifier.accept(this); + } + } + + @Override + public void visit(Term node) { + if (replacementString != null && stringToBeReplaced.equals(node.getValue())) { + node.setValue(replacementString); + } + } + + @Override + public void visit(Identifier node) { + } + + @Override + public void visit(SimpleName node) { + } + + @Override + public void visit(Index node) { + } + + /** + * Write a substitution query, for example when a term has been + * suggested to be replaced by another term. + * + * @param oldTerm the term to be replaced + * @param newTerm the replacement term + * @return the new query with the term replaced + */ + public synchronized String writeSubstitutedForm(String oldTerm, String newTerm) { + this.stringToBeReplaced = oldTerm; + this.replacementString = newTerm; + CQLParser parser = new CQLParser(model.getQuery()); + parser.parse(); + parser.getCQLQuery().accept(this); + String result = model.getQuery(); + this.stringToBeReplaced = null; + this.replacementString = null; + return result; + } + + public String withBreadcrumbs() { + return model.toCQL(); + } + + private void checkFilter(BooleanOperator op, ScopedClause node) { + if (node.getSearchClause().getIndex() != null + && CQLQueryModel.FILTER_INDEX_NAME.equals(node.getSearchClause().getIndex().getContext())) { + String filtername = node.getSearchClause().getIndex().getName(); + Comparitor filterop = node.getSearchClause().getRelation().getComparitor(); + Term filterterm = node.getSearchClause().getTerm(); + Filter filter2 = new Filter<>(filtername, filterterm, filterop); + model.addFilter(op, filter2); + } + } +} diff --git a/src/main/java/org/xbib/cql/Comparitor.java b/src/main/java/org/xbib/cql/Comparitor.java new file mode 100644 index 0000000..7be6c3b --- /dev/null +++ b/src/main/java/org/xbib/cql/Comparitor.java @@ -0,0 +1,80 @@ +package org.xbib.cql; + +import java.util.HashMap; + +/** + * CQL operators. + */ +public enum Comparitor { + + EQUALS("="), + GREATER(">"), + GREATER_EQUALS(">="), + LESS("<"), + LESS_EQUALS("<="), + NOT_EQUALS("<>"), + WITHIN("within"), + CQLWITHIN("cql.within"), + ENCLOSES("encloses"), + CQLENCLOSES("cql.encloses"), + ADJ("adj"), + CQLADJ("cql.adj"), + ALL("all"), + CQLALL("cql.all"), + ANY("any"), + CQLANY("cql.any"); + private static HashMap tokenMap; + private String token; + + /** + * Creates a new Operator object. + * + * @param token the operator token + */ + private Comparitor(String token) { + this.token = token; + map(token, this); + } + + /** + * Map token to operator + * + * @param token the token + * @param op the operator + */ + private static void map(String token, Comparitor op) { + if (tokenMap == null) { + tokenMap = new HashMap<>(); + } + tokenMap.put(token, op); + } + + /** + * Get token. + * + * @return the token + */ + public String getToken() { + return token; + } + + /** + * Get operator for token. + * + * @param token the token + * @return the operator + */ + static Comparitor forToken(Object token) { + return tokenMap.get(token.toString()); + } + + /** + * Write operator representation. + * + * @return the operator token + */ + @Override + public String toString() { + return token; + } +} diff --git a/src/main/java/org/xbib/cql/Identifier.java b/src/main/java/org/xbib/cql/Identifier.java new file mode 100644 index 0000000..39be384 --- /dev/null +++ b/src/main/java/org/xbib/cql/Identifier.java @@ -0,0 +1,33 @@ +package org.xbib.cql; + +/** + * An Identifier is a SimpleName or a String in double quotes. + */ +public class Identifier extends AbstractNode { + + private String value; + private boolean quoted; + + public Identifier(String value) { + this.value = value; + this.quoted = true; + } + + public Identifier(SimpleName name) { + this.value = name.getName(); + this.quoted = false; + } + + public String getValue() { + return value; + } + + public void accept(Visitor visitor) { + visitor.visit(this); + } + + @Override + public String toString() { + return value != null && quoted ? "\"" + value.replaceAll("\"", "\\\\\"") + "\"" : value; + } +} diff --git a/src/main/java/org/xbib/cql/Index.java b/src/main/java/org/xbib/cql/Index.java new file mode 100644 index 0000000..16e22d9 --- /dev/null +++ b/src/main/java/org/xbib/cql/Index.java @@ -0,0 +1,51 @@ +package org.xbib.cql; + +/** + * Abstract syntax tree of CQL - Index. + * The Index consists of context and name + * The default context is "cql" and is of the same concept like a namespace. + */ +public class Index extends AbstractNode { + + private String context; + private String name; + + public Index(String name) { + this.name = name; + int pos = name.indexOf('.'); + if (pos > 0) { + this.context = name.substring(0, pos); + this.name = name.substring(pos + 1); + } + } + + public Index(SimpleName name) { + this(name.getName()); + } + + /** + * @return the context of the index + */ + public String getContext() { + return context; + } + + /** + * Get the name of the index + * + * @return the name of the index + */ + public String getName() { + return name; + } + + public void accept(Visitor visitor) { + visitor.visit(this); + } + + @Override + public String toString() { + return context != null ? context + "." + name : name; + } + +} diff --git a/src/main/java/org/xbib/cql/Modifier.java b/src/main/java/org/xbib/cql/Modifier.java new file mode 100644 index 0000000..9212862 --- /dev/null +++ b/src/main/java/org/xbib/cql/Modifier.java @@ -0,0 +1,45 @@ +package org.xbib.cql; + +/** + * Modifier. + */ +public class Modifier extends AbstractNode { + + private SimpleName name; + + private Comparitor op; + + private Term term; + + public Modifier(SimpleName name, Comparitor op, Term term) { + this.name = name; + this.op = op; + this.term = term; + } + + public Modifier(SimpleName name) { + this.name = name; + } + + public SimpleName getName() { + return name; + } + + public Comparitor getOperator() { + return op; + } + + public Term getTerm() { + return term; + } + + public void accept(Visitor visitor) { + visitor.visit(this); + } + + @Override + public String toString() { + return "/" + (term != null ? name.toString() + op + term : name.toString()); + } + +} diff --git a/src/main/java/org/xbib/cql/ModifierList.java b/src/main/java/org/xbib/cql/ModifierList.java new file mode 100644 index 0000000..1d1c102 --- /dev/null +++ b/src/main/java/org/xbib/cql/ModifierList.java @@ -0,0 +1,39 @@ +package org.xbib.cql; + +import java.util.LinkedList; +import java.util.List; + +/** + * Modifier list. This is a recursive data structure with a Modifier and optionally a ModifierList. + */ +public class ModifierList extends AbstractNode { + + private List modifierList = new LinkedList<>(); + + public ModifierList(ModifierList modifiers, Modifier modifier) { + modifierList.addAll(modifiers.modifierList); + modifierList.add(modifier); + } + + public ModifierList(Modifier modifier) { + modifierList.add(modifier); + } + + public List getModifierList() { + return modifierList; + } + + public void accept(Visitor visitor) { + visitor.visit(this); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + for (Modifier m : modifierList) { + sb.append(m.toString()); + } + return sb.toString(); + } + +} diff --git a/src/main/java/org/xbib/cql/Node.java b/src/main/java/org/xbib/cql/Node.java new file mode 100644 index 0000000..67fb028 --- /dev/null +++ b/src/main/java/org/xbib/cql/Node.java @@ -0,0 +1,14 @@ +package org.xbib.cql; + +/** + * This is a node interface for the CQL abstract syntax tree. + */ +public interface Node extends Comparable { + + /** + * Accept a visitor on this node. + * + * @param visitor the visitor + */ + void accept(Visitor visitor); +} diff --git a/src/main/java/org/xbib/cql/PrefixAssignment.java b/src/main/java/org/xbib/cql/PrefixAssignment.java new file mode 100644 index 0000000..56ceb6a --- /dev/null +++ b/src/main/java/org/xbib/cql/PrefixAssignment.java @@ -0,0 +1,38 @@ +package org.xbib.cql; + +/** + * Prefix assignment. + */ +public class PrefixAssignment extends AbstractNode { + + private Term prefix; + + private Term uri; + + public PrefixAssignment(Term prefix, Term uri) { + this.prefix = prefix; + this.uri = uri; + } + + public PrefixAssignment(Term uri) { + this.uri = uri; + } + + public Term getPrefix() { + return prefix; + } + + public Term getURI() { + return uri; + } + + public void accept(Visitor visitor) { + visitor.visit(this); + } + + @Override + public String toString() { + return "> " + prefix + " = " + uri; + } + +} diff --git a/src/main/java/org/xbib/cql/Query.java b/src/main/java/org/xbib/cql/Query.java new file mode 100644 index 0000000..69f09a6 --- /dev/null +++ b/src/main/java/org/xbib/cql/Query.java @@ -0,0 +1,57 @@ +package org.xbib.cql; + +import java.util.LinkedList; +import java.util.List; + +/** + * CQL query. + */ +public class Query extends AbstractNode { + + private List prefixes = new LinkedList<>(); + + private Query query; + + private ScopedClause clause; + + Query(PrefixAssignment assignment, Query query) { + prefixes.add(assignment); + this.query = query; + } + + Query(ScopedClause clause) { + this.clause = clause; + } + + public List getPrefixAssignments() { + return prefixes; + } + + public Query getQuery() { + return query; + } + + public ScopedClause getScopedClause() { + return clause; + } + + public void accept(Visitor visitor) { + visitor.visit(this); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + for (PrefixAssignment assignment : prefixes) { + sb.append(assignment.toString()).append(' '); + } + if (query != null) { + sb.append(query); + } + if (clause != null) { + sb.append(clause); + } + return sb.toString(); + } + +} diff --git a/src/main/java/org/xbib/cql/QueryFacet.java b/src/main/java/org/xbib/cql/QueryFacet.java new file mode 100644 index 0000000..49fbf9a --- /dev/null +++ b/src/main/java/org/xbib/cql/QueryFacet.java @@ -0,0 +1,21 @@ +package org.xbib.cql; + +/** + * Query facet. + */ +public interface QueryFacet extends QueryOption { + /** + * The size of the facet. + * + * @return the facet size + */ + int getSize(); + + /** + * Get the filter name which must be used for filtering facet entries. + * + * @return the filter name + */ + String getFilterName(); + +} diff --git a/src/main/java/org/xbib/cql/QueryFilter.java b/src/main/java/org/xbib/cql/QueryFilter.java new file mode 100644 index 0000000..643797f --- /dev/null +++ b/src/main/java/org/xbib/cql/QueryFilter.java @@ -0,0 +1,7 @@ +package org.xbib.cql; + +/** + * A Filter for a query. + */ +public interface QueryFilter extends QueryOption { +} diff --git a/src/main/java/org/xbib/cql/QueryOption.java b/src/main/java/org/xbib/cql/QueryOption.java new file mode 100644 index 0000000..ef653db --- /dev/null +++ b/src/main/java/org/xbib/cql/QueryOption.java @@ -0,0 +1,16 @@ +package org.xbib.cql; + +/** + * Qery option. + * @param parameter type + */ +public interface QueryOption { + + void setName(String name); + + String getName(); + + void setValue(V value); + + V getValue(); +} diff --git a/src/main/java/org/xbib/cql/Relation.java b/src/main/java/org/xbib/cql/Relation.java new file mode 100644 index 0000000..b2becf8 --- /dev/null +++ b/src/main/java/org/xbib/cql/Relation.java @@ -0,0 +1,39 @@ +package org.xbib.cql; + +/** + * Relation to a ModifierList. + */ +public class Relation extends AbstractNode { + + private Comparitor comparitor; + private ModifierList modifiers; + + public Relation(Comparitor comparitor, ModifierList modifiers) { + this.comparitor = comparitor; + this.modifiers = modifiers; + } + + public Relation(Comparitor comparitor) { + this.comparitor = comparitor; + } + + public Comparitor getComparitor() { + return comparitor; + } + + public ModifierList getModifierList() { + return modifiers; + } + + @Override + public void accept(Visitor visitor) { + visitor.visit(this); + } + + @Override + public String toString() { + return modifiers != null ? comparitor + modifiers.toString() + : comparitor.toString(); + } + +} diff --git a/src/main/java/org/xbib/cql/ScopedClause.java b/src/main/java/org/xbib/cql/ScopedClause.java new file mode 100644 index 0000000..74c979c --- /dev/null +++ b/src/main/java/org/xbib/cql/ScopedClause.java @@ -0,0 +1,50 @@ +package org.xbib.cql; + +/** + * Scoped clause. This is a recursive data structure with a SearchClause and + * optionally a ScopedClause. + * SearchClause and ScopedClause are connected through a BooleanGroup. + */ +public class ScopedClause extends AbstractNode { + + private ScopedClause clause; + private BooleanGroup booleangroup; + private SearchClause search; + + ScopedClause(ScopedClause clause, BooleanGroup bg, SearchClause search) { + this.clause = clause; + this.booleangroup = bg; + this.search = search; + } + + ScopedClause(SearchClause search) { + this.search = search; + } + + public ScopedClause getScopedClause() { + return clause; + } + + public BooleanGroup getBooleanGroup() { + return booleangroup; + } + + public SearchClause getSearchClause() { + return search; + } + + @Override + public void accept(Visitor visitor) { + visitor.visit(this); + } + + @Override + public String toString() { + String s = search.toString(); + boolean hasQuery = s.length() > 0; + return clause != null && hasQuery ? clause + " " + booleangroup + " " + search + : clause != null ? clause.toString() + : hasQuery ? search.toString() + : ""; + } +} diff --git a/src/main/java/org/xbib/cql/SearchClause.java b/src/main/java/org/xbib/cql/SearchClause.java new file mode 100644 index 0000000..eafcc96 --- /dev/null +++ b/src/main/java/org/xbib/cql/SearchClause.java @@ -0,0 +1,62 @@ +package org.xbib.cql; + +import org.xbib.cql.model.CQLQueryModel; + +/** + * Search clause. + */ +public class SearchClause extends AbstractNode { + + private Query query; + private Index index; + private Relation relation; + private Term term; + + SearchClause(Query query) { + this.query = query; + } + + SearchClause(Index index, Relation relation, Term term) { + this.index = index; + this.relation = relation; + this.term = term; + } + + SearchClause(Term term) { + this.term = term; + } + + public Query getQuery() { + return query; + } + + public Index getIndex() { + return index; + } + + public Term getTerm() { + return term; + } + + public Relation getRelation() { + return relation; + } + + public void accept(Visitor visitor) { + visitor.visit(this); + } + + /** + * @return CQL string + */ + @Override + public String toString() { + return query != null && query.toString().length() > 0 ? "(" + query + ")" + : query != null ? "" + : index != null && !CQLQueryModel.isVisible(index.getContext()) ? "" + : index != null ? index + " " + relation + " " + term + : term != null ? term.toString() + : null; + } + +} diff --git a/src/main/java/org/xbib/cql/SimpleName.java b/src/main/java/org/xbib/cql/SimpleName.java new file mode 100644 index 0000000..394e33e --- /dev/null +++ b/src/main/java/org/xbib/cql/SimpleName.java @@ -0,0 +1,28 @@ +package org.xbib.cql; + +/** + * A SimpleName consists of a String which is not surrounded by double quotes. + */ +public class SimpleName extends AbstractNode { + + private String name; + + public SimpleName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + @Override + public void accept(Visitor visitor) { + visitor.visit(this); + } + + @Override + public String toString() { + return name; + } + +} diff --git a/src/main/java/org/xbib/cql/SingleSpec.java b/src/main/java/org/xbib/cql/SingleSpec.java new file mode 100644 index 0000000..482b287 --- /dev/null +++ b/src/main/java/org/xbib/cql/SingleSpec.java @@ -0,0 +1,37 @@ +package org.xbib.cql; + +/** + * Single spec. + */ +public class SingleSpec extends AbstractNode { + + private Index index; + private ModifierList modifiers; + + public SingleSpec(Index index, ModifierList modifiers) { + this.index = index; + this.modifiers = modifiers; + } + + public SingleSpec(Index index) { + this.index = index; + } + + public Index getIndex() { + return index; + } + + public ModifierList getModifierList() { + return modifiers; + } + + public void accept(Visitor visitor) { + visitor.visit(this); + } + + @Override + public String toString() { + return index + (modifiers != null ? modifiers.toString() : ""); + } + +} diff --git a/src/main/java/org/xbib/cql/SortSpec.java b/src/main/java/org/xbib/cql/SortSpec.java new file mode 100644 index 0000000..afca6a1 --- /dev/null +++ b/src/main/java/org/xbib/cql/SortSpec.java @@ -0,0 +1,38 @@ +package org.xbib.cql; + +/** + * Abstract syntax tree of CQL, the sort specification. + */ +public class SortSpec extends AbstractNode { + + private SortSpec sortspec; + private SingleSpec spec; + + public SortSpec(SortSpec sortspec, SingleSpec spec) { + this.sortspec = sortspec; + this.spec = spec; + } + + public SortSpec(SingleSpec spec) { + this.spec = spec; + } + + public SortSpec getSortSpec() { + return sortspec; + } + + public SingleSpec getSingleSpec() { + return spec; + } + + @Override + public void accept(Visitor visitor) { + visitor.visit(this); + } + + @Override + public String toString() { + return (sortspec != null ? sortspec + " " : "") + spec; + } + +} diff --git a/src/main/java/org/xbib/cql/SortedQuery.java b/src/main/java/org/xbib/cql/SortedQuery.java new file mode 100644 index 0000000..570bf6f --- /dev/null +++ b/src/main/java/org/xbib/cql/SortedQuery.java @@ -0,0 +1,39 @@ +package org.xbib.cql; + +/** + * Sorted query. + */ +public class SortedQuery extends AbstractNode { + + private Query query; + + private SortSpec spec; + + SortedQuery(Query query, SortSpec spec) { + this.query = query; + this.spec = spec; + } + + SortedQuery(Query query) { + this.query = query; + } + + public Query getQuery() { + return query; + } + + public SortSpec getSortSpec() { + return spec; + } + + @Override + public void accept(Visitor visitor) { + visitor.visit(this); + } + + @Override + public String toString() { + return query != null && spec != null ? query + " sortby " + spec + : query != null ? query.toString() : ""; + } +} \ No newline at end of file diff --git a/src/main/java/org/xbib/cql/SyntaxException.java b/src/main/java/org/xbib/cql/SyntaxException.java new file mode 100644 index 0000000..f8e4ed5 --- /dev/null +++ b/src/main/java/org/xbib/cql/SyntaxException.java @@ -0,0 +1,25 @@ +package org.xbib.cql; + +/** + * CQL Syntax exception. + */ +public class SyntaxException extends RuntimeException { + /** + * Creates a new SyntaxException object. + * + * @param msg the message for this syntax exception + */ + public SyntaxException(String msg) { + super(msg); + } + + /** + * Creates a new SyntaxException object. + * + * @param msg the message for this syntax exception + * @param t the throwable for this syntax exception + */ + public SyntaxException(String msg, Throwable t) { + super(msg, t); + } +} diff --git a/src/main/java/org/xbib/cql/Term.java b/src/main/java/org/xbib/cql/Term.java new file mode 100644 index 0000000..a60cc2a --- /dev/null +++ b/src/main/java/org/xbib/cql/Term.java @@ -0,0 +1,147 @@ +package org.xbib.cql; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.TimeZone; + +/** + * A CQL Term. + */ +public class Term extends AbstractNode { + + private static final TimeZone tz = TimeZone.getTimeZone("GMT"); + private static final String ISO_FORMAT_SECONDS = "yyyy-MM-dd'T'HH:mm:ss'Z'"; + private static final String ISO_FORMAT_DAYS = "yyyy-MM-dd"; + + private String value; + private Long longvalue; + private Double doublevalue; + private Identifier identifier; + private Date datevalue; + private SimpleName name; + + public Term(String value) { + this.value = value; + try { + // check for hidden dates. CQL does not support ISO dates. + this.datevalue = parseDateISO(value); + this.value = null; + } catch (Exception e) { + + } + } + + public Term(Identifier identifier) { + this.identifier = identifier; + } + + public Term(SimpleName name) { + this.name = name; + } + + public Term(Long value) { + this.longvalue = value; + } + + public Term(Double value) { + this.doublevalue = value; + } + + /** + * Set value, useful for inline replacements + * in spellcheck suggestions + * + * @param value the value + */ + public void setValue(String value) { + this.value = value; + } + + /** + * If the value is a String it is embedded in quotation marks. + * If its a Integer or a Double it is returned without + * quotation marks. + * + * @return the value as String + */ + public String getValue() { + return longvalue != null ? Long.toString(longvalue) + : doublevalue != null ? Double.toString(doublevalue) + : value != null ? value + : identifier != null ? identifier.toString() + : name != null ? name.toString() + : null; + } + + public boolean isLong() { + return longvalue != null; + } + + public boolean isFloat() { + return doublevalue != null; + } + + public boolean isString() { + return value != null; + } + + public boolean isName() { + return name != null; + } + + public boolean isIdentifier() { + return identifier != null; + } + + public boolean isDate() { + return datevalue != null; + } + + public void accept(Visitor visitor) { + visitor.visit(this); + } + + private Date parseDateISO(String value) { + if (value == null) { + return null; + } + SimpleDateFormat sdf = new SimpleDateFormat(); + sdf.applyPattern(ISO_FORMAT_SECONDS); + sdf.setTimeZone(tz); + sdf.setLenient(true); + try { + return sdf.parse(value); + } catch (ParseException pe) { + // skip + } + sdf.applyPattern(ISO_FORMAT_DAYS); + try { + return sdf.parse(value); + } catch (ParseException pe) { + return null; + } + } + + private String formatDateISO(Date date) { + if (date == null) { + return null; + } + SimpleDateFormat sdf = new SimpleDateFormat(); + sdf.applyPattern(ISO_FORMAT_SECONDS); + sdf.setTimeZone(tz); + return sdf.format(date); + } + + @Override + public String toString() { + return longvalue != null ? Long.toString(longvalue) + : doublevalue != null ? Double.toString(doublevalue) + : datevalue != null ? formatDateISO(datevalue) + : value != null ? value.startsWith("\"") && value.endsWith("\"") ? value + : "\"" + value.replaceAll("\"", "\\\\\"") + "\"" + : identifier != null ? identifier.toString() + : name != null ? name.toString() + : null; + } +} diff --git a/src/main/java/org/xbib/cql/Visitor.java b/src/main/java/org/xbib/cql/Visitor.java new file mode 100644 index 0000000..228ad44 --- /dev/null +++ b/src/main/java/org/xbib/cql/Visitor.java @@ -0,0 +1,38 @@ +package org.xbib.cql; + +/** + * CQL abstract syntax tree visitor. + */ +public interface Visitor { + + void visit(SortedQuery node); + + void visit(Query node); + + void visit(PrefixAssignment node); + + void visit(ScopedClause node); + + void visit(BooleanGroup node); + + void visit(SearchClause node); + + void visit(Relation node); + + void visit(Modifier node); + + void visit(ModifierList node); + + void visit(Term node); + + void visit(Identifier node); + + void visit(Index node); + + void visit(SimpleName node); + + void visit(SortSpec node); + + void visit(SingleSpec node); + +} diff --git a/src/main/java/org/xbib/cql/elasticsearch/ElasticsearchFilterGenerator.java b/src/main/java/org/xbib/cql/elasticsearch/ElasticsearchFilterGenerator.java new file mode 100644 index 0000000..1543d5d --- /dev/null +++ b/src/main/java/org/xbib/cql/elasticsearch/ElasticsearchFilterGenerator.java @@ -0,0 +1,349 @@ +package org.xbib.cql.elasticsearch; + +import org.xbib.content.XContentBuilder; +import org.xbib.cql.util.DateUtil; +import org.xbib.cql.BooleanGroup; +import org.xbib.cql.BooleanOperator; +import org.xbib.cql.Comparitor; +import org.xbib.cql.Identifier; +import org.xbib.cql.Index; +import org.xbib.cql.ModifierList; +import org.xbib.cql.PrefixAssignment; +import org.xbib.cql.Query; +import org.xbib.cql.Relation; +import org.xbib.cql.ScopedClause; +import org.xbib.cql.SearchClause; +import org.xbib.cql.SimpleName; +import org.xbib.cql.SingleSpec; +import org.xbib.cql.SortSpec; +import org.xbib.cql.SortedQuery; +import org.xbib.cql.SyntaxException; +import org.xbib.cql.Term; +import org.xbib.cql.Visitor; +import org.xbib.cql.elasticsearch.ast.Expression; +import org.xbib.cql.elasticsearch.ast.Modifier; +import org.xbib.cql.elasticsearch.ast.Name; +import org.xbib.cql.elasticsearch.ast.Node; +import org.xbib.cql.elasticsearch.ast.Operator; +import org.xbib.cql.elasticsearch.ast.Token; +import org.xbib.cql.elasticsearch.ast.TokenType; +import org.xbib.cql.elasticsearch.model.ElasticsearchQueryModel; + +import java.io.IOException; +import java.util.Collection; +import java.util.Stack; + +/** + * Generate Elasticsearch filter query from CQL abstract syntax tree. + */ +public class ElasticsearchFilterGenerator implements Visitor { + + private final ElasticsearchQueryModel model; + + private Stack stack; + + private FilterGenerator filterGen; + + public ElasticsearchFilterGenerator() { + this(new ElasticsearchQueryModel()); + } + + public ElasticsearchFilterGenerator(ElasticsearchQueryModel model) { + this.model = model; + this.stack = new Stack<>(); + try { + this.filterGen = new FilterGenerator(); + } catch (IOException e) { + // ignore + } + } + + public void addOrFilter(String filterKey, Collection filterValues) { + for (String value : filterValues) { + model.addDisjunctiveFilter(filterKey, new Expression(Operator.OR_FILTER, new Name(filterKey), new Token(value)), Operator.OR); + } + } + + public void addAndFilter(String filterKey, Collection filterValues) { + for (String value : filterValues) { + model.addConjunctiveFilter(filterKey, new Expression(Operator.AND_FILTER, new Name(filterKey), new Token(value)), Operator.AND); + } + } + + public XContentBuilder getResult() throws IOException { + return filterGen.getResult(); + } + + @Override + public void visit(SortedQuery node) { + try { + filterGen.start(); + node.getQuery().accept(this); + Node querynode = stack.pop(); + if (querynode instanceof Token) { + filterGen.visit(new Expression(Operator.TERM_FILTER, new Name("cql.allIndexes"), querynode)); + } else if (querynode instanceof Expression) { + filterGen.visit(new Expression(Operator.QUERY_FILTER, (Expression) querynode)); + } + if (model.hasFilter()) { + filterGen.visit(model.getFilterExpression()); + } + filterGen.end(); + } catch (IOException e) { + throw new SyntaxException("unable to build a valid query from " + node + ", reason: " + e.getMessage(), e); + } + } + + @Override + public void visit(SortSpec node) { + if (node.getSingleSpec() != null) { + node.getSingleSpec().accept(this); + } + if (node.getSortSpec() != null) { + node.getSortSpec().accept(this); + } + } + + @Override + public void visit(SingleSpec node) { + if (node.getIndex() != null) { + node.getIndex().accept(this); + } + if (node.getModifierList() != null) { + node.getModifierList().accept(this); + } + if (!stack.isEmpty()) { + model.setSort(stack); + } + } + + @Override + public void visit(Query node) { + for (PrefixAssignment assignment : node.getPrefixAssignments()) { + assignment.accept(this); + } + if (node.getScopedClause() != null) { + node.getScopedClause().accept(this); + } + } + + @Override + public void visit(PrefixAssignment node) { + node.getPrefix().accept(this); + node.getURI().accept(this); + } + + @Override + public void visit(ScopedClause node) { + if (node.getScopedClause() != null) { + node.getScopedClause().accept(this); + } + node.getSearchClause().accept(this); + if (node.getBooleanGroup() != null) { + node.getBooleanGroup().accept(this); + } + // evaluate expression + if (!stack.isEmpty() && stack.peek() instanceof Operator) { + Operator op = (Operator) stack.pop(); + if (!stack.isEmpty()) { + Node esnode = stack.pop(); + // add default context if node is a literal without a context + if (esnode instanceof Token && TokenType.STRING.equals(esnode.getType())) { + esnode = new Expression(Operator.ALL, new Name("cql.allIndexes"), esnode); + } + if (stack.isEmpty()) { + // unary expression + throw new IllegalArgumentException("unary expression not allowed, op=" + op + " node=" + esnode); + } else { + // binary expression + Node esnode2 = stack.pop(); + // add default context if node is a literal without context + if (esnode2 instanceof Token && TokenType.STRING.equals(esnode2.getType())) { + esnode2 = new Expression(Operator.ALL, new Name("cql.allIndexes"), esnode2); + } + esnode = new Expression(op, esnode2, esnode); + } + stack.push(esnode); + } + } + } + + @Override + public void visit(SearchClause node) { + if (node.getQuery() != null) { + // CQL query in parenthesis + node.getQuery().accept(this); + } + if (node.getTerm() != null) { + node.getTerm().accept(this); + } + if (node.getIndex() != null) { + node.getIndex().accept(this); + } + if (node.getRelation() != null) { + node.getRelation().accept(this); + if (node.getRelation().getModifierList() != null && node.getIndex() != null) { + // stack layout: op, list of modifiers, modifiable index + Node op = stack.pop(); + StringBuilder sb = new StringBuilder(); + Node modifier = stack.pop(); + while (modifier instanceof Modifier) { + if (sb.length() > 0) { + sb.append('.'); + } + sb.append(modifier.toString()); + modifier = stack.pop(); + } + String modifiable = sb.toString(); + stack.push(new Name(modifiable)); + stack.push(op); + } + } + // evaluate expression + if (!stack.isEmpty() && stack.peek() instanceof Operator) { + Operator op = (Operator) stack.pop(); + Node arg1 = stack.pop(); + Node arg2 = stack.pop(); + // fold two expressions if they have the same operator + boolean fold = arg1.isVisible() && arg2.isVisible() + && arg2 instanceof Expression + && ((Expression) arg2).getOperator().equals(op); + Expression expression = fold ? new Expression((Expression) arg2, arg1) : new Expression(op, arg1, arg2); + stack.push(expression); + } + } + + @Override + public void visit(BooleanGroup node) { + if (node.getModifierList() != null) { + node.getModifierList().accept(this); + } + stack.push(booleanToES(node.getOperator())); + } + + @Override + public void visit(Relation node) { + if (node.getModifierList() != null) { + node.getModifierList().accept(this); + } + stack.push(comparitorToES(node.getComparitor())); + } + + @Override + public void visit(ModifierList node) { + for (org.xbib.cql.Modifier modifier : node.getModifierList()) { + modifier.accept(this); + } + } + + @Override + public void visit(org.xbib.cql.Modifier node) { + Node term = null; + if (node.getTerm() != null) { + node.getTerm().accept(this); + term = stack.pop(); + } + node.getName().accept(this); + Node name = stack.pop(); + stack.push(new Modifier(name, term)); + } + + @Override + public void visit(Term node) { + stack.push(termToES(node)); + } + + @Override + public void visit(Identifier node) { + stack.push(new Name(node.getValue())); + } + + @Override + public void visit(Index node) { + String context = node.getContext(); + String name = context != null ? context + "." + node.getName() : node.getName(); + Name esname = new Name(name, model.getVisibility(context)); + esname.setType(model.getElasticsearchType(name)); + stack.push(esname); + } + + @Override + public void visit(SimpleName node) { + stack.push(new Name(node.getName())); + } + + private Node termToES(Term node) { + if (node.isLong()) { + return new Token(Long.parseLong(node.getValue())); + } else if (node.isFloat()) { + return new Token(Double.parseDouble(node.getValue())); + } else if (node.isIdentifier()) { + return new Token(node.getValue()); + } else if (node.isDate()) { + return new Token(DateUtil.parseDateISO(node.getValue())); + } else if (node.isString()) { + return new Token(node.getValue()); + } + return null; + } + + private Operator booleanToES(BooleanOperator bop) { + Operator op; + switch (bop) { + case AND: + op = Operator.AND; + break; + case OR: + op = Operator.OR; + break; + case NOT: + op = Operator.ANDNOT; + break; + case PROX: + op = Operator.PROX; + break; + default: + throw new IllegalArgumentException("unknown CQL operator: " + bop); + } + return op; + } + + private Operator comparitorToES(Comparitor op) { + Operator esop; + switch (op) { + case EQUALS: + esop = Operator.EQUALS; + break; + case GREATER: + esop = Operator.RANGE_GREATER_THAN; + break; + case GREATER_EQUALS: + esop = Operator.RANGE_GREATER_OR_EQUAL; + break; + case LESS: + esop = Operator.RANGE_LESS_THAN; + break; + case LESS_EQUALS: + esop = Operator.RANGE_LESS_OR_EQUALS; + break; + case NOT_EQUALS: + esop = Operator.NOT_EQUALS; + break; + case WITHIN: + esop = Operator.RANGE_WITHIN; + break; + case ADJ: + esop = Operator.PHRASE; + break; + case ALL: + esop = Operator.ALL; + break; + case ANY: + esop = Operator.ANY; + break; + default: + throw new IllegalArgumentException("unknown CQL comparitor: " + op); + } + return esop; + } +} diff --git a/src/main/java/org/xbib/cql/elasticsearch/ElasticsearchQueryGenerator.java b/src/main/java/org/xbib/cql/elasticsearch/ElasticsearchQueryGenerator.java new file mode 100644 index 0000000..3141d19 --- /dev/null +++ b/src/main/java/org/xbib/cql/elasticsearch/ElasticsearchQueryGenerator.java @@ -0,0 +1,492 @@ +package org.xbib.cql.elasticsearch; + +import org.xbib.content.XContentBuilder; +import org.xbib.cql.BooleanGroup; +import org.xbib.cql.BooleanOperator; +import org.xbib.cql.CQLParser; +import org.xbib.cql.Comparitor; +import org.xbib.cql.Identifier; +import org.xbib.cql.Index; +import org.xbib.cql.ModifierList; +import org.xbib.cql.PrefixAssignment; +import org.xbib.cql.Query; +import org.xbib.cql.Relation; +import org.xbib.cql.ScopedClause; +import org.xbib.cql.SearchClause; +import org.xbib.cql.SimpleName; +import org.xbib.cql.SingleSpec; +import org.xbib.cql.SortSpec; +import org.xbib.cql.SortedQuery; +import org.xbib.cql.SyntaxException; +import org.xbib.cql.Term; +import org.xbib.cql.Visitor; +import org.xbib.cql.elasticsearch.ast.Expression; +import org.xbib.cql.elasticsearch.ast.Modifier; +import org.xbib.cql.elasticsearch.ast.Name; +import org.xbib.cql.elasticsearch.ast.Node; +import org.xbib.cql.elasticsearch.ast.Operator; +import org.xbib.cql.elasticsearch.ast.Token; +import org.xbib.cql.elasticsearch.ast.TokenType; +import org.xbib.cql.elasticsearch.model.ElasticsearchQueryModel; +import org.xbib.cql.util.DateUtil; + +import java.io.IOException; +import java.util.Collection; +import java.util.Stack; + +/** + * Generate Elasticsearch QueryModel DSL from CQL abstract syntax tree + */ +public class ElasticsearchQueryGenerator implements Visitor { + + private ElasticsearchQueryModel model; + + private ElasticsearchFilterGenerator filterGenerator; + + private Stack stack; + + private int from; + + private int size; + + private String boostField; + + private String modifier; + + private Float factor; + + private String boostMode; + + private SourceGenerator sourceGen; + + private QueryGenerator queryGen; + + private FilterGenerator filterGen; + + private FacetsGenerator facetGen; + + private XContentBuilder sort; + + public ElasticsearchQueryGenerator() { + this.from = 0; + this.size = 10; + this.model = new ElasticsearchQueryModel(); + this.filterGenerator = new ElasticsearchFilterGenerator(model); + this.stack = new Stack<>(); + try { + this.sourceGen = new SourceGenerator(); + this.queryGen = new QueryGenerator(); + this.filterGen = new FilterGenerator(); + this.facetGen = new FacetsGenerator(); + } catch (IOException e) { + // ignore + } + } + + public ElasticsearchQueryModel getModel() { + return model; + } + + public ElasticsearchQueryGenerator setFrom(int from) { + this.from = from; + return this; + } + + public ElasticsearchQueryGenerator setSize(int size) { + this.size = size; + return this; + } + + public ElasticsearchQueryGenerator setSort(XContentBuilder sort) { + this.sort = sort; + return this; + } + + public ElasticsearchQueryGenerator setBoostParams(String boostField, String modifier, Float factor, String boostMode) { + this.boostField = boostField; + this.modifier = modifier; + this.factor = factor; + this.boostMode = boostMode; + return this; + } + + public ElasticsearchQueryGenerator filter(String filter) { + CQLParser parser = new CQLParser(filter); + parser.parse(); + parser.getCQLQuery().accept(filterGenerator); + return this; + } + + public ElasticsearchQueryGenerator andfilter(String filterKey, Collection filterValues) { + filterGenerator.addAndFilter(filterKey, filterValues); + return this; + } + + public ElasticsearchQueryGenerator orfilter(String filterKey, Collection filterValues) { + filterGenerator.addOrFilter(filterKey, filterValues); + return this; + } + + public ElasticsearchQueryGenerator facet(String facetLimit, String facetSort) { + try { + facetGen.facet(facetLimit, facetSort); + } catch (IOException e) { + // ignore + } + return this; + } + + public String getQueryResult() { + return queryGen.getResult().string(); + } + + + public String getFacetResult() { + try { + return facetGen.getResult().string(); + } catch (IOException e) { + return e.getMessage(); + } + } + + public String getSourceResult() { + return sourceGen.getResult().string(); + } + + @Override + public void visit(SortedQuery node) { + try { + if (node.getSortSpec() != null) { + node.getSortSpec().accept(this); + } + queryGen.start(); + node.getQuery().accept(this); + if (boostField != null) { + queryGen.startBoost(boostField, modifier, factor, boostMode); + } + if (model.hasFilter()) { + queryGen.startFiltered(); + } else if (filterGenerator.getResult().bytes().length() > 0) { + queryGen.startFiltered(); + } + Node querynode = stack.pop(); + if (querynode instanceof Token) { + Token token = (Token) querynode; + querynode = ".".equals(token.getString()) ? + new Expression(Operator.MATCH_ALL) : + new Expression(Operator.EQUALS, new Name("cql.allIndexes"), querynode); + } + queryGen.visit((Expression) querynode); + if (model.hasFilter()) { + queryGen.end(); + filterGen = new FilterGenerator(queryGen); + filterGen.startFilter(); + filterGen.visit(model.getFilterExpression()); + filterGen.endFilter(); + queryGen.end(); + } else if (filterGenerator.getResult().bytes().length() > 0) { + queryGen.end(); + queryGen.getResult().rawField("filter", filterGenerator.getResult().bytes().toBytes()); + queryGen.endFiltered(); + } + if (boostField != null) { + queryGen.endBoost(); + } + if (model.hasFacets()) { + facetGen = new FacetsGenerator(); + facetGen.visit(model.getFacetExpression()); + } + queryGen.end(); + Expression sortnode = model.getSort(); + SortGenerator sortGen = new SortGenerator(); + if (sortnode != null) { + sortGen.start(); + sortGen.visit(sortnode); + sortGen.end(); + sort = sortGen.getResult(); + } + sourceGen.build(queryGen, from, size, sort, facetGen.getResult()); + } catch (IOException e) { + throw new SyntaxException("unable to build a valid query from " + node + " , reason: " + e.getMessage(), e); + } + } + + @Override + public void visit(SortSpec node) { + if (node.getSingleSpec() != null) { + node.getSingleSpec().accept(this); + } + if (node.getSortSpec() != null) { + node.getSortSpec().accept(this); + } + } + + @Override + public void visit(SingleSpec node) { + if (node.getIndex() != null) { + node.getIndex().accept(this); + } + if (node.getModifierList() != null) { + node.getModifierList().accept(this); + } + if (!stack.isEmpty()) { + model.setSort(stack); + } + } + + @Override + public void visit(Query node) { + for (PrefixAssignment assignment : node.getPrefixAssignments()) { + assignment.accept(this); + } + if (node.getScopedClause() != null) { + node.getScopedClause().accept(this); + } + } + + @Override + public void visit(PrefixAssignment node) { + node.getPrefix().accept(this); + node.getURI().accept(this); + } + + @Override + public void visit(ScopedClause node) { + if (node.getScopedClause() != null) { + node.getScopedClause().accept(this); + } + node.getSearchClause().accept(this); + if (node.getBooleanGroup() != null) { + node.getBooleanGroup().accept(this); + } + // format disjunctive or conjunctive filters + if (node.getSearchClause().getIndex() != null + && model.isFilterContext(node.getSearchClause().getIndex().getContext())) { + // assume that each operator-less filter is a conjunctive filter + BooleanOperator op = node.getBooleanGroup() != null + ? node.getBooleanGroup().getOperator() : BooleanOperator.AND; + String filtername = node.getSearchClause().getIndex().getName(); + Operator filterop = comparitorToES(node.getSearchClause().getRelation().getComparitor()); + Node filterterm = termToESwithoutWildCard(node.getSearchClause().getTerm()); + if (op == BooleanOperator.AND) { + model.addConjunctiveFilter(filtername, filterterm, filterop); + } else if (op == BooleanOperator.OR) { + model.addDisjunctiveFilter(filtername, filterterm, filterop); + } + } + // evaluate expression + if (!stack.isEmpty() && stack.peek() instanceof Operator) { + Operator op = (Operator) stack.pop(); + if (!stack.isEmpty()) { + Node esnode = stack.pop(); + // add default context if node is a literal without a context + if (esnode instanceof Token && TokenType.STRING.equals(esnode.getType())) { + esnode = new Expression(Operator.EQUALS, new Name("cql.allIndexes"), esnode); + } + if (stack.isEmpty()) { + // unary expression + throw new IllegalArgumentException("unary expression not allowed, op=" + op + " node=" + esnode); + } else { + // binary expression + Node esnode2 = stack.pop(); + // add default context if node is a literal without context + if (esnode2 instanceof Token && TokenType.STRING.equals(esnode2.getType())) { + esnode2 = new Expression(Operator.EQUALS, new Name("cql.allIndexes"), esnode2); + } + esnode = new Expression(op, esnode2, esnode); + } + stack.push(esnode); + } + } + } + + @Override + public void visit(SearchClause node) { + if (node.getQuery() != null) { + // CQL query in parenthesis + node.getQuery().accept(this); + } + if (node.getTerm() != null) { + node.getTerm().accept(this); + } + if (node.getIndex() != null) { + node.getIndex().accept(this); + String context = node.getIndex().getContext(); + // format facets + if (model.isFacetContext(context)) { + model.addFacet(node.getIndex().getName(), node.getTerm().getValue()); + } + } + if (node.getRelation() != null) { + node.getRelation().accept(this); + if (node.getRelation().getModifierList() != null && node.getIndex() != null) { + // stack layout: op, list of modifiers, modifiable index + Node op = stack.pop(); + StringBuilder sb = new StringBuilder(); + Node modifier = stack.pop(); + while (modifier instanceof Modifier) { + if (sb.length() > 0) { + sb.append('.'); + } + sb.append(modifier.toString()); + modifier = stack.pop(); + } + String modifiable = sb.toString(); + stack.push(new Name(modifiable)); + stack.push(op); + } + } + // evaluate expression + if (!stack.isEmpty() && stack.peek() instanceof Operator) { + Operator op = (Operator) stack.pop(); + Node arg1 = stack.pop(); + Node arg2 = stack.pop(); + // fold two expressions if they have the same operator + boolean fold = arg1.isVisible() && arg2.isVisible() + && arg2 instanceof Expression + && ((Expression) arg2).getOperator().equals(op); + Expression expression = fold ? new Expression((Expression) arg2, arg1) : new Expression(op, arg1, arg2); + stack.push(expression); + } + } + + @Override + public void visit(BooleanGroup node) { + if (node.getModifierList() != null) { + node.getModifierList().accept(this); + } + stack.push(booleanToES(node.getOperator())); + } + + @Override + public void visit(Relation node) { + if (node.getModifierList() != null) { + node.getModifierList().accept(this); + } + stack.push(comparitorToES(node.getComparitor())); + } + + @Override + public void visit(ModifierList node) { + for (org.xbib.cql.Modifier modifier : node.getModifierList()) { + modifier.accept(this); + } + } + + @Override + public void visit(org.xbib.cql.Modifier node) { + Node term = null; + if (node.getTerm() != null) { + node.getTerm().accept(this); + term = stack.pop(); + } + node.getName().accept(this); + Node name = stack.pop(); + stack.push(new Modifier(name, term)); + } + + @Override + public void visit(Term node) { + stack.push(termToES(node)); + } + + @Override + public void visit(Identifier node) { + stack.push(new Name(node.getValue())); + } + + @Override + public void visit(Index node) { + String context = node.getContext(); + String name = context != null ? context + "." + node.getName() : node.getName(); + Name esname = new Name(name, model.getVisibility(context)); + esname.setType(model.getElasticsearchType(name)); + stack.push(esname); + } + + @Override + public void visit(SimpleName node) { + stack.push(new Name(node.getName())); + } + + private Node termToES(Term node) { + if (node.isLong()) { + return new Token(Long.parseLong(node.getValue())); + } else if (node.isFloat()) { + return new Token(Double.parseDouble(node.getValue())); + } else if (node.isIdentifier()) { + return new Token(node.getValue()); + } else if (node.isDate()) { + return new Token(DateUtil.parseDateISO(node.getValue())); + } else if (node.isString()) { + return new Token(node.getValue()); + } + return null; + } + + private Node termToESwithoutWildCard(Term node) { + return node.isString() || node.isIdentifier() + ? new Token(node.getValue().replaceAll("\\*", "")) + : termToES(node); + } + + private Operator booleanToES(BooleanOperator bop) { + Operator op; + switch (bop) { + case AND: + op = Operator.AND; + break; + case OR: + op = Operator.OR; + break; + case NOT: + op = Operator.ANDNOT; + break; + case PROX: + op = Operator.PROX; + break; + default: + throw new IllegalArgumentException("unknown CQL operator: " + bop); + } + return op; + } + + private Operator comparitorToES(Comparitor op) { + Operator esop; + switch (op) { + case EQUALS: + esop = Operator.EQUALS; + break; + case GREATER: + esop = Operator.RANGE_GREATER_THAN; + break; + case GREATER_EQUALS: + esop = Operator.RANGE_GREATER_OR_EQUAL; + break; + case LESS: + esop = Operator.RANGE_LESS_THAN; + break; + case LESS_EQUALS: + esop = Operator.RANGE_LESS_OR_EQUALS; + break; + case NOT_EQUALS: + esop = Operator.NOT_EQUALS; + break; + case WITHIN: + esop = Operator.RANGE_WITHIN; + break; + case ADJ: + esop = Operator.PHRASE; + break; + case ALL: + esop = Operator.ALL; + break; + case ANY: + esop = Operator.ANY; + break; + default: + throw new IllegalArgumentException("unknown CQL comparitor: " + op); + } + return esop; + } +} diff --git a/src/main/java/org/xbib/cql/elasticsearch/FacetsGenerator.java b/src/main/java/org/xbib/cql/elasticsearch/FacetsGenerator.java new file mode 100644 index 0000000..300da93 --- /dev/null +++ b/src/main/java/org/xbib/cql/elasticsearch/FacetsGenerator.java @@ -0,0 +1,177 @@ +package org.xbib.cql.elasticsearch; + +import static org.xbib.content.json.JsonXContent.contentBuilder; + +import org.xbib.content.XContentBuilder; +import org.xbib.cql.SyntaxException; +import org.xbib.cql.elasticsearch.ast.Expression; +import org.xbib.cql.elasticsearch.ast.Modifier; +import org.xbib.cql.elasticsearch.ast.Name; +import org.xbib.cql.elasticsearch.ast.Operator; +import org.xbib.cql.elasticsearch.ast.Token; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +/** + * Build facet from abstract syntax tree + */ +public class FacetsGenerator implements Visitor { + + private int facetlength = 10; + + private final XContentBuilder builder; + + public FacetsGenerator() throws IOException { + this.builder = contentBuilder(); + } + + public void start() throws IOException { + builder.startObject(); + } + + public void end() throws IOException { + builder.endObject(); + } + + public void startFacets() throws IOException { + builder.startObject("aggregations"); + } + + public void endFacets() throws IOException { + builder.endObject(); + } + + public XContentBuilder getResult() throws IOException { + return builder; + } + + @Override + public void visit(Token node) { + try { + builder.value(node.toString().getBytes()); + } catch (IOException e) { + throw new SyntaxException(e.getMessage(), e); + } + } + + @Override + public void visit(Name node) { + try { + builder.value(node.toString().getBytes()); + } catch (IOException e) { + throw new SyntaxException(e.getMessage(), e); + } + } + + @Override + public void visit(Modifier node) { + try { + builder.value(node.toString().getBytes()); + } catch (IOException e) { + throw new SyntaxException(e.getMessage(), e); + } + } + + @Override + public void visit(Operator node) { + try { + builder.value(node.toString().getBytes()); + } catch (IOException e) { + throw new SyntaxException(e.getMessage(), e); + } + } + + @Override + public void visit(Expression node) { + try { + Operator op = node.getOperator(); + switch (op) { + case TERMS_FACET: { + builder.startObject().field("myfacet", "myvalue") + .endObject(); + break; + } + default: + throw new IllegalArgumentException( + "unable to translate operator while building elasticsearch facet: " + op); + } + } catch (IOException e) { + throw new SyntaxException("internal error while building elasticsearch query", e); + } + } + + public FacetsGenerator facet(String facetLimit, String facetSort) throws IOException { + if (facetLimit == null) { + return this; + } + Map facetMap = parseFacet(facetLimit); + String[] sortSpec = facetSort != null ? facetSort.split(",") : new String[]{"recordCount", "descending"}; + String order = "_count"; + String dir = "desc"; + for (String s : sortSpec) { + switch (s) { + case "recordCount": + order = "_count"; + break; + case "alphanumeric": + order = "_term"; + break; + case "ascending": + dir = "asc"; + break; + } + } + builder.startObject(); + + for (String index : facetMap.keySet()) { + if ("*".equals(index)) { + continue; + } + // TODO range aggregations etc. + String facetType = "terms"; + Integer size = facetMap.get(index); + builder.field(index) + .startObject() + .field(facetType).startObject() + .field("field", index) + .field("size", size > 0 ? size : 10) + .startObject("order") + .field(order, dir) + .endObject() + .endObject(); + builder.endObject(); + } + builder.endObject(); + return this; + } + + private Map parseFacet(String spec) { + Map m = new HashMap(); + m.put("*", facetlength); + if (spec == null || spec.length() == 0) { + return m; + } + String[] params = spec.split(","); + for (String param : params) { + int pos = param.indexOf(':'); + if (pos > 0) { + int n = parseInt(param.substring(0, pos), facetlength); + m.put(param.substring(pos + 1), n); + } else if (param.length() > 0) { + int n = parseInt(param, facetlength); + m.put("*", n); + } + } + return m; + } + + private int parseInt(String s, int defaultValue) { + try { + return Integer.parseInt(s); + } catch (NumberFormatException e) { + return defaultValue; + } + } +} diff --git a/src/main/java/org/xbib/cql/elasticsearch/FilterGenerator.java b/src/main/java/org/xbib/cql/elasticsearch/FilterGenerator.java new file mode 100644 index 0000000..cad6100 --- /dev/null +++ b/src/main/java/org/xbib/cql/elasticsearch/FilterGenerator.java @@ -0,0 +1,338 @@ +package org.xbib.cql.elasticsearch; + +import static org.xbib.content.json.JsonXContent.contentBuilder; + +import org.xbib.content.XContentBuilder; +import org.xbib.cql.SyntaxException; +import org.xbib.cql.elasticsearch.ast.Expression; +import org.xbib.cql.elasticsearch.ast.Modifier; +import org.xbib.cql.elasticsearch.ast.Name; +import org.xbib.cql.elasticsearch.ast.Node; +import org.xbib.cql.elasticsearch.ast.Operator; +import org.xbib.cql.elasticsearch.ast.Token; +import org.xbib.cql.util.QuotedStringTokenizer; + +import java.io.IOException; + +/** + * Build query filter in Elasticsearch JSON syntax from abstract syntax tree + */ +public class FilterGenerator implements Visitor { + + private XContentBuilder builder; + + public FilterGenerator() throws IOException { + this.builder = contentBuilder(); + } + + public FilterGenerator(QueryGenerator queryGenerator) throws IOException { + this.builder = queryGenerator.getResult(); + } + + public FilterGenerator start() throws IOException { + builder.startObject(); + return this; + } + + public FilterGenerator end() throws IOException { + builder.endObject(); + return this; + } + + public FilterGenerator startFilter() throws IOException { + builder.startObject("filter"); + return this; + } + + public FilterGenerator endFilter() throws IOException { + builder.endObject(); + return this; + } + + public XContentBuilder getResult() throws IOException { + return builder; + } + + @Override + public void visit(Token node) { + try { + builder.value(node.getString()); + } catch (IOException e) { + throw new SyntaxException(e.getMessage(), e); + } + } + + @Override + public void visit(Name node) { + try { + builder.field(node.toString()); + } catch (IOException e) { + throw new SyntaxException(e.getMessage(), e); + } + } + + @Override + public void visit(Modifier node) { + try { + builder.value(node.toString()); + } catch (IOException e) { + throw new SyntaxException(e.getMessage(), e); + } + } + + @Override + public void visit(Operator node) { + try { + builder.value(node.toString()); + } catch (IOException e) { + throw new SyntaxException(e.getMessage(), e); + } + } + + @Override + public void visit(Expression node) { + if (!node.isVisible()) { + return; + } + try { + Operator op = node.getOperator(); + switch (op.getArity()) { + case 2: { + Node arg1 = node.getArg1(); + Node arg2 = node.getArgs().length > 1 ? node.getArg2() : null; + boolean visible = false; + for (Node arg : node.getArgs()) { + visible = visible || arg.isVisible(); + } + if (!visible) { + return; + } + Token tok2 = arg2 instanceof Token ? (Token) arg2 : null; + switch (op) { + case EQUALS: { + String field = arg1.toString(); + String value = tok2 != null ? tok2.getString() : ""; + builder.startObject(tok2 != null && tok2.isBoundary() ? "prefix" : "term"); + builder.field(field, value).endObject(); + break; + } + case NOT_EQUALS: { + String field = arg1.toString(); + String value = tok2 != null ? tok2.getString() : ""; + builder.startObject("not") + .startObject(tok2 != null && tok2.isBoundary() ? "prefix" : "term") + .field(field, value) + .endObject().endObject(); + break; + } + case ALL: { + String field = arg1.toString(); + String value = arg2 != null ? arg2.toString() : ""; + boolean phrase = arg2 instanceof Token && ((Token) arg2).isProtected(); + if (phrase) { + builder.startArray("and"); + QuotedStringTokenizer qst = new QuotedStringTokenizer(value); + while (qst.hasMoreTokens()) { + builder.startObject().startObject("term").field(field, qst.nextToken()).endObject().endObject(); + } + builder.endArray(); + } else { + builder.startObject(tok2 != null && tok2.isBoundary() ? "prefix" : "term") + .field(field, value) + .endObject(); + } + break; + } + case ANY: { + boolean phrase = arg2 instanceof Token && ((Token) arg2).isProtected(); + String field = arg1.toString(); + String value = arg2 != null ? arg2.toString() : ""; + if (phrase) { + builder.startArray("or"); + QuotedStringTokenizer qst = new QuotedStringTokenizer(value); + while (qst.hasMoreTokens()) { + builder.startObject().startObject("term") + .field(field, qst.nextToken()).endObject().endObject(); + } + builder.endArray(); + } else { + builder.startObject(tok2 != null && tok2.isBoundary() ? "prefix" : "term") + .field(field, value) + .endObject(); + } + break; + } + case RANGE_GREATER_THAN: { + String field = arg1.toString(); + String value = tok2 != null ? tok2.getString() : ""; + builder.startObject("range").startObject(field) + .field("from", value) + .field("include_lower", false) + .endObject().endObject(); + break; + } + case RANGE_GREATER_OR_EQUAL: { + String field = arg1.toString(); + String value = arg2 != null ? arg2.toString() : ""; + builder.startObject("range").startObject(field) + .field("from", value) + .field("include_lower", true) + .endObject().endObject(); + break; + } + case RANGE_LESS_THAN: { + String field = arg1.toString(); + String value = arg2 != null ? arg2.toString() : ""; + builder.startObject("range").startObject(field) + .field("to", value) + .field("include_upper", false) + .endObject().endObject(); + break; + } + case RANGE_LESS_OR_EQUALS: { + String field = arg1.toString(); + String value = arg2 != null ? arg2.toString() : ""; + builder.startObject("range").startObject(field) + .field("to", value) + .field("include_upper", true) + .endObject().endObject(); + break; + } + case RANGE_WITHIN: { + String field = arg1.toString(); + String value = tok2 != null ? tok2.getString() : ""; + String[] s = value.split(" "); + builder.startObject("range").startObject(field). + field("from", s[0]) + .field("to", s[1]) + .field("include_lower", true) + .field("include_upper", true) + .endObject().endObject(); + break; + } + case AND: { + if (arg2 == null) { + if (arg1.isVisible()) { + arg1.accept(this); + } + } else { + builder.startObject("bool"); + builder.startArray("must"); + Node[] args = node.getArgs(); + for (int i = 0; i < node.getArgs().length; i++) { + if (args[i].isVisible()) { + builder.startObject(); + args[i].accept(this); + builder.endObject(); + } + } + builder.endArray(); + builder.endObject(); + } + break; + } + case OR: { + if (arg2 == null) { + if (arg1.isVisible()) { + arg1.accept(this); + } + } else { + builder.startObject("bool"); + builder.startArray("should"); + Node[] args = node.getArgs(); + for (int i = 0; i < node.getArgs().length; i++) { + if (args[i].isVisible()) { + builder.startObject(); + args[i].accept(this); + builder.endObject(); + } + } + builder.endArray(); + builder.endObject(); + } + break; + } + case OR_FILTER: { + builder.startObject("bool"); + builder.startArray("should"); + Node[] args = node.getArgs(); + for (int i = 0; i < args.length; i += 2) { + if (args[i].isVisible()) { + builder.startObject().startObject("term"); + args[i].accept(this); + args[i + 1].accept(this); + builder.endObject().endObject(); + } + } + builder.endArray(); + builder.endObject(); + break; + } + case AND_FILTER: { + builder.startObject("bool"); + builder.startArray("must"); + Node[] args = node.getArgs(); + for (int i = 0; i < args.length; i += 2) { + if (args[i].isVisible()) { + builder.startObject().startObject("term"); + args[i].accept(this); + args[i + 1].accept(this); + builder.endObject().endObject(); + } + } + builder.endArray(); + builder.endObject(); + break; + } + case ANDNOT: { + if (arg2 == null) { + if (arg1.isVisible()) { + arg1.accept(this); + } + } else { + builder.startObject("bool"); + builder.startArray("must_not"); + Node[] args = node.getArgs(); + for (int i = 0; i < node.getArgs().length; i++) { + if (args[i].isVisible()) { + builder.startObject(); + args[i].accept(this); + builder.endObject(); + } + } + builder.endArray(); + builder.endObject(); + } + break; + } + case PROX: { + String field = arg1.toString(); + // we assume a default of 10 words is enough for proximity + String value = arg2 != null ? arg2.toString() + "~10" : ""; + builder.startObject("field").field(field, value).endObject(); + break; + } + case TERM_FILTER: { + String field = arg1.toString(); + String value = arg2 != null ? arg2.toString() : ""; + builder.startObject("term").field(field, value).endObject(); + break; + } + case QUERY_FILTER: { + builder.startObject("query"); + arg1.accept(this); + builder.endObject(); + break; + } + default: + throw new IllegalArgumentException("unable to translate operator while building elasticsearch query filter: " + op); + } + break; + } + } + } catch (IOException e) { + throw new SyntaxException("internal error while building elasticsearch query filter", e); + } + } + +} diff --git a/src/main/java/org/xbib/cql/elasticsearch/QueryGenerator.java b/src/main/java/org/xbib/cql/elasticsearch/QueryGenerator.java new file mode 100644 index 0000000..8d6bcb4 --- /dev/null +++ b/src/main/java/org/xbib/cql/elasticsearch/QueryGenerator.java @@ -0,0 +1,381 @@ +package org.xbib.cql.elasticsearch; + +import static org.xbib.content.json.JsonXContent.contentBuilder; + +import org.xbib.content.XContentBuilder; +import org.xbib.cql.SyntaxException; +import org.xbib.cql.elasticsearch.ast.Expression; +import org.xbib.cql.elasticsearch.ast.Modifier; +import org.xbib.cql.elasticsearch.ast.Name; +import org.xbib.cql.elasticsearch.ast.Node; +import org.xbib.cql.elasticsearch.ast.Operator; +import org.xbib.cql.elasticsearch.ast.Token; + +import java.io.IOException; + +/** + * Build Elasticsearch query from abstract syntax tree + */ +public class QueryGenerator implements Visitor { + + private final XContentBuilder builder; + + public QueryGenerator() throws IOException { + this.builder = contentBuilder(); + } + + public void start() throws IOException { + builder.startObject(); + } + + public void end() throws IOException { + builder.endObject(); + } + + public void startFiltered() throws IOException { + builder.startObject("filtered").startObject("query"); + } + + public void endFiltered() throws IOException { + builder.endObject(); + } + + public void startBoost(String boostField, String modifier, Float factor, String boostMode) throws IOException { + builder.startObject("function_score") + .startObject("field_value_factor") + .field("field", boostField) + .field("modifier", modifier != null ? modifier : "log1p") + .field("factor", factor != null ? factor : 1.0f) + .endObject() + .field("boost_mode", boostMode != null ? boostMode : "multiply") + .startObject("query"); + } + + public void endBoost() throws IOException { + builder.endObject().endObject(); + } + + public XContentBuilder getResult() { + return builder; + } + + @Override + public void visit(Token token) { + try { + switch (token.getType()) { + case BOOL: + builder.value(token.getBoolean()); + break; + case INT: + builder.value(token.getInteger()); + break; + case FLOAT: + builder.value(token.getFloat()); + break; + case DATETIME: + builder.value(token.getDate()); + break; + case STRING: + builder.value(token.getString()); + break; + default: + throw new IOException("unknown token type: " + token); + } + } catch (IOException e) { + throw new SyntaxException(e.getMessage(), e); + } + } + + @Override + public void visit(Name node) { + try { + builder.field(node.toString()); + } catch (IOException e) { + throw new SyntaxException(e.getMessage(), e); + } + } + + @Override + public void visit(Modifier node) { + try { + builder.value(node.toString()); + } catch (IOException e) { + throw new SyntaxException(e.getMessage(), e); + } + } + + @Override + public void visit(Operator node) { + try { + builder.value(node.toString()); + } catch (IOException e) { + throw new SyntaxException(e.getMessage(), e); + } + } + + @Override + public void visit(Expression node) { + if (!node.isVisible()) { + return; + } + try { + Operator op = node.getOperator(); + switch (op.getArity()) { + case 0: { + switch (op) { + case MATCH_ALL: { + builder.startObject("match_all").endObject(); + break; + } + } + break; + } + case 1: { + // unary operators, anyone? + break; + } + case 2: { + // binary operators + Node arg1 = node.getArg1(); + Node arg2 = node.getArgs().length > 1 ? node.getArg2() : null; + Token tok2 = arg2 instanceof Token ? (Token) arg2 : null; + boolean visible = false; + for (Node arg : node.getArgs()) { + visible = visible || arg.isVisible(); + } + if (!visible) { + return; + } + switch (op) { + case EQUALS: { + String field = arg1.toString(); + String value = arg2 != null ? arg2.toString() : ""; + builder.startObject("simple_query_string") + .field("query", value) + .field("fields", new String[]{field}) + .field("analyze_wildcard", true) + .field("default_operator", "and") + .endObject(); + break; + } + case NOT_EQUALS: { + String field = arg1.toString(); + String value = arg2 != null ? arg2.toString() : ""; + builder.startObject("bool").startObject("must_not"); + builder.startObject("simple_query_string") + .field("query", value) + .field("fields", new String[]{field}) + .field("analyze_wildcard", true) + .field("default_operator", "and") + .endObject(); + builder.endObject().endObject(); + break; + } + case ALL: { + String field = arg1.toString(); + String value = tok2 != null ? tok2.getString() : ""; + builder.startObject("simple_query_string") + .field("query", value) + .field("fields", new String[]{field}) + .field("analyze_wildcard", true) + .field("default_operator", "and") + .endObject(); + break; + } + case ANY: { + String field = arg1.toString(); + String value = tok2 != null ? tok2.getString() : ""; + builder.startObject("simple_query_string") + .field("query", value) + .field("fields", new String[]{field}) + .field("analyze_wildcard", true) + .field("default_operator", "or") + .endObject(); + break; + } + case PHRASE: { + String field = arg1.toString(); + String value = arg2 != null ? arg2.toString() : ""; + if (tok2 != null) { + if (tok2.isProtected()) { + builder.startObject("match_phrase") + .startObject(field) + .field("query", tok2.getString()) + .field("slop", 0) + .endObject() + .endObject(); + } else if (tok2.isAll()) { + builder.startObject("match_all").endObject(); + } else if (tok2.isWildcard()) { + builder.startObject("wildcard").field(field, value).endObject(); + } else if (tok2.isBoundary()) { + builder.startObject("prefix").field(field, value).endObject(); + } else { + builder.startObject("match_phrase") + .startObject(field) + .field("query", value) + .field("slop", 0) + .endObject() + .endObject(); + } + } + break; + } + case RANGE_GREATER_THAN: { + String field = arg1.toString(); + String value = arg2 != null ? arg2.toString() : ""; + builder.startObject("range").startObject(field) + .field("from", value) + .field("include_lower", false) + .endObject().endObject(); + break; + } + case RANGE_GREATER_OR_EQUAL: { + String field = arg1.toString(); + String value = arg2 != null ? arg2.toString() : ""; + builder.startObject("range").startObject(field) + .field("from", value) + .field("include_lower", true) + .endObject().endObject(); + break; + } + case RANGE_LESS_THAN: { + String field = arg1.toString(); + String value = arg2 != null ? arg2.toString() : ""; + builder.startObject("range").startObject(field) + .field("to", value) + .field("include_upper", false) + .endObject().endObject(); + break; + } + case RANGE_LESS_OR_EQUALS: { + String field = arg1.toString(); + String value = arg2 != null ? arg2.toString() : ""; + builder.startObject("range").startObject(field) + .field("to", value) + .field("include_upper", true) + .endObject().endObject(); + break; + } + case RANGE_WITHIN: { + // borders are inclusive + String field = arg1.toString(); + String value = arg2 != null ? arg2.toString() : ""; + String from = null; + String to = null; + if (tok2 != null) { + if (!tok2.isProtected()) { + throw new IllegalArgumentException("range within: unable to derive range from a non-phrase: " + value); + } + if (tok2.getStringList().size() != 2) { + throw new IllegalArgumentException("range within: unable to derive range from a phrase of length not equals to 2: " + tok2.getStringList()); + } + from = tok2.getStringList().get(0); + to = tok2.getStringList().get(1); + } + builder.startObject("range").startObject(field) + .field("from", from) + .field("to", to) + .field("include_lower", true) + .field("include_upper", true) + .endObject().endObject(); + break; + } + case AND: { + if (arg2 == null) { + if (arg1.isVisible()) { + arg1.accept(this); + } + } else { + builder.startObject("bool"); + if (arg1.isVisible() && arg2.isVisible()) { + builder.startArray("must").startObject(); + arg1.accept(this); + builder.endObject().startObject(); + arg2.accept(this); + builder.endObject().endArray(); + } else if (arg1.isVisible()) { + builder.startObject("must"); + arg1.accept(this); + builder.endObject(); + } else if (arg2.isVisible()) { + builder.startObject("must"); + arg2.accept(this); + builder.endObject(); + } + builder.endObject(); + } + break; + } + case OR: { + // short expression + if (arg2 == null) { + if (arg1.isVisible()) { + arg1.accept(this); + } + } else { + builder.startObject("bool"); + if (arg1.isVisible() && arg2.isVisible()) { + builder.startArray("should").startObject(); + arg1.accept(this); + builder.endObject().startObject(); + arg2.accept(this); + builder.endObject().endArray(); + } else if (arg1.isVisible()) { + builder.startObject("should"); + arg1.accept(this); + builder.endObject(); + } else if (arg2.isVisible()) { + builder.startObject("should"); + arg2.accept(this); + builder.endObject(); + } + builder.endObject(); + } + break; + } + case ANDNOT: { + if (arg2 == null) { + if (arg1.isVisible()) { + arg1.accept(this); + } + } else { + builder.startObject("bool"); + if (arg1.isVisible() && arg2.isVisible()) { + builder.startArray("must_not").startObject(); + arg1.accept(this); + builder.endObject().startObject(); + arg2.accept(this); + builder.endObject().endArray(); + } else if (arg1.isVisible()) { + builder.startObject("must_not"); + arg1.accept(this); + builder.endObject(); + } else if (arg2.isVisible()) { + builder.startObject("must_not"); + arg2.accept(this); + builder.endObject(); + } + builder.endObject(); + } + break; + } + case PROX: { + String field = arg1.toString(); + // we assume a default of 10 words is enough for proximity + String value = arg2 != null ? arg2.toString() + "~10" : ""; + builder.startObject("field").field(field, value).endObject(); + break; + } + default: + throw new IllegalArgumentException("unable to translate operator while building elasticsearch query: " + op); + } + break; + } + } + } catch (IOException e) { + throw new SyntaxException("internal error while building elasticsearch query", e); + } + } + +} diff --git a/src/main/java/org/xbib/cql/elasticsearch/SortGenerator.java b/src/main/java/org/xbib/cql/elasticsearch/SortGenerator.java new file mode 100644 index 0000000..6527a34 --- /dev/null +++ b/src/main/java/org/xbib/cql/elasticsearch/SortGenerator.java @@ -0,0 +1,109 @@ +package org.xbib.cql.elasticsearch; + +import static org.xbib.content.json.JsonXContent.contentBuilder; + +import org.xbib.content.XContentBuilder; +import org.xbib.cql.SyntaxException; +import org.xbib.cql.elasticsearch.ast.Expression; +import org.xbib.cql.elasticsearch.ast.Modifier; +import org.xbib.cql.elasticsearch.ast.Name; +import org.xbib.cql.elasticsearch.ast.Node; +import org.xbib.cql.elasticsearch.ast.Operator; +import org.xbib.cql.elasticsearch.ast.Token; + +import java.io.IOException; +import java.util.Stack; + +/** + * Build sort in Elasticsearch JSON syntax from abstract syntax tree + */ +public class SortGenerator implements Visitor { + + private final XContentBuilder builder; + + private final Stack modifiers; + + public SortGenerator() throws IOException { + this.builder = contentBuilder(); + this.modifiers = new Stack<>(); + } + + public void start() throws IOException { + builder.startArray(); + } + + public void end() throws IOException { + builder.endArray(); + } + + public XContentBuilder getResult() { + return builder; + } + + @Override + public void visit(Token node) { + } + + @Override + public void visit(Name node) { + try { + if (modifiers.isEmpty()) { + builder.startObject() + .field(node.getName()) + .startObject() + .field("unmapped_type", "string") + .field("missing", "_last") + .endObject() + .endObject(); + } else { + builder.startObject().field(node.getName()).startObject(); + while (!modifiers.isEmpty()) { + Modifier mod = modifiers.pop(); + String s = mod.getName().toString(); + switch (s) { + case "ascending": + case "sort.ascending": { + builder.field("order", "asc"); + break; + } + case "descending": + case "sort.descending": { + builder.field("order", "desc"); + break; + } + default: { + builder.field(s, mod.getTerm()); + break; + } + } + } + builder.field("unmapped_type", "string"); + builder.field("missing", "_last"); + builder.endObject(); + builder.endObject(); + } + } catch (IOException e) { + throw new SyntaxException(e.getMessage(), e); + } + } + + @Override + public void visit(Modifier node) { + modifiers.push(node); + } + + @Override + public void visit(Operator node) { + } + + @Override + public void visit(Expression node) { + Operator op = node.getOperator(); + if (op == Operator.SORT) { + for (Node arg : node.getArgs()) { + arg.accept(this); + } + } + } + +} diff --git a/src/main/java/org/xbib/cql/elasticsearch/SourceGenerator.java b/src/main/java/org/xbib/cql/elasticsearch/SourceGenerator.java new file mode 100644 index 0000000..9cc295a --- /dev/null +++ b/src/main/java/org/xbib/cql/elasticsearch/SourceGenerator.java @@ -0,0 +1,43 @@ +package org.xbib.cql.elasticsearch; + +import static org.xbib.content.json.JsonXContent.contentBuilder; + +import org.xbib.content.XContentBuilder; + +import java.io.IOException; + +/** + * + */ +public class SourceGenerator { + + private final XContentBuilder builder; + + public SourceGenerator() throws IOException { + this.builder = contentBuilder(); + } + + public void build(QueryGenerator query, + int from, int size) throws IOException { + build(query, from, size, null, null); + } + + public void build(QueryGenerator query, int from, int size, XContentBuilder sort, XContentBuilder facets) throws IOException { + builder.startObject(); + builder.field("from", from); + builder.field("size", size); + builder.rawField("query", query.getResult().bytes().toBytes() ); + if (sort != null && sort.bytes().length() > 0) { + builder.rawField("sort", sort.bytes().toBytes()); + } + if (facets != null && facets.bytes().length() > 0) { + builder.rawField("aggregations", facets.bytes().toBytes()); + } + builder.endObject(); + builder.close(); + } + + public XContentBuilder getResult() { + return builder; + } +} diff --git a/src/main/java/org/xbib/cql/elasticsearch/Visitor.java b/src/main/java/org/xbib/cql/elasticsearch/Visitor.java new file mode 100644 index 0000000..9e13868 --- /dev/null +++ b/src/main/java/org/xbib/cql/elasticsearch/Visitor.java @@ -0,0 +1,24 @@ +package org.xbib.cql.elasticsearch; + +import org.xbib.cql.elasticsearch.ast.Expression; +import org.xbib.cql.elasticsearch.ast.Modifier; +import org.xbib.cql.elasticsearch.ast.Name; +import org.xbib.cql.elasticsearch.ast.Operator; +import org.xbib.cql.elasticsearch.ast.Token; + +/** + * + */ +public interface Visitor { + + void visit(Token node); + + void visit(Name node); + + void visit(Modifier node); + + void visit(Operator node); + + void visit(Expression node); + +} diff --git a/src/main/java/org/xbib/cql/elasticsearch/ast/Expression.java b/src/main/java/org/xbib/cql/elasticsearch/ast/Expression.java new file mode 100644 index 0000000..48a2c8d --- /dev/null +++ b/src/main/java/org/xbib/cql/elasticsearch/ast/Expression.java @@ -0,0 +1,110 @@ +package org.xbib.cql.elasticsearch.ast; + +import org.xbib.cql.elasticsearch.Visitor; + +/** + * Elasticsearch expression + */ +public class Expression implements Node { + + private Operator op; + + private Node[] args; + + private TokenType type; + + private boolean visible; + + /** + * Constructor for folding nodes. + * + * @param expr the expression + * @param arg the new argument + */ + public Expression(Expression expr, Node arg) { + this.type = TokenType.EXPRESSION; + this.op = expr.getOperator(); + if (arg instanceof Expression) { + Expression expr2 = (Expression) arg; + this.args = new Node[expr.getArgs().length + expr2.getArgs().length]; + System.arraycopy(expr.getArgs(), 0, this.args, 0, expr.getArgs().length); + System.arraycopy(expr2.getArgs(), 0, this.args, expr.getArgs().length, expr2.getArgs().length); + } else { + Node[] exprargs = expr.getArgs(); + this.args = new Node[exprargs.length + 1]; + // to avoid copy, organization of the argument list is reverse, the most recent arg is at position 0 + this.args[0] = arg; + System.arraycopy(exprargs, 0, this.args, 1, exprargs.length); + } + this.visible = false; + for (Node node : args) { + if (node instanceof Name || node instanceof Expression) { + this.visible = visible || arg.isVisible(); + } + } + } + + public Expression(Operator op, Node... args) { + this.op = op; + this.type = TokenType.EXPRESSION; + this.args = args; + if (args != null && args.length > 0) { + this.visible = false; + for (Node arg : args) { + if (arg instanceof Name || arg instanceof Expression) { + this.visible = visible || arg.isVisible(); + } + } + } else { + this.visible = true; + } + } + + public Operator getOperator() { + return op; + } + + public Node[] getArgs() { + return args; + } + + public Node getArg1() { + return args[0]; + } + + public Node getArg2() { + return args[1]; + } + + @Override + public boolean isVisible() { + return visible; + } + + @Override + public TokenType getType() { + return type; + } + + @Override + public void accept(Visitor visitor) { + visitor.visit(this); + } + + @Override + public String toString() { + if (!visible) { + return ""; + } + StringBuilder sb = new StringBuilder(op.toString()); + sb.append('('); + for (int i = 0; i < args.length; i++) { + sb.append(args[i]); + if (i < args.length - 1) { + sb.append(','); + } + } + sb.append(')'); + return sb.toString(); + } +} diff --git a/src/main/java/org/xbib/cql/elasticsearch/ast/Modifier.java b/src/main/java/org/xbib/cql/elasticsearch/ast/Modifier.java new file mode 100644 index 0000000..434b268 --- /dev/null +++ b/src/main/java/org/xbib/cql/elasticsearch/ast/Modifier.java @@ -0,0 +1,49 @@ +package org.xbib.cql.elasticsearch.ast; + +import org.xbib.cql.elasticsearch.Visitor; + +/** + * This is a modifier node for Elasticsearch query language + */ +public class Modifier implements Node { + + private Node name; + private Node term; + + public Modifier(Node name, Node term) { + this.name = name; + this.term = term; + } + + public Modifier(Node name) { + this.name = name; + } + + public Node getName() { + return name; + } + + public Node getTerm() { + return term; + } + + @Override + public boolean isVisible() { + return true; + } + + @Override + public void accept(Visitor visitor) { + visitor.visit(this); + } + + @Override + public TokenType getType() { + return TokenType.OPERATOR; + } + + @Override + public String toString() { + return name + "=" + term; + } +} diff --git a/src/main/java/org/xbib/cql/elasticsearch/ast/Name.java b/src/main/java/org/xbib/cql/elasticsearch/ast/Name.java new file mode 100644 index 0000000..c2cd1ef --- /dev/null +++ b/src/main/java/org/xbib/cql/elasticsearch/ast/Name.java @@ -0,0 +1,52 @@ +package org.xbib.cql.elasticsearch.ast; + +import org.xbib.cql.elasticsearch.Visitor; + +/** + * A name for Elasticsearch fields + */ +public class Name implements Node { + + private String name; + + private TokenType type; + + private boolean visible; + + public Name(String name) { + this(name, true); + } + + public Name(String name, boolean visible) { + this.name = name; + this.visible = visible; + } + + public String getName() { + return name; + } + + public void setType(TokenType type) { + this.type = type; + } + + @Override + public TokenType getType() { + return type; + } + + @Override + public boolean isVisible() { + return visible; + } + + @Override + public void accept(Visitor visitor) { + visitor.visit(this); + } + + @Override + public String toString() { + return name; + } +} diff --git a/src/main/java/org/xbib/cql/elasticsearch/ast/Node.java b/src/main/java/org/xbib/cql/elasticsearch/ast/Node.java new file mode 100644 index 0000000..fdd05ca --- /dev/null +++ b/src/main/java/org/xbib/cql/elasticsearch/ast/Node.java @@ -0,0 +1,16 @@ +package org.xbib.cql.elasticsearch.ast; + +import org.xbib.cql.elasticsearch.Visitor; + +/** + * This node class is the base class for the Elasticsearch Query Lange abstract syntax tree + */ +public interface Node { + + void accept(Visitor visitor); + + boolean isVisible(); + + TokenType getType(); + +} \ No newline at end of file diff --git a/src/main/java/org/xbib/cql/elasticsearch/ast/Operator.java b/src/main/java/org/xbib/cql/elasticsearch/ast/Operator.java new file mode 100644 index 0000000..45ff772 --- /dev/null +++ b/src/main/java/org/xbib/cql/elasticsearch/ast/Operator.java @@ -0,0 +1,62 @@ +package org.xbib.cql.elasticsearch.ast; + +import org.xbib.cql.elasticsearch.Visitor; + +/** + * Elasticsearch operators + */ +public enum Operator implements Node { + EQUALS(2), + NOT_EQUALS(2), + RANGE_LESS_THAN(2), + RANGE_LESS_OR_EQUALS(2), + RANGE_GREATER_THAN(2), + RANGE_GREATER_OR_EQUAL(2), + RANGE_WITHIN(2), + AND(2), + ANDNOT(2), + OR(2), + PROX(2), + ALL(2), + ANY(2), + PHRASE(2), + TERM_FILTER(2), + QUERY_FILTER(2), + SORT(0), + TERMS_FACET(0), + OR_FILTER(2), + AND_FILTER(2), + MATCH_ALL(0); + + + private final int arity; + + Operator(int arity) { + this.arity = arity; + } + + @Override + public boolean isVisible() { + return true; + } + + @Override + public TokenType getType() { + return TokenType.OPERATOR; + } + + public int getArity() { + return arity; + } + + @Override + public void accept(Visitor visitor) { + visitor.visit(this); + } + + @Override + public String toString() { + return this.name(); + } + +} diff --git a/src/main/java/org/xbib/cql/elasticsearch/ast/Token.java b/src/main/java/org/xbib/cql/elasticsearch/ast/Token.java new file mode 100644 index 0000000..9ec3576 --- /dev/null +++ b/src/main/java/org/xbib/cql/elasticsearch/ast/Token.java @@ -0,0 +1,213 @@ +package org.xbib.cql.elasticsearch.ast; + +import static java.util.stream.Collectors.toList; + +import org.xbib.cql.elasticsearch.Visitor; +import org.xbib.cql.util.DateUtil; +import org.xbib.cql.util.QuotedStringTokenizer; +import org.xbib.cql.util.UnterminatedQuotedStringException; + +import java.util.Collections; +import java.util.Date; +import java.util.EnumSet; +import java.util.List; +import java.util.regex.Pattern; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +/** + * Elasticsearch query tokens. + */ +public class Token implements Node { + + public enum TokenClass { + + NORMAL, ALL, WILDCARD, BOUNDARY, PROTECTED + } + + private TokenType type; + + private String value; + + private String stringvalue; + + private Boolean booleanvalue; + + private Long longvalue; + + private Double doublevalue; + + private Date datevalue; + + private List values; + + private final EnumSet tokenClass; + + public Token(String value) { + this.value = value; + this.tokenClass = EnumSet.of(TokenClass.NORMAL); + this.type = TokenType.STRING; + // if this string is equal to true/false or on/off or yes/no, convert silently to bool + if (value.equals("true") || value.equals("yes") || value.equals("on")) { + this.booleanvalue = true; + this.value = null; + this.type = TokenType.BOOL; + + } else if (value.equals("false") || value.equals("no") || value.equals("off")) { + this.booleanvalue = false; + this.value = null; + this.type = TokenType.BOOL; + + } + if (this.value != null) { + // protected? + if (value.startsWith("\"") && value.endsWith("\"")) { + this.stringvalue = value; + this.value = value.substring(1, value.length() - 1).replaceAll("\\\\\"", "\""); + this.values = parseQuot(this.value); + tokenClass.add(TokenClass.PROTECTED); + } + // wildcard? + if (this.value.indexOf('*') >= 0 || this.value.indexOf('?') >= 0) { + tokenClass.add(TokenClass.WILDCARD); + // all? + if (this.value.length() == 1) { + tokenClass.add(TokenClass.ALL); + } + } + // prefix? + if (this.value.length() > 0 && this.value.charAt(0) == '^') { + tokenClass.add(TokenClass.BOUNDARY); + this.value = this.value.substring(1); + } + } + } + + public Token(Boolean value) { + this.booleanvalue = value; + this.type = TokenType.BOOL; + this.tokenClass = EnumSet.of(TokenClass.NORMAL); + } + + public Token(Long value) { + this.longvalue = value; + this.type = TokenType.INT; + this.tokenClass = EnumSet.of(TokenClass.NORMAL); + } + + public Token(Double value) { + this.doublevalue = value; + this.type = TokenType.FLOAT; + this.tokenClass = EnumSet.of(TokenClass.NORMAL); + } + + public Token(Date value) { + this.datevalue = value; + // this will enforce dates to get formatted as long values (years) + this.longvalue = Long.parseLong(DateUtil.formatDate(datevalue, "yyyy")); + this.type = TokenType.DATETIME; + this.tokenClass = EnumSet.of(TokenClass.NORMAL); + } + + /** + * Same as toString(), but ignore stringvalue. + */ + public String getString() { + StringBuilder sb = new StringBuilder(); + if (booleanvalue != null) { + sb.append(booleanvalue); + } else if (longvalue != null) { + sb.append(longvalue); + } else if (doublevalue != null) { + sb.append(doublevalue); + } else if (datevalue != null) { + sb.append(DateUtil.formatDateISO(datevalue)); + } else if (value != null) { + sb.append(value); + } + return sb.toString(); + } + + public Boolean getBoolean() { + return booleanvalue; + } + + public Long getInteger() { + return longvalue; + } + + public Double getFloat() { + return doublevalue; + } + + public Date getDate() { + return datevalue; + } + + public List getStringList() { + return values; + } + + @Override + public TokenType getType() { + return type; + } + + @Override + public boolean isVisible() { + return true; + } + + @Override + public void accept(Visitor visitor) { + visitor.visit(this); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + if (booleanvalue != null) { + sb.append(booleanvalue); + } else if (longvalue != null) { + sb.append(longvalue); + } else if (doublevalue != null) { + sb.append(doublevalue); + } else if (datevalue != null) { + sb.append(DateUtil.formatDateISO(datevalue)); + } else if (stringvalue != null) { + sb.append(stringvalue); + } else if (value != null) { + sb.append(value); + } + return sb.toString(); + } + + public boolean isProtected() { + return tokenClass.contains(TokenClass.PROTECTED); + } + + public boolean isBoundary() { + return tokenClass.contains(TokenClass.BOUNDARY); + } + + public boolean isWildcard() { + return tokenClass.contains(TokenClass.WILDCARD); + } + + public boolean isAll() { + return tokenClass.contains(TokenClass.ALL); + } + + private List parseQuot(String s) { + try { + QuotedStringTokenizer qst = new QuotedStringTokenizer(s, " \t\n\r\f", "\"", '\\', false); + Iterable iterable = () -> qst; + Stream stream = StreamSupport.stream(iterable.spliterator(), false); + return stream.filter(str -> !word.matcher(str).matches()).collect(toList()); + } catch (UnterminatedQuotedStringException e) { + return Collections.singletonList(s); + } + } + + private final static Pattern word = Pattern.compile("[\\P{IsWord}]"); +} diff --git a/src/main/java/org/xbib/cql/elasticsearch/ast/TokenType.java b/src/main/java/org/xbib/cql/elasticsearch/ast/TokenType.java new file mode 100644 index 0000000..3d08da8 --- /dev/null +++ b/src/main/java/org/xbib/cql/elasticsearch/ast/TokenType.java @@ -0,0 +1,9 @@ +package org.xbib.cql.elasticsearch.ast; + +/** + * Elasticsearch query language token types. + */ +public enum TokenType { + + STRING, BOOL, INT, FLOAT, DATETIME, NAME, OPERATOR, EXPRESSION +} diff --git a/src/main/java/org/xbib/cql/elasticsearch/ast/package-info.java b/src/main/java/org/xbib/cql/elasticsearch/ast/package-info.java new file mode 100644 index 0000000..a378a60 --- /dev/null +++ b/src/main/java/org/xbib/cql/elasticsearch/ast/package-info.java @@ -0,0 +1,4 @@ +/** + * Classes for abstract syntax tree construction for Elasticsearch query generation. + */ +package org.xbib.cql.elasticsearch.ast; diff --git a/src/main/java/org/xbib/cql/elasticsearch/model/ElasticsearchFacet.java b/src/main/java/org/xbib/cql/elasticsearch/model/ElasticsearchFacet.java new file mode 100644 index 0000000..a0be1b2 --- /dev/null +++ b/src/main/java/org/xbib/cql/elasticsearch/model/ElasticsearchFacet.java @@ -0,0 +1,93 @@ +package org.xbib.cql.elasticsearch.model; + +import org.xbib.cql.QueryFacet; + +/** + * Elasticsearch facet. + * + * @param parameter type + */ +public final class ElasticsearchFacet implements QueryFacet, Comparable> { + + public enum Type { + TERMS, + RANGE, + HISTOGRAM, + DATEHISTOGRAM, + FILTER, + QUERY, + STATISTICAL, + TERMS_STATS, + GEO_DISTANCE + } + + public static int DEFAULT_FACET_SIZE = 10; + + private Type type; + + private String name; + + private V value; + + private int size; + + public ElasticsearchFacet(Type type, String name, V value) { + this(type, name, value, DEFAULT_FACET_SIZE); + } + + public ElasticsearchFacet(Type type, String name, V value, int size) { + this.type = type; + this.name = name; + this.value = value; + this.size = size; + } + + @Override + public void setName(String name) { + this.name = name; + } + + @Override + public String getName() { + return name; + } + + public void setType(Type type) { + this.type = type; + } + + public Type getType() { + return type; + } + + @Override + public void setValue(V value) { + this.value = value; + } + + @Override + public V getValue() { + return value; + } + + @Override + public int getSize() { + return size; + } + + @Override + public String getFilterName() { + return name; + } + + @Override + public int compareTo(ElasticsearchFacet o) { + return name.compareTo(((ElasticsearchFacet) o).getName()); + } + + @Override + public String toString() { + return "facet [name=" + name + ",value=" + value + ",size=" + size + "]"; + } + +} diff --git a/src/main/java/org/xbib/cql/elasticsearch/model/ElasticsearchFilter.java b/src/main/java/org/xbib/cql/elasticsearch/model/ElasticsearchFilter.java new file mode 100644 index 0000000..b57502a --- /dev/null +++ b/src/main/java/org/xbib/cql/elasticsearch/model/ElasticsearchFilter.java @@ -0,0 +1,53 @@ +package org.xbib.cql.elasticsearch.model; + +import org.xbib.cql.QueryFilter; +import org.xbib.cql.elasticsearch.ast.Operator; + +/** + * Elasticsearch filter. + * @param parameter type + */ +public class ElasticsearchFilter implements QueryFilter, Comparable> { + + private String name; + + private V value; + + private Operator op; + + public ElasticsearchFilter(String name, V value, Operator op) { + this.name = name; + this.op = op; + this.value = value; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setValue(V value) { + this.value = value; + } + + public V getValue() { + return value; + } + + public Operator getFilterOperation() { + return op; + } + + @Override + public int compareTo(ElasticsearchFilter o) { + return toString().compareTo(o.toString()); + } + + @Override + public String toString() { + return name + " " + op + " " + value; + } +} diff --git a/src/main/java/org/xbib/cql/elasticsearch/model/ElasticsearchQueryModel.java b/src/main/java/org/xbib/cql/elasticsearch/model/ElasticsearchQueryModel.java new file mode 100644 index 0000000..518e0fa --- /dev/null +++ b/src/main/java/org/xbib/cql/elasticsearch/model/ElasticsearchQueryModel.java @@ -0,0 +1,200 @@ +package org.xbib.cql.elasticsearch.model; + +import org.xbib.cql.elasticsearch.ast.Expression; +import org.xbib.cql.elasticsearch.ast.Name; +import org.xbib.cql.elasticsearch.ast.Node; +import org.xbib.cql.elasticsearch.ast.Operator; +import org.xbib.cql.elasticsearch.ast.Token; +import org.xbib.cql.elasticsearch.ast.TokenType; +import org.xbib.cql.model.CQLQueryModel; + +import java.util.HashMap; +import java.util.Map; +import java.util.Stack; + +/** + * Elasticsearch query model. + */ +public final class ElasticsearchQueryModel { + + private final Map conjunctivefilters; + + private final Map disjunctivefilters; + + private final Map facets; + + private Expression sortexpr; + + public ElasticsearchQueryModel() { + this.conjunctivefilters = new HashMap<>(); + this.disjunctivefilters = new HashMap<>(); + this.facets = new HashMap<>(); + } + + /** + * Determine if the key has a type. Default type is string. + * + * @param key the key to check + * @return the type of the key + */ + public TokenType getElasticsearchType(String key) { + if ("datetime".equals(key)) { + return TokenType.DATETIME; + } + if ("int".equals(key)) { + return TokenType.INT; + } + if ("long".equals(key)) { + return TokenType.INT; + } + if ("float".equals(key)) { + return TokenType.FLOAT; + } + return TokenType.STRING; + } + + /** + * Get expression visibility of a given context. + * + * @param context the context + * @return true if visible + */ + public boolean getVisibility(String context) { + return !CQLQueryModel.isFacetContext(context) + && !CQLQueryModel.isFilterContext(context) + && !CQLQueryModel.isOptionContext(context); + } + + /** + * Check if this context is the facet context. + * + * @param context the context + * @return true if facet context + */ + public boolean isFacetContext(String context) { + return CQLQueryModel.isFacetContext(context); + } + + /** + * Check if this context is the filter context. + * + * @param context the context + * @return true if filter context + */ + public boolean isFilterContext(String context) { + return CQLQueryModel.isFilterContext(context); + } + + + public boolean hasFacets() { + return !facets.isEmpty(); + } + + public void addFacet(String key, String value) { + ElasticsearchFacet facet = new ElasticsearchFacet(ElasticsearchFacet.Type.TERMS, key, new Name(value)); + facets.put(facet.getName(), new Expression(Operator.TERMS_FACET, facet.getValue())); + } + + public Expression getFacetExpression() { + return new Expression(Operator.TERMS_FACET, facets.values().toArray(new Node[facets.size()])); + } + + public void addConjunctiveFilter(String name, Node value, Operator op) { + addFilter(conjunctivefilters, new ElasticsearchFilter<>(name, value, op)); + } + + public void addDisjunctiveFilter(String name, Node value, Operator op) { + addFilter(disjunctivefilters, new ElasticsearchFilter<>(name, value, op)); + } + + public boolean hasFilter() { + return !conjunctivefilters.isEmpty() || !disjunctivefilters.isEmpty(); + } + + /** + * Get filter expression. + * Only one filter expression is allowed per query. + * First, build conjunctive and disjunctive filter terms. + * If both are null, there is no filter at all. + * Otherwise, combine conjunctive and disjunctive filter terms with a + * disjunction, and apply filter function, and return this expression. + * + * @return a single filter expression or null if there are no filter terms + */ + public Expression getFilterExpression() { + if (!hasFilter()) { + return null; + } + Expression conjunctiveclause = null; + if (!conjunctivefilters.isEmpty()) { + conjunctiveclause = new Expression(Operator.AND, + conjunctivefilters.values().toArray(new Node[conjunctivefilters.size()])); + } + Expression disjunctiveclause = null; + if (!disjunctivefilters.isEmpty()) { + disjunctiveclause = new Expression(Operator.OR, + disjunctivefilters.values().toArray(new Node[disjunctivefilters.size()])); + } + if (conjunctiveclause != null && disjunctiveclause == null) { + return conjunctiveclause; + } else if (conjunctiveclause == null && disjunctiveclause != null) { + return disjunctiveclause; + } else { + return new Expression(Operator.OR, conjunctiveclause, disjunctiveclause); + } + } + + /** + * Add sort expression. + * + * @param indexAndModifier the index with modifiers + */ + public void setSort(Stack indexAndModifier) { + this.sortexpr = new Expression(Operator.SORT, reverse(indexAndModifier).toArray(new Node[indexAndModifier.size()])); + } + + /** + * Get sort expression. + * + * @return the sort expression + */ + public Expression getSort() { + return sortexpr; + } + + /** + * Helper method to add a filter. + * + * @param filters the filter list + * @param filter the filter to add + */ + private void addFilter(Map filters, ElasticsearchFilter filter) { + Name name = new Name(filter.getName()); + name.setType(getElasticsearchType(filter.getName())); + Node value = filter.getValue(); + if (value instanceof Token) { + value = new Expression(filter.getFilterOperation(), name, value); + } + if (filters.containsKey(filter.getName())) { + Expression expression = filters.get(filter.getName()); + expression = new Expression(expression, value); + filters.put(filter.getName(), expression); + } else { + filters.put(filter.getName(), (Expression) value); + } + } + + /** + * Helper method to reverse an expression stack. + * + * @param in the stack to reverse + * @return the reversed stack + */ + private Stack reverse(Stack in) { + Stack out = new Stack(); + while (!in.empty()) { + out.push(in.pop()); + } + return out; + } +} diff --git a/src/main/java/org/xbib/cql/elasticsearch/model/package-info.java b/src/main/java/org/xbib/cql/elasticsearch/model/package-info.java new file mode 100644 index 0000000..e9b7c89 --- /dev/null +++ b/src/main/java/org/xbib/cql/elasticsearch/model/package-info.java @@ -0,0 +1,4 @@ +/** + * Classes for Elasticsearch query model. + */ +package org.xbib.cql.elasticsearch.model; \ No newline at end of file diff --git a/src/main/java/org/xbib/cql/elasticsearch/package-info.java b/src/main/java/org/xbib/cql/elasticsearch/package-info.java new file mode 100644 index 0000000..f37279f --- /dev/null +++ b/src/main/java/org/xbib/cql/elasticsearch/package-info.java @@ -0,0 +1,4 @@ +/** + * Classes for compiling CQL to Elasticsearch queries. + */ +package org.xbib.cql.elasticsearch; \ No newline at end of file diff --git a/src/main/java/org/xbib/cql/model/CQLQueryModel.java b/src/main/java/org/xbib/cql/model/CQLQueryModel.java new file mode 100644 index 0000000..657868d --- /dev/null +++ b/src/main/java/org/xbib/cql/model/CQLQueryModel.java @@ -0,0 +1,230 @@ +package org.xbib.cql.model; + +import org.xbib.cql.AbstractNode; +import org.xbib.cql.BooleanOperator; +import org.xbib.cql.Term; +import org.xbib.cql.model.breadcrumb.FacetBreadcrumbTrail; +import org.xbib.cql.model.breadcrumb.FilterBreadcrumbTrail; +import org.xbib.cql.model.breadcrumb.OptionBreadcrumbTrail; + +/** + * A CQL query model. + * Special contexts are facet, filter, + * and option. + * These contexts form breadcrumb trails. + * Bread crumbs provide a means for a server to track an chronologically + * ordered set of client actions. Bread crumbs are typically rendered as a + * user-driven constructed list of links, and are useful when + * users select them to drill down and up in a structure, + * so that they can find their way and have a notion of where they + * currently are. + * Bread crumbs in the original sense just represent where users are + * situated in a site hierarchy. For example, when browsing a + * library catalog, bread crumbs could look like this: + *
+ *  Home > Scientific literature > Arts & Human > Philosophy
+ * 
+ * or + *
+ *   Main library > Branch library > First floor > Rare book room
+ * 
+ * These items would be rendered as links to the corresponding location. + * Classes that implement this interface are responsible for managing + * such a bread crumb structure. A typical implementation regards + * bread crumbs as a set of elements. + * When a bread crumb is activated that was not in the set yet, + * it would add it to the set, or when a bread crumb is activated + * that is already on the set, it would roll back to the corresponding depth. + * In this model, multiple bread crumb trails may exist side by side. They are + * separate and do not depend on each other. There is a list of bread crumb + * trails, and the notion of a currently active bread crumb within a trail. + * This model does not make any presumptions on how it should interact with + * breadcrumbs except that a breadcrumb model should be serializable into + * a writer. + */ +public final class CQLQueryModel { + + /** + * Contexts 'facet', 'filter', and 'option'. + */ + public static final String FACET_INDEX_NAME = "facet"; + + public static final String FILTER_INDEX_NAME = "filter"; + + public static final String OPTION_INDEX_NAME = "option"; + + private static final String AND_OP = " and "; + + private static final String OR_OP = " or "; + + /** + * the CQL query string. + */ + private String query; + + /** + * breadcrumb trail for facets. + */ + private FacetBreadcrumbTrail facetTrail; + + /** + * breadcrumb trail for conjunctive filters. + */ + private FilterBreadcrumbTrail conjunctivefilterTrail; + + /** + * breadcrumb trail for disjunctive filters. + */ + private FilterBreadcrumbTrail disjunctivefilterTrail; + + /** + * breadcrumb trail for options. + */ + private OptionBreadcrumbTrail optionTrail; + + public CQLQueryModel() { + this.facetTrail = new FacetBreadcrumbTrail(); + this.conjunctivefilterTrail = new FilterBreadcrumbTrail(BooleanOperator.AND); + this.disjunctivefilterTrail = new FilterBreadcrumbTrail(BooleanOperator.OR); + this.optionTrail = new OptionBreadcrumbTrail(); + } + + public void setQuery(String query) { + this.query = query; + } + + public String getQuery() { + return query; + } + + public void addFacet(Facet facet) { + facetTrail.add(facet); + } + + public void removeFacet(Facet facet) { + facetTrail.remove(facet); + } + + /** + * Add CQL filter. + * + * @param op boolean operator, AND for conjunctive filter, OR for disjunctive filter + * @param filter the filter to add + */ + public void addFilter(BooleanOperator op, Filter filter) { + if (op == BooleanOperator.AND && !disjunctivefilterTrail.contains(filter)) { + conjunctivefilterTrail.add(filter); + } + if (op == BooleanOperator.OR && !conjunctivefilterTrail.contains(filter)) { + disjunctivefilterTrail.add(filter); + } + } + + /** + * Remove CQL filter. + * + * @param filter the filter to remove + */ + public void removeFilter(Filter filter) { + conjunctivefilterTrail.remove(filter); + disjunctivefilterTrail.remove(filter); + } + + public void addOption(Option option) { + optionTrail.add(option); + } + + public void removeOption(Option option) { + optionTrail.remove(option); + } + + public FacetBreadcrumbTrail getFacetTrail() { + return facetTrail; + } + + public String getFilterTrail() { + StringBuilder sb = new StringBuilder(); + if (!conjunctivefilterTrail.isEmpty()) { + sb.append(AND_OP).append(conjunctivefilterTrail.toString()); + } + if (disjunctivefilterTrail.size() == 1) { + sb.append(OR_OP).append(disjunctivefilterTrail.toString()); + } else if (disjunctivefilterTrail.size() > 1) { + sb.append(AND_OP).append(disjunctivefilterTrail.toString()); + } + return sb.toString(); + } + + /** + * Get the option breadcrumb trail. + * + * @return the option breadcrumb trail + */ + public OptionBreadcrumbTrail getOptionTrail() { + return optionTrail; + } + + /** + * Get query of a given context. + * + * @param context the context + * @return true if visible, false if not + */ + public static boolean isVisible(String context) { + return !isFacetContext(context) + && !isFilterContext(context) + && !isOptionContext(context); + } + + /** + * Check if this context is the facet context. + * + * @param context the context + * @return true if facet contet + */ + public static boolean isFacetContext(String context) { + return FACET_INDEX_NAME.equals(context); + } + + /** + * Check if this context is the filter context. + * + * @param context the context + * @return true if filter context + */ + public static boolean isFilterContext(String context) { + return FILTER_INDEX_NAME.equals(context); + } + + /** + * Check if this context is the option context + * + * @param context the context + * @return true if option context + */ + public static boolean isOptionContext(String context) { + return OPTION_INDEX_NAME.equals(context); + } + + /** + * Write the CQL query model as CQL string. + * + * @return the query model as CQL + */ + public String toCQL() { + StringBuilder sb = new StringBuilder(query); + String facets = getFacetTrail().toCQL(); + if (facets.length() > 0) { + sb.append(AND_OP).append(facets); + } + String filters = getFilterTrail(); + if (filters.length() > 0) { + sb.append(filters); + } + String options = getOptionTrail().toCQL(); + if (options.length() > 0) { + sb.append(AND_OP).append(options); + } + return sb.toString(); + } +} diff --git a/src/main/java/org/xbib/cql/model/Facet.java b/src/main/java/org/xbib/cql/model/Facet.java new file mode 100644 index 0000000..88fc168 --- /dev/null +++ b/src/main/java/org/xbib/cql/model/Facet.java @@ -0,0 +1,70 @@ +package org.xbib.cql.model; + +import org.xbib.cql.QueryFacet; + +/** + * Facet. + * + * @param parameter type + */ +public final class Facet implements QueryFacet, Comparable> { + + private int size; + private String filterName; + private String name; + private V value; + + public Facet(String name) { + this.name = name; + } + + public Facet(String name, String filterName, int size) { + this.name = name; + this.filterName = filterName; + this.size = size; + } + + @Override + public void setName(String name) { + this.name = name; + } + + @Override + public String getName() { + return name; + } + + @Override + public void setValue(V value) { + this.value = value; + } + + @Override + public V getValue() { + return value; + } + + @Override + public int getSize() { + return size; + } + + @Override + public String getFilterName() { + return filterName; + } + + public String toCQL() { + return CQLQueryModel.FACET_INDEX_NAME + "." + name + " = " + value; + } + + @Override + public int compareTo(Facet o) { + return name.compareTo((o).getName()); + } + + @Override + public String toString() { + return toCQL(); + } +} diff --git a/src/main/java/org/xbib/cql/model/Filter.java b/src/main/java/org/xbib/cql/model/Filter.java new file mode 100644 index 0000000..c7a9862 --- /dev/null +++ b/src/main/java/org/xbib/cql/model/Filter.java @@ -0,0 +1,68 @@ +package org.xbib.cql.model; + +import org.xbib.cql.QueryFilter; +import org.xbib.cql.Comparitor; + +/** + * Filter. + * @param filter parameter type + */ +public class Filter implements QueryFilter, Comparable> { + + private String name; + private V value; + private Comparitor op; + private String label; + + public Filter(String name, V value, Comparitor op) { + this.name = name; + this.op = op; + this.value = value; + } + + public Filter(String name, V value, Comparitor op, String label) { + this.name = name; + this.op = op; + this.value = value; + this.label = label; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setValue(V value) { + this.value = value; + } + + public V getValue() { + return value; + } + + public Comparitor getFilterOperation() { + return op; + } + + public String getLabel() { + return label; + } + + public String toCQL() { + return CQLQueryModel.FILTER_INDEX_NAME + "." + name + " " + op.getToken() + " " + value; + } + + @Override + public int compareTo(Filter o) { + return toString().compareTo((o).toString()); + } + + @Override + public String toString() { + return name + " " + op + " " + value; + } + +} diff --git a/src/main/java/org/xbib/cql/model/Option.java b/src/main/java/org/xbib/cql/model/Option.java new file mode 100644 index 0000000..e31e981 --- /dev/null +++ b/src/main/java/org/xbib/cql/model/Option.java @@ -0,0 +1,48 @@ +package org.xbib.cql.model; + +import org.xbib.cql.QueryOption; + +/** + * Option. + * @param parameter type + */ +public class Option implements QueryOption, Comparable> { + + private String name; + private V value; + + @Override + public void setName(String name) { + this.name = name; + } + + @Override + public String getName() { + return name; + } + + @Override + public void setValue(V value) { + this.value = value; + } + + @Override + public V getValue() { + return value; + } + + public String toCQL() { + return CQLQueryModel.OPTION_INDEX_NAME + "." + name + " = " + value; + } + + @Override + public int compareTo(Option o) { + return name.compareTo((o).getName()); + } + + @Override + public String toString() { + return toCQL(); + } + +} diff --git a/src/main/java/org/xbib/cql/model/breadcrumb/FacetBreadcrumbTrail.java b/src/main/java/org/xbib/cql/model/breadcrumb/FacetBreadcrumbTrail.java new file mode 100644 index 0000000..3cba055 --- /dev/null +++ b/src/main/java/org/xbib/cql/model/breadcrumb/FacetBreadcrumbTrail.java @@ -0,0 +1,32 @@ +package org.xbib.cql.model.breadcrumb; + +import org.xbib.cql.model.Facet; + +import java.util.Iterator; +import java.util.TreeSet; + +/** + * Facet breadcrumb trail. + */ +public class FacetBreadcrumbTrail extends TreeSet { + + @Override + public String toString() { + return toCQL(); + } + + public String toCQL() { + StringBuilder sb = new StringBuilder(); + if (isEmpty()) { + return sb.toString(); + } + Iterator it = iterator(); + if (it.hasNext()) { + sb.append(it.next().toCQL()); + } + while (it.hasNext()) { + sb.append(" and ").append(it.next().toCQL()); + } + return sb.toString(); + } +} diff --git a/src/main/java/org/xbib/cql/model/breadcrumb/FilterBreadcrumbTrail.java b/src/main/java/org/xbib/cql/model/breadcrumb/FilterBreadcrumbTrail.java new file mode 100644 index 0000000..4c88b13 --- /dev/null +++ b/src/main/java/org/xbib/cql/model/breadcrumb/FilterBreadcrumbTrail.java @@ -0,0 +1,44 @@ +package org.xbib.cql.model.breadcrumb; + +import org.xbib.cql.BooleanOperator; +import org.xbib.cql.model.Filter; + +import java.util.Iterator; +import java.util.TreeSet; + +/** + * Filter breadcrumbs. + */ +public class FilterBreadcrumbTrail extends TreeSet { + + private BooleanOperator op; + + public FilterBreadcrumbTrail(BooleanOperator op) { + super(); + this.op = op; + } + + @Override + public String toString() { + return toCQL(); + } + + public String toCQL() { + StringBuilder sb = new StringBuilder(); + if (isEmpty()) { + return sb.toString(); + } + if (op == BooleanOperator.OR && size() > 1) { + sb.append('('); + } + Iterator it = this.iterator(); + sb.append(it.next().toCQL()); + while (it.hasNext()) { + sb.append(' ').append(op).append(' ').append(it.next().toCQL()); + } + if (op == BooleanOperator.OR && size() > 1) { + sb.append(')'); + } + return sb.toString(); + } +} diff --git a/src/main/java/org/xbib/cql/model/breadcrumb/OptionBreadcrumbTrail.java b/src/main/java/org/xbib/cql/model/breadcrumb/OptionBreadcrumbTrail.java new file mode 100644 index 0000000..931bc9b --- /dev/null +++ b/src/main/java/org/xbib/cql/model/breadcrumb/OptionBreadcrumbTrail.java @@ -0,0 +1,39 @@ +package org.xbib.cql.model.breadcrumb; + +import org.xbib.cql.model.Option; + +import java.util.Iterator; +import java.util.TreeSet; + +/** + * An Option breadcrumb trail is a trail of attributes (key/value pairs). + * There is no interdependency between attributes; all values are allowed, + * even if they interfere with each other, the trail does not resolve it. + */ +public class OptionBreadcrumbTrail extends TreeSet
HashDOS vulnerability, the decoder + * limits the maximum number of decoded key-value parameter pairs, up to {@literal 1024} by + * default, and you can configure it when you construct the decoder by passing an additional + * integer parameter. + */ +public class QueryStringDecoder { + + private static final int DEFAULT_MAX_PARAMS = 1024; + + private final Charset charset; + private final String uri; + private final boolean hasPath; + private final int maxParams; + private String path; + private Map> params; + private int nParams; + + /** + * Creates a new decoder that decodes the specified URI. The decoder will + * assume that the query string is encoded in UTF-8. + */ + public QueryStringDecoder(String uri) { + this(uri, StandardCharsets.UTF_8); + } + + /** + * Creates a new decoder that decodes the specified URI encoded in the + * specified charset. + */ + public QueryStringDecoder(String uri, boolean hasPath) { + this(uri, StandardCharsets.UTF_8, hasPath); + } + + /** + * Creates a new decoder that decodes the specified URI encoded in the + * specified charset. + */ + public QueryStringDecoder(String uri, Charset charset) { + this(uri, charset, true); + } + + /** + * Creates a new decoder that decodes the specified URI encoded in the + * specified charset. + */ + public QueryStringDecoder(String uri, Charset charset, boolean hasPath) { + this(uri, charset, hasPath, DEFAULT_MAX_PARAMS); + } + + /** + * Creates a new decoder that decodes the specified URI encoded in the + * specified charset. + */ + public QueryStringDecoder(String uri, Charset charset, boolean hasPath, int maxParams) { + if (uri == null) { + throw new NullPointerException("getUri"); + } + if (charset == null) { + throw new NullPointerException("charset"); + } + if (maxParams <= 0) { + throw new IllegalArgumentException( + "maxParams: " + maxParams + " (expected: a positive integer)"); + } + + this.uri = uri; + this.charset = charset; + this.maxParams = maxParams; + this.hasPath = hasPath; + } + + /** + * Creates a new decoder that decodes the specified URI. The decoder will + * assume that the query string is encoded in UTF-8. + */ + public QueryStringDecoder(URI uri) { + this(uri, StandardCharsets.UTF_8); + } + + /** + * Creates a new decoder that decodes the specified URI encoded in the + * specified charset. + */ + public QueryStringDecoder(URI uri, Charset charset) { + this(uri, charset, DEFAULT_MAX_PARAMS); + } + + /** + * Creates a new decoder that decodes the specified URI encoded in the + * specified charset. + */ + public QueryStringDecoder(URI uri, Charset charset, int maxParams) { + if (uri == null) { + throw new NullPointerException("getUri"); + } + if (charset == null) { + throw new NullPointerException("charset"); + } + if (maxParams <= 0) { + throw new IllegalArgumentException( + "maxParams: " + maxParams + " (expected: a positive integer)"); + } + + String rawPath = uri.getRawPath(); + if (rawPath != null) { + hasPath = true; + } else { + rawPath = ""; + hasPath = false; + } + // Also take care of cut of things like "http://localhost" + this.uri = rawPath + '?' + uri.getRawQuery(); + + this.charset = charset; + this.maxParams = maxParams; + } + + /** + * Returns the uri used to initialize this {@link QueryStringDecoder}. + */ + public String uri() { + return uri; + } + + /** + * Returns the decoded path string of the URI. + */ + public String path() { + if (path == null) { + if (!hasPath) { + return path = ""; + } + + int pathEndPos = uri.indexOf('?'); + if (pathEndPos < 0) { + path = uri; + } else { + return path = uri.substring(0, pathEndPos); + } + } + return path; + } + + /** + * Returns the decoded key-value parameter pairs of the URI. + */ + public Map> parameters() { + if (params == null) { + if (hasPath) { + int pathLength = path().length(); + if (uri.length() == pathLength) { + return Collections.emptyMap(); + } + decodeParams(uri.substring(pathLength + 1)); + } else { + if (uri.isEmpty()) { + return Collections.emptyMap(); + } + decodeParams(uri); + } + } + return params; + } + + private void decodeParams(String s) { + Map> params = this.params = new LinkedHashMap>(); + nParams = 0; + String name = null; + int pos = 0; // Beginning of the unprocessed region + int i; // End of the unprocessed region + char c; // Current character + for (i = 0; i < s.length(); i++) { + c = s.charAt(i); + if (c == '=' && name == null) { + if (pos != i) { + name = decodeComponent(s.substring(pos, i), charset); + } + pos = i + 1; + // http://www.w3.org/TR/html401/appendix/notes.html#h-B.2.2 + } else if (c == '&' || c == ';') { + if (name == null && pos != i) { + // We haven't seen an `=' so far but moved forward. + // Must be a param of the form '&a&' so add it with + // an empty value. + if (!addParam(params, decodeComponent(s.substring(pos, i), charset), "")) { + return; + } + } else if (name != null) { + if (!addParam(params, name, decodeComponent(s.substring(pos, i), charset))) { + return; + } + name = null; + } + pos = i + 1; + } + } + + if (pos != i) { // Are there characters we haven't dealt with? + if (name == null) { // Yes and we haven't seen any `='. + addParam(params, decodeComponent(s.substring(pos, i), charset), ""); + } else { // Yes and this must be the last value. + addParam(params, name, decodeComponent(s.substring(pos, i), charset)); + } + } else if (name != null) { // Have we seen a name without value? + addParam(params, name, ""); + } + } + + private boolean addParam(Map> params, String name, String value) { + if (nParams >= maxParams) { + return false; + } + + List values = params.get(name); + if (values == null) { + values = new ArrayList(1); // Often there's only 1 value. + params.put(name, values); + } + values.add(value); + nParams++; + return true; + } + + /** + * Decodes a bit of an URL encoded by a browser. + * This is equivalent to calling {@link #decodeComponent(String, Charset)} + * with the UTF-8 charset (recommended to comply with RFC 3986, Section 2). + * + * @param s The string to decode (can be empty). + * @return The decoded string, or {@code s} if there's nothing to decode. + * If the string to decode is {@code null}, returns an empty string. + * @throws IllegalArgumentException if the string contains a malformed + * escape sequence. + */ + public static String decodeComponent(final String s) { + return decodeComponent(s, StandardCharsets.UTF_8); + } + + /** + * Decodes a bit of an URL encoded by a browser. + * The string is expected to be encoded as per RFC 3986, Section 2. + * This is the encoding used by JavaScript functions {@code encodeURI} + * and {@code encodeURIComponent}, but not {@code escape}. For example + * in this encoding, é (in Unicode {@code U+00E9} or in UTF-8 + * {@code 0xC3 0xA9}) is encoded as {@code %C3%A9} or {@code %c3%a9}. + * This is essentially equivalent to calling + * {@link URLDecoder#decode(String, String) URLDecoder.decode(s, charset.name())} + * except that it's over 2x faster and generates less garbage for the GC. + * Actually this function doesn't allocate any memory if there's nothing + * to decode, the argument itself is returned. + * + * @param s The string to decode (can be empty). + * @param charset The charset to use to decode the string (should really + * be UTF-8). + * @return The decoded string, or {@code s} if there's nothing to decode. + * If the string to decode is {@code null}, returns an empty string. + * @throws IllegalArgumentException if the string contains a malformed + * escape sequence. + */ + public static String decodeComponent(final String s, final Charset charset) { + if (s == null) { + return ""; + } + final int size = s.length(); + boolean modified = false; + for (int i = 0; i < size; i++) { + final char c = s.charAt(i); + if (c == '%' || c == '+') { + modified = true; + break; + } + } + if (!modified) { + return s; + } + final byte[] buf = new byte[size]; + int pos = 0; // position in `buf'. + for (int i = 0; i < size; i++) { + char c = s.charAt(i); + switch (c) { + case '+': + buf[pos++] = ' '; // "+" -> " " + break; + case '%': + if (i == size - 1) { + throw new IllegalArgumentException("unterminated escape" + + " sequence at end of string: " + s); + } + c = s.charAt(++i); + if (c == '%') { + buf[pos++] = '%'; // "%%" -> "%" + break; + } + if (i == size - 1) { + throw new IllegalArgumentException("partial escape" + + " sequence at end of string: " + s); + } + c = decodeHexNibble(c); + final char c2 = decodeHexNibble(s.charAt(++i)); + if (c == Character.MAX_VALUE || c2 == Character.MAX_VALUE) { + throw new IllegalArgumentException( + "invalid escape sequence `%" + s.charAt(i - 1) + + s.charAt(i) + "' at index " + (i - 2) + + " of: " + s); + } + c = (char) (c * 16 + c2); + // Fall through. + default: + buf[pos++] = (byte) c; + break; + } + } + return new String(buf, 0, pos, charset); + } + + /** + * Helper to decode half of a hexadecimal number from a string. + * + * @param c The ASCII character of the hexadecimal number to decode. + * Must be in the range {@code [0-9a-fA-F]}. + * @return The hexadecimal value represented in the ASCII character + * given, or {@link Character#MAX_VALUE} if the character is invalid. + */ + private static char decodeHexNibble(final char c) { + if ('0' <= c && c <= '9') { + return (char) (c - '0'); + } else if ('a' <= c && c <= 'f') { + return (char) (c - 'a' + 10); + } else if ('A' <= c && c <= 'F') { + return (char) (c - 'A' + 10); + } else { + return Character.MAX_VALUE; + } + } +} \ No newline at end of file diff --git a/src/main/java/org/xbib/cql/util/QuotedStringTokenizer.java b/src/main/java/org/xbib/cql/util/QuotedStringTokenizer.java new file mode 100644 index 0000000..9ce35a0 --- /dev/null +++ b/src/main/java/org/xbib/cql/util/QuotedStringTokenizer.java @@ -0,0 +1,233 @@ +package org.xbib.cql.util; + +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.StringTokenizer; + +/** + * A string tokenizer that understands quotes and escape characters. + */ +public class QuotedStringTokenizer extends StringTokenizer implements Iterator { + + private String str; + + private String delim; + + private String quotes; + + private char escape; + + private boolean returnDelims; + + private int pos; + + private int len; + + private StringBuilder token; + + /** + * Constructs a string tokenizer for the specified string. + * The default delimiters for StringTokenizer are used. + * "\"\'" are used as quotes, and '\\' is used as the escape character. + */ + public QuotedStringTokenizer(String str) { + this(str, " \t\n\r\f", "\"\'", '\\', false); + } + + /** + * Constructs a string tokenizer for the specified string. + * "\"\'" are used as quotes, and '\\' is used as the escape character. + */ + public QuotedStringTokenizer(String str, String delim) { + this(str, delim, "\"\'", '\\', false); + } + + /** + * Constructs a string tokenizer for the specified string. + * Quotes cannot be delimiters, and the escape character can be neither a + * quote nor a delimiter. + */ + public QuotedStringTokenizer(String str, String delim, String quotes, char escape, boolean returnDelims) { + super(str, delim, returnDelims); + this.str = str; + this.len = str.length(); + this.delim = delim; + this.quotes = quotes; + this.pos = 0; + for (int i = 0; i < quotes.length(); i++) { + if (delim.indexOf(quotes.charAt(i)) >= 0) { + throw new IllegalArgumentException("Invalid quote character '" + quotes.charAt(i) + "'"); + } + } + this.escape = escape; + if (delim.indexOf(escape) >= 0) { + throw new IllegalArgumentException("Invalid escape character '" + escape + "'"); + } + if (quotes.indexOf(escape) >= 0) { + throw new IllegalArgumentException("Invalid escape character '" + escape + "'"); + } + this.returnDelims = returnDelims; + } + + /** + * Returns the position of the next non-delimiter character. + * Pre-condition: not inside a quoted string (token). + */ + private int skipDelim(int pos) { + int p = pos; + while (p < len && delim.indexOf(str.charAt(p)) >= 0) { + p++; + } + return p; + } + + /** + * Returns the position of the next delimiter character after the token. + * If collect is true, collects the token into the StringBuffer. + * Pre-condition: not on a delimiter. + */ + private int skipToken(int pos, boolean collect) { + int p = pos; + if (collect) { + token = new StringBuilder(); + } + boolean quoted = false; + char quote = '\000'; + boolean escaped = false; + for (; p < len; p++) { + char curr = str.charAt(p); + if (escaped) { + escaped = false; + if (collect) { + token.append(curr); + } + continue; + } + if (curr == escape) { // escape character + escaped = true; + continue; + } + if (quoted) { + if (curr == quote) { // closing quote + quoted = false; + quote = '\000'; + } else if (collect) { + token.append(curr); + } + continue; + } + if (quotes.indexOf(curr) >= 0) { + // opening quote + quoted = true; + quote = curr; + continue; + } + if (delim.indexOf(str.charAt(p)) >= 0) { + // unquoted delimiter + break; + } + if (collect) { + token.append(curr); + } + } + if (escaped || quoted) { + throw new UnterminatedQuotedStringException(str); + } + return p; + } + + /** + * Tests if there are more tokens available from this tokenizer's string. + * Pre-condition: not inside a quoted string (token). + */ + @Override + public boolean hasMoreTokens() { + if (!returnDelims) { + pos = skipDelim(pos); + } + return (pos < len); + } + + /** + * Returns the next token from this string tokenizer. + */ + @Override + public String nextToken() { + if (!returnDelims) { + pos = skipDelim(pos); + } + if (pos >= len) { + throw new NoSuchElementException(); + } + if (returnDelims && delim.indexOf(str.charAt(pos)) >= 0) { + return str.substring(pos, ++pos); + } + pos = skipToken(pos, true); + return token.toString(); + } + + /** + * Returns the next token in this string tokenizer's string. + */ + @Override + public String nextToken(String delim) { + this.delim = delim; + return nextToken(); + } + + /** + * Calculates the number of times that this tokenizer's nextToken method + * can be called before it generates an exception. + */ + @Override + public int countTokens() { + int count = 0; + int dcount = 0; + int curr = pos; + while (curr < len) { + if (delim.indexOf(str.charAt(curr)) >= 0) { + curr++; + dcount++; + } else { + curr = skipToken(curr, false); + count++; + } + } + if (returnDelims) { + return count + dcount; + } + return count; + } + + /** + * Returns the same value as the hasMoreTokens method. + */ + @Override + public boolean hasMoreElements() { + return hasMoreTokens(); + } + + /** + * Returns the same value as the nextToken method, except that its declared + * return value is Object rather than String. + */ + @Override + public Object nextElement() { + return nextToken(); + } + + @Override + public boolean hasNext() { + return hasMoreTokens(); + } + + @Override + public String next() { + return nextToken(); + } + + @Override + public void remove() { + + } +} diff --git a/src/main/java/org/xbib/cql/util/UnterminatedQuotedStringException.java b/src/main/java/org/xbib/cql/util/UnterminatedQuotedStringException.java new file mode 100644 index 0000000..da8bcbe --- /dev/null +++ b/src/main/java/org/xbib/cql/util/UnterminatedQuotedStringException.java @@ -0,0 +1,11 @@ +package org.xbib.cql.util; + +/** + * Exception for string tokenizing. + */ +public class UnterminatedQuotedStringException extends RuntimeException { + + public UnterminatedQuotedStringException(String msg) { + super(msg); + } +} diff --git a/src/main/java/org/xbib/cql/util/package-info.java b/src/main/java/org/xbib/cql/util/package-info.java new file mode 100644 index 0000000..6daa5bb --- /dev/null +++ b/src/main/java/org/xbib/cql/util/package-info.java @@ -0,0 +1,4 @@ +/** + * Classes for CQL utilities. + */ +package org.xbib.cql.util; diff --git a/src/main/jflex/org/xbib/cql/CQL.jflex b/src/main/jflex/org/xbib/cql/CQL.jflex new file mode 100644 index 0000000..79be5e5 --- /dev/null +++ b/src/main/jflex/org/xbib/cql/CQL.jflex @@ -0,0 +1,194 @@ +package org.xbib.cql; + +import java.io.IOException; + +%% +%class CQLLexer +%implements CQLTokens +%unicode +%integer +%eofval{ + return 0; +%eofval} +%line +%column + +%{ + private Object yylval; + private int token; + private StringBuilder sb = new StringBuilder(); + + public int getToken() { + return token; + } + + public int nextToken() { + try { + token = yylex(); + return token; + } + catch (IOException e) { + throw new RuntimeException(e); + } + } + + public Object getSemantic() { + return yylval; + } + + public int getLine() { + return yyline; + } + + public int getColumn() { + return yycolumn; + } + +%} +NL = \n | \r | \r\n +LPAR = "(" +RPAR = ")" +AND = [aA][nN][dD] +OR = [oO][rR] +NOT = [nN][oO][tT] +PROX = [pP][rR][oO][xX] +SORTBY = [sS][oO][rR][tT][bB][yY] +SIMPLESTRING = [^ \t\"()=<>\/]+ +QUOTEDSTRING = [^\"] +LT = "<" +GT = ">" +EQ = "=" +GE = ">=" +LE = "<=" +NE = "<>" +EXACT = "==" +NAMEDCOMPARITORS = [cC][qQ][lL] "." [eE][xX][aA][cC][tT] | [eE][xX][aA][cC][tT] | [cC][qQ][lL] "." [wW][iI][tT][hH][iI][nN] | [wW][iI][tT][hH][iI][nN] | [cC][qQ][lL] "." [aA][dD][jJ] | [aA][dD][jJ] | [cC][qQ][lL] "." [aA][lL][lL] | [aA][lL][lL] | [cC][qQ][lL] "." [aA][nN][yY] | [aA][nN][yY] | [cC][qQ][lL] "." [eE][nN][cC][lL][oO][sS][eE][sS] | [eE][nN][cC][lL][oO][sS][eE][sS] +INTEGER = 0 | [1-9][0-9]* +FLOAT = [0-9]+ "." [0-9]+ +SLASH = "/" + +%state STRING2 + +%% + +\" { + yybegin(STRING2); + sb.setLength(0); + } + + { + +\\\" { + sb.append("\""); + } +{QUOTEDSTRING} { + sb.append(yytext()); + } +\" { + yybegin(YYINITIAL); + yylval = sb.toString(); + return QUOTEDSTRING; + } +} + +{NL} { + return NL; + } + +" "|\t { + } + +{FLOAT} { + yylval = Double.parseDouble(yytext()); + return FLOAT; + } + +{INTEGER} { + yylval = Long.parseLong(yytext()); + return INTEGER; + } + +{NAMEDCOMPARITORS} { + yylval = yytext(); + return NAMEDCOMPARITORS; + } + +{GE} { + yylval = yytext(); + return GE; + } + +{LE} { + yylval = yytext(); + return LE; + } + +{NE} { + yylval = yytext(); + return NE; + } + +{EXACT} { + yylval = yytext(); + return EXACT; + } + +{GT} { + yylval = yytext(); + return GT; + } + +{LT} { + yylval = yytext(); + return LT; + } + +{EQ} { + yylval = yytext(); + return EQ; + } + +{AND} { + yylval = yytext(); + return AND; + } + +{OR} { + yylval = yytext(); + return OR; + } + +{NOT} { + yylval = yytext(); + return NOT; + } + +{PROX} { + yylval = yytext(); + return PROX; + } + +{SORTBY} { + yylval = yytext(); + return SORTBY; + } + +{SIMPLESTRING} { + yylval = yytext(); + return SIMPLESTRING; + } + +{LPAR} { + yylval = yytext(); + return LPAR; + } + +{RPAR} { + yylval = yytext(); + return RPAR; + } + +{SLASH} { + yylval = yytext(); + return SLASH; + } diff --git a/src/test/java/org/xbib/cql/QueryTest.java b/src/test/java/org/xbib/cql/QueryTest.java new file mode 100644 index 0000000..db580ff --- /dev/null +++ b/src/test/java/org/xbib/cql/QueryTest.java @@ -0,0 +1,60 @@ +package org.xbib.cql; + +import org.junit.Assert; +import org.junit.Test; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.LineNumberReader; +import java.nio.charset.StandardCharsets; + +/** + * + */ +public class QueryTest extends Assert { + + @Test + public void testValidQueries() throws IOException { + test("queries.txt"); + } + + private void test(String path) throws IOException { + int count = 0; + int ok = 0; + int errors = 0; + LineNumberReader lr = new LineNumberReader(new InputStreamReader(getClass().getResourceAsStream(path), + StandardCharsets.UTF_8)); + String line; + while ((line = lr.readLine()) != null) { + if (line.trim().length() > 0 && !line.startsWith("#")) { + try { + int pos = line.indexOf('|'); + if (pos > 0) { + validate(line.substring(0, pos), line.substring(pos + 1)); + } else { + validate(line); + } + ok++; + } catch (Exception e) { + errors++; + } + count++; + } + } + lr.close(); + assertEquals(errors, 0); + assertEquals(ok, count); + } + + private void validate(String line) throws Exception { + CQLParser parser = new CQLParser(line); + parser.parse(); + assertEquals(line, parser.getCQLQuery().toString()); + } + + private void validate(String line, String expected) throws Exception { + CQLParser parser = new CQLParser(line); + parser.parse(); + assertEquals(expected, parser.getCQLQuery().toString()); + } +} diff --git a/src/test/java/org/xbib/cql/elasticsearch/ElasticsearchQueryTest.java b/src/test/java/org/xbib/cql/elasticsearch/ElasticsearchQueryTest.java new file mode 100644 index 0000000..7257430 --- /dev/null +++ b/src/test/java/org/xbib/cql/elasticsearch/ElasticsearchQueryTest.java @@ -0,0 +1,128 @@ +package org.xbib.cql.elasticsearch; + +import org.junit.Assert; +import org.junit.Test; +import org.xbib.cql.CQLParser; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.LineNumberReader; +import java.nio.charset.StandardCharsets; + +/** + * + */ +public class ElasticsearchQueryTest extends Assert { + + @Test + public void testValidQueries() throws IOException { + test("queries.txt"); + } + + @Test + public void testSimpleTermFilter() throws Exception { + String cql = "Jörg"; + CQLParser parser = new CQLParser(cql); + parser.parse(); + ElasticsearchFilterGenerator generator = new ElasticsearchFilterGenerator(); + parser.getCQLQuery().accept(generator); + String json = generator.getResult().string(); + //logger.info("{} --> {}", cql, json); + assertEquals(json, "{\"term\":{\"cql.allIndexes\":\"Jörg\"}}"); + } + + @Test + public void testFieldTermFilter() throws Exception { + String cql = "dc.type = electronic"; + CQLParser parser = new CQLParser(cql); + parser.parse(); + ElasticsearchFilterGenerator generator = new ElasticsearchFilterGenerator(); + parser.getCQLQuery().accept(generator); + String json = generator.getResult().string(); + //logger.info("{} --> {}", cql, json); + assertEquals(json, "{\"query\":{\"term\":{\"dc.type\":\"electronic\"}}}"); + } + + @Test + public void testDoubleFieldTermFilter() throws Exception { + String cql = "dc.type = electronic and dc.date = 2013"; + CQLParser parser = new CQLParser(cql); + parser.parse(); + ElasticsearchFilterGenerator generator = new ElasticsearchFilterGenerator(); + parser.getCQLQuery().accept(generator); + String json = generator.getResult().string(); + //logger.info("{} --> {}", cql, json); + assertEquals( + "{\"query\":{\"bool\":{\"must\":[{\"term\":{\"dc.type\":\"electronic\"}},{\"term\":{\"dc.date\":\"2013\"}}]}}}", + json + ); + } + + @Test + public void testTripleFieldTermFilter() throws Exception { + String cql = "dc.format = online and dc.type = electronic and dc.date = 2013"; + CQLParser parser = new CQLParser(cql); + parser.parse(); + ElasticsearchFilterGenerator generator = new ElasticsearchFilterGenerator(); + parser.getCQLQuery().accept(generator); + String json = generator.getResult().string(); + //logger.info("{} --> {}", cql, json); + assertEquals( + "{\"query\":{\"bool\":{\"must\":[{\"bool\":{\"must\":[{\"term\":{\"dc.format\":\"online\"}}," + + "{\"term\":{\"dc.type\":\"electronic\"}}]}},{\"term\":{\"dc.date\":\"2013\"}}]}}}", + json); + } + + @Test + public void testBoost() throws Exception { + String cql = "Jörg"; + CQLParser parser = new CQLParser(cql); + parser.parse(); + ElasticsearchQueryGenerator generator = new ElasticsearchQueryGenerator(); + generator.setBoostParams("boost", "log2p", 2.0f, "sum"); + parser.getCQLQuery().accept(generator); + String json = generator.getSourceResult(); + assertEquals( + "{\"from\":0,\"size\":10,\"query\":{\"function_score\":{\"field_value_factor\":{\"field\":\"boost\"," + + "\"modifier\":\"log2p\",\"factor\":2.0},\"boost_mode\":\"sum\"," + + "\"query\":{\"simple_query_string\":{\"query\":\"Jörg\",\"fields\":[\"cql.allIndexes\"]," + + "\"analyze_wildcard\":true,\"default_operator\":\"and\"}}}}}", + json); + } + + private void test(String path) throws IOException { + int count = 0; + int ok = 0; + int errors = 0; + LineNumberReader lr = new LineNumberReader(new InputStreamReader(getClass().getResourceAsStream(path), + StandardCharsets.UTF_8)); + String line; + while ((line = lr.readLine()) != null) { + if (line.trim().length() > 0 && !line.startsWith("#")) { + try { + int pos = line.indexOf('|'); + if (pos > 0) { + validate(line.substring(0, pos), line.substring(pos + 1)); + ok++; + } + } catch (Exception e) { + errors++; + } + count++; + } + } + lr.close(); + assertEquals(0, errors); + assertEquals(count, ok); + } + + private void validate(String cql, String expected) throws Exception { + CQLParser parser = new CQLParser(cql); + parser.parse(); + ElasticsearchQueryGenerator generator = new ElasticsearchQueryGenerator(); + parser.getCQLQuery().accept(generator); + String elasticsearchQuery = generator.getSourceResult(); + assertEquals(expected, elasticsearchQuery); + } + +} diff --git a/src/test/java/org/xbib/cql/elasticsearch/package-info.java b/src/test/java/org/xbib/cql/elasticsearch/package-info.java new file mode 100644 index 0000000..030d65b --- /dev/null +++ b/src/test/java/org/xbib/cql/elasticsearch/package-info.java @@ -0,0 +1,4 @@ +/** + * Classes for Elasticsearch CQL testing. + */ +package org.xbib.cql.elasticsearch; diff --git a/src/test/java/org/xbib/cql/package-info.java b/src/test/java/org/xbib/cql/package-info.java new file mode 100644 index 0000000..2a9cf54 --- /dev/null +++ b/src/test/java/org/xbib/cql/package-info.java @@ -0,0 +1,4 @@ +/** + * Classes for CQL testing. + */ +package org.xbib.cql; diff --git a/src/test/java/org/xbib/cql/util/QuotedStringTokenizerTest.java b/src/test/java/org/xbib/cql/util/QuotedStringTokenizerTest.java new file mode 100644 index 0000000..940bcfa --- /dev/null +++ b/src/test/java/org/xbib/cql/util/QuotedStringTokenizerTest.java @@ -0,0 +1,22 @@ +package org.xbib.cql.util; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +/** + * + */ +public class QuotedStringTokenizerTest { + + @Test + public void testTokenizer() throws Exception { + String s = "Linux is \"pinguin's best friend\", not Windows"; + QuotedStringTokenizer tokenizer = new QuotedStringTokenizer(s); + assertEquals("Linux", tokenizer.nextToken()); + assertEquals("is", tokenizer.nextToken()); + assertEquals("pinguin's best friend,", tokenizer.nextToken()); + assertEquals("not", tokenizer.nextToken()); + assertEquals("Windows", tokenizer.nextToken()); + } +} diff --git a/src/test/java/org/xbib/cql/util/package-info.java b/src/test/java/org/xbib/cql/util/package-info.java new file mode 100644 index 0000000..6daa5bb --- /dev/null +++ b/src/test/java/org/xbib/cql/util/package-info.java @@ -0,0 +1,4 @@ +/** + * Classes for CQL utilities. + */ +package org.xbib.cql.util; diff --git a/src/test/resources/log4j2.xml b/src/test/resources/log4j2.xml new file mode 100644 index 0000000..f71aced --- /dev/null +++ b/src/test/resources/log4j2.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/resources/org/xbib/cql/elasticsearch/queries.txt b/src/test/resources/org/xbib/cql/elasticsearch/queries.txt new file mode 100644 index 0000000..5283585 --- /dev/null +++ b/src/test/resources/org/xbib/cql/elasticsearch/queries.txt @@ -0,0 +1,123 @@ +id = 8a666b7e-6597-3cfb-b478-313cc3c25011|{"from":0,"size":10,"query":{"simple_query_string":{"query":"8a666b7e-6597-3cfb-b478-313cc3c25011","fields":["id"],"analyze_wildcard":true,"default_operator":"and"}}} +unix|{"from":0,"size":10,"query":{"simple_query_string":{"query":"unix","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}} +financing|{"from":0,"size":10,"query":{"simple_query_string":{"query":"financing","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}} +"Christine Wolfinger"|{"from":0,"size":10,"query":{"simple_query_string":{"query":"\"Christine Wolfinger\"","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}} +"der die das"|{"from":0,"size":10,"query":{"simple_query_string":{"query":"\"der die das\"","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}} +1234|{"from":0,"size":10,"query":{"simple_query_string":{"query":"1234","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}} +"1234"|{"from":0,"size":10,"query":{"simple_query_string":{"query":"\"1234\"","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}} +"unix AND wolfinger"|{"from":0,"size":10,"query":{"simple_query_string":{"query":"\"unix AND wolfinger\"","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}} +"to be or not to be"|{"from":0,"size":10,"query":{"simple_query_string":{"query":"\"to be or not to be\"","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}} +"not macht erfinderisch"|{"from":0,"size":10,"query":{"simple_query_string":{"query":"\"not macht erfinderisch\"","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}} +"to be or not to be"|{"from":0,"size":10,"query":{"simple_query_string":{"query":"\"to be or not to be\"","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}} +unix$|{"from":0,"size":10,"query":{"simple_query_string":{"query":"unix$","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}} +"^linux"|{"from":0,"size":10,"query":{"simple_query_string":{"query":"\"^linux\"","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}} +finan*|{"from":0,"size":10,"query":{"simple_query_string":{"query":"finan*","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}} +finan?|{"from":0,"size":10,"query":{"simple_query_string":{"query":"finan?","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}} +finan*ng|{"from":0,"size":10,"query":{"simple_query_string":{"query":"finan*ng","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}} +finan?ier?ng|{"from":0,"size":10,"query":{"simple_query_string":{"query":"finan?ier?ng","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}} +title = "duck"|{"from":0,"size":10,"query":{"simple_query_string":{"query":"\"duck\"","fields":["title"],"analyze_wildcard":true,"default_operator":"and"}}} +title = "Dinosaur Systematics"|{"from":0,"size":10,"query":{"simple_query_string":{"query":"\"Dinosaur Systematics\"","fields":["title"],"analyze_wildcard":true,"default_operator":"and"}}} +title <> linux|{"from":0,"size":10,"query":{"bool":{"must_not":{"simple_query_string":{"query":"linux","fields":["title"],"analyze_wildcard":true,"default_operator":"and"}}}}} +cql.resultSetId = HT000011990|{"from":0,"size":10,"query":{"simple_query_string":{"query":"HT000011990","fields":["cql.resultSetId"],"analyze_wildcard":true,"default_operator":"and"}}} +cql.allRecords = 2|{"from":0,"size":10,"query":{"simple_query_string":{"query":"2","fields":["cql.allRecords"],"analyze_wildcard":true,"default_operator":"and"}}} +cql.allRecords = 1 NOT title = fish|{"from":0,"size":10,"query":{"bool":{"must_not":[{"simple_query_string":{"query":"1","fields":["cql.allRecords"],"analyze_wildcard":true,"default_operator":"and"}},{"simple_query_string":{"query":"fish","fields":["title"],"analyze_wildcard":true,"default_operator":"and"}}]}}} +title any "unix linux"|{"from":0,"size":10,"query":{"simple_query_string":{"query":"unix linux","fields":["title"],"analyze_wildcard":true,"default_operator":"or"}}} +title all "unix linux"|{"from":0,"size":10,"query":{"simple_query_string":{"query":"unix linux","fields":["title"],"analyze_wildcard":true,"default_operator":"and"}}} +title all "unix 'linux' test"|{"from":0,"size":10,"query":{"simple_query_string":{"query":"unix 'linux' test","fields":["title"],"analyze_wildcard":true,"default_operator":"and"}}} +title all "linux \"pinguin's best friend\" unix"|{"from":0,"size":10,"query":{"simple_query_string":{"query":"linux \"pinguin's best friend\" unix","fields":["title"],"analyze_wildcard":true,"default_operator":"and"}}} +dc.title adj "lord of the rings"|{"from":0,"size":10,"query":{"match_phrase":{"dc.title":{"query":"lord of the rings","slop":0}}}} +anywhere = "linux unix \"grundkurs für einsteiger\""|{"from":0,"size":10,"query":{"simple_query_string":{"query":"\"linux unix \\\"grundkurs für einsteiger\\\"\"","fields":["anywhere"],"analyze_wildcard":true,"default_operator":"and"}}} +dc.date=2003|{"from":0,"size":10,"query":{"simple_query_string":{"query":"2003","fields":["dc.date"],"analyze_wildcard":true,"default_operator":"and"}}} +dc.date="2003"|{"from":0,"size":10,"query":{"simple_query_string":{"query":"\"2003\"","fields":["dc.date"],"analyze_wildcard":true,"default_operator":"and"}}} +dc.creator=smith|{"from":0,"size":10,"query":{"simple_query_string":{"query":"smith","fields":["dc.creator"],"analyze_wildcard":true,"default_operator":"and"}}} +dc.title=financing|{"from":0,"size":10,"query":{"simple_query_string":{"query":"financing","fields":["dc.title"],"analyze_wildcard":true,"default_operator":"and"}}} +dc.subject=financing|{"from":0,"size":10,"query":{"simple_query_string":{"query":"financing","fields":["dc.subject"],"analyze_wildcard":true,"default_operator":"and"}}} +"feathered dinosaur" and (yixian or jehol)|{"from":0,"size":10,"query":{"bool":{"must":[{"simple_query_string":{"query":"\"feathered dinosaur\"","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}},{"bool":{"should":[{"simple_query_string":{"query":"yixian","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}},{"simple_query_string":{"query":"jehol","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}]}}]}}} +(a or b) and (c or d)|{"from":0,"size":10,"query":{"bool":{"must":[{"bool":{"should":[{"simple_query_string":{"query":"a","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}},{"simple_query_string":{"query":"b","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}]}},{"bool":{"should":[{"simple_query_string":{"query":"c","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}},{"simple_query_string":{"query":"d","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}]}}]}}} +unix AND wolfinger|{"from":0,"size":10,"query":{"bool":{"must":[{"simple_query_string":{"query":"unix","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}},{"simple_query_string":{"query":"wolfinger","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}]}}} +"keine angst" AND unix|{"from":0,"size":10,"query":{"bool":{"must":[{"simple_query_string":{"query":"\"keine angst\"","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}},{"simple_query_string":{"query":"unix","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}]}}} +dc.title=unix or wolfinger|{"from":0,"size":10,"query":{"bool":{"should":[{"simple_query_string":{"query":"unix","fields":["dc.title"],"analyze_wildcard":true,"default_operator":"and"}},{"simple_query_string":{"query":"wolfinger","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}]}}} +(dc.title = unix or dc.date = 2003) and ( dc.creator = wolfinger and dc.creator = christine or dc.creator = maier )|{"from":0,"size":10,"query":{"bool":{"must":[{"bool":{"should":[{"simple_query_string":{"query":"unix","fields":["dc.title"],"analyze_wildcard":true,"default_operator":"and"}},{"simple_query_string":{"query":"2003","fields":["dc.date"],"analyze_wildcard":true,"default_operator":"and"}}]}},{"bool":{"should":[{"bool":{"must":[{"simple_query_string":{"query":"wolfinger","fields":["dc.creator"],"analyze_wildcard":true,"default_operator":"and"}},{"simple_query_string":{"query":"christine","fields":["dc.creator"],"analyze_wildcard":true,"default_operator":"and"}}]}},{"simple_query_string":{"query":"maier","fields":["dc.creator"],"analyze_wildcard":true,"default_operator":"and"}}]}}]}}} +financing AND success|{"from":0,"size":10,"query":{"bool":{"must":[{"simple_query_string":{"query":"financing","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}},{"simple_query_string":{"query":"success","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}]}}} +financing OR monetary|{"from":0,"size":10,"query":{"bool":{"should":[{"simple_query_string":{"query":"financing","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}},{"simple_query_string":{"query":"monetary","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}]}}} +financing NOT success|{"from":0,"size":10,"query":{"bool":{"must_not":[{"simple_query_string":{"query":"financing","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}},{"simple_query_string":{"query":"success","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}]}}} +(financing AND monetary) OR success|{"from":0,"size":10,"query":{"bool":{"should":[{"bool":{"must":[{"simple_query_string":{"query":"financing","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}},{"simple_query_string":{"query":"monetary","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}]}},{"simple_query_string":{"query":"success","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}]}}} +financing AND (monetary OR success)|{"from":0,"size":10,"query":{"bool":{"must":[{"simple_query_string":{"query":"financing","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}},{"bool":{"should":[{"simple_query_string":{"query":"monetary","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}},{"simple_query_string":{"query":"success","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}]}}]}}} +"financing constraints" OR success|{"from":0,"size":10,"query":{"bool":{"should":[{"simple_query_string":{"query":"\"financing constraints\"","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}},{"simple_query_string":{"query":"success","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}]}}} +"financing constraints" NOT model|{"from":0,"size":10,"query":{"bool":{"must_not":[{"simple_query_string":{"query":"\"financing constraints\"","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}},{"simple_query_string":{"query":"model","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}]}}} +("financing constraints" AND model) OR success|{"from":0,"size":10,"query":{"bool":{"should":[{"bool":{"must":[{"simple_query_string":{"query":"\"financing constraints\"","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}},{"simple_query_string":{"query":"model","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}]}},{"simple_query_string":{"query":"success","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}]}}} +"financing constraints" AND (model OR success)|{"from":0,"size":10,"query":{"bool":{"must":[{"simple_query_string":{"query":"\"financing constraints\"","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}},{"bool":{"should":[{"simple_query_string":{"query":"model","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}},{"simple_query_string":{"query":"success","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}]}}]}}} +dinosaur or bird|{"from":0,"size":10,"query":{"bool":{"should":[{"simple_query_string":{"query":"dinosaur","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}},{"simple_query_string":{"query":"bird","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}]}}} +dino and "eiszeit"|{"from":0,"size":10,"query":{"bool":{"must":[{"simple_query_string":{"query":"dino","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}},{"simple_query_string":{"query":"\"eiszeit\"","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}]}}} +dinosaur not reptile|{"from":0,"size":10,"query":{"bool":{"must_not":[{"simple_query_string":{"query":"dinosaur","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}},{"simple_query_string":{"query":"reptile","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}]}}} +(dc.title = "unix" or dc.title = "linux") and ( dc.creator = "wolfinger" and dc.creator = "christine" )|{"from":0,"size":10,"query":{"bool":{"must":[{"bool":{"should":[{"simple_query_string":{"query":"\"unix\"","fields":["dc.title"],"analyze_wildcard":true,"default_operator":"and"}},{"simple_query_string":{"query":"\"linux\"","fields":["dc.title"],"analyze_wildcard":true,"default_operator":"and"}}]}},{"bool":{"must":[{"simple_query_string":{"query":"\"wolfinger\"","fields":["dc.creator"],"analyze_wildcard":true,"default_operator":"and"}},{"simple_query_string":{"query":"\"christine\"","fields":["dc.creator"],"analyze_wildcard":true,"default_operator":"and"}}]}}]}}} +date = 2007-09-30 or date = "2007-09-30T12:34:56"|{"from":0,"size":10,"query":{"bool":{"should":[{"simple_query_string":{"query":"2007-09-30","fields":["date"],"analyze_wildcard":true,"default_operator":"and"}},{"simple_query_string":{"query":"\"2007-09-30T12:34:56\"","fields":["date"],"analyze_wildcard":true,"default_operator":"and"}}]}}} +dinosaur and bird or dinobird|{"from":0,"size":10,"query":{"bool":{"should":[{"bool":{"must":[{"simple_query_string":{"query":"dinosaur","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}},{"simple_query_string":{"query":"bird","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}]}},{"simple_query_string":{"query":"dinobird","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}]}}} +(bird or dinosaur) and (feathers or scales)|{"from":0,"size":10,"query":{"bool":{"must":[{"bool":{"should":[{"simple_query_string":{"query":"bird","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}},{"simple_query_string":{"query":"dinosaur","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}]}},{"bool":{"should":[{"simple_query_string":{"query":"feathers","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}},{"simple_query_string":{"query":"scales","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}]}}]}}} +linux and creator = wolfinger|{"from":0,"size":10,"query":{"bool":{"must":[{"simple_query_string":{"query":"linux","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}},{"simple_query_string":{"query":"wolfinger","fields":["creator"],"analyze_wildcard":true,"default_operator":"and"}}]}}} +dc.title=linux and dc.title = unix|{"from":0,"size":10,"query":{"bool":{"must":[{"simple_query_string":{"query":"linux","fields":["dc.title"],"analyze_wildcard":true,"default_operator":"and"}},{"simple_query_string":{"query":"unix","fields":["dc.title"],"analyze_wildcard":true,"default_operator":"and"}}]}}} +dc.title = unix and dc.date = 2000|{"from":0,"size":10,"query":{"bool":{"must":[{"simple_query_string":{"query":"unix","fields":["dc.title"],"analyze_wildcard":true,"default_operator":"and"}},{"simple_query_string":{"query":"2000","fields":["dc.date"],"analyze_wildcard":true,"default_operator":"and"}}]}}} +dc.title = "unix" and dc.creator = "wolfinger"|{"from":0,"size":10,"query":{"bool":{"must":[{"simple_query_string":{"query":"\"unix\"","fields":["dc.title"],"analyze_wildcard":true,"default_operator":"and"}},{"simple_query_string":{"query":"\"wolfinger\"","fields":["dc.creator"],"analyze_wildcard":true,"default_operator":"and"}}]}}} +dc.title = "unix" or dc.creator = "wolfinger"|{"from":0,"size":10,"query":{"bool":{"should":[{"simple_query_string":{"query":"\"unix\"","fields":["dc.title"],"analyze_wildcard":true,"default_operator":"and"}},{"simple_query_string":{"query":"\"wolfinger\"","fields":["dc.creator"],"analyze_wildcard":true,"default_operator":"and"}}]}}} +dc.title = "unix" and ( dc.creator = "wolfinger" or dc.creator = "meyer" )|{"from":0,"size":10,"query":{"bool":{"must":[{"simple_query_string":{"query":"\"unix\"","fields":["dc.title"],"analyze_wildcard":true,"default_operator":"and"}},{"bool":{"should":[{"simple_query_string":{"query":"\"wolfinger\"","fields":["dc.creator"],"analyze_wildcard":true,"default_operator":"and"}},{"simple_query_string":{"query":"\"meyer\"","fields":["dc.creator"],"analyze_wildcard":true,"default_operator":"and"}}]}}]}}} +dc.title = "unix" and dc.creator = "wolfinger" and dc.creator = "christine"|{"from":0,"size":10,"query":{"bool":{"must":[{"bool":{"must":[{"simple_query_string":{"query":"\"unix\"","fields":["dc.title"],"analyze_wildcard":true,"default_operator":"and"}},{"simple_query_string":{"query":"\"wolfinger\"","fields":["dc.creator"],"analyze_wildcard":true,"default_operator":"and"}}]}},{"simple_query_string":{"query":"\"christine\"","fields":["dc.creator"],"analyze_wildcard":true,"default_operator":"and"}}]}}} +(dc.title = "unix" or dc.title = "linux") and ( dc.creator = "wolfinger" and dc.creator = "meyer" )|{"from":0,"size":10,"query":{"bool":{"must":[{"bool":{"should":[{"simple_query_string":{"query":"\"unix\"","fields":["dc.title"],"analyze_wildcard":true,"default_operator":"and"}},{"simple_query_string":{"query":"\"linux\"","fields":["dc.title"],"analyze_wildcard":true,"default_operator":"and"}}]}},{"bool":{"must":[{"simple_query_string":{"query":"\"wolfinger\"","fields":["dc.creator"],"analyze_wildcard":true,"default_operator":"and"}},{"simple_query_string":{"query":"\"meyer\"","fields":["dc.creator"],"analyze_wildcard":true,"default_operator":"and"}}]}}]}}} +dc.title = "foo" and (dc.creator = "smith" or dc.creator = "jones")|{"from":0,"size":10,"query":{"bool":{"must":[{"simple_query_string":{"query":"\"foo\"","fields":["dc.title"],"analyze_wildcard":true,"default_operator":"and"}},{"bool":{"should":[{"simple_query_string":{"query":"\"smith\"","fields":["dc.creator"],"analyze_wildcard":true,"default_operator":"and"}},{"simple_query_string":{"query":"\"jones\"","fields":["dc.creator"],"analyze_wildcard":true,"default_operator":"and"}}]}}]}}} +dc.creator = "smith" and dc.creator = "jones"|{"from":0,"size":10,"query":{"bool":{"must":[{"simple_query_string":{"query":"\"smith\"","fields":["dc.creator"],"analyze_wildcard":true,"default_operator":"and"}},{"simple_query_string":{"query":"\"jones\"","fields":["dc.creator"],"analyze_wildcard":true,"default_operator":"and"}}]}}} +dc.date = 2007-09-30 or dc.date = "2007-09-30T12:34:56"|{"from":0,"size":10,"query":{"bool":{"should":[{"simple_query_string":{"query":"2007-09-30","fields":["dc.date"],"analyze_wildcard":true,"default_operator":"and"}},{"simple_query_string":{"query":"\"2007-09-30T12:34:56\"","fields":["dc.date"],"analyze_wildcard":true,"default_operator":"and"}}]}}} +identifier = 0783923126590|{"from":0,"size":10,"query":{"simple_query_string":{"query":"0783923126590","fields":["identifier"],"analyze_wildcard":true,"default_operator":"and"}}} +identifier = "9783923126590"|{"from":0,"size":10,"query":{"simple_query_string":{"query":"\"9783923126590\"","fields":["identifier"],"analyze_wildcard":true,"default_operator":"and"}}} +identifier = "9783923126590*"|{"from":0,"size":10,"query":{"simple_query_string":{"query":"\"9783923126590*\"","fields":["identifier"],"analyze_wildcard":true,"default_operator":"and"}}} +dc.identifier =/bib.identifierAuthority=isbn "0201563177"|{"from":0,"size":10,"query":{"simple_query_string":{"query":"\"0201563177\"","fields":["bib.identifierAuthority=isbn"],"analyze_wildcard":true,"default_operator":"and"}}} +dc.identifier =/bib.identifierAuthority=isbn "0201563177" and dc.title=unix sortby dc.date|{"from":0,"size":10,"query":{"bool":{"must":[{"simple_query_string":{"query":"\"0201563177\"","fields":["bib.identifierAuthority=isbn"],"analyze_wildcard":true,"default_operator":"and"}},{"simple_query_string":{"query":"unix","fields":["dc.title"],"analyze_wildcard":true,"default_operator":"and"}}]}},"sort":[{"dc.date":{"unmapped_type":"string","missing":"_last"}}]} +dc.date > 2007-09-30 and dc.date < "2007-10-30T12:34:56"|{"from":0,"size":10,"query":{"bool":{"must":[{"range":{"dc.date":{"from":"2007-09-30","include_lower":false}}},{"range":{"dc.date":{"to":"\"2007-10-30T12:34:56\"","include_upper":false}}}]}}} +date > 2007-01-01|{"from":0,"size":10,"query":{"range":{"date":{"from":"2007-01-01","include_lower":false}}}} +dc.date <= 2006-07-01|{"from":0,"size":10,"query":{"range":{"dc.date":{"to":"2006-07-01","include_upper":true}}}} +dc.date >= 2005-02-31|{"from":0,"size":10,"query":{"range":{"dc.date":{"from":"2005-02-31","include_lower":true}}}} +dc.date > 2011|{"from":0,"size":10,"query":{"range":{"dc.date":{"from":"2011","include_lower":false}}}} +dc.date = "> 2003"|{"from":0,"size":10,"query":{"simple_query_string":{"query":"\"> 2003\"","fields":["dc.date"],"analyze_wildcard":true,"default_operator":"and"}}} +dc.date = "20012010"|{"from":0,"size":10,"query":{"simple_query_string":{"query":"\"20012010\"","fields":["dc.date"],"analyze_wildcard":true,"default_operator":"and"}}} +(dc.title = "unix" or dc.title = "linux") and ( dc.creator = "wolfinger" and dc.creator = "meyer" ) and filter.subject = "computer"|{"from":0,"size":10,"query":{"filtered":{"query":{"bool":{"must":{"bool":{"must":[{"bool":{"should":[{"simple_query_string":{"query":"\"unix\"","fields":["dc.title"],"analyze_wildcard":true,"default_operator":"and"}},{"simple_query_string":{"query":"\"linux\"","fields":["dc.title"],"analyze_wildcard":true,"default_operator":"and"}}]}},{"bool":{"must":[{"simple_query_string":{"query":"\"wolfinger\"","fields":["dc.creator"],"analyze_wildcard":true,"default_operator":"and"}},{"simple_query_string":{"query":"\"meyer\"","fields":["dc.creator"],"analyze_wildcard":true,"default_operator":"and"}}]}}]}}}},"filter":{"term":{"subject":"computer"}}}}} +unix and filter.date > 2006-01-01|{"from":0,"size":10,"query":{"filtered":{"query":{"bool":{"must":{"simple_query_string":{"query":"unix","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}}},"filter":{"range":{"date":{"from":"2006-01-01","include_lower":false}}}}}} +unix and (filter.date > 2006-01-01 and filter.date > 2007-01-01)|{"from":0,"size":10,"query":{"filtered":{"query":{"bool":{"must":{"simple_query_string":{"query":"unix","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}}},"filter":{"range":{"date":{"from":"2006-01-01","include_lower":false}}}}}} +unix and dc.date within "2006 2007"|{"from":0,"size":10,"query":{"bool":{"must":[{"simple_query_string":{"query":"unix","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}},{"range":{"dc.date":{"from":"2006","to":"2007","include_lower":true,"include_upper":true}}}]}}} +unix and dc.date within "2006-01-01 2007-01-01"|{"from":0,"size":10,"query":{"bool":{"must":[{"simple_query_string":{"query":"unix","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}},{"range":{"dc.date":{"from":"2006-01-01","to":"2007-01-01","include_lower":true,"include_upper":true}}}]}}} +unix and filter.date within "2006-01-01 2007-01-01"|{"from":0,"size":10,"query":{"filtered":{"query":{"bool":{"must":{"simple_query_string":{"query":"unix","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}}},"filter":{"range":{"date":{"from":"2006-01-01","to":"2007-01-01","include_lower":true,"include_upper":true}}}}}} +dc.title = "unix" and filter.creator = "wolfinger"|{"from":0,"size":10,"query":{"filtered":{"query":{"bool":{"must":{"simple_query_string":{"query":"\"unix\"","fields":["dc.title"],"analyze_wildcard":true,"default_operator":"and"}}}},"filter":{"term":{"creator":"wolfinger"}}}}} +dc.title = "unix" and filter.creator = "wolfinger" or filter.creator = "meyer"|{"from":0,"size":10,"query":{"filtered":{"query":{"bool":{"should":{"bool":{"must":{"simple_query_string":{"query":"\"unix\"","fields":["dc.title"],"analyze_wildcard":true,"default_operator":"and"}}}}}},"filter":{"bool":{"should":[{"term":{"creator":"wolfinger"}},{"term":{"creator":"meyer"}}]}}}}} +dc.title = "unix" and (filter.creator = "wolfinger" and filter.subject= Computer)|{"from":0,"size":10,"query":{"filtered":{"query":{"bool":{"must":{"simple_query_string":{"query":"\"unix\"","fields":["dc.title"],"analyze_wildcard":true,"default_operator":"and"}}}},"filter":{"bool":{"must":[{"term":{"creator":"wolfinger"}},{"term":{"subject":"Computer"}}]}}}}} +(dc.title = "unix" or dc.title = "linux") and ( dc.creator = "wolfinger" and dc.creator = "meyer" ) and filter.subject = "computer"|{"from":0,"size":10,"query":{"filtered":{"query":{"bool":{"must":{"bool":{"must":[{"bool":{"should":[{"simple_query_string":{"query":"\"unix\"","fields":["dc.title"],"analyze_wildcard":true,"default_operator":"and"}},{"simple_query_string":{"query":"\"linux\"","fields":["dc.title"],"analyze_wildcard":true,"default_operator":"and"}}]}},{"bool":{"must":[{"simple_query_string":{"query":"\"wolfinger\"","fields":["dc.creator"],"analyze_wildcard":true,"default_operator":"and"}},{"simple_query_string":{"query":"\"meyer\"","fields":["dc.creator"],"analyze_wildcard":true,"default_operator":"and"}}]}}]}}}},"filter":{"term":{"subject":"computer"}}}}} +test and (filter.creator = "a" and filter.subject = "b")|{"from":0,"size":10,"query":{"filtered":{"query":{"bool":{"must":{"simple_query_string":{"query":"test","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}}},"filter":{"bool":{"must":[{"term":{"creator":"a"}},{"term":{"subject":"b"}}]}}}}} +test and filter.creator = "a" or filter.subject = "b"|{"from":0,"size":10,"query":{"filtered":{"query":{"bool":{"should":{"bool":{"must":{"simple_query_string":{"query":"test","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}}}}},"filter":{"bool":{"should":[{"term":{"creator":"a"}},{"term":{"subject":"b"}}]}}}}} +test and filter.creator = "smith"|{"from":0,"size":10,"query":{"filtered":{"query":{"bool":{"must":{"simple_query_string":{"query":"test","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}}},"filter":{"term":{"creator":"smith"}}}}} +test and filter.creator = "smith" or filter.creator = "jones"|{"from":0,"size":10,"query":{"filtered":{"query":{"bool":{"should":{"bool":{"must":{"simple_query_string":{"query":"test","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}}}}},"filter":{"bool":{"should":[{"term":{"creator":"smith"}},{"term":{"creator":"jones"}}]}}}}} +test and (filter.creator = "smith" and filter.creator = "jones")|{"from":0,"size":10,"query":{"filtered":{"query":{"bool":{"must":{"simple_query_string":{"query":"test","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}}},"filter":{"term":{"creator":"smith"}}}}} +test or filter.creator = "smith" and filter.creator = "jones"|{"from":0,"size":10,"query":{"filtered":{"query":{"bool":{"must":{"bool":{"should":{"simple_query_string":{"query":"test","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}}}}},"filter":{"bool":{"should":[{"term":{"creator":"jones"}},{"term":{"creator":"smith"}}]}}}}} +test or (filter.creator = "smith" and filter.creator = "jones")|{"from":0,"size":10,"query":{"filtered":{"query":{"bool":{"should":{"simple_query_string":{"query":"test","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}}},"filter":{"term":{"creator":"smith"}}}}} +test and (filter.creator = "smith" or filter.creator = "jones")|{"from":0,"size":10,"query":{"filtered":{"query":{"bool":{"must":{"simple_query_string":{"query":"test","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}}},"filter":{"bool":{"should":[{"term":{"creator":"smith"}},{"term":{"creator":"jones"}}]}}}}} +test or (filter.creator = "smith" or filter.creator = "jones")|{"from":0,"size":10,"query":{"filtered":{"query":{"bool":{"should":{"simple_query_string":{"query":"test","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}}},"filter":{"bool":{"should":[{"term":{"creator":"smith"}},{"term":{"creator":"jones"}}]}}}}} +test and (filter.creator = "smith" or filter.creator = "jones" and filter.subject = "unix")|{"from":0,"size":10,"query":{"filtered":{"query":{"bool":{"must":{"simple_query_string":{"query":"test","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}}},"filter":{"bool":{"should":[{"bool":{"must":[{"term":{"creator":"smith"}},{"term":{"subject":"unix"}}]}},{"term":{"creator":"jones"}}]}}}}} +structure AND filter.creator="smith"|{"from":0,"size":10,"query":{"filtered":{"query":{"bool":{"must":{"simple_query_string":{"query":"structure","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}}},"filter":{"term":{"creator":"smith"}}}}} +structure AND filter.subject="data"|{"from":0,"size":10,"query":{"filtered":{"query":{"bool":{"must":{"simple_query_string":{"query":"structure","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}}},"filter":{"term":{"subject":"data"}}}}} +structure AND filter.date="2003"|{"from":0,"size":10,"query":{"filtered":{"query":{"bool":{"must":{"simple_query_string":{"query":"structure","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}}},"filter":{"term":{"date":"2003"}}}}} +pädagogik AND filter.taxonomy="0/24/*"|{"from":0,"size":10,"query":{"filtered":{"query":{"bool":{"must":{"simple_query_string":{"query":"pädagogik","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}}},"filter":{"term":{"taxonomy":"0/24/"}}}}} +pädagogik AND filter.taxonomy="0/24/313/*"|{"from":0,"size":10,"query":{"filtered":{"query":{"bool":{"must":{"simple_query_string":{"query":"pädagogik","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}}},"filter":{"term":{"taxonomy":"0/24/313/"}}}}} +pädagogik AND filter.taxonomy="0/24/313/21/*"|{"from":0,"size":10,"query":{"filtered":{"query":{"bool":{"must":{"simple_query_string":{"query":"pädagogik","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}}},"filter":{"term":{"taxonomy":"0/24/313/21/"}}}}} +linux and filter.creator <> "Wolfinger"|{"from":0,"size":10,"query":{"filtered":{"query":{"bool":{"must":{"simple_query_string":{"query":"linux","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}}},"filter":{"not":{"term":{"creator":"Wolfinger"}}}}}} +unix and option.offset = 10 and option.length = 20|{"from":0,"size":10,"query":{"bool":{"must":{"bool":{"must":{"simple_query_string":{"query":"unix","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}}}}}} +test and option.length = 1 and option.length = 2 and option.length = 3|{"from":0,"size":10,"query":{"bool":{"must":{"bool":{"must":{"bool":{"must":{"simple_query_string":{"query":"test","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}}}}}}}} +unix sortby date|{"from":0,"size":10,"query":{"simple_query_string":{"query":"unix","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}},"sort":[{"date":{"unmapped_type":"string","missing":"_last"}}]} +unix sortby date/sort.descending|{"from":0,"size":10,"query":{"simple_query_string":{"query":"unix","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}},"sort":[{"date":{"order":"desc","unmapped_type":"string","missing":"_last"}}]} +unix sortby date/sort.descending geo/sort.ascending|{"from":0,"size":10,"query":{"simple_query_string":{"query":"unix","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}},"sort":[{"date":{"order":"desc","unmapped_type":"string","missing":"_last"}}]} +unix sortby geo/sort.ascending/sort.unit=km/sort.lat=50.9415016174/sort.lon=6.95853996277|{"from":0,"size":10,"query":{"simple_query_string":{"query":"unix","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}},"sort":[{"geo":{"order":"asc","sort.unit":"km","sort.lat":"50.9415016174","sort.lon":"6.95853996277","unmapped_type":"string","missing":"_last"}}]} +unix sortby geo/sort.ascending/sort.unit=km/sort.center="(50.9415016174,6.95853996277)"|{"from":0,"size":10,"query":{"simple_query_string":{"query":"unix","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}},"sort":[{"geo":{"order":"asc","sort.unit":"km","sort.center":"\"(50.9415016174,6.95853996277)\"","unmapped_type":"string","missing":"_last"}}]} +bib.namePersonal = meier|{"from":0,"size":10,"query":{"simple_query_string":{"query":"meier","fields":["bib.namePersonal"],"analyze_wildcard":true,"default_operator":"and"}}} +unix and filter.location any "DE-929 DE-107 DE-Zw1"|{"from":0,"size":10,"query":{"filtered":{"query":{"bool":{"must":{"simple_query_string":{"query":"unix","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}}},"filter":{"or":[{"term":{"location":"DE-929 DE-107 DE-Zw1"}}]}}}} +unix and filter.location any "DE-929 DE-107 DE-Zw1" sortby date/sort.descending|{"from":0,"size":10,"query":{"filtered":{"query":{"bool":{"must":{"simple_query_string":{"query":"unix","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}}},"filter":{"or":[{"term":{"location":"DE-929 DE-107 DE-Zw1"}}]}}},"sort":[{"date":{"order":"desc","unmapped_type":"string","missing":"_last"}}]} +unix and option.offset = 10 and option.length = 20 and filter.location any "DE-929 DE-107 DE-Zw1" sortby date/sort.descending|{"from":0,"size":10,"query":{"filtered":{"query":{"bool":{"must":{"bool":{"must":{"bool":{"must":{"simple_query_string":{"query":"unix","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}}}}}}},"filter":{"or":[{"term":{"location":"DE-929 DE-107 DE-Zw1"}}]}}},"sort":[{"date":{"order":"desc","unmapped_type":"string","missing":"_last"}}]} +unix and facet.creator = "on"|{"from":0,"size":10,"query":{"bool":{"must":{"simple_query_string":{"query":"unix","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}}},"aggregations":{"myfacet":"myvalue"}} +unix and facet.creator = "off"|{"from":0,"size":10,"query":{"bool":{"must":{"simple_query_string":{"query":"unix","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}}},"aggregations":{"myfacet":"myvalue"}} +unix and facet.creator = "on" and facet.subject = "on" and facet.date = "off"|{"from":0,"size":10,"query":{"bool":{"must":{"bool":{"must":{"bool":{"must":{"simple_query_string":{"query":"unix","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}}}}}}},"aggregations":{"myfacet":"myvalue"}} +unix and facet.date = on|{"from":0,"size":10,"query":{"bool":{"must":{"simple_query_string":{"query":"unix","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}}},"aggregations":{"myfacet":"myvalue"}} +(cql.allIndexes = "")|{"from":0,"size":10,"query":{"simple_query_string":{"query":"\"\"","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}} +cql.allIndexes all 3125294126|{"from":0,"size":10,"query":{"simple_query_string":{"query":"3125294126","fields":["cql.allIndexes"],"analyze_wildcard":true,"default_operator":"and"}}} diff --git a/src/test/resources/org/xbib/cql/queries.txt b/src/test/resources/org/xbib/cql/queries.txt new file mode 100644 index 0000000..8767446 --- /dev/null +++ b/src/test/resources/org/xbib/cql/queries.txt @@ -0,0 +1,141 @@ +"to be or not to be" +publicationYear < 1980 +lengthOfFemur > 2.4 +bioMass >= 100 +id = 12345678 +id = 8a666b7e-6597-3cfb-b478-313cc3c25011 +contentid = "0a1af248-7339-3b59-bc07-3a460275456f" +isbn = "0818631678" +title = "duck" and author = "sanderson" +unix +financing +"Christine Wolfinger" +"der die das" +1234 +"1234" +1.234 +"1.234" +"unix AND wolfinger" +"to be or not to be" +"not macht erfinderisch" +"to be or not to be" +unix$ +"^linux" +finan* +finan? +finan*ng +finan?ier?ng +title = "duck" +title = "Dinosaur Systematics" +title <> linux +cql.resultSetId = HT000011990 +cql.allRecords = 2 +cql.allRecords = 1 NOT title = fish|cql.allRecords = 1 not title = fish +title any "unix linux" +title all "unix linux" +title all "unix 'linux' test" +title all "linux \"pinguin's best friend\" unix" +dc.title adj "lord of the rings" +anywhere = "linux unix \"grundkurs für einsteiger\"" +dc.date=2003|dc.date = 2003 +dc.date="2003"|dc.date = "2003" +dc.creator=smith|dc.creator = smith +dc.title=financing|dc.title = financing +dc.subject=financing|dc.subject = financing +"feathered dinosaur" and (yixian or jehol) +(a or b) and (c or d) +unix AND wolfinger|unix and wolfinger +"keine angst" AND unix|"keine angst" and unix +unix and 2012 +dc.title=unix or wolfinger|dc.title = unix or wolfinger +(dc.title = unix or dc.date = 2003) and ( dc.creator = wolfinger and dc.creator = christine or dc.creator = maier )|(dc.title = unix or dc.date = 2003) and (dc.creator = wolfinger and dc.creator = christine or dc.creator = maier) +(dc.title = "unix" or dc.title = "linux") and ( dc.creator = "wolfinger" and dc.creator = "christine" )|(dc.title = "unix" or dc.title = "linux") and (dc.creator = "wolfinger" and dc.creator = "christine") +financing AND success|financing and success +financing OR monetary|financing or monetary +financing NOT success|financing not success +(financing AND monetary) OR success|(financing and monetary) or success +financing AND (monetary OR success)|financing and (monetary or success) +"financing constraints" OR success|"financing constraints" or success +"financing constraints" NOT model|"financing constraints" not model +("financing constraints" AND model) OR success|("financing constraints" and model) or success +"financing constraints" AND (model OR success)|"financing constraints" and (model or success) +dinosaur or bird +dino and "eiszeit" +dinosaur not reptile +date = 2007-09-30 or date = "2007-09-30T12:34:56" +dinosaur and bird or dinobird +(bird or dinosaur) and (feathers or scales) +linux and creator = wolfinger +dc.title=linux and dc.title = unix|dc.title = linux and dc.title = unix +dc.title = unix and dc.date = 2000 +dc.title = "unix" and dc.creator = "wolfinger" +dc.title = "unix" or dc.creator = "wolfinger" +dc.title = "unix" and dc.creator = "wolfinger" and dc.creator = "christine" +dc.title = "unix" and ( dc.creator = "wolfinger" or dc.creator = "meyer" )|dc.title = "unix" and (dc.creator = "wolfinger" or dc.creator = "meyer") +(dc.title = "unix" or dc.title = "linux") and ( dc.creator = "wolfinger" and dc.creator = "meyer" )|(dc.title = "unix" or dc.title = "linux") and (dc.creator = "wolfinger" and dc.creator = "meyer") +dc.title = "foo" and (dc.creator = "smith" or dc.creator = "jones") +dc.creator = "smith" and dc.creator = "jones" +dc.date = 2007-09-30 or dc.date = "2007-09-30T12:34:56" +identifier = 0783923126590 +identifier = "9783923126590" +identifier = "9783923126590*" +dc.identifier=/bib.identifierAuthority=isbn "0201563177"|dc.identifier =/bib.identifierAuthority=isbn "0201563177" +dc.identifier =/bib.identifierAuthority=isbn "0201563177"|dc.identifier =/bib.identifierAuthority=isbn "0201563177" +dc.identifier =/bib.identifierAuthority=isbn "0201563177" and dc.title=unix sortby date|dc.identifier =/bib.identifierAuthority=isbn "0201563177" and dc.title = unix sortby date +dc.date > 2007-09-30 and dc.date < "2007-10-30T12:34:56" +date > 2007-01-01 +dc.date <= 2006-07-01 +dc.date >= 2005-02-31 +dc.date within "2006-01-01 2007-01-01" +dc.date > 2011 +dc.date = "> 2003" +dc.date = "20012010" +test and filter.collection = "test"|test +dc.title = test and filter.collection = "test"|dc.title = test +(dc.title = "unix" or dc.title = "linux") and ( dc.creator = "wolfinger" and dc.creator = "meyer" ) and filter.subject = "computer"|(dc.title = "unix" or dc.title = "linux") and (dc.creator = "wolfinger" and dc.creator = "meyer") +dc.title = "unix" and filter.creator = "wolfinger"|dc.title = "unix" +dc.title = "unix" and filter.creator = "wolfinger" or filter.creator = "meyer"|dc.title = "unix" +dc.title = "unix" and (filter.creator = "wolfinger" and filter.subject= Computer)|dc.title = "unix" +unix and filter.date > 2006-01-01|unix +unix and (filter.date > 2006-01-01 and filter.date > 2007-01-01)|unix +unix and filter.date within "2006-01-01 2007-01-01"|unix +unix and filter.collection = "info:sid/a.b.c.d:module"|unix +unix and filter.collection = "info:sid/a.b.c.d:module" or filter.collection = "info:sid/e.f.g.h:module"|unix +unix and (filter.collection = "info:sid/a.b.c.d:module" and filter.creator ="Wolfinger, Christine")|unix +test and filter.collection = "test"|test +test and (filter.creator = "a" and filter.subject = "b")|test +test and filter.creator = "a" or filter.subject = "b"|test +test and filter.creator = "smith"|test +test and (filter.creator = "jones" and filter.collection = "test")|test +test and filter.creator = "smith" or filter.creator = "jones"|test +test and (filter.creator = "smith" and filter.creator = "jones")|test +test or filter.creator = "smith" and filter.creator = "jones"|test +test or (filter.creator = "smith" and filter.creator = "jones")|test +test and (filter.creator = "smith" or filter.creator = "jones")|test +test or (filter.creator = "smith" or filter.creator = "jones")|test +test and (filter.creator = "smith" or filter.creator = "jones" and filter.subject = "unix")|test +structure AND filter.creator="smith"|structure +structure AND filter.subject="data"|structure +structure AND filter.date="2003"|structure +pädagogik AND filter.taxonomy="0/24/*"|pädagogik +pädagogik AND filter.taxonomy="0/24/313/*"|pädagogik +pädagogik AND filter.taxonomy="0/24/313/21/*"|pädagogik +linux and filter.creator <> "Wolfinger"|linux +unix and option.offset = 10 and option.length = 20|unix +test and option.length = 1 and option.length = 2 and option.length = 3|test +bib.namePersonal = meier +unix sortby date/sort.descending +unix sortby date/sort.descending geo/sort.ascending +unix sortby geo/sort.ascending/sort.unit=km/sort.lat=50.9415016174/sort.lon=6.95853996277 +unix sortby geo/sort.ascending/sort.unit=km/sort.center="(50.9415016174,6.95853996277)" +unix and filter.location any "DE-929 DE-107 DE-Zw1"|unix +unix and filter.location any "DE-929 DE-107 DE-Zw1" sortby date/sort.descending|unix sortby date/sort.descending +unix and option.offset = 10 and option.length = 20 and filter.location any "DE-929 DE-107 DE-Zw1" sortby date/sort.descending|unix sortby date/sort.descending +unix and facet.dc.creator = "on"|unix +unix and facet.dc.creator = "off"|unix +unix and facet.dc.creator = "on" and facet.dc.subject = "on" and facet.dc.date = "off"|unix +unix and facet.dc.date = on|unix +unix and facet.dc.creator = "on" and facet.dc.subject = "on" and facet.dc.subject = "buckets=10"|unix +unix and facet.dc.date = "on" and facet.dc.subject = "on" and facet.dc.subject = "buckets=20"|unix +unix and facet.dc.creator = "on" and facet.dc.subject = "on" and facet.dc.subject = "buckets=20"|unix +cql.allIndexes all "linux;"