diff --git a/.github/workflows/settings.xml b/.github/workflows/settings.xml
new file mode 100644
index 00000000..d8be2eb4
--- /dev/null
+++ b/.github/workflows/settings.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ github-datawave
+ ${env.USER_NAME}
+ ${env.ACCESS_TOKEN}
+
+
+
diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml
index 234556ee..543fd726 100644
--- a/.github/workflows/tests.yaml
+++ b/.github/workflows/tests.yaml
@@ -31,11 +31,13 @@ jobs:
${{ runner.os }}-maven-
- name: Format code
run: |
- mvn -V -B -e clean formatter:format sortpom:sort -Pautoformat
+ mvn -s $GITHUB_WORKSPACE/.github/workflows/settings.xml -V -B -e clean formatter:format sortpom:sort -Pautoformat
git status
git diff-index --quiet HEAD || (echo "Error! There are modified files after formatting." && false)
env:
MAVEN_OPTS: "-Dhttps.protocols=TLSv1.2 -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=WARN -Djava.awt.headless=true"
+ USER_NAME: ${{ secrets.USER_NAME }}
+ ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }}
# Build the code and run the unit/integration tests.
build-and-test:
@@ -56,9 +58,11 @@ jobs:
${{ runner.os }}-maven-format-
${{ runner.os }}-maven-
- name: Build and Run Unit Tests
- run: mvn -V -B -e -Ddist clean verify
+ run: mvn -s $GITHUB_WORKSPACE/.github/workflows/settings.xml -V -B -e -Ddist clean verify
env:
MAVEN_OPTS: "-Dhttps.protocols=TLSv1.2 -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=WARN -Djava.awt.headless=true"
+ USER_NAME: ${{ secrets.USER_NAME }}
+ ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }}
# Here's an example of how you'd deploy the image to the github package registry.
# We don't want to do this by default since packages on github cannot be deleted
@@ -68,11 +72,13 @@ jobs:
# env:
# IMAGE_REGISTRY: "docker.pkg.github.com"
# IMAGE_USERNAME: "NationalSecurityAgency"
+ # USER_NAME: ${{ secrets.USER_NAME }}
+ # ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }}
# run: |
# # Set up env vars
- # IMAGE_NAME=$(mvn -q -N -Pdocker -f service/pom.xml -Dexec.executable='echo' -Dexec.args='${project.version}' exec:exec)
- # IMAGE_PREFIX=$(mvn -q -N -Pdocker -f service/pom.xml -Dexec.executable='echo' -Dexec.args='${docker.image.prefix}' exec:exec)
- # IMAGE_TAG=$(mvn -q -N -Pdocker -f service/pom.xml -Dexec.executable='echo' -Dexec.args='${project.artifactId}' exec:exec)
+ # IMAGE_NAME=$(mvn -s $GITHUB_WORKSPACE/.github/workflows/settings.xml -q -N -Pdocker -f service/pom.xml -Dexec.executable='echo' -Dexec.args='${project.version}' exec:exec)
+ # IMAGE_PREFIX=$(mvn -s $GITHUB_WORKSPACE/.github/workflows/settings.xml -q -N -Pdocker -f service/pom.xml -Dexec.executable='echo' -Dexec.args='${docker.image.prefix}' exec:exec)
+ # IMAGE_TAG=$(mvn -s $GITHUB_WORKSPACE/.github/workflows/settings.xml -q -N -Pdocker -f service/pom.xml -Dexec.executable='echo' -Dexec.args='${project.artifactId}' exec:exec)
# REMOTE_IMAGE_NAME="${IMAGE_REGISTRY}/${IMAGE_USERNAME}/${IMAGE_PREFIX}${IMAGE_NAME}"
# # Log in to the package registry
# echo ${{ secrets.GITHUB_TOKEN }} | docker login docker.pkg.github.com --username ${GITHUB_ACTOR} --password-stdin
diff --git a/README.md b/README.md
new file mode 100644
index 00000000..c6b87b28
--- /dev/null
+++ b/README.md
@@ -0,0 +1,66 @@
+# Query Service
+
+[![Apache License][li]][ll] ![Build Status](https://github.com/NationalSecurityAgency/datawave/workflows/Tests/badge.svg)
+
+The query service is a user-facing DATAWAVE microservice that serves as the main REST interface for DataWave query functionality.
+
+### Query Context
+
+*https://host:port/query/v1/*
+
+### User API
+
+| Done? | New? |Admin? | Method | Operation | Description | Path Param | Request Body | Response Body |
+|:--------|:--------|:--------|:--------------|:-----------------------------------------|:-----------------------------------------------------------------------------------------------------------|:------------------------|:-------------------------------|:-------------------------------------------|
+| ✓ | | | `GET` | /listQueryLogic | List QueryLogic types that are currently available | N/A | N/A | [QueryLogicResponse] |
+| ✓ | | | `POST` | /{queryLogic}/define | Define a query using the specified query logic and params | [QueryLogicName] | [QueryParameters] | [GenericResponse] |
+| ✓ | | | `POST` | /{queryLogic}/create | Create a query using the specified query logic and params | [QueryLogicName] | [QueryParameters] | [GenericResponse] |
+| ✓ | | | `POST` | /{queryLogic}/plan | Generate a query plan using the specified query logic and params | [QueryLogicName] | [QueryParameters] | [GenericResponse] |
+| ✓ | | | `POST` | /{queryLogic}/predict | Generate a query prediction using the specified query logic and params | [QueryLogicName] | [QueryParameters] | [GenericResponse] |
+| ✓ | | | `POST` | /{queryLogic}/async/create | Create a query using the specified query logic and params | [QueryLogicName] | [QueryParameters] | [GenericResponse] |
+| ✓ | | | `PUT` `POST` | /{id}/reset | Resets the specified query | [QueryId] | N/A | [VoidResponse] [GenericResponse] |
+| ✓ | | | `POST` | /{queryLogic}/createAndNext | Create a query using the specified query logic and params, and get the first page | [QueryLogicName] | [QueryParameters] | [BaseQueryResponse] |
+| ✓ | | | `POST` | /{queryLogic}/async/createAndNext | Create a query using the specified query logic and params, and get the first page | [QueryLogicName] | [QueryParameters] | [BaseQueryResponse] |
+| | | | `GET` | /lookupContentUUID/{uuidType}/{uuid} | Returns content associated with the given UUID | [UUIDType], [UUID] | N/A | [BaseQueryResponse] or [StreamingOutput] |
+| | | | `POST` | /lookupContentUUID | Returns content associated with the given batch of UUIDs | N/A | [QueryParameters] | [BaseQueryResponse] or [StreamingOutput] |
+| | | | `GET` | /lookupUUID/{uuidType}/{uuid} | Returns event associated with the given batch of UUID | [UUIDType], [UUID] | N/A | [BaseQueryResponse] or [StreamingOutput] |
+| | | | `POST` | /lookupUUID | Returns event(s) associated with the given batch of UUIDs | N/A | [QueryParameters] | [BaseQueryResponse] or [StreamingOutput] |
+| ✓ | | | `GET` | /{id}/plan | Returns the plan for the specified query | [QueryId] | N/A | [GenericResponse] |
+| ✓ | | | `GET` | /{id}/predictions | Returns the predictions for the specified query | [QueryId] | N/A | [GenericResponse] |
+| ✓ | | | `GET` | /{id}/async/next | Returns the next page of results for the specified query | [QueryId] | N/A | [BaseQueryResponse] |
+| ✓ | | | `GET` | /{id}/next | Returns the next page of results for the specified query | [QueryId] | N/A | [BaseQueryResponse] |
+| ✓ | | | `PUT` `POST` | /{id}/close | Closes the specified query | [QueryId] | N/A | [VoidResponse] |
+| ✓ | | ✓ | `PUT` `POST` | /{id}/adminClose | Closes the specified query | [QueryId] | N/A | [VoidResponse] |
+| ✓ | ✓ | ✓ | `PUT` `POST` | /adminCloseAll | Closes all running queries | N/A | N/A | [VoidResponse] |
+| ✓ | | | `PUT` `POST` | /{id}/cancel | Cancels the specified query | [QueryId] | N/A | [VoidResponse] |
+| ✓ | | ✓ | `PUT` `POST` | /{id}/adminCancel | Cancels the specified query | [QueryId] | N/A | [VoidResponse] |
+| ✓ | ✓ | ✓ | `PUT` `POST` | /adminCancelAll | Cancels all running queries | N/A | N/A | [VoidResponse] |
+| ✓ | | | `GET` | /listAll | Returns a list of queries associated with the current user | N/A | N/A | [QueryImplListResponse] |
+| ✓ | | | `GET` | /{id} | Returns query info for the specified query | [QueryId] | N/A | [QueryImplListResponse] |
+| ✓ | | | `GET` | /list | Returns a list of queries for this caller, filtering by the (optional) query id, and (optional) query name | N/A | [QueryId], [QueryName] | [QueryImplListResponse] |
+| ✓ | ✓ | ✓ | `GET` | /adminList | Returns a list of queries, filtered by the (optional) user, (optional) query id, and (optional) query name | N/A | [User], [QueryId], [QueryName] | [QueryImplListResponse] |
+| ✓ | | | `DELETE` | /{id}/remove | Remove (delete) the specified query | [QueryId] | N/A | [VoidResponse] |
+| ✓ | ✓ | ✓ | `DELETE` | /{id}/adminRemove | Remove (delete) the specified query | [QueryId] | N/A | [VoidResponse] |
+| ✓ | ✓ | ✓ | `DELETE` | /{id}/adminRemoveAll | Removes all queries which aren't running | N/A | N/A | [VoidResponse] |
+| ✓ | | | `POST` | /{id}/duplicate | Duplicates the specified query | [QueryId] | [QueryParameters] | [GenericResponse] |
+| ✓ | | | `PUT` `POST` | /{id}/update | Updates the specified query | [QueryId] | [QueryParameters] | [GenericResponse] |
+| ✓ | | | `GET` | /{id}/listAll | Returns a list of queries associated with the specified user | [UserId] | N/A | [QueryImplListResponse] |
+| ✓ | | | `POST` | /purgeQueryCache | Purges the cache of query objects | N/A | N/A | [VoidResponse] |
+| ✓ | | | `GET` | /enableTracing | Enables tracing for queries which match the given criteria | N/A | [QueryRegex], [User] | [VoidResponse] |
+| ✓ | | | `GET` | /disableTracing | Disables tracing for queries which match the given criteria | N/A | [QueryRegex], [User] | [VoidResponse] |
+| ✓ | | | `GET` | /disableAllTracing | Disables tracing for all queries | N/A | N/A | [VoidResponse] |
+| | | | `POST` | /{logicName}/execute | Create a query using the specified query logic and params, and stream the results | [QueryLogicName] | [QueryParameters] | [StreamingOutput] |
+| | | | `POST` | /{logicName}/async/execute | Create a query using the specified query logic and params, and stream the results | [QueryLogicName] | [QueryParameters] | [StreamingOutput] |
+
+---
+
+### Getting Started
+
+TBD
+
+For now, refer to the [Datawave Docker Compose Readme][getting-started]
+
+[getting-started]:https://github.com/NationalSecurityAgency/datawave/blob/feature/queryMicroservices/docker/README.md#datawave-docker-compose
+
+[li]: http://img.shields.io/badge/license-ASL-blue.svg
+[ll]: https://www.apache.org/licenses/LICENSE-2.0
diff --git a/api/pom.xml b/api/pom.xml
new file mode 100644
index 00000000..0d1c496c
--- /dev/null
+++ b/api/pom.xml
@@ -0,0 +1,171 @@
+
+
+ 4.0.0
+
+ gov.nsa.datawave.microservice
+ datawave-microservice-parent
+ 4.0.0-SNAPSHOT
+ ../../../microservice-parent/pom.xml
+
+ query-api
+ 1.0.0-SNAPSHOT
+ https://github.com/NationalSecurityAgency/datawave-query-service
+
+ scm:git:https://github.com/NationalSecurityAgency/datawave-query-service.git
+ scm:git:git@github.com:NationalSecurityAgency/datawave-query-service.git
+ HEAD
+ https://github.com/NationalSecurityAgency/datawave-query-service
+
+
+ http://webservice.datawave.nsa/v1
+ 4.0.0-SNAPSHOT
+ 3.0.0-SNAPSHOT
+ 2.0.2
+
+
+
+
+ gov.nsa.datawave.microservice
+ base-rest-responses
+ ${version.microservice.base-rest-responses}
+
+
+ log4j
+ log4j
+
+
+ slf4j-reload4j
+ org.slf4j
+
+
+
+
+ gov.nsa.datawave.microservice
+ type-utils
+ ${version.microservice.type-utils}
+
+
+ slf4j-reload4j
+ org.slf4j
+
+
+ log4j
+ log4j
+
+
+
+
+ jakarta.validation
+ jakarta.validation-api
+ ${version.validation-api}
+
+
+
+
+
+ gov.nsa.datawave.microservice
+ base-rest-responses
+
+
+ gov.nsa.datawave.microservice
+ type-utils
+
+
+ jakarta.validation
+ jakarta.validation-api
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ test
+
+
+
+
+
+ true
+
+
+ false
+
+ github-datawave
+ https://maven.pkg.github.com/NationalSecurityAgency/datawave
+
+
+
+
+
+ true
+ src/main/resources
+
+ source-templates/**
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ 2.4
+
+
+ jboss
+
+ jar
+
+
+ jboss
+
+
+
+
+
+
+ META-INF/beans.xml
+ META-INF/jboss-ejb3.xml
+
+
+
+
+ maven-resources-plugin
+
+
+ copy-templated-sources
+ validate
+
+ copy-resources
+
+
+ ${project.build.directory}/generated-sources/templated-sources
+
+
+ src/main/resources/source-templates
+ true
+
+
+
+
+
+
+
+ org.codehaus.mojo
+ build-helper-maven-plugin
+ 3.3.0
+
+
+ add-source
+ generate-sources
+
+ add-source
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/api/src/main/java/datawave/microservice/query/DefaultQueryParameters.java b/api/src/main/java/datawave/microservice/query/DefaultQueryParameters.java
new file mode 100644
index 00000000..6001b714
--- /dev/null
+++ b/api/src/main/java/datawave/microservice/query/DefaultQueryParameters.java
@@ -0,0 +1,631 @@
+package datawave.microservice.query;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.time.DateUtils;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Splitter;
+
+public class DefaultQueryParameters implements QueryParameters {
+
+ private static final List KNOWN_PARAMS = Arrays.asList(QUERY_STRING, QUERY_NAME, QUERY_PERSISTENCE, QUERY_PAGESIZE, QUERY_PAGETIMEOUT,
+ QUERY_AUTHORIZATIONS, QUERY_EXPIRATION, QUERY_TRACE, QUERY_BEGIN, QUERY_END, QUERY_VISIBILITY, QUERY_LOGIC_NAME, QUERY_POOL,
+ QUERY_MAX_RESULTS_OVERRIDE, QUERY_MAX_CONCURRENT_TASKS, QUERY_SYSTEM_FROM);
+
+ protected String query;
+ protected String queryName;
+ protected QueryPersistence persistenceMode;
+ protected int pagesize;
+ protected int pageTimeout;
+ protected boolean isMaxResultsOverridden;
+ protected long maxResultsOverride;
+ protected String auths;
+ protected Date expirationDate;
+ protected boolean trace;
+ protected Date beginDate;
+ protected Date endDate;
+ protected String visibility;
+ protected String logicName;
+ protected String systemFrom;
+ protected String pool;
+ protected boolean isMaxConcurrentTasksOverridden;
+ protected int maxConcurrentTasks;
+ protected boolean expandFields;
+ protected boolean expandValues;
+ protected Map> requestHeaders;
+
+ public DefaultQueryParameters() {
+ clear();
+ }
+
+ /**
+ * Configure internal variables via the incoming parameter map, performing validation of values.
+ *
+ * QueryParameters are considered valid if the following required parameters are present.
+ *
+ *
'query'
+ *
'queryName'
+ *
'persistence'
+ *
'auths'
+ *
'expiration'
+ *
'queryLogicName'
+ *
+ *
+ * QueryParameters may also include the following optional parameters.
+ *
+ *
'pagesize'
+ *
'pageTimeout'
+ *
'begin'
+ *
'end'
+ *
+ *
+ * @param parameters
+ * - a Map of QueryParameters
+ * @throws IllegalArgumentException
+ * when a bad argument is encountered
+ */
+ public void validate(Map> parameters) throws IllegalArgumentException {
+ for (String param : KNOWN_PARAMS) {
+ List values = parameters.get(param);
+ if (null == values) {
+ continue;
+ }
+ if (values.isEmpty() || values.size() > 1) {
+ throw new IllegalArgumentException("Known parameter [" + param + "] only accepts one value");
+ }
+ if (QUERY_STRING.equals(param)) {
+ this.query = values.get(0);
+ } else if (QUERY_NAME.equals(param)) {
+ this.queryName = values.get(0);
+ } else if (QUERY_PERSISTENCE.equals(param)) {
+ this.persistenceMode = QueryPersistence.valueOf(values.get(0));
+ } else if (QUERY_PAGESIZE.equals(param)) {
+ this.pagesize = Integer.parseInt(values.get(0));
+ } else if (QUERY_PAGETIMEOUT.equals(param)) {
+ this.pageTimeout = Integer.parseInt(values.get(0));
+ } else if (QUERY_MAX_RESULTS_OVERRIDE.equals(param)) {
+ this.maxResultsOverride = Long.parseLong(values.get(0));
+ this.isMaxResultsOverridden = true;
+ } else if (QUERY_MAX_CONCURRENT_TASKS.equals(param)) {
+ this.maxConcurrentTasks = Integer.parseInt(values.get(0));
+ this.isMaxConcurrentTasksOverridden = true;
+ } else if (QUERY_AUTHORIZATIONS.equals(param)) {
+ // ensure that auths are comma separated with no empty values or spaces
+ Splitter splitter = Splitter.on(',').omitEmptyStrings().trimResults();
+ this.auths = StringUtils.join(splitter.splitToList(values.get(0)), ",");
+ } else if (QUERY_EXPIRATION.equals(param)) {
+ try {
+ this.expirationDate = parseEndDate(values.get(0));
+ } catch (ParseException e) {
+ throw new IllegalArgumentException("Error parsing expiration date", e);
+ }
+ } else if (QUERY_TRACE.equals(param)) {
+ this.trace = Boolean.parseBoolean(values.get(0));
+ } else if (QUERY_BEGIN.equals(param)) {
+ try {
+ this.beginDate = values.get(0) == null ? null : parseStartDate(values.get(0));
+ } catch (ParseException e) {
+ throw new IllegalArgumentException("Error parsing begin date", e);
+ }
+ } else if (QUERY_END.equals(param)) {
+ try {
+ this.endDate = values.get(0) == null ? null : parseEndDate(values.get(0));
+ } catch (ParseException e) {
+ throw new IllegalArgumentException("Error parsing end date", e);
+ }
+ } else if (QUERY_VISIBILITY.equals(param)) {
+ this.visibility = values.get(0);
+ } else if (QUERY_LOGIC_NAME.equals(param)) {
+ this.logicName = values.get(0);
+ } else if (QUERY_POOL.equals(param)) {
+ this.pool = values.get(0);
+ } else if (QUERY_PLAN_EXPAND_FIELDS.equals(param)) {
+ this.expandFields = Boolean.parseBoolean(values.get(0));
+ } else if (QUERY_PLAN_EXPAND_VALUES.equals(param)) {
+ this.expandValues = Boolean.parseBoolean(values.get(0));
+ } else if (QUERY_SYSTEM_FROM.equals(param)) {
+ this.systemFrom = values.get(0);
+ } else {
+ throw new IllegalArgumentException("Unknown condition.");
+ }
+ }
+
+ try {
+ Preconditions.checkNotNull(this.query, "QueryParameter 'query' cannot be null");
+ Preconditions.checkNotNull(this.queryName, "QueryParameter 'queryName' cannot be null");
+ Preconditions.checkNotNull(this.persistenceMode, "QueryParameter 'persistence' mode cannot be null");
+ Preconditions.checkNotNull(this.auths, "QueryParameter 'auths' cannot be null");
+ Preconditions.checkNotNull(this.expirationDate, "QueryParameter 'expirationDate' cannot be null");
+ Preconditions.checkNotNull(this.logicName, "QueryParameter 'logicName' cannot be null");
+ } catch (NullPointerException e) {
+ throw new IllegalArgumentException("Missing one or more required QueryParameters", e);
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+
+ DefaultQueryParameters that = (DefaultQueryParameters) o;
+
+ if (pagesize != that.pagesize)
+ return false;
+ if (pageTimeout != that.pageTimeout)
+ return false;
+ if (isMaxResultsOverridden != that.isMaxResultsOverridden)
+ return false;
+ if (isMaxResultsOverridden) {
+ if (maxResultsOverride != that.maxResultsOverride)
+ return false;
+ }
+ if (isMaxConcurrentTasksOverridden != that.isMaxConcurrentTasksOverridden)
+ return false;
+ if (maxConcurrentTasks != that.maxConcurrentTasks)
+ return false;
+ if (expandFields != that.expandFields)
+ return false;
+ if (expandValues != that.expandValues)
+ return false;
+ if (trace != that.trace)
+ return false;
+ if (!auths.equals(that.auths))
+ return false;
+ if (beginDate != null ? !beginDate.equals(that.beginDate) : that.beginDate != null)
+ return false;
+ if (visibility != null ? !visibility.equals(that.visibility) : that.visibility != null)
+ return false;
+ if (endDate != null ? !endDate.equals(that.endDate) : that.endDate != null)
+ return false;
+ if (!expirationDate.equals(that.expirationDate))
+ return false;
+ if (logicName != null ? !logicName.equals(that.logicName) : that.logicName != null)
+ return false;
+ if (pool != null ? !pool.equals(that.pool) : that.pool != null)
+ return false;
+ if (persistenceMode != that.persistenceMode)
+ return false;
+ if (!query.equals(that.query))
+ return false;
+ if (!queryName.equals(that.queryName))
+ return false;
+ if (requestHeaders != null ? !requestHeaders.equals(that.requestHeaders) : that.requestHeaders != null)
+ return false;
+ if (systemFrom != null ? !systemFrom.equals(that.systemFrom) : that.systemFrom != null)
+ return false;
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = query.hashCode();
+ result = 31 * result + queryName.hashCode();
+ result = 31 * result + persistenceMode.hashCode();
+ result = 31 * result + pagesize;
+ result = 31 * result + pageTimeout;
+ if (isMaxResultsOverridden) {
+ result = 31 * result + (int) (maxResultsOverride);
+ }
+ if (isMaxConcurrentTasksOverridden) {
+ result = 31 * result + maxConcurrentTasks;
+ }
+ result = 31 * result + Boolean.hashCode(expandFields);
+ result = 31 * result + Boolean.hashCode(expandValues);
+ result = 31 * result + auths.hashCode();
+ result = 31 * result + expirationDate.hashCode();
+ result = 31 * result + (trace ? 1 : 0);
+ result = 31 * result + (beginDate != null ? beginDate.hashCode() : 0);
+ result = 31 * result + (endDate != null ? endDate.hashCode() : 0);
+ result = 31 * result + (visibility != null ? visibility.hashCode() : 0);
+ result = 31 * result + (logicName != null ? logicName.hashCode() : 0);
+ result = 31 * result + (pool != null ? pool.hashCode() : 0);
+ result = 31 * result + (requestHeaders != null ? requestHeaders.hashCode() : 0);
+ result = 31 * result + (systemFrom != null ? systemFrom.hashCode() : 0);
+ return result;
+ }
+
+ public static synchronized String formatDate(Date d) throws ParseException {
+ String formatPattern = "yyyyMMdd HHmmss.SSS";
+ SimpleDateFormat formatter = new SimpleDateFormat(formatPattern);
+ formatter.setLenient(false);
+ return formatter.format(d);
+ }
+
+ protected static final String defaultStartTime = "000000";
+ protected static final String defaultStartMillisec = "000";
+ protected static final String defaultEndTime = "235959";
+ protected static final String defaultEndMillisec = "999";
+ protected static final String formatPattern = "yyyyMMdd HHmmss.SSS";
+ private static final SimpleDateFormat dateFormat;
+
+ static {
+ dateFormat = new SimpleDateFormat(formatPattern);
+ dateFormat.setLenient(false);
+ }
+
+ public static Date parseStartDate(String s) throws ParseException {
+ return parseDate(s, defaultStartTime, defaultStartMillisec);
+ }
+
+ public static Date parseEndDate(String s) throws ParseException {
+ return parseDate(s, defaultEndTime, defaultEndMillisec);
+ }
+
+ public static synchronized Date parseDate(String s, String defaultTime, String defaultMillisec) throws ParseException {
+ Date d;
+ ParseException e = null;
+ synchronized (DefaultQueryParameters.dateFormat) {
+ String str = s;
+ if (str.equals("+24Hours")) {
+ d = DateUtils.addDays(new Date(), 1);
+ } else {
+ if (StringUtils.isNotBlank(defaultTime) && !str.contains(" ")) {
+ str = str + " " + defaultTime;
+ }
+
+ if (StringUtils.isNotBlank(defaultMillisec) && !str.contains(".")) {
+ str = str + "." + defaultMillisec;
+ }
+
+ try {
+ d = DefaultQueryParameters.dateFormat.parse(str);
+ // if any time value in HHmmss was set either by default or by the user
+ // then we want to include ALL of that second by setting the milliseconds to 999
+ if (DateUtils.getFragmentInMilliseconds(d, Calendar.HOUR_OF_DAY) > 0) {
+ DateUtils.setMilliseconds(d, 999);
+ }
+ } catch (ParseException pe) {
+ throw new RuntimeException("Unable to parse date " + str + " with format " + formatPattern, e);
+ }
+ }
+ }
+ return d;
+ }
+
+ /**
+ * Convenience method to generate a {@code Map>} from the specified arguments. If an argument is null, it's associated parameter name
+ * (key) will not be added to the map, which is why Integer and Boolean wrappers are used for greater flexibility.
+ *
+ * The 'parameters' argument will not be parsed, so its internal elements will not be placed into the map. If non-null, the 'parameters' value will be
+ * mapped directly to the QUERY_PARAMS key.
+ *
+ * No attempt is made to determine whether or not the given arguments constitute a valid query. If validation is desired, see the {@link #validate(Map)}
+ * method
+ *
+ * @param queryLogicName
+ * - name of QueryLogic to use
+ * @param query
+ * - the raw query string
+ * @param queryName
+ * - client-supplied name of query
+ * @param queryVisibility
+ * - query
+ * @param beginDate
+ * - start date
+ * @param endDate
+ * - end date
+ * @param queryAuthorizations
+ * - what auths the query should run with
+ * @param expirationDate
+ * - expiration date
+ * @param pagesize
+ * - page size
+ * @param pageTimeout
+ * - page timeout
+ * @param maxResultsOverride
+ * - max results override
+ * @param persistenceMode
+ * - persistence mode
+ * @param systemFrom
+ * - system from
+ * @param parameters
+ * - additional parameters passed in as map
+ * @param trace
+ * - trace flag
+ * @return parameter map
+ * @throws ParseException
+ * on date parse/format error
+ */
+ public static Map> paramsToMap(String queryLogicName, String query, String queryName, String queryVisibility, Date beginDate,
+ Date endDate, String queryAuthorizations, Date expirationDate, Integer pagesize, Integer pageTimeout, Long maxResultsOverride,
+ QueryPersistence persistenceMode, String systemFrom, String parameters, Boolean trace) throws ParseException {
+
+ MultiValueMap p = new LinkedMultiValueMap<>();
+ if (queryLogicName != null) {
+ p.set(QueryParameters.QUERY_LOGIC_NAME, queryLogicName);
+ }
+ if (query != null) {
+ p.set(QueryParameters.QUERY_STRING, query);
+ }
+ if (queryName != null) {
+ p.set(QueryParameters.QUERY_NAME, queryName);
+ }
+ if (queryVisibility != null) {
+ p.set(QueryParameters.QUERY_VISIBILITY, queryVisibility);
+ }
+ if (beginDate != null) {
+ p.set(QueryParameters.QUERY_BEGIN, formatDate(beginDate));
+ }
+ if (endDate != null) {
+ p.set(QueryParameters.QUERY_END, formatDate(endDate));
+ }
+ if (queryAuthorizations != null) {
+ // ensure that auths are comma separated with no empty values or spaces
+ Splitter splitter = Splitter.on(',').omitEmptyStrings().trimResults();
+ p.set(QueryParameters.QUERY_AUTHORIZATIONS, StringUtils.join(splitter.splitToList(queryAuthorizations), ","));
+ }
+ if (expirationDate != null) {
+ p.set(QueryParameters.QUERY_EXPIRATION, formatDate(expirationDate));
+ }
+ if (pagesize != null) {
+ p.set(QueryParameters.QUERY_PAGESIZE, pagesize.toString());
+ }
+ if (pageTimeout != null) {
+ p.set(QueryParameters.QUERY_PAGETIMEOUT, pageTimeout.toString());
+ }
+ if (maxResultsOverride != null) {
+ p.set(QueryParameters.QUERY_MAX_RESULTS_OVERRIDE, maxResultsOverride.toString());
+ }
+ if (persistenceMode != null) {
+ p.set(QueryParameters.QUERY_PERSISTENCE, persistenceMode.name());
+ }
+ if (trace != null) {
+ p.set(QueryParameters.QUERY_TRACE, trace.toString());
+ }
+ if (systemFrom != null) {
+ p.set(QueryParameters.QUERY_SYSTEM_FROM, systemFrom);
+ }
+ if (parameters != null) {
+ p.set(QueryParameters.QUERY_PARAMS, parameters);
+ }
+
+ return p;
+ }
+
+ @Override
+ public String getQuery() {
+ return query;
+ }
+
+ @Override
+ public void setQuery(String query) {
+ this.query = query;
+ }
+
+ @Override
+ public String getQueryName() {
+ return queryName;
+ }
+
+ @Override
+ public void setQueryName(String queryName) {
+ this.queryName = queryName;
+ }
+
+ @Override
+ public QueryPersistence getPersistenceMode() {
+ return persistenceMode;
+ }
+
+ @Override
+ public void setPersistenceMode(QueryPersistence persistenceMode) {
+ this.persistenceMode = persistenceMode;
+ }
+
+ @Override
+ public int getPagesize() {
+ return pagesize;
+ }
+
+ @Override
+ public void setPagesize(int pagesize) {
+ this.pagesize = pagesize;
+ }
+
+ @Override
+ public int getPageTimeout() {
+ return pageTimeout;
+ }
+
+ @Override
+ public void setPageTimeout(int pageTimeout) {
+ this.pageTimeout = pageTimeout;
+ }
+
+ @Override
+ public long getMaxResultsOverride() {
+ return maxResultsOverride;
+ }
+
+ @Override
+ public void setMaxResultsOverride(long maxResultsOverride) {
+ this.maxResultsOverride = maxResultsOverride;
+ }
+
+ @Override
+ public boolean isMaxResultsOverridden() {
+ return this.isMaxResultsOverridden;
+ }
+
+ @Override
+ public String getAuths() {
+ return auths;
+ }
+
+ @Override
+ public void setAuths(String auths) {
+ this.auths = auths;
+ }
+
+ @Override
+ public Date getExpirationDate() {
+ return expirationDate;
+ }
+
+ @Override
+ public void setExpirationDate(Date expirationDate) {
+ this.expirationDate = expirationDate;
+ }
+
+ @Override
+ public boolean isTrace() {
+ return trace;
+ }
+
+ @Override
+ public void setTrace(boolean trace) {
+ this.trace = trace;
+ }
+
+ @Override
+ public Date getBeginDate() {
+ return beginDate;
+ }
+
+ @Override
+ public Date getEndDate() {
+ return endDate;
+ }
+
+ @Override
+ public void setBeginDate(Date beginDate) {
+ this.beginDate = beginDate;
+ }
+
+ @Override
+ public void setEndDate(Date endDate) {
+ this.endDate = endDate;
+ }
+
+ @Override
+ public String getVisibility() {
+ return visibility;
+ }
+
+ @Override
+ public void setVisibility(String visibility) {
+ this.visibility = visibility;
+ }
+
+ @Override
+ public String getLogicName() {
+ return logicName;
+ }
+
+ @Override
+ public void setLogicName(String logicName) {
+ this.logicName = logicName;
+ }
+
+ @Override
+ public String getSystemFrom() {
+ return systemFrom;
+ }
+
+ @Override
+ public void setSystemFrom(String systemFrom) {
+ this.systemFrom = systemFrom;
+ }
+
+ @Override
+ public String getPool() {
+ return pool;
+ }
+
+ @Override
+ public void setPool(String pool) {
+ this.pool = pool;
+ }
+
+ @Override
+ public int getMaxConcurrentTasks() {
+ return maxConcurrentTasks;
+ }
+
+ @Override
+ public void setMaxConcurrentTasks(int maxConcurrentTasks) {
+ this.maxConcurrentTasks = maxConcurrentTasks;
+ }
+
+ @Override
+ public boolean isMaxConcurrentTasksOverridden() {
+ return isMaxConcurrentTasksOverridden;
+ }
+
+ @Override
+ public boolean isExpandFields() {
+ return expandFields;
+ }
+
+ @Override
+ public void setExpandFields(boolean expandFields) {
+ this.expandFields = expandFields;
+ }
+
+ @Override
+ public boolean isExpandValues() {
+ return expandValues;
+ }
+
+ @Override
+ public void setExpandValues(boolean expandValues) {
+ this.expandValues = expandValues;
+ }
+
+ @Override
+ public Map> getRequestHeaders() {
+ return requestHeaders;
+ }
+
+ @Override
+ public void setRequestHeaders(Map> requestHeaders) {
+ this.requestHeaders = requestHeaders;
+ }
+
+ @Override
+ public MultiValueMap getUnknownParameters(Map> allQueryParameters) {
+ MultiValueMap p = new LinkedMultiValueMap<>();
+ for (String key : allQueryParameters.keySet()) {
+ if (!KNOWN_PARAMS.contains(key)) {
+ p.put(key, allQueryParameters.get(key));
+ }
+ }
+ return p;
+ }
+
+ @Override
+ public void clear() {
+ this.query = null;
+ this.queryName = null;
+ this.persistenceMode = QueryPersistence.TRANSIENT;
+ this.pagesize = 10;
+ this.pageTimeout = -1;
+ this.isMaxResultsOverridden = false;
+ this.auths = null;
+ this.expirationDate = DateUtils.addDays(new Date(), 1);
+ this.trace = false;
+ this.beginDate = null;
+ this.endDate = null;
+ this.visibility = null;
+ this.logicName = null;
+ this.pool = null;
+ this.isMaxConcurrentTasksOverridden = false;
+ this.maxConcurrentTasks = 0;
+ this.expandFields = true;
+ this.expandValues = false;
+ this.requestHeaders = null;
+ this.systemFrom = null;
+ }
+}
diff --git a/api/src/main/java/datawave/microservice/query/Query.java b/api/src/main/java/datawave/microservice/query/Query.java
new file mode 100644
index 00000000..e5eaca9a
--- /dev/null
+++ b/api/src/main/java/datawave/microservice/query/Query.java
@@ -0,0 +1,167 @@
+package datawave.microservice.query;
+
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlSeeAlso;
+
+import datawave.microservice.query.QueryImpl.Parameter;
+import datawave.webservice.query.util.QueryUncaughtExceptionHandler;
+
+@XmlAccessorType(XmlAccessType.NONE)
+@XmlSeeAlso(QueryImpl.class)
+public abstract class Query implements Externalizable {
+
+ private static final long serialVersionUID = -5980134700364340930L;
+
+ public abstract void initialize(String userDN, List dnList, String queryLogicName, QueryParameters qp,
+ Map> optionalQueryParameters);
+
+ public abstract String getQueryLogicName();
+
+ public abstract UUID getId();
+
+ public abstract void setId(UUID id);
+
+ public abstract String getQueryName();
+
+ public abstract void setQueryName(String queryName);
+
+ public abstract String getUserDN();
+
+ public abstract void setUserDN(String userDN);
+
+ public abstract String getQuery();
+
+ public abstract void setQuery(String query);
+
+ public abstract String getQueryAuthorizations();
+
+ public abstract void setQueryAuthorizations(String authorizations);
+
+ public abstract Date getExpirationDate();
+
+ public abstract void setExpirationDate(Date expirationDate);
+
+ public abstract int getPagesize();
+
+ public abstract void setPagesize(int pagesize);
+
+ public abstract int getPageTimeout();
+
+ public abstract void setPageTimeout(int pageTimeout);
+
+ public abstract long getMaxResultsOverride();
+
+ public abstract void setMaxResultsOverride(long maxResults);
+
+ public abstract boolean isMaxResultsOverridden();
+
+ public abstract void setMaxResultsOverridden(boolean maxResultsOverridden);
+
+ public abstract Set getParameters();
+
+ public abstract void setParameters(Set params);
+
+ public abstract void setQueryLogicName(String name);
+
+ public abstract Date getBeginDate();
+
+ public abstract void setBeginDate(Date beginDate);
+
+ public abstract Date getEndDate();
+
+ public abstract void setEndDate(Date endDate);
+
+ public abstract String getSystemFrom();
+
+ public abstract void setSystemFrom(String systemFrom);
+
+ public abstract Query duplicate(String newQueryName);
+
+ public abstract Parameter findParameter(String parameter);
+
+ public abstract void setParameters(Map parameters);
+
+ public abstract void addParameter(String key, String val);
+
+ public abstract void addParameters(Map parameters);
+
+ public abstract void setDnList(List dnList);
+
+ public abstract List getDnList();
+
+ public abstract QueryUncaughtExceptionHandler getUncaughtExceptionHandler();
+
+ public abstract void setUncaughtExceptionHandler(QueryUncaughtExceptionHandler uncaughtExceptionHandler);
+
+ public abstract void setOwner(String owner);
+
+ public abstract String getOwner();
+
+ public abstract void setColumnVisibility(String colviz);
+
+ public abstract String getColumnVisibility();
+
+ public abstract Map> toMap();
+
+ public abstract void readMap(Map> map) throws ParseException;
+
+ public abstract Map getCardinalityFields();
+
+ public abstract void setOptionalQueryParameters(Map> optionalQueryParameters);
+
+ public abstract Map> getOptionalQueryParameters();
+
+ public abstract void removeParameter(String key);
+
+ @Override
+ public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
+ Map> map = new HashMap<>();
+ int numKeys = in.readInt();
+ for (int i = 0; i < numKeys; i++) {
+ String key = in.readUTF();
+ int numValues = in.readInt();
+ List values = new ArrayList<>(numValues);
+ for (int j = 0; j < numValues; j++) {
+ String value = in.readUTF();
+ values.add(value);
+ }
+ map.put(key, values);
+ }
+ try {
+ readMap(map);
+ } catch (ParseException pe) {
+ throw new IOException("Could not parse value", pe);
+ }
+ }
+
+ @Override
+ public void writeExternal(ObjectOutput out) throws IOException {
+ Map> map = toMap();
+ Set keys = map.keySet();
+ out.writeInt(keys.size());
+ for (String key : keys) {
+ out.writeUTF(key);
+ List values = map.get(key);
+ out.writeInt(values.size());
+ for (String value : values) {
+ out.writeUTF(value);
+ }
+ }
+ }
+
+ public abstract void populateTrackingMap(Map trackingMap);
+}
diff --git a/api/src/main/java/datawave/microservice/query/QueryImpl.java b/api/src/main/java/datawave/microservice/query/QueryImpl.java
new file mode 100644
index 00000000..3640ce1f
--- /dev/null
+++ b/api/src/main/java/datawave/microservice/query/QueryImpl.java
@@ -0,0 +1,1011 @@
+package datawave.microservice.query;
+
+import static datawave.microservice.query.QueryParameters.QUERY_SYSTEM_FROM;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Objects;
+import java.util.Set;
+import java.util.UUID;
+
+import javax.naming.InvalidNameException;
+import javax.naming.ldap.LdapName;
+import javax.naming.ldap.Rdn;
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlTransient;
+import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
+
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.collections4.MapUtils;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
+
+import datawave.webservice.query.util.OptionallyEncodedStringAdapter;
+import datawave.webservice.query.util.QueryUncaughtExceptionHandler;
+import io.protostuff.Input;
+import io.protostuff.Message;
+import io.protostuff.Output;
+import io.protostuff.Schema;
+import io.protostuff.UninitializedMessageException;
+
+@XmlRootElement(name = "QueryImpl")
+@XmlAccessorType(XmlAccessType.NONE)
+public class QueryImpl extends Query implements Serializable, Message {
+
+ public static final String PARAMETER_SEPARATOR = ";";
+ public static final String PARAMETER_NAME_VALUE_SEPARATOR = ":";
+
+ public static final String USER_DN = "userDN";
+ public static final String DN_LIST = "dnList";
+ public static final String COLUMN_VISIBILITY = "columnVisibility";
+ public static final String QUERY_LOGIC_NAME = QueryParameters.QUERY_LOGIC_NAME;
+ public static final String QUERY_NAME = QueryParameters.QUERY_NAME;
+ public static final String EXPIRATION_DATE = QueryParameters.QUERY_EXPIRATION;
+ public static final String QUERY_ID = "uuid";
+ public static final String PAGESIZE = QueryParameters.QUERY_PAGESIZE;
+ public static final String PAGE_TIMEOUT = QueryParameters.QUERY_PAGETIMEOUT;
+ public static final String MAX_RESULTS_OVERRIDE = QueryParameters.QUERY_MAX_RESULTS_OVERRIDE;
+ public static final String QUERY = QueryParameters.QUERY_STRING;
+ public static final String QUERY_AUTHORIZATIONS = QueryParameters.QUERY_AUTHORIZATIONS;
+ public static final String OWNER = "owner";
+ public static final String PARAMETERS = "parameters";
+ public static final String BEGIN_DATE = QueryParameters.QUERY_BEGIN;
+ public static final String END_DATE = QueryParameters.QUERY_END;
+ public static final String QUERY_USER_FIELD = "QUERY_USER";
+ public static final String QUERY_LOGIC_NAME_FIELD = "QUERY_LOGIC_NAME";
+ public static final String POOL = QueryParameters.QUERY_POOL;
+
+ @XmlAccessorType(XmlAccessType.FIELD)
+ public static final class Parameter implements Serializable, Message {
+
+ private static final long serialVersionUID = 2L;
+
+ @XmlElement(name = "name")
+ private String parameterName;
+ @XmlElement(name = "value")
+ private String parameterValue;
+
+ public Parameter() {}
+
+ public Parameter(String name, String value) {
+ this.parameterName = name;
+ this.parameterValue = value;
+ }
+
+ public String getParameterName() {
+ return parameterName;
+ }
+
+ public void setParameterName(String parameterName) {
+ this.parameterName = parameterName;
+ }
+
+ public String getParameterValue() {
+ return parameterValue;
+ }
+
+ public void setParameterValue(String parameterValue) {
+ this.parameterValue = parameterValue;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder(256);
+ sb.append("[name=").append(this.parameterName);
+ sb.append(",value=").append(this.parameterValue).append("]");
+ return sb.toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (null == o)
+ return false;
+ if (!(o instanceof Parameter))
+ return false;
+ if (this == o)
+ return true;
+ Parameter other = (Parameter) o;
+ if (this.getParameterName().equals(other.getParameterName()) && this.getParameterValue().equals(other.getParameterValue()))
+ return true;
+ else
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return getParameterName() == null ? 0 : getParameterName().hashCode();
+ }
+
+ @XmlTransient
+ public static final Schema SCHEMA = new Schema() {
+ public Parameter newMessage() {
+ return new Parameter();
+ }
+
+ public Class typeClass() {
+ return Parameter.class;
+ }
+
+ public String messageName() {
+ return Parameter.class.getSimpleName();
+ }
+
+ public String messageFullName() {
+ return Parameter.class.getName();
+ }
+
+ public boolean isInitialized(Parameter message) {
+ return message.parameterName != null && message.parameterValue != null;
+ }
+
+ public void writeTo(Output output, Parameter message) throws IOException {
+ if (message.parameterName == null)
+ throw new UninitializedMessageException(message);
+ output.writeString(1, message.parameterName, false);
+
+ if (message.parameterValue == null)
+ throw new UninitializedMessageException(message);
+ output.writeString(2, message.parameterValue, false);
+ }
+
+ public void mergeFrom(Input input, Parameter message) throws IOException {
+ int number;
+ while ((number = input.readFieldNumber(this)) != 0) {
+ switch (number) {
+ case 1:
+ message.parameterName = input.readString();
+ break;
+ case 2:
+ message.parameterValue = input.readString();
+ break;
+ default:
+ input.handleUnknownField(number, this);
+ break;
+ }
+ }
+ }
+
+ public String getFieldName(int number) {
+ switch (number) {
+ case 1:
+ return "parameterName";
+ case 2:
+ return "parameterValue";
+ default:
+ return null;
+ }
+ }
+
+ public int getFieldNumber(String name) {
+ final Integer number = fieldMap.get(name);
+ return number == null ? 0 : number.intValue();
+ }
+
+ final java.util.HashMap fieldMap = new java.util.HashMap();
+
+ {
+ fieldMap.put("parameterName", 1);
+ fieldMap.put("parameterValue", 2);
+ }
+ };
+
+ public static Schema getSchema() {
+ return SCHEMA;
+ }
+
+ @Override
+ public Schema cachedSchema() {
+ return SCHEMA;
+ }
+
+ }
+
+ private static final long serialVersionUID = 2L;
+
+ @XmlElement
+ protected String queryLogicName;
+ @XmlElement
+ protected String id;
+ @XmlElement
+ protected String queryName;
+ @XmlElement
+ protected String userDN;
+ @XmlElement
+ @XmlJavaTypeAdapter(OptionallyEncodedStringAdapter.class)
+ @JsonSerialize(using = ToStringSerializer.class)
+ protected String query;
+ @XmlElement
+ protected Date beginDate;
+ @XmlElement
+ protected Date endDate;
+ @XmlElement
+ protected String queryAuthorizations;
+ @XmlElement
+ protected Date expirationDate;
+ @XmlElement
+ protected int pagesize;
+ @XmlElement
+ protected int pageTimeout;
+ @XmlElement
+ protected boolean maxResultsOverridden;
+ @XmlElement
+ protected long maxResultsOverride;
+ @XmlElement
+ protected HashSet parameters = new HashSet();
+ @XmlElement
+ protected List dnList;
+ @XmlElement
+ protected String owner;
+ @XmlElement
+ protected String columnVisibility;
+ @XmlElement
+ protected String systemFrom;
+ @XmlElement
+ protected String pool;
+ @XmlTransient
+ protected Map> optionalQueryParameters;
+
+ protected transient QueryUncaughtExceptionHandler uncaughtExceptionHandler;
+
+ protected transient HashMap paramLookup = new HashMap();
+
+ public String getQueryLogicName() {
+ return queryLogicName;
+ }
+
+ public UUID getId() {
+ if (null == id)
+ return null;
+ return java.util.UUID.fromString(id);
+ }
+
+ public String getQueryName() {
+ return queryName;
+ }
+
+ public String getUserDN() {
+ return userDN;
+ }
+
+ public String getQuery() {
+ return query;
+ }
+
+ public String getQueryAuthorizations() {
+ return queryAuthorizations;
+ }
+
+ public Date getExpirationDate() {
+ return expirationDate;
+ }
+
+ public int getPagesize() {
+ return pagesize;
+ }
+
+ public int getPageTimeout() {
+ return pageTimeout;
+ }
+
+ public String getPool() {
+ return pool;
+ }
+
+ public long getMaxResultsOverride() {
+ return maxResultsOverride;
+ }
+
+ public boolean isMaxResultsOverridden() {
+ return maxResultsOverridden;
+ }
+
+ public void setMaxResultsOverridden(boolean maxResultsOverridden) {
+ this.maxResultsOverridden = maxResultsOverridden;
+ }
+
+ public Set getParameters() {
+ return parameters == null ? null : Collections.unmodifiableSet(parameters);
+ }
+
+ public void setQueryLogicName(String name) {
+ this.queryLogicName = name;
+ }
+
+ public void setId(UUID id) {
+ this.id = id.toString();
+ }
+
+ public void setQueryName(String queryName) {
+ this.queryName = queryName;
+ }
+
+ public void setUserDN(String userDN) {
+ this.userDN = userDN;
+ }
+
+ public void setQuery(String query) {
+ this.query = query;
+ }
+
+ public void setQueryAuthorizations(String queryAuthorizations) {
+ this.queryAuthorizations = queryAuthorizations;
+ }
+
+ public void setExpirationDate(Date expirationDate) {
+ this.expirationDate = expirationDate;
+ }
+
+ public void setMaxResultsOverride(long maxResults) {
+ this.maxResultsOverride = maxResults;
+ }
+
+ public void setPagesize(int pagesize) {
+ this.pagesize = pagesize;
+ }
+
+ public void setPageTimeout(int pageTimeout) {
+ this.pageTimeout = pageTimeout;
+ }
+
+ public void setPool(String pool) {
+ this.pool = pool;
+ }
+
+ public void setParameters(Set parameters) {
+ this.parameters.clear();
+ this.parameters.addAll(parameters);
+ this.paramLookup.clear();
+ for (Parameter p : this.parameters) {
+ this.paramLookup.put(p.getParameterName(), p);
+ }
+ }
+
+ public void addParameter(String key, String val) {
+ Parameter p = new Parameter(key, val);
+ this.parameters.add(p);
+ this.paramLookup.put(p.getParameterName(), p);
+ }
+
+ public void addParameters(Map parameters) {
+ for (Entry p : parameters.entrySet()) {
+ addParameter(p.getKey(), p.getValue());
+ }
+ }
+
+ public void setParameters(Map parameters) {
+ HashSet paramObjs = new HashSet(parameters.size());
+ for (Entry param : parameters.entrySet()) {
+ Parameter p = new Parameter(param.getKey(), param.getValue());
+ paramObjs.add(p);
+ }
+ this.setParameters(paramObjs);
+ }
+
+ public List getDnList() {
+ return dnList;
+ }
+
+ public void setDnList(List dnList) {
+ this.dnList = dnList;
+ }
+
+ public String getColumnVisibility() {
+ return columnVisibility;
+ }
+
+ public void setColumnVisibility(String columnVisibility) {
+ this.columnVisibility = columnVisibility;
+ }
+
+ public Date getBeginDate() {
+ return beginDate;
+ }
+
+ public void setBeginDate(Date beginDate) {
+ this.beginDate = beginDate;
+ }
+
+ public Date getEndDate() {
+ return endDate;
+ }
+
+ public void setEndDate(Date endDate) {
+ this.endDate = endDate;
+ }
+
+ @Override
+ public String getSystemFrom() {
+ return systemFrom;
+ }
+
+ @Override
+ public void setSystemFrom(String systemFrom) {
+ this.systemFrom = systemFrom;
+ }
+
+ public Map> getOptionalQueryParameters() {
+ return optionalQueryParameters;
+ }
+
+ public void setOptionalQueryParameters(Map> optionalQueryParameters) {
+ this.optionalQueryParameters = optionalQueryParameters;
+ }
+
+ @Override
+ public QueryImpl duplicate(String newQueryName) {
+ QueryImpl query = new QueryImpl();
+ query.setQueryLogicName(this.getQueryLogicName());
+ query.setQueryName(newQueryName);
+ query.setExpirationDate(this.getExpirationDate());
+ query.setId(java.util.UUID.randomUUID());
+ query.setPagesize(this.getPagesize());
+ query.setPageTimeout(this.getPageTimeout());
+ query.setMaxResultsOverridden(this.isMaxResultsOverridden());
+ query.setMaxResultsOverride(this.getMaxResultsOverride());
+ query.setQuery(this.getQuery());
+ query.setQueryAuthorizations(this.getQueryAuthorizations());
+ query.setUserDN(this.getUserDN());
+ query.setOwner(this.getOwner());
+ query.setColumnVisibility(this.getColumnVisibility());
+ query.setBeginDate(this.getBeginDate());
+ query.setEndDate(this.getEndDate());
+ query.setSystemFrom(this.getSystemFrom());
+ query.setPool(this.getPool());
+ if (CollectionUtils.isNotEmpty(this.parameters))
+ query.setParameters(new HashSet(this.parameters));
+ query.setDnList(this.dnList);
+ if (MapUtils.isNotEmpty(this.optionalQueryParameters)) {
+ Map> optionalDuplicate = new HashMap<>();
+ this.optionalQueryParameters.entrySet().stream().forEach(e -> optionalDuplicate.put(e.getKey(), new ArrayList(e.getValue())));
+ query.setOptionalQueryParameters(optionalDuplicate);
+ }
+ query.setUncaughtExceptionHandler(this.getUncaughtExceptionHandler());
+ return query;
+ }
+
+ @Override
+ public String toString() {
+ ToStringBuilder tsb = new ToStringBuilder(this);
+ tsb.append(QUERY_LOGIC_NAME, this.getQueryLogicName());
+ tsb.append(QUERY_NAME, this.getQueryName());
+ tsb.append(EXPIRATION_DATE, this.getExpirationDate());
+ tsb.append(QUERY_ID, this.getId());
+ tsb.append(PAGESIZE, this.getPagesize());
+ tsb.append(PAGE_TIMEOUT, this.getPageTimeout());
+ tsb.append(MAX_RESULTS_OVERRIDE, (this.isMaxResultsOverridden() ? this.getMaxResultsOverride() : "NA"));
+ tsb.append(QUERY, this.getQuery());
+ tsb.append(QUERY_AUTHORIZATIONS, this.getQueryAuthorizations());
+ tsb.append(USER_DN, this.getUserDN());
+ tsb.append(OWNER, this.getOwner());
+ tsb.append(PARAMETERS, this.getParameters());
+ tsb.append(DN_LIST, this.getDnList());
+ tsb.append(COLUMN_VISIBILITY, this.getColumnVisibility());
+ tsb.append(BEGIN_DATE, this.getBeginDate());
+ tsb.append(END_DATE, this.getEndDate());
+ tsb.append(QUERY_SYSTEM_FROM, this.getSystemFrom());
+ tsb.append(POOL, this.getPool());
+ return tsb.toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+ QueryImpl that = (QueryImpl) o;
+ return pagesize == that.pagesize && pageTimeout == that.pageTimeout && maxResultsOverridden == that.maxResultsOverridden
+ && maxResultsOverride == that.maxResultsOverride && Objects.equals(queryLogicName, that.queryLogicName) && Objects.equals(id, that.id)
+ && Objects.equals(queryName, that.queryName) && Objects.equals(userDN, that.userDN) && Objects.equals(query, that.query)
+ && Objects.equals(beginDate, that.beginDate) && Objects.equals(endDate, that.endDate)
+ && Objects.equals(queryAuthorizations, that.queryAuthorizations) && Objects.equals(expirationDate, that.expirationDate)
+ && Objects.equals(parameters, that.parameters) && Objects.equals(dnList, that.dnList) && Objects.equals(owner, that.owner)
+ && Objects.equals(columnVisibility, that.columnVisibility) && Objects.equals(optionalQueryParameters, that.optionalQueryParameters)
+ && Objects.equals(systemFrom, that.systemFrom) && Objects.equals(pool, that.pool);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(queryLogicName, id, queryName, userDN, query, beginDate, endDate, queryAuthorizations, expirationDate, pagesize, pageTimeout,
+ maxResultsOverridden, maxResultsOverride, parameters, dnList, owner, columnVisibility, optionalQueryParameters, systemFrom, pool);
+ }
+
+ public Parameter findParameter(String parameter) {
+ if (!paramLookup.containsKey(parameter)) {
+ return new Parameter(parameter, "");
+ } else {
+ return paramLookup.get(parameter);
+ }
+ }
+
+ @XmlTransient
+ private static final Schema SCHEMA = new Schema() {
+ public QueryImpl newMessage() {
+ return new QueryImpl();
+ }
+
+ public Class typeClass() {
+ return QueryImpl.class;
+ }
+
+ public String messageName() {
+ return QueryImpl.class.getSimpleName();
+ }
+
+ public String messageFullName() {
+ return QueryImpl.class.getName();
+ }
+
+ public boolean isInitialized(QueryImpl message) {
+ return message.queryLogicName != null && message.id != null && message.userDN != null && message.query != null
+ && message.queryAuthorizations != null && message.expirationDate != null && message.pagesize > 0 && message.pageTimeout != 0;
+ }
+
+ public void writeTo(Output output, QueryImpl message) throws IOException {
+ if (message.queryLogicName == null)
+ throw new UninitializedMessageException(message, SCHEMA);
+ output.writeString(1, message.queryLogicName, false);
+
+ if (message.id == null)
+ throw new UninitializedMessageException(message, SCHEMA);
+ output.writeString(2, message.id, false);
+
+ if (message.queryName != null)
+ output.writeString(3, message.queryName, false);
+
+ if (message.userDN == null)
+ throw new UninitializedMessageException(message, SCHEMA);
+ output.writeString(4, message.userDN, false);
+
+ if (message.query == null)
+ throw new UninitializedMessageException(message, SCHEMA);
+ output.writeString(5, message.query, false);
+
+ if (message.beginDate != null)
+ output.writeInt64(6, message.beginDate.getTime(), false);
+
+ if (message.endDate != null)
+ output.writeInt64(7, message.endDate.getTime(), false);
+
+ if (message.queryAuthorizations == null)
+ throw new UninitializedMessageException(message, SCHEMA);
+ output.writeString(8, message.queryAuthorizations, false);
+
+ if (message.expirationDate == null)
+ throw new UninitializedMessageException(message, SCHEMA);
+ output.writeInt64(9, message.expirationDate.getTime(), false);
+
+ if (message.pagesize <= 0)
+ throw new UninitializedMessageException(message, SCHEMA);
+ output.writeUInt32(10, message.pagesize, false);
+
+ if (message.parameters != null) {
+ for (Parameter p : message.parameters) {
+ output.writeObject(11, p, Parameter.SCHEMA, true);
+ }
+ }
+
+ if (message.owner == null)
+ throw new UninitializedMessageException(message, SCHEMA);
+ output.writeString(12, message.owner, false);
+
+ if (null != message.dnList) {
+ for (String dn : message.dnList)
+ output.writeString(13, dn, true);
+ }
+
+ if (message.columnVisibility != null) {
+ output.writeString(14, message.columnVisibility, false);
+ }
+
+ if (message.pageTimeout == 0)
+ throw new UninitializedMessageException(message, SCHEMA);
+ output.writeUInt32(15, message.pageTimeout, false);
+
+ if (message.systemFrom != null)
+ output.writeString(16, message.systemFrom, false);
+
+ if (message.pool != null) {
+ output.writeString(17, message.pool, false);
+ }
+ }
+
+ public void mergeFrom(Input input, QueryImpl message) throws IOException {
+ int number;
+ while ((number = input.readFieldNumber(this)) != 0) {
+ switch (number) {
+ case 1:
+ message.queryLogicName = input.readString();
+ break;
+ case 2:
+ message.id = input.readString();
+ break;
+ case 3:
+ message.queryName = input.readString();
+ break;
+ case 4:
+ message.userDN = input.readString();
+ break;
+ case 5:
+ message.query = input.readString();
+ break;
+
+ case 6:
+ message.beginDate = new Date(input.readInt64());
+ break;
+ case 7:
+ message.endDate = new Date(input.readInt64());
+ break;
+ case 8:
+ message.queryAuthorizations = input.readString();
+ break;
+ case 9:
+ message.expirationDate = new Date(input.readInt64());
+ break;
+ case 10:
+ message.pagesize = input.readUInt32();
+ break;
+ case 11:
+ if (message.parameters == null)
+ message.parameters = new HashSet();
+ Parameter p = input.mergeObject(null, Parameter.SCHEMA);
+ message.addParameter(p.getParameterName(), p.getParameterValue());
+ break;
+ case 12:
+ message.owner = input.readString();
+ break;
+ case 13:
+ if (null == message.dnList)
+ message.dnList = new ArrayList();
+ message.dnList.add(input.readString());
+ break;
+ case 14:
+ message.columnVisibility = input.readString();
+ break;
+ case 15:
+ message.pageTimeout = input.readUInt32();
+ break;
+ case 16:
+ message.systemFrom = input.readString();
+ case 17:
+ message.pool = input.readString();
+ default:
+ input.handleUnknownField(number, this);
+ break;
+ }
+ }
+ }
+
+ public String getFieldName(int number) {
+ switch (number) {
+ case 1:
+ return QueryParameters.QUERY_LOGIC_NAME;
+ case 2:
+ return QUERY_ID;
+ case 3:
+ return QueryParameters.QUERY_NAME;
+ case 4:
+ return USER_DN;
+ case 5:
+ return QUERY;
+ case 6:
+ return BEGIN_DATE;
+ case 7:
+ return END_DATE;
+ case 8:
+ return QUERY_AUTHORIZATIONS;
+ case 9:
+ return EXPIRATION_DATE;
+ case 10:
+ return PAGESIZE;
+ case 11:
+ return PARAMETERS;
+ case 12:
+ return OWNER;
+ case 13:
+ return DN_LIST;
+ case 14:
+ return COLUMN_VISIBILITY;
+ case 15:
+ return PAGE_TIMEOUT;
+ case 16:
+ return QUERY_SYSTEM_FROM;
+ case 17:
+ return POOL;
+ default:
+ return null;
+ }
+ }
+
+ public int getFieldNumber(String name) {
+ final Integer number = fieldMap.get(name);
+ return number == null ? 0 : number.intValue();
+ }
+
+ final java.util.HashMap fieldMap = new java.util.HashMap();
+
+ {
+ fieldMap.put(QUERY_LOGIC_NAME, 1);
+ fieldMap.put(QUERY_ID, 2);
+ fieldMap.put(QUERY_NAME, 3);
+ fieldMap.put(USER_DN, 4);
+ fieldMap.put(QUERY, 5);
+ fieldMap.put(BEGIN_DATE, 6);
+ fieldMap.put(END_DATE, 7);
+ fieldMap.put(QUERY_AUTHORIZATIONS, 8);
+ fieldMap.put(EXPIRATION_DATE, 9);
+ fieldMap.put(PAGESIZE, 10);
+ fieldMap.put(PARAMETERS, 11);
+ fieldMap.put(OWNER, 12);
+ fieldMap.put(DN_LIST, 13);
+ fieldMap.put(COLUMN_VISIBILITY, 14);
+ fieldMap.put(PAGE_TIMEOUT, 15);
+ fieldMap.put(QUERY_SYSTEM_FROM, 16);
+ fieldMap.put(POOL, 17);
+ }
+ };
+
+ public QueryUncaughtExceptionHandler getUncaughtExceptionHandler() {
+ return this.uncaughtExceptionHandler;
+ }
+
+ public void setUncaughtExceptionHandler(QueryUncaughtExceptionHandler uncaughtExceptionHandler) {
+ this.uncaughtExceptionHandler = uncaughtExceptionHandler;
+ }
+
+ public void initialize(String userDN, List dnList, String queryLogicName, QueryParameters qp, Map> optionalQueryParameters) {
+ this.dnList = dnList;
+ this.expirationDate = qp.getExpirationDate();
+ this.id = java.util.UUID.randomUUID().toString();
+ this.pagesize = qp.getPagesize();
+ this.pageTimeout = qp.getPageTimeout();
+ this.query = qp.getQuery();
+ this.queryAuthorizations = qp.getAuths();
+ this.queryLogicName = queryLogicName;
+ this.queryName = qp.getQueryName();
+ this.userDN = userDN;
+ this.owner = getOwner(this.userDN);
+ this.beginDate = qp.getBeginDate();
+ this.endDate = qp.getEndDate();
+ this.systemFrom = qp.getSystemFrom();
+ this.pool = qp.getPool();
+ if (optionalQueryParameters != null) {
+ for (Entry> entry : optionalQueryParameters.entrySet()) {
+ if (entry.getValue().get(0) != null) {
+ this.addParameter(entry.getKey(), entry.getValue().get(0));
+ }
+ }
+ }
+ }
+
+ private static String getCommonName(String dn) {
+ String[] comps = getComponents(dn, "CN");
+ return comps.length >= 1 ? comps[0] : null;
+ }
+
+ private static String[] getComponents(String dn, String componentName) {
+ componentName = componentName.toUpperCase();
+ ArrayList components = new ArrayList();
+ try {
+ LdapName name = new LdapName(dn);
+ for (Rdn rdn : name.getRdns()) {
+ if (componentName.equals(rdn.getType().toUpperCase())) {
+ components.add(String.valueOf(rdn.getValue()));
+ }
+ }
+ } catch (InvalidNameException e) {
+ // ignore -- invalid name, so can't find components
+ }
+ return components.toArray(new String[0]);
+ }
+
+ public static String getOwner(String dn) {
+ String sid = null;
+ if (dn != null) {
+ String cn = getCommonName(dn);
+ if (cn == null)
+ cn = dn;
+ sid = cn;
+ int idx = cn.lastIndexOf(' ');
+ if (idx >= 0)
+ sid = cn.substring(idx + 1);
+ }
+ return sid;
+ }
+
+ public void setOwner(String owner) {
+ this.owner = owner;
+ }
+
+ public String getOwner() {
+ return this.owner;
+ }
+
+ public Map> toMap() {
+ MultiValueMap p = new LinkedMultiValueMap<>();
+ if (this.id != null) {
+ p.set(QUERY_ID, this.id);
+ }
+ if (this.queryAuthorizations != null) {
+ p.set(QueryParameters.QUERY_AUTHORIZATIONS, this.queryAuthorizations);
+ }
+ if (this.expirationDate != null) {
+ try {
+ p.set(QueryParameters.QUERY_EXPIRATION, DefaultQueryParameters.formatDate(this.expirationDate));
+ } catch (ParseException e) {
+ throw new RuntimeException("Error formatting date", e);
+ }
+ }
+ if (this.queryName != null) {
+ p.set(QueryParameters.QUERY_NAME, this.queryName);
+ }
+ if (this.queryLogicName != null) {
+ p.set(QueryParameters.QUERY_LOGIC_NAME, this.queryLogicName);
+ }
+ // no null check on primitives
+ p.set(QueryParameters.QUERY_PAGESIZE, Integer.toString(this.pagesize));
+ if (this.query != null) {
+ p.set(QueryParameters.QUERY_STRING, this.query);
+ }
+ if (this.userDN != null) {
+ p.set(USER_DN, this.userDN);
+ }
+ if (this.dnList != null) {
+ p.put(DN_LIST, this.dnList);
+ }
+ if (this.owner != null) {
+ p.set(OWNER, this.owner);
+ }
+ if (this.columnVisibility != null) {
+ p.set(COLUMN_VISIBILITY, this.columnVisibility);
+ }
+ if (this.beginDate != null) {
+ try {
+ p.set(QueryParameters.QUERY_BEGIN, DefaultQueryParameters.formatDate(this.beginDate));
+ } catch (ParseException e) {
+ throw new RuntimeException("Error formatting date", e);
+ }
+ }
+ if (this.endDate != null) {
+ try {
+ p.set(QueryParameters.QUERY_END, DefaultQueryParameters.formatDate(this.endDate));
+ } catch (ParseException e) {
+ throw new RuntimeException("Error formatting date", e);
+ }
+ }
+ p.set(PAGE_TIMEOUT, Integer.toString(this.pageTimeout));
+ if (this.isMaxResultsOverridden()) {
+ p.set(MAX_RESULTS_OVERRIDE, Long.toString(this.maxResultsOverride));
+ }
+ if (this.pool != null) {
+ p.set(QueryParameters.QUERY_POOL, this.pool);
+ }
+
+ if (this.systemFrom != null) {
+ p.set("systemFrom", this.systemFrom);
+ }
+
+ if (this.parameters != null) {
+ for (Parameter parameter : parameters) {
+ p.set(parameter.getParameterName(), parameter.getParameterValue());
+ }
+ }
+ if (this.optionalQueryParameters != null) {
+ p.putAll(this.optionalQueryParameters);
+ }
+ return p;
+ }
+
+ @Override
+ public void readMap(Map> map) throws ParseException {
+ for (String key : map.keySet()) {
+ switch (key) {
+ case QUERY_ID:
+ setId(UUID.fromString(map.get(key).get(0)));
+ break;
+ case QueryParameters.QUERY_AUTHORIZATIONS:
+ setQueryAuthorizations(map.get(key).get(0));
+ break;
+ case QueryParameters.QUERY_EXPIRATION:
+ setExpirationDate(DefaultQueryParameters.parseDate(map.get(key).get(0), null, null));
+ break;
+ case QueryParameters.QUERY_NAME:
+ setQueryName(map.get(key).get(0));
+ break;
+ case QueryParameters.QUERY_LOGIC_NAME:
+ setQueryLogicName(map.get(key).get(0));
+ break;
+ case QueryParameters.QUERY_PAGESIZE:
+ setPagesize(Integer.parseInt(map.get(key).get(0)));
+ break;
+ case QueryParameters.QUERY_STRING:
+ setQuery(map.get(key).get(0));
+ break;
+ case USER_DN:
+ setUserDN(map.get(key).get(0));
+ setOwner(getOwner(getUserDN()));
+ break;
+ case DN_LIST:
+ setDnList(map.get(key));
+ break;
+ case OWNER:
+ setOwner(map.get(key).get(0));
+ break;
+ case COLUMN_VISIBILITY:
+ setColumnVisibility(map.get(key).get(0));
+ break;
+ case QueryParameters.QUERY_BEGIN:
+ setBeginDate(DefaultQueryParameters.parseStartDate(map.get(key).get(0)));
+ break;
+ case QueryParameters.QUERY_END:
+ setEndDate(DefaultQueryParameters.parseEndDate(map.get(key).get(0)));
+ break;
+ case PAGE_TIMEOUT:
+ setPageTimeout(Integer.parseInt(map.get(key).get(0)));
+ break;
+ case MAX_RESULTS_OVERRIDE:
+ String maxResultsOverride = map.get(key).get(0);
+ if (maxResultsOverride != null) {
+ setMaxResultsOverridden(true);
+ setMaxResultsOverride(Long.parseLong(maxResultsOverride));
+ }
+ break;
+ case POOL:
+ setPool(map.get(key).get(0));
+ default:
+ addParameter(key, map.get(key).get(0));
+ break;
+ }
+ }
+ }
+
+ @Override
+ public Map getCardinalityFields() {
+ Map cardinalityFields = new HashMap();
+ cardinalityFields.put(QUERY_USER_FIELD, getOwner());
+ cardinalityFields.put(QUERY_LOGIC_NAME_FIELD, getQueryLogicName());
+ return cardinalityFields;
+ }
+
+ @Override
+ public Schema cachedSchema() {
+ return SCHEMA;
+ }
+
+ @Override
+ public void removeParameter(String key) {
+ this.parameters.remove(paramLookup.get(key));
+ this.paramLookup.remove(key);
+ }
+
+ @Override
+ public void populateTrackingMap(Map trackingMap) {
+ if (trackingMap != null) {
+ if (this.owner != null) {
+ trackingMap.put("query.user", this.owner);
+ }
+ if (this.id != null) {
+ trackingMap.put("query.id", this.id);
+ }
+ if (this.query != null) {
+ trackingMap.put("query.query", this.query);
+ }
+ }
+ }
+}
diff --git a/api/src/main/java/datawave/microservice/query/QueryParameters.java b/api/src/main/java/datawave/microservice/query/QueryParameters.java
new file mode 100644
index 00000000..367d7563
--- /dev/null
+++ b/api/src/main/java/datawave/microservice/query/QueryParameters.java
@@ -0,0 +1,121 @@
+package datawave.microservice.query;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+import org.springframework.util.MultiValueMap;
+
+import datawave.validation.ParameterValidator;
+
+/**
+ * QueryParameters passed in from a client, they are validated and passed through to the iterator stack as QueryOptions.
+ *
+ */
+public interface QueryParameters extends ParameterValidator {
+
+ String QUERY_STRING = "query";
+ String QUERY_NAME = "queryName";
+ String QUERY_PERSISTENCE = "persistence";
+ String QUERY_PAGESIZE = "pagesize";
+ String QUERY_PAGETIMEOUT = "pageTimeout";
+ String QUERY_MAX_RESULTS_OVERRIDE = "max.results.override";
+ String QUERY_AUTHORIZATIONS = "auths";
+ String QUERY_EXPIRATION = "expiration";
+ String QUERY_TRACE = "trace";
+ String QUERY_BEGIN = "begin";
+ String QUERY_END = "end";
+ String QUERY_PARAMS = "params";
+ String QUERY_VISIBILITY = "columnVisibility";
+ String QUERY_LOGIC_NAME = "logicName";
+ String QUERY_POOL = "pool";
+ String QUERY_MAX_CONCURRENT_TASKS = "maxConcurrentTasks";
+ String QUERY_PLAN_EXPAND_FIELDS = "expand.fields";
+ String QUERY_PLAN_EXPAND_VALUES = "expand.values";
+ String QUERY_SYSTEM_FROM = "systemFrom";
+
+ String getQuery();
+
+ void setQuery(String query);
+
+ String getQueryName();
+
+ void setQueryName(String queryName);
+
+ QueryPersistence getPersistenceMode();
+
+ void setPersistenceMode(QueryPersistence persistenceMode);
+
+ int getPagesize();
+
+ void setPagesize(int pagesize);
+
+ int getPageTimeout();
+
+ void setPageTimeout(int pageTimeout);
+
+ long getMaxResultsOverride();
+
+ void setMaxResultsOverride(long maxResults);
+
+ boolean isMaxResultsOverridden();
+
+ String getAuths();
+
+ void setAuths(String auths);
+
+ Date getExpirationDate();
+
+ void setExpirationDate(Date expirationDate);
+
+ boolean isTrace();
+
+ void setTrace(boolean trace);
+
+ Date getBeginDate();
+
+ Date getEndDate();
+
+ void setBeginDate(Date beginDate);
+
+ void setEndDate(Date endDate);
+
+ String getVisibility();
+
+ void setVisibility(String visibility);
+
+ String getLogicName();
+
+ void setLogicName(String logicName);
+
+ String getSystemFrom();
+
+ void setSystemFrom(String systemFrom);
+
+ String getPool();
+
+ void setPool(String pool);
+
+ int getMaxConcurrentTasks();
+
+ void setMaxConcurrentTasks(int maxConcurrentTasks);
+
+ boolean isMaxConcurrentTasksOverridden();
+
+ void setExpandFields(boolean expandFields);
+
+ boolean isExpandFields();
+
+ void setExpandValues(boolean expandVues);
+
+ boolean isExpandValues();
+
+ Map> getRequestHeaders();
+
+ void setRequestHeaders(Map> requestHeaders);
+
+ MultiValueMap getUnknownParameters(Map> allQueryParameters);
+
+ void clear();
+
+}
diff --git a/api/src/main/java/datawave/microservice/query/QueryPersistence.java b/api/src/main/java/datawave/microservice/query/QueryPersistence.java
new file mode 100644
index 00000000..3c9a0533
--- /dev/null
+++ b/api/src/main/java/datawave/microservice/query/QueryPersistence.java
@@ -0,0 +1,7 @@
+package datawave.microservice.query;
+
+public enum QueryPersistence {
+
+ PERSISTENT, TRANSIENT;
+
+}
diff --git a/api/src/main/java/datawave/microservice/query/config/QueryExpirationProperties.java b/api/src/main/java/datawave/microservice/query/config/QueryExpirationProperties.java
new file mode 100644
index 00000000..8e0f4c35
--- /dev/null
+++ b/api/src/main/java/datawave/microservice/query/config/QueryExpirationProperties.java
@@ -0,0 +1,242 @@
+package datawave.microservice.query.config;
+
+import java.util.concurrent.TimeUnit;
+
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Positive;
+
+import org.springframework.validation.annotation.Validated;
+
+@Validated
+public class QueryExpirationProperties {
+ @Positive
+ private long idleTimeout = 15;
+ @NotNull
+ private TimeUnit idleTimeoutUnit = TimeUnit.MINUTES;
+ @Positive
+ private long progressTimeout = 5;
+ @NotNull
+ private TimeUnit progressTimeoutUnit = TimeUnit.MINUTES;
+ @Positive
+ private long callTimeout = 60;
+ @NotNull
+ private TimeUnit callTimeoutUnit = TimeUnit.MINUTES;
+ @Positive
+ private long callTimeoutInterval = 1;
+ @NotNull
+ private TimeUnit callTimeoutIntervalUnit = TimeUnit.MINUTES;
+ @Positive
+ private long pageMinTimeout = 1;
+ @NotNull
+ private TimeUnit pageMinTimeoutUnit = TimeUnit.MINUTES;
+ @Positive
+ private long pageMaxTimeout = 60;
+ @NotNull
+ private TimeUnit pageMaxTimeoutUnit = TimeUnit.MINUTES;
+ @Positive
+ private long shortCircuitCheckTime = callTimeout / 2;
+ @NotNull
+ private TimeUnit shortCircuitCheckTimeUnit = TimeUnit.MINUTES;
+ @Positive
+ private long shortCircuitTimeout = Math.round(0.97 * callTimeout);
+ @NotNull
+ private TimeUnit shortCircuitTimeoutUnit = TimeUnit.MINUTES;
+
+ @Positive
+ @Deprecated // to be replaced by the long running query timeout
+ private int maxLongRunningTimeoutRetries = 3;
+
+ @Positive
+ private long longRunningQueryTimeout = (maxLongRunningTimeoutRetries + 1) * callTimeoutUnit.toMinutes(callTimeout);
+
+ @NotNull
+ private TimeUnit longRunningQueryTimeoutUnit = TimeUnit.MINUTES;
+
+ public long getIdleTimeout() {
+ return idleTimeout;
+ }
+
+ public long getIdleTimeoutMillis() {
+ return idleTimeoutUnit.toMillis(idleTimeout);
+ }
+
+ public void setIdleTimeout(long idleTimeout) {
+ this.idleTimeout = idleTimeout;
+ }
+
+ public TimeUnit getIdleTimeoutUnit() {
+ return idleTimeoutUnit;
+ }
+
+ public void setIdleTimeoutUnit(TimeUnit idleTimeoutUnit) {
+ this.idleTimeoutUnit = idleTimeoutUnit;
+ }
+
+ public long getProgressTimeout() {
+ return progressTimeout;
+ }
+
+ public long getProgressTimeoutMillis() {
+ return progressTimeoutUnit.toMillis(progressTimeout);
+ }
+
+ public void setProgressTimeout(long progressTimeout) {
+ this.progressTimeout = progressTimeout;
+ }
+
+ public TimeUnit getProgressTimeoutUnit() {
+ return progressTimeoutUnit;
+ }
+
+ public void setProgressTimeoutUnit(TimeUnit progressTimeoutUnit) {
+ this.progressTimeoutUnit = progressTimeoutUnit;
+ }
+
+ public long getCallTimeout() {
+ return callTimeout;
+ }
+
+ public long getCallTimeoutMillis() {
+ return callTimeoutUnit.toMillis(callTimeout);
+ }
+
+ public void setCallTimeout(long callTimeout) {
+ this.callTimeout = callTimeout;
+ }
+
+ public TimeUnit getCallTimeoutUnit() {
+ return callTimeoutUnit;
+ }
+
+ public void setCallTimeoutUnit(TimeUnit callTimeoutUnit) {
+ this.callTimeoutUnit = callTimeoutUnit;
+ }
+
+ public long getCallTimeoutInterval() {
+ return callTimeoutInterval;
+ }
+
+ public long getCallTimeoutIntervalMillis() {
+ return callTimeoutIntervalUnit.toMillis(callTimeoutInterval);
+ }
+
+ public void setCallTimeoutInterval(long callTimeoutInterval) {
+ this.callTimeoutInterval = callTimeoutInterval;
+ }
+
+ public TimeUnit getCallTimeoutIntervalUnit() {
+ return callTimeoutIntervalUnit;
+ }
+
+ public void setCallTimeoutIntervalUnit(TimeUnit callTimeoutIntervalUnit) {
+ this.callTimeoutIntervalUnit = callTimeoutIntervalUnit;
+ }
+
+ public long getPageMinTimeout() {
+ return pageMinTimeout;
+ }
+
+ public long getPageMinTimeoutMillis() {
+ return pageMinTimeoutUnit.toMillis(pageMinTimeout);
+ }
+
+ public void setPageMinTimeout(long pageMinTimeout) {
+ this.pageMinTimeout = pageMinTimeout;
+ }
+
+ public TimeUnit getPageMinTimeoutUnit() {
+ return pageMinTimeoutUnit;
+ }
+
+ public void setPageMinTimeoutUnit(TimeUnit pageMinTimeoutUnit) {
+ this.pageMinTimeoutUnit = pageMinTimeoutUnit;
+ }
+
+ public long getPageMaxTimeout() {
+ return pageMaxTimeout;
+ }
+
+ public long getPageMaxTimeoutMillis() {
+ return pageMaxTimeoutUnit.toMillis(pageMaxTimeout);
+ }
+
+ public void setPageMaxTimeout(long pageMaxTimeout) {
+ this.pageMaxTimeout = pageMaxTimeout;
+ }
+
+ public TimeUnit getPageMaxTimeoutUnit() {
+ return pageMaxTimeoutUnit;
+ }
+
+ public void setPageMaxTimeoutUnit(TimeUnit pageMaxTimeoutUnit) {
+ this.pageMaxTimeoutUnit = pageMaxTimeoutUnit;
+ }
+
+ public long getShortCircuitCheckTime() {
+ return shortCircuitCheckTime;
+ }
+
+ public long getShortCircuitCheckTimeMillis() {
+ return shortCircuitCheckTimeUnit.toMillis(shortCircuitCheckTime);
+ }
+
+ public void setShortCircuitCheckTime(long shortCircuitCheckTime) {
+ this.shortCircuitCheckTime = shortCircuitCheckTime;
+ }
+
+ public TimeUnit getShortCircuitCheckTimeUnit() {
+ return shortCircuitCheckTimeUnit;
+ }
+
+ public void setShortCircuitCheckTimeUnit(TimeUnit shortCircuitCheckTimeUnit) {
+ this.shortCircuitCheckTimeUnit = shortCircuitCheckTimeUnit;
+ }
+
+ public long getShortCircuitTimeout() {
+ return shortCircuitTimeout;
+ }
+
+ public long getShortCircuitTimeoutMillis() {
+ return shortCircuitTimeoutUnit.toMillis(shortCircuitTimeout);
+ }
+
+ public void setShortCircuitTimeout(long shortCircuitTimeout) {
+ this.shortCircuitTimeout = shortCircuitTimeout;
+ }
+
+ public TimeUnit getShortCircuitTimeoutUnit() {
+ return shortCircuitTimeoutUnit;
+ }
+
+ public void setShortCircuitTimeoutUnit(TimeUnit shortCircuitTimeoutUnit) {
+ this.shortCircuitTimeoutUnit = shortCircuitTimeoutUnit;
+ }
+
+ public int getMaxLongRunningTimeoutRetries() {
+ return maxLongRunningTimeoutRetries;
+ }
+
+ public void setMaxLongRunningTimeoutRetries(int maxLongRunningTimeoutRetries) {
+ this.maxLongRunningTimeoutRetries = maxLongRunningTimeoutRetries;
+ }
+
+ public long getLongRunningQueryTimeout() {
+ return longRunningQueryTimeout;
+ }
+
+ public void setLongRunningQueryTimeout(long longRunningQueryTimeout) {
+ this.longRunningQueryTimeout = longRunningQueryTimeout;
+ }
+
+ public TimeUnit getLongRunningQueryTimeoutUnit() {
+ return longRunningQueryTimeoutUnit;
+ }
+
+ public void setLongRunningQueryTimeoutUnit(TimeUnit longRunningQueryTimeoutUnit) {
+ this.longRunningQueryTimeoutUnit = longRunningQueryTimeoutUnit;
+ }
+
+ public long getLongRunningQueryTimeoutMillis() {
+ return longRunningQueryTimeoutUnit.toMillis(longRunningQueryTimeout);
+ }
+}
diff --git a/api/src/main/resources/META-INF/beans.xml b/api/src/main/resources/META-INF/beans.xml
new file mode 100644
index 00000000..4ca201f8
--- /dev/null
+++ b/api/src/main/resources/META-INF/beans.xml
@@ -0,0 +1,9 @@
+
+
+
+
\ No newline at end of file
diff --git a/api/src/main/resources/META-INF/jboss-ejb3.xml b/api/src/main/resources/META-INF/jboss-ejb3.xml
new file mode 100644
index 00000000..8cf49db8
--- /dev/null
+++ b/api/src/main/resources/META-INF/jboss-ejb3.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+ *
+ datawave
+
+
+
+
\ No newline at end of file
diff --git a/api/src/main/resources/source-templates/datawave/microservice/query/package-info.java b/api/src/main/resources/source-templates/datawave/microservice/query/package-info.java
new file mode 100644
index 00000000..ad28e37a
--- /dev/null
+++ b/api/src/main/resources/source-templates/datawave/microservice/query/package-info.java
@@ -0,0 +1,6 @@
+@XmlSchema(namespace="${datawave.webservice.namespace}", elementFormDefault=XmlNsForm.QUALIFIED, xmlns={@XmlNs(prefix = "", namespaceURI = "${datawave.webservice.namespace}")})
+package datawave.microservice.query;
+
+import javax.xml.bind.annotation.XmlNs;
+import javax.xml.bind.annotation.XmlNsForm;
+import javax.xml.bind.annotation.XmlSchema;
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 00000000..e1b60905
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,47 @@
+
+
+ 4.0.0
+
+ gov.nsa.datawave.microservice
+ datawave-microservice-parent
+ 4.0.0-SNAPSHOT
+ ../../microservice-parent/pom.xml
+
+ query-service-parent
+ 1.0.0-SNAPSHOT
+ pom
+ https://github.com/NationalSecurityAgency/datawave-query-service
+
+ api
+
+
+ scm:git:https://github.com/NationalSecurityAgency/datawave-query-service.git
+ scm:git:git@github.com:NationalSecurityAgency/datawave-query-service.git
+ https://github.com/NationalSecurityAgency/datawave-query-service
+
+
+
+
+ true
+
+
+ false
+
+ github-datawave
+ https://maven.pkg.github.com/NationalSecurityAgency/datawave
+
+
+
+
+ services
+
+
+ !skipServices
+
+
+
+ service
+
+
+
+
diff --git a/service/pom.xml b/service/pom.xml
new file mode 100644
index 00000000..98b7c465
--- /dev/null
+++ b/service/pom.xml
@@ -0,0 +1,334 @@
+
+
+ 4.0.0
+
+ gov.nsa.datawave.microservice
+ datawave-microservice-service-parent
+ 5.0.0-SNAPSHOT
+ ../../../microservice-service-parent/pom.xml
+
+ query-service
+ 1.0.0-SNAPSHOT
+ DATAWAVE Query Microservice
+ https://github.com/NationalSecurityAgency/datawave-query-service
+
+ scm:git:https://github.com/NationalSecurityAgency/datawave-query-service.git
+ scm:git:git@github.com:NationalSecurityAgency/datawave-query-service.git
+ HEAD
+ https://github.com/NationalSecurityAgency/datawave-query-service
+
+
+ datawave.microservice.query.QueryService
+ 3.3.4
+ 1.0.0-SNAPSHOT
+ 4.0.0-SNAPSHOT
+ 1.0.0-SNAPSHOT
+ 1.0.0-SNAPSHOT
+ 6.4.3-1
+ 3.3.1-1
+ 0.30
+
+
+
+
+ gov.nsa.datawave.microservice
+ query-api
+ ${version.microservice.query-api}
+
+
+ gov.nsa.datawave.microservice
+ spring-boot-starter-datawave-audit
+ ${version.microservice.starter-audit}
+
+
+ gov.nsa.datawave.microservice
+ spring-boot-starter-datawave-cached-results
+ ${version.microservice.starter-cached-results}
+
+
+ gov.nsa.datawave.microservice
+ spring-boot-starter-datawave-query
+ ${version.microservice.starter-query}
+
+
+ org.apache.hadoop
+ hadoop-client
+ ${version.hadoop}
+
+
+ *
+ org.eclipse.jetty
+
+
+
+
+ org.apache.hadoop
+ hadoop-common
+ ${version.hadoop}
+
+
+ *
+ org.mortbay.jetty
+
+
+ *
+ org.eclipse.jetty
+
+
+ *
+ log4j
+
+
+ *
+ org.slf4j
+
+
+ *
+ org.codehaus.jackson
+
+
+ *
+ commons-logging
+
+
+ avro
+ org.apache.avro
+
+
+ *
+ org.apache.curator
+
+
+ *
+ org.apache.zookeeper
+
+
+
+
+ org.apache.hadoop
+ hadoop-hdfs
+ ${version.hadoop}
+
+
+ *
+ org.mortbay.jetty
+
+
+ *
+ org.eclipse.jetty
+
+
+ *
+ io.netty
+
+
+ *
+ log4j
+
+
+ *
+ commons-logging
+
+
+
+
+ gov.nsa.datawave.microservice
+ spring-boot-starter-datawave-cached-results
+ ${version.microservice.starter-cached-results}
+ pom
+ import
+
+
+ gov.nsa.datawave.microservice
+ spring-boot-starter-datawave-query
+ ${version.microservice.starter-query}
+ pom
+ import
+
+
+ gov.nsa.datawave.microservice
+ spring-boot-starter-datawave-query
+ ${version.microservice.starter-query}
+ test-jar
+ test
+
+
+
+
+
+ gov.nsa.datawave.core
+ datawave-core-query
+
+
+ log4j
+ log4j
+
+
+ slf4j-reload4j
+ org.slf4j
+
+
+
+
+ gov.nsa.datawave.microservice
+ query-api
+
+
+ gov.nsa.datawave.microservice
+ spring-boot-starter-datawave-audit
+
+
+ gov.nsa.datawave.microservice
+ spring-boot-starter-datawave-cached-results
+
+
+ gov.nsa.datawave.microservice
+ spring-boot-starter-datawave-query
+
+
+ org.apache.hadoop
+ hadoop-common
+
+
+ org.apache.hadoop
+ hadoop-hdfs
+
+
+ org.springframework.boot
+ spring-boot-starter-thymeleaf
+
+
+ org.webjars
+ foundation
+ ${version.webjars.foundation}
+
+
+ org.webjars
+ jquery
+ ${version.webjars.jquery}
+
+
+ org.webjars
+ webjars-locator-core
+ ${version.webjars.locator-core}
+
+
+ gov.nsa.datawave.microservice
+ spring-boot-starter-datawave-query
+ test-jar
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ test
+
+
+ org.springframework.cloud
+ spring-cloud-stream-test-support
+ test
+
+
+
+
+
+ true
+
+
+ false
+
+ github-datawave
+ https://maven.pkg.github.com/NationalSecurityAgency/datawave
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+ 2.8
+
+
+ unpack-test-resources
+ process-test-resources
+
+ unpack
+
+
+
+
+ gov.nsa.datawave.microservice
+ spring-boot-starter-datawave-query
+ test-jar
+ **/application-*.yml
+ ${project.build.testOutputDirectory}
+
+
+
+
+
+ unpack
+ package
+
+ unpack
+
+
+
+
+ gov.nsa.datawave.microservice
+ spring-boot-starter-datawave-query
+ **/QueryLogicFactory.xml
+ ${project.build.outputDirectory}
+
+
+
+
+
+
+
+ ch.qos.reload4j
+ reload4j
+ 1.2.22
+
+
+ org.apache.maven.shared
+ maven-common-artifact-filters
+ 1.4
+
+
+ log4j
+ log4j
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+ pl.project13.maven
+ git-commit-id-plugin
+
+
+
+
+
+ docker
+
+
+ microservice-docker
+
+
+
+
+
+ com.spotify
+ docker-maven-plugin
+
+
+
+
+
+
diff --git a/service/src/main/docker/Dockerfile b/service/src/main/docker/Dockerfile
new file mode 100644
index 00000000..7c37f330
--- /dev/null
+++ b/service/src/main/docker/Dockerfile
@@ -0,0 +1,11 @@
+FROM azul/zulu-openjdk-alpine:11
+
+LABEL version=${project.version} \
+ run="docker run ${docker.image.prefix}${project.artifactId}:latest" \
+ description="${project.description}"
+
+ADD ${project.build.finalName}-exec.jar /app.jar
+RUN apk add libc6-compat curl
+
+EXPOSE 8443 8080
+ENTRYPOINT ["java","-jar","app.jar"]
\ No newline at end of file
diff --git a/service/src/main/java/datawave/microservice/query/QueryController.java b/service/src/main/java/datawave/microservice/query/QueryController.java
new file mode 100644
index 00000000..822f1d23
--- /dev/null
+++ b/service/src/main/java/datawave/microservice/query/QueryController.java
@@ -0,0 +1,2738 @@
+package datawave.microservice.query;
+
+import static datawave.core.query.logic.lookup.LookupQueryLogic.LOOKUP_KEY_VALUE_DELIMITER;
+import static datawave.microservice.query.QueryParameters.QUERY_AUTHORIZATIONS;
+import static datawave.microservice.query.QueryParameters.QUERY_BEGIN;
+import static datawave.microservice.query.QueryParameters.QUERY_END;
+import static datawave.microservice.query.QueryParameters.QUERY_MAX_CONCURRENT_TASKS;
+import static datawave.microservice.query.QueryParameters.QUERY_MAX_RESULTS_OVERRIDE;
+import static datawave.microservice.query.QueryParameters.QUERY_NAME;
+import static datawave.microservice.query.QueryParameters.QUERY_PAGESIZE;
+import static datawave.microservice.query.QueryParameters.QUERY_PAGETIMEOUT;
+import static datawave.microservice.query.QueryParameters.QUERY_PARAMS;
+import static datawave.microservice.query.QueryParameters.QUERY_PLAN_EXPAND_FIELDS;
+import static datawave.microservice.query.QueryParameters.QUERY_PLAN_EXPAND_VALUES;
+import static datawave.microservice.query.QueryParameters.QUERY_POOL;
+import static datawave.microservice.query.QueryParameters.QUERY_STRING;
+import static datawave.microservice.query.QueryParameters.QUERY_VISIBILITY;
+import static datawave.microservice.query.lookup.LookupService.LOOKUP_CONTEXT;
+import static datawave.microservice.query.lookup.LookupService.LOOKUP_STREAMING;
+import static datawave.microservice.query.lookup.LookupService.LOOKUP_UUID_PAIRS;
+import static datawave.microservice.query.translateid.TranslateIdService.TRANSLATE_ID;
+import static datawave.query.QueryParameters.QUERY_SYNTAX;
+
+import java.util.List;
+import java.util.function.Supplier;
+
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.annotation.Secured;
+import org.springframework.security.core.annotation.AuthenticationPrincipal;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestHeader;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter;
+
+import com.codahale.metrics.annotation.Timed;
+
+import datawave.microservice.authorization.user.DatawaveUserDetails;
+import datawave.microservice.query.lookup.LookupService;
+import datawave.microservice.query.stream.StreamingProperties;
+import datawave.microservice.query.stream.StreamingService;
+import datawave.microservice.query.stream.listener.CountingResponseBodyEmitterListener;
+import datawave.microservice.query.stream.listener.StreamingResponseListener;
+import datawave.microservice.query.translateid.TranslateIdService;
+import datawave.microservice.query.web.annotation.EnrichQueryMetrics;
+import datawave.microservice.query.web.filter.BaseMethodStatsFilter;
+import datawave.microservice.query.web.filter.CountingResponseBodyEmitter;
+import datawave.microservice.query.web.filter.QueryMetricsEnrichmentFilterAdvice;
+import datawave.webservice.query.exception.QueryException;
+import datawave.webservice.result.BaseQueryResponse;
+import datawave.webservice.result.GenericResponse;
+import datawave.webservice.result.QueryImplListResponse;
+import datawave.webservice.result.QueryLogicResponse;
+import datawave.webservice.result.VoidResponse;
+import io.swagger.v3.oas.annotations.ExternalDocumentation;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.Parameters;
+import io.swagger.v3.oas.annotations.enums.ParameterIn;
+import io.swagger.v3.oas.annotations.headers.Header;
+import io.swagger.v3.oas.annotations.media.ArraySchema;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.responses.ApiResponses;
+import io.swagger.v3.oas.annotations.tags.Tag;
+
+@Tag(name = "Query Controller /v1", description = "DataWave Query Management",
+ externalDocs = @ExternalDocumentation(description = "Query Service Documentation",
+ url = "https://github.com/NationalSecurityAgency/datawave-query-service"))
+@RestController
+@RequestMapping(path = "/v1/query", produces = MediaType.APPLICATION_JSON_VALUE)
+public class QueryController {
+ private final QueryManagementService queryManagementService;
+ private final LookupService lookupService;
+ private final StreamingService streamingService;
+ private final TranslateIdService translateIdService;
+
+ private final StreamingProperties streamingProperties;
+
+ private final Supplier serverUserDetailsSupplier;
+
+ // Note: baseMethodStatsContext needs to be request scoped
+ private final BaseMethodStatsFilter.BaseMethodStatsContext baseMethodStatsContext;
+ // Note: queryMetricsEnrichmentContest needs to be request scoped
+ private final QueryMetricsEnrichmentFilterAdvice.QueryMetricsEnrichmentContext queryMetricsEnrichmentContext;
+
+ public QueryController(QueryManagementService queryManagementService, LookupService lookupService, StreamingService streamingService,
+ TranslateIdService translateIdService, StreamingProperties streamingProperties,
+ @Qualifier("serverUserDetailsSupplier") Supplier serverUserDetailsSupplier,
+ BaseMethodStatsFilter.BaseMethodStatsContext baseMethodStatsContext,
+ QueryMetricsEnrichmentFilterAdvice.QueryMetricsEnrichmentContext queryMetricsEnrichmentContext) {
+ this.queryManagementService = queryManagementService;
+ this.lookupService = lookupService;
+ this.streamingService = streamingService;
+ this.translateIdService = translateIdService;
+ this.streamingProperties = streamingProperties;
+ this.serverUserDetailsSupplier = serverUserDetailsSupplier;
+ this.baseMethodStatsContext = baseMethodStatsContext;
+ this.queryMetricsEnrichmentContext = queryMetricsEnrichmentContext;
+ }
+
+ /**
+ * @see QueryManagementService#define(String, MultiValueMap, String, DatawaveUserDetails)
+ */
+ // @formatter:off
+ @Operation(
+ summary = "Defines a query using the given query logic and parameters.",
+ description = "Defined queries cannot be started and run. " +
+ "Auditing is not performed when defining a query. " +
+ "Updates can be made to any parameter using update. " +
+ "Create a runnable query from a defined query using duplicate, reset, or mapreduce/submit. " +
+ "Delete a defined query using remove. " +
+ "Aside from a limited set of admin actions, only the query owner can act on a defined query.")
+ @ApiResponses({
+ @ApiResponse(
+ description = "if successful, returns a generic response containing the query id",
+ responseCode = "200",
+ content = @Content(schema = @Schema(implementation = GenericResponse.class)),
+ headers = {
+ @Header(
+ name = "Pool",
+ description = "the executor pool to target",
+ schema = @Schema(defaultValue = "default"))}),
+ @ApiResponse(
+ description = "if parameter validation fails " +
+ "if query logic parameter validation fails " +
+ "if security marking validation fails",
+ responseCode = "400",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if the user doesn't have access to the requested query logic",
+ responseCode = "401",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if query storage fails " +
+ "if there is an unknown error",
+ responseCode = "500",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class)))})
+ @Parameters({
+ @Parameter(
+ name = QUERY_BEGIN,
+ in = ParameterIn.QUERY,
+ description = "The query begin date",
+ required = true,
+ schema = @Schema(implementation = String.class),
+ example = "\"19660908 000000.000\""),
+ @Parameter(
+ name = QUERY_END,
+ in = ParameterIn.QUERY,
+ description = "The query end date",
+ required = true,
+ schema = @Schema(implementation = String.class),
+ example = "\"20161002 235959.999\""),
+ @Parameter(
+ name = QUERY_NAME,
+ in = ParameterIn.QUERY,
+ description = "The query name",
+ required = true,
+ schema = @Schema(implementation = String.class),
+ example = "Developer Test Query"),
+ @Parameter(
+ name = QUERY_STRING,
+ in = ParameterIn.QUERY,
+ description = "The query string",
+ required = true,
+ schema = @Schema(implementation = String.class),
+ example = "GENRES:[Action to Western]"),
+ @Parameter(
+ name = QUERY_AUTHORIZATIONS,
+ in = ParameterIn.QUERY,
+ description = "The query auths",
+ required = true,
+ schema = @Schema(implementation = String.class),
+ example = "PUBLIC,PRIVATE,BAR,FOO"),
+ @Parameter(
+ name = QUERY_VISIBILITY,
+ in = ParameterIn.QUERY,
+ description = "The visibility to use when storing metrics for this query",
+ required = true,
+ schema = @Schema(implementation = String.class),
+ example = "PUBLIC"),
+ @Parameter(
+ name = QUERY_SYNTAX,
+ in = ParameterIn.QUERY,
+ description = "The syntax used in the query",
+ schema = @Schema(implementation = String.class),
+ example = "LUCENE"),
+ @Parameter(
+ name = QUERY_MAX_CONCURRENT_TASKS,
+ in = ParameterIn.QUERY,
+ description = "The max number of concurrent tasks to run for this query",
+ schema = @Schema(implementation = Integer.class),
+ example = "10"),
+ @Parameter(
+ name = QUERY_POOL,
+ in = ParameterIn.QUERY,
+ description = "The executor pool to run against",
+ schema = @Schema(implementation = String.class),
+ example = "pool1"),
+ @Parameter(
+ name = QUERY_PAGESIZE,
+ in = ParameterIn.QUERY,
+ description = "The requested page size",
+ schema = @Schema(implementation = Integer.class),
+ example = "10"),
+ @Parameter(
+ name = QUERY_PAGETIMEOUT,
+ in = ParameterIn.QUERY,
+ description = "The call timeout when requesting a page, in minutes",
+ schema = @Schema(implementation = Integer.class),
+ example = "60"),
+ @Parameter(
+ name = QUERY_MAX_RESULTS_OVERRIDE,
+ in = ParameterIn.QUERY,
+ description = "The max results override value",
+ schema = @Schema(implementation = Integer.class),
+ example = "5000"),
+ @Parameter(
+ name = QUERY_PARAMS,
+ in = ParameterIn.QUERY,
+ description = "Additional query parameters",
+ schema = @Schema(implementation = String.class),
+ example = "KEY_1:VALUE_1;KEY_2:VALUE_2")
+ })
+ // @formatter:on
+ @Timed(name = "dw.query.defineQuery", absolute = true)
+ @EnrichQueryMetrics(methodType = EnrichQueryMetrics.MethodType.CREATE)
+ @RequestMapping(path = "{queryLogic}/define", method = {RequestMethod.POST}, produces = {"application/xml", "text/xml", "application/json", "text/yaml",
+ "text/x-yaml", "application/x-yaml", "application/x-protobuf", "application/x-protostuff"})
+ public GenericResponse define(@Parameter(description = "The query logic", example = "EventQuery") @PathVariable String queryLogic,
+ @Parameter(hidden = true) @RequestParam MultiValueMap parameters, @RequestHeader HttpHeaders headers,
+ @AuthenticationPrincipal DatawaveUserDetails currentUser) throws QueryException {
+ return queryManagementService.define(queryLogic, parameters, getPool(headers), currentUser);
+ }
+
+ /**
+ * @see QueryManagementService#listQueryLogic(DatawaveUserDetails)
+ */
+ @Operation(summary = "Gets a list of descriptions for the configured query logics, sorted by query logic name.",
+ description = "The descriptions include things like the audit type, optional and required parameters, required roles, and response class.")
+ @Timed(name = "dw.query.listQueryLogic", absolute = true)
+ @RequestMapping(path = "listQueryLogic", method = {RequestMethod.GET},
+ produces = {"application/xml", "text/xml", "application/json", "text/yaml", "text/x-yaml", "application/x-yaml", "text/html"})
+ public QueryLogicResponse listQueryLogic(@AuthenticationPrincipal DatawaveUserDetails currentUser) {
+ return queryManagementService.listQueryLogic(currentUser);
+ }
+
+ /**
+ * @see QueryManagementService#create(String, MultiValueMap, String, DatawaveUserDetails)
+ */
+ // @formatter:off
+ @Operation(
+ summary = "Creates a query using the given query logic and parameters.",
+ description = "Created queries will start running immediately. " +
+ "Auditing is performed before the query is started. " +
+ "Query results can be retrieved using next. " +
+ "Updates can be made to any parameter which doesn't affect the scope of the query using update. " +
+ "Stop a running query gracefully using close or forcefully using cancel. " +
+ "Stop, and restart a running query using reset. " +
+ "Create a copy of a running query using duplicate. " +
+ "Aside from a limited set of admin actions, only the query owner can act on a running query.")
+ @ApiResponses({
+ @ApiResponse(
+ description = "if successful, returns a generic response containing the query id",
+ responseCode = "200",
+ content = @Content(schema = @Schema(implementation = GenericResponse.class)),
+ headers = {
+ @Header(
+ name = "Pool",
+ description = "the executor pool to target",
+ schema = @Schema(defaultValue = "default"))}),
+ @ApiResponse(
+ description = "if parameter validation fails " +
+ "if query logic parameter validation fails " +
+ "if security marking validation fails " +
+ "if auditing fails",
+ responseCode = "400",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if the user doesn't have access to the requested query logic",
+ responseCode = "401",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if query storage fails " +
+ "if there is an unknown error",
+ responseCode = "500",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class)))})
+ @Parameters({
+ @Parameter(
+ name = QUERY_BEGIN,
+ in = ParameterIn.QUERY,
+ description = "The query begin date",
+ required = true,
+ schema = @Schema(implementation = String.class),
+ example = "\"19660908 000000.000\""),
+ @Parameter(
+ name = QUERY_END,
+ in = ParameterIn.QUERY,
+ description = "The query end date",
+ required = true,
+ schema = @Schema(implementation = String.class),
+ example = "\"20161002 235959.999\""),
+ @Parameter(
+ name = QUERY_NAME,
+ in = ParameterIn.QUERY,
+ description = "The query name",
+ required = true,
+ schema = @Schema(implementation = String.class),
+ example = "Developer Test Query"),
+ @Parameter(
+ name = QUERY_STRING,
+ in = ParameterIn.QUERY,
+ description = "The query string",
+ required = true,
+ schema = @Schema(implementation = String.class),
+ example = "GENRES:[Action to Western]"),
+ @Parameter(
+ name = QUERY_AUTHORIZATIONS,
+ in = ParameterIn.QUERY,
+ description = "The query auths",
+ required = true,
+ schema = @Schema(implementation = String.class),
+ example = "PUBLIC,PRIVATE,BAR,FOO"),
+ @Parameter(
+ name = QUERY_VISIBILITY,
+ in = ParameterIn.QUERY,
+ description = "The visibility to use when storing metrics for this query",
+ required = true,
+ schema = @Schema(implementation = String.class),
+ example = "PUBLIC"),
+ @Parameter(
+ name = QUERY_SYNTAX,
+ in = ParameterIn.QUERY,
+ description = "The syntax used in the query",
+ schema = @Schema(implementation = String.class),
+ example = "LUCENE"),
+ @Parameter(
+ name = QUERY_MAX_CONCURRENT_TASKS,
+ in = ParameterIn.QUERY,
+ description = "The max number of concurrent tasks to run for this query",
+ schema = @Schema(implementation = Integer.class),
+ example = "10"),
+ @Parameter(
+ name = QUERY_POOL,
+ in = ParameterIn.QUERY,
+ description = "The executor pool to run against",
+ schema = @Schema(implementation = String.class),
+ example = "pool1"),
+ @Parameter(
+ name = QUERY_PAGESIZE,
+ in = ParameterIn.QUERY,
+ description = "The requested page size",
+ schema = @Schema(implementation = Integer.class),
+ example = "10"),
+ @Parameter(
+ name = QUERY_PAGETIMEOUT,
+ in = ParameterIn.QUERY,
+ description = "The call timeout when requesting a page, in minutes",
+ schema = @Schema(implementation = Integer.class),
+ example = "60"),
+ @Parameter(
+ name = QUERY_MAX_RESULTS_OVERRIDE,
+ in = ParameterIn.QUERY,
+ description = "The max results override value",
+ schema = @Schema(implementation = Integer.class),
+ example = "5000"),
+ @Parameter(
+ name = QUERY_PARAMS,
+ in = ParameterIn.QUERY,
+ description = "Additional query parameters",
+ schema = @Schema(implementation = String.class),
+ example = "KEY_1:VALUE_1;KEY_2:VALUE_2")
+ })
+ // @formatter:on
+ @Timed(name = "dw.query.createQuery", absolute = true)
+ @EnrichQueryMetrics(methodType = EnrichQueryMetrics.MethodType.CREATE)
+ @RequestMapping(path = "{queryLogic}/create", method = {RequestMethod.POST}, produces = {"application/xml", "text/xml", "application/json", "text/yaml",
+ "text/x-yaml", "application/x-yaml", "application/x-protobuf", "application/x-protostuff"})
+ public GenericResponse create(@Parameter(description = "The query logic", example = "EventQuery") @PathVariable String queryLogic,
+ @Parameter(hidden = true) @RequestParam MultiValueMap parameters, @RequestHeader HttpHeaders headers,
+ @AuthenticationPrincipal DatawaveUserDetails currentUser) throws QueryException {
+ return queryManagementService.create(queryLogic, parameters, getPool(headers), currentUser);
+ }
+
+ /**
+ * @see QueryManagementService#plan(String, MultiValueMap, String, DatawaveUserDetails)
+ */
+ // @formatter:off
+ @Operation(
+ summary = "Generates a query plan using the given query logic and parameters.",
+ description = "Created queries will begin planning immediately. " +
+ "Auditing is performed if we are expanding indices. " +
+ "Query plan will be returned in the response. " +
+ "Updates can be made to any parameter which doesn't affect the scope of the query using update. " +
+ "Stop a running query gracefully using close or forcefully using cancel. " +
+ "Stop, and restart a running query using reset. " +
+ "Create a copy of a running query using duplicate. " +
+ "Aside from a limited set of admin actions, only the query owner can act on a running query.")
+ @ApiResponses({
+ @ApiResponse(
+ description = "if successful, returns a generic response containing the query plan",
+ responseCode = "200",
+ content = @Content(schema = @Schema(implementation = GenericResponse.class)),
+ headers = {
+ @Header(
+ name = "Pool",
+ description = "the executor pool to target",
+ schema = @Schema(defaultValue = "default"))}),
+ @ApiResponse(
+ description = "if parameter validation fails " +
+ "if query logic parameter validation fails " +
+ "if security marking validation fails " +
+ "if auditing fails",
+ responseCode = "400",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if the user doesn't have access to the requested query logic",
+ responseCode = "401",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if query storage fails " +
+ "if there is an unknown error",
+ responseCode = "500",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class)))})
+ @Parameters({
+ @Parameter(
+ name = QUERY_BEGIN,
+ in = ParameterIn.QUERY,
+ description = "The query begin date",
+ required = true,
+ schema = @Schema(implementation = String.class),
+ example = "\"19660908 000000.000\""),
+ @Parameter(
+ name = QUERY_END,
+ in = ParameterIn.QUERY,
+ description = "The query end date",
+ required = true,
+ schema = @Schema(implementation = String.class),
+ example = "\"20161002 235959.999\""),
+ @Parameter(
+ name = QUERY_NAME,
+ in = ParameterIn.QUERY,
+ description = "The query name",
+ required = true,
+ schema = @Schema(implementation = String.class),
+ example = "Developer Test Query"),
+ @Parameter(
+ name = QUERY_STRING,
+ in = ParameterIn.QUERY,
+ description = "The query string",
+ required = true,
+ schema = @Schema(implementation = String.class),
+ example = "GENRES:[Action to Western]"),
+ @Parameter(
+ name = QUERY_AUTHORIZATIONS,
+ in = ParameterIn.QUERY,
+ description = "The query auths",
+ required = true,
+ schema = @Schema(implementation = String.class),
+ example = "PUBLIC,PRIVATE,BAR,FOO"),
+ @Parameter(
+ name = QUERY_VISIBILITY,
+ in = ParameterIn.QUERY,
+ description = "The visibility to use when storing metrics for this query",
+ required = true,
+ schema = @Schema(implementation = String.class),
+ example = "PUBLIC"),
+ @Parameter(
+ name = QUERY_PLAN_EXPAND_FIELDS,
+ in = ParameterIn.QUERY,
+ description = "Whether to expand unfielded terms",
+ schema = @Schema(implementation = Boolean.class),
+ example = "true"
+ ),
+ @Parameter(
+ name = QUERY_PLAN_EXPAND_VALUES,
+ in = ParameterIn.QUERY,
+ description = "Whether to expand regex and/or ranges into discrete values " +
+ "If 'true', auditing will be performed",
+ schema = @Schema(implementation = Boolean.class),
+ example = "true"
+ ),
+ @Parameter(
+ name = QUERY_SYNTAX,
+ in = ParameterIn.QUERY,
+ description = "The syntax used in the query",
+ schema = @Schema(implementation = String.class),
+ example = "LUCENE"),
+ @Parameter(
+ name = QUERY_MAX_CONCURRENT_TASKS,
+ in = ParameterIn.QUERY,
+ description = "The max number of concurrent tasks to run for this query",
+ schema = @Schema(implementation = Integer.class),
+ example = "10"),
+ @Parameter(
+ name = QUERY_POOL,
+ in = ParameterIn.QUERY,
+ description = "The executor pool to run against",
+ schema = @Schema(implementation = String.class),
+ example = "pool1"),
+ @Parameter(
+ name = QUERY_PAGESIZE,
+ in = ParameterIn.QUERY,
+ description = "The requested page size",
+ schema = @Schema(implementation = Integer.class),
+ example = "10"),
+ @Parameter(
+ name = QUERY_PAGETIMEOUT,
+ in = ParameterIn.QUERY,
+ description = "The call timeout when requesting a page, in minutes",
+ schema = @Schema(implementation = Integer.class),
+ example = "60"),
+ @Parameter(
+ name = QUERY_MAX_RESULTS_OVERRIDE,
+ in = ParameterIn.QUERY,
+ description = "The max results override value",
+ schema = @Schema(implementation = Integer.class),
+ example = "5000"),
+ @Parameter(
+ name = QUERY_PARAMS,
+ in = ParameterIn.QUERY,
+ description = "Additional query parameters",
+ schema = @Schema(implementation = String.class),
+ example = "KEY_1:VALUE_1;KEY_2:VALUE_2")
+ })
+ // @formatter:on
+ @Timed(name = "dw.query.planQuery", absolute = true)
+ @RequestMapping(path = "{queryLogic}/plan", method = {RequestMethod.POST}, produces = {"application/xml", "text/xml", "application/json", "text/yaml",
+ "text/x-yaml", "application/x-yaml", "application/x-protobuf", "application/x-protostuff"})
+ public GenericResponse plan(@Parameter(description = "The query logic", example = "EventQuery") @PathVariable String queryLogic,
+ @Parameter(hidden = true) @RequestParam MultiValueMap parameters, @RequestHeader HttpHeaders headers,
+ @AuthenticationPrincipal DatawaveUserDetails currentUser) throws QueryException {
+ return queryManagementService.plan(queryLogic, parameters, getPool(headers), currentUser);
+ }
+
+ /**
+ * @see QueryManagementService#predict(String, MultiValueMap, String, DatawaveUserDetails)
+ */
+ // @formatter:off
+ @Operation(
+ summary = "Generates a query prediction using the given query logic and parameters.",
+ description = "Created queries will begin predicting immediately. " +
+ "Auditing is not performed. " +
+ "Query prediction will be returned in the response. " +
+ "Updates can be made to any parameter which doesn't affect the scope of the query using update. ")
+ @ApiResponses({
+ @ApiResponse(
+ description = "if successful, returns a generic response containing the query plan",
+ responseCode = "200",
+ content = @Content(schema = @Schema(implementation = GenericResponse.class)),
+ headers = {
+ @Header(
+ name = "Pool",
+ description = "the executor pool to target",
+ schema = @Schema(defaultValue = "default"))}),
+ @ApiResponse(
+ description = "if parameter validation fails " +
+ "if query logic parameter validation fails " +
+ "if security marking validation fails " +
+ "if auditing fails",
+ responseCode = "400",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if the user doesn't have access to the requested query logic",
+ responseCode = "401",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if query storage fails " +
+ "if there is an unknown error",
+ responseCode = "500",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class)))})
+ @Parameters({
+ @Parameter(
+ name = QUERY_BEGIN,
+ in = ParameterIn.QUERY,
+ description = "The query begin date",
+ required = true,
+ schema = @Schema(implementation = String.class),
+ example = "\"19660908 000000.000\""),
+ @Parameter(
+ name = QUERY_END,
+ in = ParameterIn.QUERY,
+ description = "The query end date",
+ required = true,
+ schema = @Schema(implementation = String.class),
+ example = "\"20161002 235959.999\""),
+ @Parameter(
+ name = QUERY_NAME,
+ in = ParameterIn.QUERY,
+ description = "The query name",
+ required = true,
+ schema = @Schema(implementation = String.class),
+ example = "Developer Test Query"),
+ @Parameter(
+ name = QUERY_STRING,
+ in = ParameterIn.QUERY,
+ description = "The query string",
+ required = true,
+ schema = @Schema(implementation = String.class),
+ example = "GENRES:[Action to Western]"),
+ @Parameter(
+ name = QUERY_AUTHORIZATIONS,
+ in = ParameterIn.QUERY,
+ description = "The query auths",
+ required = true,
+ schema = @Schema(implementation = String.class),
+ example = "PUBLIC,PRIVATE,BAR,FOO"),
+ @Parameter(
+ name = QUERY_VISIBILITY,
+ in = ParameterIn.QUERY,
+ description = "The visibility to use when storing metrics for this query",
+ required = true,
+ schema = @Schema(implementation = String.class),
+ example = "PUBLIC"),
+ @Parameter(
+ name = QUERY_SYNTAX,
+ in = ParameterIn.QUERY,
+ description = "The syntax used in the query",
+ schema = @Schema(implementation = String.class),
+ example = "LUCENE"),
+ @Parameter(
+ name = QUERY_MAX_CONCURRENT_TASKS,
+ in = ParameterIn.QUERY,
+ description = "The max number of concurrent tasks to run for this query",
+ schema = @Schema(implementation = Integer.class),
+ example = "10"),
+ @Parameter(
+ name = QUERY_POOL,
+ in = ParameterIn.QUERY,
+ description = "The executor pool to run against",
+ schema = @Schema(implementation = String.class),
+ example = "pool1"),
+ @Parameter(
+ name = QUERY_PAGESIZE,
+ in = ParameterIn.QUERY,
+ description = "The requested page size",
+ schema = @Schema(implementation = Integer.class),
+ example = "10"),
+ @Parameter(
+ name = QUERY_PAGETIMEOUT,
+ in = ParameterIn.QUERY,
+ description = "The call timeout when requesting a page, in minutes",
+ schema = @Schema(implementation = Integer.class),
+ example = "60"),
+ @Parameter(
+ name = QUERY_MAX_RESULTS_OVERRIDE,
+ in = ParameterIn.QUERY,
+ description = "The max results override value",
+ schema = @Schema(implementation = Integer.class),
+ example = "5000"),
+ @Parameter(
+ name = QUERY_PARAMS,
+ in = ParameterIn.QUERY,
+ description = "Additional query parameters",
+ schema = @Schema(implementation = String.class),
+ example = "KEY_1:VALUE_1;KEY_2:VALUE_2")
+ })
+ // @formatter:on
+ @Timed(name = "dw.query.predictQuery", absolute = true)
+ @RequestMapping(path = "{queryLogic}/predict", method = {RequestMethod.POST}, produces = {"application/xml", "text/xml", "application/json", "text/yaml",
+ "text/x-yaml", "application/x-yaml", "application/x-protobuf", "application/x-protostuff"})
+ public GenericResponse predict(@Parameter(description = "The query logic", example = "EventQuery") @PathVariable String queryLogic,
+ @Parameter(hidden = true) @RequestParam MultiValueMap parameters, @RequestHeader HttpHeaders headers,
+ @AuthenticationPrincipal DatawaveUserDetails currentUser) throws QueryException {
+ return queryManagementService.predict(queryLogic, parameters, getPool(headers), currentUser);
+ }
+
+ /**
+ * @see LookupService#lookupUUID(MultiValueMap, String, DatawaveUserDetails)
+ * @see LookupService#lookupUUID(MultiValueMap, String, DatawaveUserDetails, StreamingResponseListener)
+ */
+ // @formatter:off
+ @Operation(
+ summary = "Creates an event lookup query using the query logic associated with the given uuid type(s) and parameters, and returns the first page of results.",
+ description = "Lookup queries will start running immediately. " +
+ "Auditing is performed before the query is started. " +
+ "Each of the uuid pairs must map to the same query logic. " +
+ "After the first page is returned, the query will be closed.")
+ @ApiResponses({
+ @ApiResponse(
+ description = "if successful, returns a base query response containing the first page of results",
+ responseCode = "200",
+ content = @Content(schema = @Schema(implementation = BaseQueryResponse.class)),
+ headers = {
+ @Header(
+ name = "Pool",
+ description = "the executor pool to target",
+ schema = @Schema(defaultValue = "default"))}),
+ @ApiResponse(
+ description = "if no query results are found",
+ responseCode = "204",
+ content = @Content(schema = @Schema(hidden = true))),
+ @ApiResponse(
+ description = "if parameter validation fails " +
+ "if query logic parameter validation fails " +
+ "if security marking validation fails " +
+ "if auditing fails",
+ responseCode = "400",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if the user doesn't have access to the requested query logic",
+ responseCode = "401",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if query storage fails " +
+ "if the next call times out " +
+ "if the next task is rejected by the executor " +
+ "if there is an unknown error",
+ responseCode = "500",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class)))})
+ @Parameters({
+ @Parameter(
+ name = LOOKUP_STREAMING,
+ in = ParameterIn.QUERY,
+ description = "if true, streams all results back",
+ schema = @Schema(implementation = Boolean.class),
+ example = "true"),
+ @Parameter(
+ name = LOOKUP_CONTEXT,
+ in = ParameterIn.QUERY,
+ description = "The lookup UUID type context",
+ example = "default",
+ array = @ArraySchema(schema = @Schema(implementation = String.class))),
+ @Parameter(
+ name = QUERY_BEGIN,
+ in = ParameterIn.QUERY,
+ description = "The query begin date",
+ required = true,
+ schema = @Schema(implementation = String.class),
+ example = "\"19660908 000000.000\""),
+ @Parameter(
+ name = QUERY_END,
+ in = ParameterIn.QUERY,
+ description = "The query end date",
+ required = true,
+ schema = @Schema(implementation = String.class),
+ example = "\"20161002 235959.999\""),
+ @Parameter(
+ name = QUERY_NAME,
+ in = ParameterIn.QUERY,
+ description = "The query name",
+ required = true,
+ schema = @Schema(implementation = String.class),
+ example = "Developer Test Query"),
+ @Parameter(
+ name = QUERY_AUTHORIZATIONS,
+ in = ParameterIn.QUERY,
+ description = "The query auths",
+ required = true,
+ schema = @Schema(implementation = String.class),
+ example = "PUBLIC,PRIVATE,BAR,FOO"),
+ @Parameter(
+ name = QUERY_VISIBILITY,
+ in = ParameterIn.QUERY,
+ description = "The visibility to use when storing metrics for this query",
+ required = true,
+ schema = @Schema(implementation = String.class),
+ example = "PUBLIC"),
+ @Parameter(
+ name = QUERY_MAX_CONCURRENT_TASKS,
+ in = ParameterIn.QUERY,
+ description = "The max number of concurrent tasks to run for this query",
+ schema = @Schema(implementation = Integer.class),
+ example = "10"),
+ @Parameter(
+ name = QUERY_POOL,
+ in = ParameterIn.QUERY,
+ description = "The executor pool to run against",
+ schema = @Schema(implementation = String.class),
+ example = "pool1"),
+ @Parameter(
+ name = QUERY_PAGESIZE,
+ in = ParameterIn.QUERY,
+ description = "The requested page size",
+ schema = @Schema(implementation = Integer.class),
+ example = "10"),
+ @Parameter(
+ name = QUERY_PAGETIMEOUT,
+ in = ParameterIn.QUERY,
+ description = "The call timeout when requesting a page, in minutes",
+ schema = @Schema(implementation = Integer.class),
+ example = "60"),
+ @Parameter(
+ name = QUERY_MAX_RESULTS_OVERRIDE,
+ in = ParameterIn.QUERY,
+ description = "The max results override value",
+ schema = @Schema(implementation = Integer.class),
+ example = "5000"),
+ @Parameter(
+ name = QUERY_PARAMS,
+ in = ParameterIn.QUERY,
+ description = "Additional query parameters",
+ schema = @Schema(implementation = String.class),
+ example = "KEY_1:VALUE_1;KEY_2:VALUE_2")
+ })
+ // @formatter:on
+ @Timed(name = "dw.query.lookupUUID", absolute = true)
+ @RequestMapping(path = "lookupUUID/{uuidType}/{uuid}", method = {RequestMethod.GET}, produces = {"application/xml", "text/xml", "application/json",
+ "text/yaml", "text/x-yaml", "application/x-yaml", "application/x-protobuf", "application/x-protostuff"})
+ public Object lookupUUID(@Parameter(description = "The UUID type", example = "PAGE_TITLE") @PathVariable(required = false) String uuidType,
+ @Parameter(description = "The UUID", example = "anarchism") @PathVariable(required = false) String uuid,
+ @Parameter(hidden = true) @RequestParam MultiValueMap parameters, @RequestHeader HttpHeaders headers,
+ @AuthenticationPrincipal DatawaveUserDetails currentUser) throws QueryException {
+ parameters.add(LOOKUP_UUID_PAIRS, String.join(LOOKUP_KEY_VALUE_DELIMITER, uuidType, uuid));
+
+ if (Boolean.parseBoolean(parameters.getFirst(LOOKUP_STREAMING))) {
+ MediaType contentType = determineContentType(headers.getAccept(), MediaType.parseMediaType(streamingProperties.getDefaultContentType()));
+ CountingResponseBodyEmitter emitter = baseMethodStatsContext.createCountingResponseBodyEmitter(streamingProperties.getCallTimeoutMillis());
+ lookupService.lookupUUID(parameters, getPool(headers), currentUser, new CountingResponseBodyEmitterListener(emitter, contentType));
+ return emitter;
+ } else {
+ return lookupService.lookupUUID(parameters, getPool(headers), currentUser);
+ }
+ }
+
+ /**
+ * @see LookupService#lookupUUID(MultiValueMap, String, DatawaveUserDetails)
+ * @see LookupService#lookupUUID(MultiValueMap, String, DatawaveUserDetails, StreamingResponseListener)
+ */
+ // @formatter:off
+ @Operation(
+ summary = "Creates an event lookup query using the query logic associated with the given uuid type(s) and parameters, and returns the first page of results.",
+ description = "Lookup queries will start running immediately. " +
+ "Auditing is performed before the query is started. " +
+ "Each of the uuid pairs must map to the same query logic. " +
+ "After the first page is returned, the query will be closed.")
+ @ApiResponses({
+ @ApiResponse(
+ description = "if successful, returns a base query response containing the first page of results",
+ responseCode = "200",
+ content = @Content(schema = @Schema(implementation = BaseQueryResponse.class)),
+ headers = {
+ @Header(
+ name = "Pool",
+ description = "the executor pool to target",
+ schema = @Schema(defaultValue = "default"))}),
+ @ApiResponse(
+ description = "if no query results are found",
+ responseCode = "204",
+ content = @Content(schema = @Schema(hidden = true))),
+ @ApiResponse(
+ description = "if parameter validation fails " +
+ "if query logic parameter validation fails " +
+ "if security marking validation fails " +
+ "if auditing fails",
+ responseCode = "400",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if the user doesn't have access to the requested query logic",
+ responseCode = "401",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if query storage fails " +
+ "if the next call times out " +
+ "if the next task is rejected by the executor " +
+ "if there is an unknown error",
+ responseCode = "500",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class)))})
+ @Parameters({
+ @Parameter(
+ name = LOOKUP_STREAMING,
+ in = ParameterIn.QUERY,
+ description = "if true, streams all results back",
+ schema = @Schema(implementation = Boolean.class),
+ example = "true"),
+ @Parameter(
+ name = LOOKUP_CONTEXT,
+ in = ParameterIn.QUERY,
+ description = "The lookup UUID type context",
+ example = "default",
+ array = @ArraySchema(schema = @Schema(implementation = String.class))),
+ @Parameter(
+ name = LOOKUP_UUID_PAIRS,
+ in = ParameterIn.QUERY,
+ description = "The lookup UUID pairs " +
+ "To lookup multiple UUID pairs, submit multiples of this parameter",
+ required = true,
+ example = "PAGE_TITLE:anarchism",
+ array = @ArraySchema(schema = @Schema(implementation = String.class))),
+ @Parameter(
+ name = QUERY_BEGIN,
+ in = ParameterIn.QUERY,
+ description = "The query begin date",
+ required = true,
+ schema = @Schema(implementation = String.class),
+ example = "\"19660908 000000.000\""),
+ @Parameter(
+ name = QUERY_END,
+ in = ParameterIn.QUERY,
+ description = "The query end date",
+ required = true,
+ schema = @Schema(implementation = String.class),
+ example = "\"20161002 235959.999\""),
+ @Parameter(
+ name = QUERY_NAME,
+ in = ParameterIn.QUERY,
+ description = "The query name",
+ required = true,
+ schema = @Schema(implementation = String.class),
+ example = "Developer Test Query"),
+ @Parameter(
+ name = QUERY_AUTHORIZATIONS,
+ in = ParameterIn.QUERY,
+ description = "The query auths",
+ required = true,
+ schema = @Schema(implementation = String.class),
+ example = "PUBLIC,PRIVATE,BAR,FOO"),
+ @Parameter(
+ name = QUERY_VISIBILITY,
+ in = ParameterIn.QUERY,
+ description = "The visibility to use when storing metrics for this query",
+ required = true,
+ schema = @Schema(implementation = String.class),
+ example = "PUBLIC"),
+ @Parameter(
+ name = QUERY_MAX_CONCURRENT_TASKS,
+ in = ParameterIn.QUERY,
+ description = "The max number of concurrent tasks to run for this query",
+ schema = @Schema(implementation = Integer.class),
+ example = "10"),
+ @Parameter(
+ name = QUERY_POOL,
+ in = ParameterIn.QUERY,
+ description = "The executor pool to run against",
+ schema = @Schema(implementation = String.class),
+ example = "pool1"),
+ @Parameter(
+ name = QUERY_PAGESIZE,
+ in = ParameterIn.QUERY,
+ description = "The requested page size",
+ schema = @Schema(implementation = Integer.class),
+ example = "10"),
+ @Parameter(
+ name = QUERY_PAGETIMEOUT,
+ in = ParameterIn.QUERY,
+ description = "The call timeout when requesting a page, in minutes",
+ schema = @Schema(implementation = Integer.class),
+ example = "60"),
+ @Parameter(
+ name = QUERY_MAX_RESULTS_OVERRIDE,
+ in = ParameterIn.QUERY,
+ description = "The max results override value",
+ schema = @Schema(implementation = Integer.class),
+ example = "5000"),
+ @Parameter(
+ name = QUERY_PARAMS,
+ in = ParameterIn.QUERY,
+ description = "Additional query parameters",
+ schema = @Schema(implementation = String.class),
+ example = "KEY_1:VALUE_1;KEY_2:VALUE_2")
+ })
+ // @formatter:on
+ @Timed(name = "dw.query.lookupUUIDBatch", absolute = true)
+ @RequestMapping(path = "lookupUUID", method = {RequestMethod.POST}, produces = {"application/xml", "text/xml", "application/json", "text/yaml",
+ "text/x-yaml", "application/x-yaml", "application/x-protobuf", "application/x-protostuff"})
+ public Object lookupUUIDBatch(@Parameter(hidden = true) @RequestParam MultiValueMap parameters, @RequestHeader HttpHeaders headers,
+ @AuthenticationPrincipal DatawaveUserDetails currentUser) throws QueryException {
+ if (Boolean.parseBoolean(parameters.getFirst(LOOKUP_STREAMING))) {
+ MediaType contentType = determineContentType(headers.getAccept(), MediaType.parseMediaType(streamingProperties.getDefaultContentType()));
+ CountingResponseBodyEmitter emitter = baseMethodStatsContext.createCountingResponseBodyEmitter(streamingProperties.getCallTimeoutMillis());
+ lookupService.lookupUUID(parameters, getPool(headers), currentUser, new CountingResponseBodyEmitterListener(emitter, contentType));
+ return emitter;
+ } else {
+ return lookupService.lookupUUID(parameters, getPool(headers), currentUser);
+ }
+ }
+
+ /**
+ * @see LookupService#lookupContentUUID(MultiValueMap, String, DatawaveUserDetails)
+ * @see LookupService#lookupContentUUID(MultiValueMap, String, DatawaveUserDetails, StreamingResponseListener)
+ */
+ // @formatter:off
+ @Operation(
+ summary = "Creates a content lookup query using the query logic associated with the given uuid type(s) and parameters, and returns the first page of results.",
+ description = "Lookup queries will start running immediately. " +
+ "Auditing is performed before the query is started. " +
+ "Each of the uuid pairs must map to the same query logic. " +
+ "After the first page is returned, the query will be closed.")
+ @ApiResponses({
+ @ApiResponse(
+ description = "if successful, returns a base query response containing the first page of results",
+ responseCode = "200",
+ content = @Content(schema = @Schema(implementation = BaseQueryResponse.class)),
+ headers = {
+ @Header(
+ name = "Pool",
+ description = "the executor pool to target",
+ schema = @Schema(defaultValue = "default"))}),
+ @ApiResponse(
+ description = "if no query results are found",
+ responseCode = "204",
+ content = @Content(schema = @Schema(hidden = true))),
+ @ApiResponse(
+ description = "if parameter validation fails " +
+ "if query logic parameter validation fails " +
+ "if security marking validation fails " +
+ "if auditing fails",
+ responseCode = "400",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if the user doesn't have access to the requested query logic",
+ responseCode = "401",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if query storage fails " +
+ "if the next call times out " +
+ "if the next task is rejected by the executor " +
+ "if there is an unknown error",
+ responseCode = "500",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class)))})
+ @Parameters({
+ @Parameter(
+ name = LOOKUP_STREAMING,
+ in = ParameterIn.QUERY,
+ description = "if true, streams all results back",
+ schema = @Schema(implementation = Boolean.class),
+ example = "true"),
+ @Parameter(
+ name = LOOKUP_CONTEXT,
+ in = ParameterIn.QUERY,
+ description = "The lookup UUID type context",
+ example = "default",
+ array = @ArraySchema(schema = @Schema(implementation = String.class))),
+ @Parameter(
+ name = QUERY_BEGIN,
+ in = ParameterIn.QUERY,
+ description = "The query begin date",
+ required = true,
+ schema = @Schema(implementation = String.class),
+ example = "\"19660908 000000.000\""),
+ @Parameter(
+ name = QUERY_END,
+ in = ParameterIn.QUERY,
+ description = "The query end date",
+ required = true,
+ schema = @Schema(implementation = String.class),
+ example = "\"20161002 235959.999\""),
+ @Parameter(
+ name = QUERY_NAME,
+ in = ParameterIn.QUERY,
+ description = "The query name",
+ required = true,
+ schema = @Schema(implementation = String.class),
+ example = "Developer Test Query"),
+ @Parameter(
+ name = QUERY_AUTHORIZATIONS,
+ in = ParameterIn.QUERY,
+ description = "The query auths",
+ required = true,
+ schema = @Schema(implementation = String.class),
+ example = "PUBLIC,PRIVATE,BAR,FOO"),
+ @Parameter(
+ name = QUERY_VISIBILITY,
+ in = ParameterIn.QUERY,
+ description = "The visibility to use when storing metrics for this query",
+ required = true,
+ schema = @Schema(implementation = String.class),
+ example = "PUBLIC"),
+ @Parameter(
+ name = QUERY_MAX_CONCURRENT_TASKS,
+ in = ParameterIn.QUERY,
+ description = "The max number of concurrent tasks to run for this query",
+ schema = @Schema(implementation = Integer.class),
+ example = "10"),
+ @Parameter(
+ name = QUERY_POOL,
+ in = ParameterIn.QUERY,
+ description = "The executor pool to run against",
+ schema = @Schema(implementation = String.class),
+ example = "pool1"),
+ @Parameter(
+ name = QUERY_PAGESIZE,
+ in = ParameterIn.QUERY,
+ description = "The requested page size",
+ schema = @Schema(implementation = Integer.class),
+ example = "10"),
+ @Parameter(
+ name = QUERY_PAGETIMEOUT,
+ in = ParameterIn.QUERY,
+ description = "The call timeout when requesting a page, in minutes",
+ schema = @Schema(implementation = Integer.class),
+ example = "60"),
+ @Parameter(
+ name = QUERY_MAX_RESULTS_OVERRIDE,
+ in = ParameterIn.QUERY,
+ description = "The max results override value",
+ schema = @Schema(implementation = Integer.class),
+ example = "5000"),
+ @Parameter(
+ name = QUERY_PARAMS,
+ in = ParameterIn.QUERY,
+ description = "Additional query parameters",
+ schema = @Schema(implementation = String.class),
+ example = "KEY_1:VALUE_1;KEY_2:VALUE_2")
+ })
+ // @formatter:on
+ @Timed(name = "dw.query.lookupContentUUID", absolute = true)
+ @RequestMapping(path = "lookupContentUUID/{uuidType}/{uuid}", method = {RequestMethod.GET}, produces = {"application/xml", "text/xml", "application/json",
+ "text/yaml", "text/x-yaml", "application/x-yaml", "application/x-protobuf", "application/x-protostuff"})
+ public Object lookupContentUUID(@Parameter(description = "The UUID type", example = "PAGE_TITLE") @PathVariable(required = false) String uuidType,
+ @Parameter(description = "The UUID", example = "anarchism") @PathVariable(required = false) String uuid,
+ @Parameter(hidden = true) @RequestParam MultiValueMap parameters, @RequestHeader HttpHeaders headers,
+ @AuthenticationPrincipal DatawaveUserDetails currentUser) throws QueryException {
+ parameters.add(LOOKUP_UUID_PAIRS, String.join(LOOKUP_KEY_VALUE_DELIMITER, uuidType, uuid));
+
+ if (Boolean.parseBoolean(parameters.getFirst(LOOKUP_STREAMING))) {
+ MediaType contentType = determineContentType(headers.getAccept(), MediaType.parseMediaType(streamingProperties.getDefaultContentType()));
+ CountingResponseBodyEmitter emitter = baseMethodStatsContext.createCountingResponseBodyEmitter(streamingProperties.getCallTimeoutMillis());
+ lookupService.lookupContentUUID(parameters, getPool(headers), currentUser, new CountingResponseBodyEmitterListener(emitter, contentType));
+ return emitter;
+ } else {
+ return lookupService.lookupContentUUID(parameters, getPool(headers), currentUser);
+ }
+ }
+
+ /**
+ * @see LookupService#lookupContentUUID(MultiValueMap, String, DatawaveUserDetails)
+ * @see LookupService#lookupContentUUID(MultiValueMap, String, DatawaveUserDetails)
+ */
+ // @formatter:off
+ @Operation(
+ summary = "Creates a batch content lookup query using the query logic associated with the given uuid type(s) and parameters, and returns the first page of results.",
+ description = "Lookup queries will start running immediately. " +
+ "Auditing is performed before the query is started. " +
+ "Each of the uuid pairs must map to the same query logic. " +
+ "After the first page is returned, the query will be closed.")
+ @ApiResponses({
+ @ApiResponse(
+ description = "if successful, returns a base query response containing the first page of results",
+ responseCode = "200",
+ content = @Content(schema = @Schema(implementation = BaseQueryResponse.class)),
+ headers = {
+ @Header(
+ name = "Pool",
+ description = "the executor pool to target",
+ schema = @Schema(defaultValue = "default"))}),
+ @ApiResponse(
+ description = "if no query results are found",
+ responseCode = "204",
+ content = @Content(schema = @Schema(hidden = true))),
+ @ApiResponse(
+ description = "if parameter validation fails " +
+ "if query logic parameter validation fails " +
+ "if security marking validation fails " +
+ "if auditing fails",
+ responseCode = "400",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if the user doesn't have access to the requested query logic",
+ responseCode = "401",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if query storage fails " +
+ "if the next call times out " +
+ "if the next task is rejected by the executor " +
+ "if there is an unknown error",
+ responseCode = "500",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class)))})
+ @Parameters({
+ @Parameter(
+ name = LOOKUP_STREAMING,
+ in = ParameterIn.QUERY,
+ description = "if true, streams all results back",
+ schema = @Schema(implementation = Boolean.class),
+ example = "true"),
+ @Parameter(
+ name = LOOKUP_CONTEXT,
+ in = ParameterIn.QUERY,
+ description = "The lookup UUID type context",
+ example = "default",
+ array = @ArraySchema(schema = @Schema(implementation = String.class))),
+ @Parameter(
+ name = LOOKUP_UUID_PAIRS,
+ in = ParameterIn.QUERY,
+ description = "The lookup UUID pairs " +
+ "To lookup multiple UUID pairs, submit multiples of this parameter",
+ required = true,
+ example = "PAGE_TITLE:anarchism",
+ array = @ArraySchema(schema = @Schema(implementation = String.class))),
+ @Parameter(
+ name = QUERY_BEGIN,
+ in = ParameterIn.QUERY,
+ description = "The query begin date",
+ required = true,
+ schema = @Schema(implementation = String.class),
+ example = "\"19660908 000000.000\""),
+ @Parameter(
+ name = QUERY_END,
+ in = ParameterIn.QUERY,
+ description = "The query end date",
+ required = true,
+ schema = @Schema(implementation = String.class),
+ example = "\"20161002 235959.999\""),
+ @Parameter(
+ name = QUERY_NAME,
+ in = ParameterIn.QUERY,
+ description = "The query name",
+ required = true,
+ schema = @Schema(implementation = String.class),
+ example = "Developer Test Query"),
+ @Parameter(
+ name = QUERY_AUTHORIZATIONS,
+ in = ParameterIn.QUERY,
+ description = "The query auths",
+ required = true,
+ schema = @Schema(implementation = String.class),
+ example = "PUBLIC,PRIVATE,BAR,FOO"),
+ @Parameter(
+ name = QUERY_VISIBILITY,
+ in = ParameterIn.QUERY,
+ description = "The visibility to use when storing metrics for this query",
+ required = true,
+ schema = @Schema(implementation = String.class),
+ example = "PUBLIC"),
+ @Parameter(
+ name = QUERY_MAX_CONCURRENT_TASKS,
+ in = ParameterIn.QUERY,
+ description = "The max number of concurrent tasks to run for this query",
+ schema = @Schema(implementation = Integer.class),
+ example = "10"),
+ @Parameter(
+ name = QUERY_POOL,
+ in = ParameterIn.QUERY,
+ description = "The executor pool to run against",
+ schema = @Schema(implementation = String.class),
+ example = "pool1"),
+ @Parameter(
+ name = QUERY_PAGESIZE,
+ in = ParameterIn.QUERY,
+ description = "The requested page size",
+ schema = @Schema(implementation = Integer.class),
+ example = "10"),
+ @Parameter(
+ name = QUERY_PAGETIMEOUT,
+ in = ParameterIn.QUERY,
+ description = "The call timeout when requesting a page, in minutes",
+ schema = @Schema(implementation = Integer.class),
+ example = "60"),
+ @Parameter(
+ name = QUERY_MAX_RESULTS_OVERRIDE,
+ in = ParameterIn.QUERY,
+ description = "The max results override value",
+ schema = @Schema(implementation = Integer.class),
+ example = "5000"),
+ @Parameter(
+ name = QUERY_PARAMS,
+ in = ParameterIn.QUERY,
+ description = "Additional query parameters",
+ schema = @Schema(implementation = String.class),
+ example = "KEY_1:VALUE_1;KEY_2:VALUE_2")
+ })
+ // @formatter:on
+ @Timed(name = "dw.query.lookupContentUUIDBatch", absolute = true)
+ @RequestMapping(path = "lookupContentUUID", method = {RequestMethod.POST}, produces = {"application/xml", "text/xml", "application/json", "text/yaml",
+ "text/x-yaml", "application/x-yaml", "application/x-protobuf", "application/x-protostuff"})
+ public Object lookupContentUUIDBatch(@Parameter(hidden = true) @RequestParam MultiValueMap parameters, @RequestHeader HttpHeaders headers,
+ @AuthenticationPrincipal DatawaveUserDetails currentUser) throws QueryException {
+ if (Boolean.parseBoolean(parameters.getFirst(LOOKUP_STREAMING))) {
+ MediaType contentType = determineContentType(headers.getAccept(), MediaType.parseMediaType(streamingProperties.getDefaultContentType()));
+ CountingResponseBodyEmitter emitter = baseMethodStatsContext.createCountingResponseBodyEmitter(streamingProperties.getCallTimeoutMillis());
+ lookupService.lookupContentUUID(parameters, getPool(headers), currentUser, new CountingResponseBodyEmitterListener(emitter, contentType));
+ return emitter;
+ } else {
+ return lookupService.lookupContentUUID(parameters, getPool(headers), currentUser);
+ }
+ }
+
+ /**
+ * @see TranslateIdService#translateId(String, MultiValueMap, String, DatawaveUserDetails)
+ */
+ // @formatter:off
+ @Operation(
+ summary = "Get one or more ID(s), if any, that correspond to the given ID.",
+ description = "This method only returns the first page, so set pagesize appropriately. " +
+ "Since the underlying query is automatically closed, callers are NOT expected to request additional pages or close the query.")
+ @ApiResponses({
+ @ApiResponse(
+ description = "if successful, returns a base query response containing the first page of results",
+ responseCode = "200",
+ content = @Content(schema = @Schema(implementation = BaseQueryResponse.class)),
+ headers = {
+ @Header(
+ name = "Pool",
+ description = "the executor pool to target",
+ schema = @Schema(defaultValue = "default"))}),
+ @ApiResponse(
+ description = "if no query results are found",
+ responseCode = "204",
+ content = @Content(schema = @Schema(hidden = true))),
+ @ApiResponse(
+ description = "if parameter validation fails " +
+ "if query logic parameter validation fails " +
+ "if security marking validation fails " +
+ "if auditing fails",
+ responseCode = "400",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if the user doesn't have access to the requested query logic",
+ responseCode = "401",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if query storage fails " +
+ "if the next call times out " +
+ "if the next task is rejected by the executor " +
+ "if there is an unknown error",
+ responseCode = "500",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class)))})
+ @Parameters({
+ @Parameter(
+ name = QUERY_BEGIN,
+ in = ParameterIn.QUERY,
+ description = "The query begin date",
+ required = true,
+ schema = @Schema(implementation = String.class),
+ example = "\"19660908 000000.000\""),
+ @Parameter(
+ name = QUERY_END,
+ in = ParameterIn.QUERY,
+ description = "The query end date",
+ required = true,
+ schema = @Schema(implementation = String.class),
+ example = "\"20161002 235959.999\""),
+ @Parameter(
+ name = QUERY_NAME,
+ in = ParameterIn.QUERY,
+ description = "The query name",
+ required = true,
+ schema = @Schema(implementation = String.class),
+ example = "Developer Test Query"),
+ @Parameter(
+ name = QUERY_AUTHORIZATIONS,
+ in = ParameterIn.QUERY,
+ description = "The query auths",
+ required = true,
+ schema = @Schema(implementation = String.class),
+ example = "PUBLIC,PRIVATE,BAR,FOO"),
+ @Parameter(
+ name = QUERY_VISIBILITY,
+ in = ParameterIn.QUERY,
+ description = "The visibility to use when storing metrics for this query",
+ required = true,
+ schema = @Schema(implementation = String.class),
+ example = "PUBLIC"),
+ @Parameter(
+ name = QUERY_MAX_CONCURRENT_TASKS,
+ in = ParameterIn.QUERY,
+ description = "The max number of concurrent tasks to run for this query",
+ schema = @Schema(implementation = Integer.class),
+ example = "10"),
+ @Parameter(
+ name = QUERY_POOL,
+ in = ParameterIn.QUERY,
+ description = "The executor pool to run against",
+ schema = @Schema(implementation = String.class),
+ example = "pool1"),
+ @Parameter(
+ name = QUERY_PAGESIZE,
+ in = ParameterIn.QUERY,
+ description = "The requested page size",
+ schema = @Schema(implementation = Integer.class),
+ example = "10"),
+ @Parameter(
+ name = QUERY_PAGETIMEOUT,
+ in = ParameterIn.QUERY,
+ description = "The call timeout when requesting a page, in minutes",
+ schema = @Schema(implementation = Integer.class),
+ example = "60"),
+ @Parameter(
+ name = QUERY_MAX_RESULTS_OVERRIDE,
+ in = ParameterIn.QUERY,
+ description = "The max results override value",
+ schema = @Schema(implementation = Integer.class),
+ example = "5000"),
+ @Parameter(
+ name = QUERY_PARAMS,
+ in = ParameterIn.QUERY,
+ description = "Additional query parameters",
+ schema = @Schema(implementation = String.class),
+ example = "KEY_1:VALUE_1;KEY_2:VALUE_2")
+ })
+ // @formatter:on
+ @RequestMapping(path = "translateId/{id}", method = {RequestMethod.GET}, produces = {"application/xml", "text/xml", "application/json", "text/yaml",
+ "text/x-yaml", "application/x-yaml", "application/x-protobuf", "application/x-protostuff"})
+ public BaseQueryResponse translateId(@Parameter(description = "The ID to translate") @PathVariable String id,
+ @Parameter(hidden = true) @RequestParam MultiValueMap parameters, @RequestHeader HttpHeaders headers,
+ @AuthenticationPrincipal DatawaveUserDetails currentUser) throws QueryException {
+ return translateIdService.translateId(id, parameters, getPool(headers), currentUser);
+ }
+
+ /**
+ * @see TranslateIdService#translateIds(MultiValueMap, String, DatawaveUserDetails)
+ */
+ // @formatter:off
+ @Operation(
+ summary = "Get the ID(s), if any, associated with the specified IDs.",
+ description = "Because the query created by this call may return multiple pages, callers are expected to request additional pages and eventually close the query.")
+ @ApiResponses({
+ @ApiResponse(
+ description = "if successful, returns a base query response containing the first page of results",
+ responseCode = "200",
+ content = @Content(schema = @Schema(implementation = BaseQueryResponse.class)),
+ headers = {
+ @Header(
+ name = "Pool",
+ description = "the executor pool to target",
+ schema = @Schema(defaultValue = "default"))}),
+ @ApiResponse(
+ description = "if no query results are found",
+ responseCode = "204",
+ content = @Content(schema = @Schema(hidden = true))),
+ @ApiResponse(
+ description = "if parameter validation fails " +
+ "if query logic parameter validation fails " +
+ "if security marking validation fails " +
+ "if auditing fails",
+ responseCode = "400",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if the user doesn't have access to the requested query logic",
+ responseCode = "401",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if query storage fails " +
+ "if the next call times out " +
+ "if the next task is rejected by the executor " +
+ "if there is an unknown error",
+ responseCode = "500",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class)))})
+ @Parameters({
+ @Parameter(
+ name = TRANSLATE_ID,
+ in = ParameterIn.QUERY,
+ description = "The IDs to translate",
+ required = true,
+ schema = @Schema(implementation = String.class),
+ example = "\"19660908 000000.000\""),
+ @Parameter(
+ name = QUERY_BEGIN,
+ in = ParameterIn.QUERY,
+ description = "The query begin date",
+ required = true,
+ schema = @Schema(implementation = String.class),
+ example = "\"19660908 000000.000\""),
+ @Parameter(
+ name = QUERY_END,
+ in = ParameterIn.QUERY,
+ description = "The query end date",
+ required = true,
+ schema = @Schema(implementation = String.class),
+ example = "\"20161002 235959.999\""),
+ @Parameter(
+ name = QUERY_NAME,
+ in = ParameterIn.QUERY,
+ description = "The query name",
+ required = true,
+ schema = @Schema(implementation = String.class),
+ example = "Developer Test Query"),
+ @Parameter(
+ name = QUERY_AUTHORIZATIONS,
+ in = ParameterIn.QUERY,
+ description = "The query auths",
+ required = true,
+ schema = @Schema(implementation = String.class),
+ example = "PUBLIC,PRIVATE,BAR,FOO"),
+ @Parameter(
+ name = QUERY_VISIBILITY,
+ in = ParameterIn.QUERY,
+ description = "The visibility to use when storing metrics for this query",
+ required = true,
+ schema = @Schema(implementation = String.class),
+ example = "PUBLIC"),
+ @Parameter(
+ name = QUERY_MAX_CONCURRENT_TASKS,
+ in = ParameterIn.QUERY,
+ description = "The max number of concurrent tasks to run for this query",
+ schema = @Schema(implementation = Integer.class),
+ example = "10"),
+ @Parameter(
+ name = QUERY_POOL,
+ in = ParameterIn.QUERY,
+ description = "The executor pool to run against",
+ schema = @Schema(implementation = String.class),
+ example = "pool1"),
+ @Parameter(
+ name = QUERY_PAGESIZE,
+ in = ParameterIn.QUERY,
+ description = "The requested page size",
+ schema = @Schema(implementation = Integer.class),
+ example = "10"),
+ @Parameter(
+ name = QUERY_PAGETIMEOUT,
+ in = ParameterIn.QUERY,
+ description = "The call timeout when requesting a page, in minutes",
+ schema = @Schema(implementation = Integer.class),
+ example = "60"),
+ @Parameter(
+ name = QUERY_MAX_RESULTS_OVERRIDE,
+ in = ParameterIn.QUERY,
+ description = "The max results override value",
+ schema = @Schema(implementation = Integer.class),
+ example = "5000"),
+ @Parameter(
+ name = QUERY_PARAMS,
+ in = ParameterIn.QUERY,
+ description = "Additional query parameters",
+ schema = @Schema(implementation = String.class),
+ example = "KEY_1:VALUE_1;KEY_2:VALUE_2")
+ })
+ // @formatter:on
+ @RequestMapping(path = "translateIDs", method = {RequestMethod.POST}, produces = {"application/xml", "text/xml", "application/json", "text/yaml",
+ "text/x-yaml", "application/x-yaml", "application/x-protobuf", "application/x-protostuff"})
+ public BaseQueryResponse translateIDs(@Parameter(hidden = true) @RequestParam MultiValueMap parameters, @RequestHeader HttpHeaders headers,
+ @AuthenticationPrincipal DatawaveUserDetails currentUser) throws QueryException {
+ return translateIdService.translateIds(parameters, getPool(headers), currentUser);
+ }
+
+ /**
+ * @see QueryManagementService#createAndNext(String, MultiValueMap, String, DatawaveUserDetails)
+ */
+ // @formatter:off
+ @Operation(
+ summary = "Creates a query using the given query logic and parameters, and returns the first page of results.",
+ description = "Created queries will start running immediately. " +
+ "Auditing is performed before the query is started. " +
+ "Subsequent query results can be retrieved using next. " +
+ "Updates can be made to any parameter which doesn't affect the scope of the query using update. " +
+ "Stop a running query gracefully using close or forcefully using cancel. " +
+ "Stop, and restart a running query using reset. " +
+ "Create a copy of a running query using duplicate. " +
+ "Aside from a limited set of admin actions, only the query owner can act on a running query.")
+ @ApiResponses({
+ @ApiResponse(
+ description = "if successful, returns a base query response containing the first page of results",
+ responseCode = "200",
+ content = @Content(schema = @Schema(implementation = BaseQueryResponse.class)),
+ headers = {
+ @Header(
+ name = "Pool",
+ description = "the executor pool to target",
+ schema = @Schema(defaultValue = "default"))}),
+ @ApiResponse(
+ description = "if no query results are found",
+ responseCode = "204",
+ content = @Content(schema = @Schema(hidden = true))),
+ @ApiResponse(
+ description = "if parameter validation fails " +
+ "if query logic parameter validation fails " +
+ "if security marking validation fails " +
+ "if auditing fails",
+ responseCode = "400",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if the user doesn't have access to the requested query logic",
+ responseCode = "401",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if query storage fails " +
+ "if the next call times out " +
+ "if the next task is rejected by the executor " +
+ "if there is an unknown error",
+ responseCode = "500",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class)))})
+ @Parameters({
+ @Parameter(
+ name = QUERY_BEGIN,
+ in = ParameterIn.QUERY,
+ description = "The query begin date",
+ required = true,
+ schema = @Schema(implementation = String.class),
+ example = "\"19660908 000000.000\""),
+ @Parameter(
+ name = QUERY_END,
+ in = ParameterIn.QUERY,
+ description = "The query end date",
+ required = true,
+ schema = @Schema(implementation = String.class),
+ example = "\"20161002 235959.999\""),
+ @Parameter(
+ name = QUERY_NAME,
+ in = ParameterIn.QUERY,
+ description = "The query name",
+ required = true,
+ schema = @Schema(implementation = String.class),
+ example = "Developer Test Query"),
+ @Parameter(
+ name = QUERY_STRING,
+ in = ParameterIn.QUERY,
+ description = "The query string",
+ required = true,
+ schema = @Schema(implementation = String.class),
+ example = "GENRES:[Action to Western]"),
+ @Parameter(
+ name = QUERY_AUTHORIZATIONS,
+ in = ParameterIn.QUERY,
+ description = "The query auths",
+ required = true,
+ schema = @Schema(implementation = String.class),
+ example = "PUBLIC,PRIVATE,BAR,FOO"),
+ @Parameter(
+ name = QUERY_VISIBILITY,
+ in = ParameterIn.QUERY,
+ description = "The visibility to use when storing metrics for this query",
+ required = true,
+ schema = @Schema(implementation = String.class),
+ example = "PUBLIC"),
+ @Parameter(
+ name = QUERY_SYNTAX,
+ in = ParameterIn.QUERY,
+ description = "The syntax used in the query",
+ schema = @Schema(implementation = String.class),
+ example = "LUCENE"),
+ @Parameter(
+ name = QUERY_MAX_CONCURRENT_TASKS,
+ in = ParameterIn.QUERY,
+ description = "The max number of concurrent tasks to run for this query",
+ schema = @Schema(implementation = Integer.class),
+ example = "10"),
+ @Parameter(
+ name = QUERY_POOL,
+ in = ParameterIn.QUERY,
+ description = "The executor pool to run against",
+ schema = @Schema(implementation = String.class),
+ example = "pool1"),
+ @Parameter(
+ name = QUERY_PAGESIZE,
+ in = ParameterIn.QUERY,
+ description = "The requested page size",
+ schema = @Schema(implementation = Integer.class),
+ example = "10"),
+ @Parameter(
+ name = QUERY_PAGETIMEOUT,
+ in = ParameterIn.QUERY,
+ description = "The call timeout when requesting a page, in minutes",
+ schema = @Schema(implementation = Integer.class),
+ example = "60"),
+ @Parameter(
+ name = QUERY_MAX_RESULTS_OVERRIDE,
+ in = ParameterIn.QUERY,
+ description = "The max results override value",
+ schema = @Schema(implementation = Integer.class),
+ example = "5000"),
+ @Parameter(
+ name = QUERY_PARAMS,
+ in = ParameterIn.QUERY,
+ description = "Additional query parameters",
+ schema = @Schema(implementation = String.class),
+ example = "KEY_1:VALUE_1;KEY_2:VALUE_2")
+ })
+ // @formatter:on
+ @Timed(name = "dw.query.createAndNext", absolute = true)
+ @EnrichQueryMetrics(methodType = EnrichQueryMetrics.MethodType.CREATE_AND_NEXT)
+ @RequestMapping(path = "{queryLogic}/createAndNext", method = {RequestMethod.POST}, produces = {"application/xml", "text/xml", "application/json",
+ "text/yaml", "text/x-yaml", "application/x-yaml", "application/x-protobuf", "application/x-protostuff"})
+ public BaseQueryResponse createAndNext(@Parameter(description = "The query logic", example = "EventQuery") @PathVariable String queryLogic,
+ @Parameter(hidden = true) @RequestParam MultiValueMap parameters, @RequestHeader HttpHeaders headers,
+ @AuthenticationPrincipal DatawaveUserDetails currentUser) throws QueryException {
+ return queryManagementService.createAndNext(queryLogic, parameters, getPool(headers), currentUser);
+ }
+
+ /**
+ * @see QueryManagementService#next(String, DatawaveUserDetails)
+ */
+ // @formatter:off
+ @Operation(
+ summary = "Gets the next page of results for the specified query.",
+ description = "Next can only be called on a running query. " +
+ "If configuration allows, multiple next calls may be run concurrently for a query. " +
+ "Only the query owner can call next on the specified query.")
+ @ApiResponses({
+ @ApiResponse(
+ description = "if successful, returns a base query response containing the next page of results",
+ responseCode = "200",
+ content = @Content(schema = @Schema(implementation = BaseQueryResponse.class))),
+ @ApiResponse(
+ description = "if no query results are found",
+ responseCode = "204",
+ content = @Content(schema = @Schema(hidden = true))),
+ @ApiResponse(
+ description = "if the query is not running",
+ responseCode = "400",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if the user doesn't own the query",
+ responseCode = "401",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if the query cannot be found",
+ responseCode = "404",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if query lock acquisition fails " +
+ "if the next call is interrupted " +
+ "if the query times out " +
+ "if the next task is rejected by the executor " +
+ "if next call execution fails " +
+ "if query logic creation fails " +
+ "if there is an unknown error",
+ responseCode = "500",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class)))})
+ // @formatter:on
+ @Timed(name = "dw.query.next", absolute = true)
+ @EnrichQueryMetrics(methodType = EnrichQueryMetrics.MethodType.NEXT)
+ @RequestMapping(path = "{queryId}/next", method = {RequestMethod.GET}, produces = {"application/xml", "text/xml", "application/json", "text/yaml",
+ "text/x-yaml", "application/x-yaml", "application/x-protobuf", "application/x-protostuff"})
+ public BaseQueryResponse next(@Parameter(description = "The query ID") @PathVariable String queryId,
+ @AuthenticationPrincipal DatawaveUserDetails currentUser) throws QueryException {
+ return queryManagementService.next(queryId, currentUser);
+ }
+
+ /**
+ * @see QueryManagementService#cancel(String, DatawaveUserDetails)
+ */
+ // @formatter:off
+ @Operation(
+ summary = "Cancels the specified query.",
+ description = "Cancel can only be called on a running query, or a query that is in the process of closing. " +
+ "Outstanding next calls will be stopped immediately, but will return partial results if applicable. " +
+ "Aside from admins, only the query owner can cancel the specified query.")
+ @ApiResponses({
+ @ApiResponse(
+ description = "if successful, returns a void response indicating that the query was canceled",
+ responseCode = "200",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if the query is not running",
+ responseCode = "400",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if the user doesn't own the query",
+ responseCode = "401",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if the query cannot be found",
+ responseCode = "404",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if query lock acquisition fails " +
+ "if the cancel call is interrupted " +
+ "if there is an unknown error",
+ responseCode = "500",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class)))})
+ // @formatter:on
+ @Timed(name = "dw.query.cancel", absolute = true)
+ @RequestMapping(path = "{queryId}/cancel", method = {RequestMethod.PUT, RequestMethod.POST}, produces = {"application/xml", "text/xml", "application/json",
+ "text/yaml", "text/x-yaml", "application/x-yaml", "application/x-protobuf", "application/x-protostuff"})
+ public VoidResponse cancel(@Parameter(description = "The query ID") @PathVariable String queryId, @AuthenticationPrincipal DatawaveUserDetails currentUser)
+ throws QueryException {
+ return queryManagementService.cancel(queryId, currentUser);
+ }
+
+ /**
+ * @see QueryManagementService#adminCancel(String, DatawaveUserDetails)
+ */
+ // @formatter:off
+ @Operation(
+ summary = "Cancels the specified query using admin privileges.",
+ description = "Cancel can only be called on a running query, or a query that is in the process of closing. " +
+ "Outstanding next calls will be stopped immediately, but will return partial results if applicable. " +
+ "Only admin users should be allowed to call this method.")
+ @ApiResponses({
+ @ApiResponse(
+ description = "if successful, returns a void response indicating that the query was canceled",
+ responseCode = "200",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if the query is not running",
+ responseCode = "400",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if the user doesn't own the query",
+ responseCode = "401",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if the query cannot be found",
+ responseCode = "404",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if query lock acquisition fails " +
+ "if the cancel call is interrupted " +
+ "if there is an unknown error",
+ responseCode = "500",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class)))})
+ // @formatter:on
+ @Timed(name = "dw.query.adminCancel", absolute = true)
+ @Secured({"Administrator", "JBossAdministrator"})
+ @RequestMapping(path = "{queryId}/adminCancel", method = {RequestMethod.PUT, RequestMethod.POST}, produces = {"application/xml", "text/xml",
+ "application/json", "text/yaml", "text/x-yaml", "application/x-yaml", "application/x-protobuf", "application/x-protostuff"})
+ public VoidResponse adminCancel(@Parameter(description = "The query ID") @PathVariable String queryId,
+ @AuthenticationPrincipal DatawaveUserDetails currentUser) throws QueryException {
+ return queryManagementService.adminCancel(queryId, currentUser);
+ }
+
+ /**
+ * @see QueryManagementService#close(String, DatawaveUserDetails)
+ */
+ // @formatter:off
+ @Operation(
+ summary = "Closes the specified query.",
+ description = "Close can only be called on a running query. " +
+ "Outstanding next calls will be allowed to run until they can return a full page, or they timeout. " +
+ "Aside from admins, only the query owner can close the specified query.")
+ @ApiResponses({
+ @ApiResponse(
+ description = "if successful, returns a void response indicating that the query was closed",
+ responseCode = "200",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if the query is not running",
+ responseCode = "400",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if the user doesn't own the query",
+ responseCode = "401",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if the query cannot be found",
+ responseCode = "404",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if query lock acquisition fails " +
+ "if the close call is interrupted " +
+ "if there is an unknown error",
+ responseCode = "500",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class)))})
+ // @formatter:on
+ @Timed(name = "dw.query.close", absolute = true)
+ @RequestMapping(path = "{queryId}/close", method = {RequestMethod.PUT, RequestMethod.POST}, produces = {"application/xml", "text/xml", "application/json",
+ "text/yaml", "text/x-yaml", "application/x-yaml", "application/x-protobuf", "application/x-protostuff"})
+ public VoidResponse close(@Parameter(description = "The query ID") @PathVariable String queryId, @AuthenticationPrincipal DatawaveUserDetails currentUser)
+ throws QueryException {
+ return queryManagementService.close(queryId, currentUser);
+ }
+
+ /**
+ * @see QueryManagementService#adminClose(String, DatawaveUserDetails)
+ */
+ // @formatter:off
+ @Operation(
+ summary = "Closes the specified query using admin privileges.",
+ description = "Close can only be called on a running query. " +
+ "Outstanding next calls will be allowed to run until they can return a full page, or they timeout. " +
+ "Only admin users should be allowed to call this method.")
+ @ApiResponses({
+ @ApiResponse(
+ description = "if successful, returns a void response indicating that the query was closed",
+ responseCode = "200",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if the query is not running",
+ responseCode = "400",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if the user doesn't own the query",
+ responseCode = "401",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if the query cannot be found",
+ responseCode = "404",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if query lock acquisition fails " +
+ "if the close call is interrupted " +
+ "if there is an unknown error",
+ responseCode = "500",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class)))})
+ // @formatter:on
+ @Timed(name = "dw.query.adminClose", absolute = true)
+ @Secured({"Administrator", "JBossAdministrator"})
+ @RequestMapping(path = "{queryId}/adminClose", method = {RequestMethod.PUT, RequestMethod.POST}, produces = {"application/xml", "text/xml",
+ "application/json", "text/yaml", "text/x-yaml", "application/x-yaml", "application/x-protobuf", "application/x-protostuff"})
+ public VoidResponse adminClose(@Parameter(description = "The query ID") @PathVariable String queryId,
+ @AuthenticationPrincipal DatawaveUserDetails currentUser) throws QueryException {
+ return queryManagementService.adminClose(queryId, currentUser);
+ }
+
+ /**
+ * @see QueryManagementService#reset(String, DatawaveUserDetails)
+ */
+ // @formatter:off
+ @Operation(
+ summary = "Stops, and restarts the specified query.",
+ description = "Reset can be called on any query, whether it's running or not. " +
+ "If the specified query is still running, it will be canceled. See cancel. " +
+ "Reset creates a new, identical query, with a new query id. " +
+ "Reset queries will start running immediately. " +
+ "Auditing is performed before the new query is started.")
+ @ApiResponses({
+ @ApiResponse(
+ description = "if successful, returns a generic response containing the new query id",
+ responseCode = "200",
+ content = @Content(schema = @Schema(implementation = BaseQueryResponse.class))),
+ @ApiResponse(
+ description = "if parameter validation fails " +
+ "if query logic parameter validation fails " +
+ "if security marking validation fails " +
+ "if auditing fails",
+ responseCode = "400",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if the user doesn't own the query " +
+ "if the user doesn't have access to the requested query logic",
+ responseCode = "401",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if the query cannot be found",
+ responseCode = "404",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if query lock acquisition fails " +
+ "if the cancel call is interrupted " +
+ "if query storage fails " +
+ "if there is an unknown error",
+ responseCode = "500",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class)))})
+ // @formatter:on
+ @Timed(name = "dw.query.reset", absolute = true)
+ @RequestMapping(path = "{queryId}/reset", method = {RequestMethod.PUT, RequestMethod.POST}, produces = {"application/xml", "text/xml", "application/json",
+ "text/yaml", "text/x-yaml", "application/x-yaml", "application/x-protobuf", "application/x-protostuff"})
+ public GenericResponse reset(@Parameter(description = "The query ID") @PathVariable String queryId,
+ @AuthenticationPrincipal DatawaveUserDetails currentUser) throws QueryException {
+ return queryManagementService.reset(queryId, currentUser);
+ }
+
+ /**
+ * @see QueryManagementService#remove(String, DatawaveUserDetails)
+ */
+ // @formatter:off
+ @Operation(
+ summary = "Removes the specified query from query storage.",
+ description = "Remove can only be called on a query that is not running. " +
+ "Aside from admins, only the query owner can remove the specified query.")
+ @ApiResponses({
+ @ApiResponse(
+ description = "if successful, returns a void response indicating that the query was removed",
+ responseCode = "200",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if the query is running",
+ responseCode = "400",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if the user doesn't own the query",
+ responseCode = "401",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if the query cannot be found",
+ responseCode = "404",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if there is an unknown error",
+ responseCode = "500",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class)))})
+ // @formatter:on
+ @Timed(name = "dw.query.remove", absolute = true)
+ @RequestMapping(path = "{queryId}/remove", method = {RequestMethod.DELETE}, produces = {"application/xml", "text/xml", "application/json", "text/yaml",
+ "text/x-yaml", "application/x-yaml", "application/x-protobuf", "application/x-protostuff"})
+ public VoidResponse remove(@Parameter(description = "The query ID") @PathVariable String queryId, @AuthenticationPrincipal DatawaveUserDetails currentUser)
+ throws QueryException {
+ return queryManagementService.remove(queryId, currentUser);
+ }
+
+ /**
+ * @see QueryManagementService#adminRemove(String, DatawaveUserDetails)
+ */
+ // @formatter:off
+ @Operation(
+ summary = "Removes the specified query from query storage using admin privileges.",
+ description = "Remove can only be called on a query that is not running. " +
+ "Only admin users should be allowed to call this method.")
+ @ApiResponses({
+ @ApiResponse(
+ description = "if successful, returns a void response indicating that the query was removed",
+ responseCode = "200",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if the query is running",
+ responseCode = "400",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if the query cannot be found",
+ responseCode = "404",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if there is an unknown error",
+ responseCode = "500",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class)))})
+ // @formatter:on
+ @Timed(name = "dw.query.adminRemove", absolute = true)
+ @Secured({"Administrator", "JBossAdministrator"})
+ @RequestMapping(path = "{queryId}/adminRemove", method = {RequestMethod.DELETE}, produces = {"application/xml", "text/xml", "application/json", "text/yaml",
+ "text/x-yaml", "application/x-yaml", "application/x-protobuf", "application/x-protostuff"})
+ public VoidResponse adminRemove(@Parameter(description = "The query ID") @PathVariable String queryId,
+ @AuthenticationPrincipal DatawaveUserDetails currentUser) throws QueryException {
+ return queryManagementService.adminRemove(queryId, currentUser);
+ }
+
+ /**
+ * @see QueryManagementService#update(String, MultiValueMap, DatawaveUserDetails)
+ */
+ // @formatter:off
+ @Operation(
+ summary = "Updates the specified query.",
+ description = "Update can only be called on a defined, or running query. " +
+ "Auditing is not performed when updating a defined query. " +
+ "No auditable parameters should be updated when updating a running query. " +
+ "Any query parameter can be updated for a defined query. " +
+ "Query parameters which don't affect the scope of the query can be updated for a running query. " +
+ "The list of parameters that can be updated for a running query is configurable. " +
+ "Auditable parameters should never be added to the updatable parameters configuration. " +
+ "Query string, date range, query logic, and auths should never be updated for a running query. " +
+ "Only the query owner can call update on the specified query.")
+ @ApiResponses({
+ @ApiResponse(
+ description = "if successful, returns a generic response containing the query id",
+ responseCode = "200",
+ content = @Content(schema = @Schema(implementation = GenericResponse.class))),
+ @ApiResponse(
+ description = "if parameter validation fails " +
+ "if query logic parameter validation fails " +
+ "if security marking validation fails " +
+ "if the query is not defined, or running " +
+ " if no parameters are specified",
+ responseCode = "400",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if the user doesn't own the query " +
+ "if the user doesn't have access to the requested query logic",
+ responseCode = "401",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if the query cannot be found",
+ responseCode = "404",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if query lock acquisition fails " +
+ "if the update call is interrupted " +
+ "if query storage fails " +
+ "if there is an unknown error",
+ responseCode = "500",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class)))})
+ @Parameters({
+ @Parameter(
+ name = QUERY_BEGIN,
+ in = ParameterIn.QUERY,
+ description = "The query begin date",
+ schema = @Schema(implementation = String.class),
+ example = "\"19660908 000000.000\""),
+ @Parameter(
+ name = QUERY_END,
+ in = ParameterIn.QUERY,
+ description = "The query end date",
+ schema = @Schema(implementation = String.class),
+ example = "\"20161002 235959.999\""),
+ @Parameter(
+ name = QUERY_NAME,
+ in = ParameterIn.QUERY,
+ description = "The query name",
+ schema = @Schema(implementation = String.class),
+ example = "Developer Test Query"),
+ @Parameter(
+ name = QUERY_STRING,
+ in = ParameterIn.QUERY,
+ description = "The query string",
+ schema = @Schema(implementation = String.class),
+ example = "GENRES:[Action to Western]"),
+ @Parameter(
+ name = QUERY_AUTHORIZATIONS,
+ in = ParameterIn.QUERY,
+ description = "The query auths",
+ schema = @Schema(implementation = String.class),
+ example = "PUBLIC,PRIVATE,BAR,FOO"),
+ @Parameter(
+ name = QUERY_VISIBILITY,
+ in = ParameterIn.QUERY,
+ description = "The visibility to use when storing metrics for this query",
+ schema = @Schema(implementation = String.class),
+ example = "PUBLIC"),
+ @Parameter(
+ name = QUERY_SYNTAX,
+ in = ParameterIn.QUERY,
+ description = "The syntax used in the query",
+ schema = @Schema(implementation = String.class),
+ example = "LUCENE"),
+ @Parameter(
+ name = QUERY_MAX_CONCURRENT_TASKS,
+ in = ParameterIn.QUERY,
+ description = "The max number of concurrent tasks to run for this query",
+ schema = @Schema(implementation = Integer.class),
+ example = "10"),
+ @Parameter(
+ name = QUERY_POOL,
+ in = ParameterIn.QUERY,
+ description = "The executor pool to run against",
+ schema = @Schema(implementation = String.class),
+ example = "pool1"),
+ @Parameter(
+ name = QUERY_PAGESIZE,
+ in = ParameterIn.QUERY,
+ description = "The requested page size",
+ schema = @Schema(implementation = Integer.class),
+ example = "10"),
+ @Parameter(
+ name = QUERY_PAGETIMEOUT,
+ in = ParameterIn.QUERY,
+ description = "The call timeout when requesting a page, in minutes",
+ schema = @Schema(implementation = Integer.class),
+ example = "60"),
+ @Parameter(
+ name = QUERY_MAX_RESULTS_OVERRIDE,
+ in = ParameterIn.QUERY,
+ description = "The max results override value",
+ schema = @Schema(implementation = Integer.class),
+ example = "5000"),
+ @Parameter(
+ name = QUERY_PARAMS,
+ in = ParameterIn.QUERY,
+ description = "Additional query parameters",
+ schema = @Schema(implementation = String.class),
+ example = "KEY_1:VALUE_1;KEY_2:VALUE_2")
+ })
+ // @formatter:on
+ @Timed(name = "dw.query.update", absolute = true)
+ @RequestMapping(path = "{queryId}/update", method = {RequestMethod.PUT, RequestMethod.POST}, produces = {"application/xml", "text/xml", "application/json",
+ "text/yaml", "text/x-yaml", "application/x-yaml", "application/x-protobuf", "application/x-protostuff"})
+ public GenericResponse update(@Parameter(description = "The query ID") @PathVariable String queryId,
+ @Parameter(hidden = true) @RequestParam MultiValueMap parameters, @AuthenticationPrincipal DatawaveUserDetails currentUser)
+ throws QueryException {
+ return queryManagementService.update(queryId, parameters, currentUser);
+ }
+
+ /**
+ * @see QueryManagementService#duplicate(String, MultiValueMap, DatawaveUserDetails)
+ */
+ // @formatter:off
+ @Operation(
+ summary = "Creates a copy of the specified query.",
+ description = "Duplicate can be called on any query, whether it's running or not. " +
+ "Duplicate creates a new, identical query, with a new query id. " +
+ "Provided parameter updates will be applied to the new query. " +
+ "Duplicated queries will start running immediately. " +
+ "Auditing is performed before the new query is started.")
+ @ApiResponses({
+ @ApiResponse(
+ description = "if successful, returns a generic response containing the query id",
+ responseCode = "200",
+ content = @Content(schema = @Schema(implementation = GenericResponse.class))),
+ @ApiResponse(
+ description = "if parameter validation fails " +
+ "if query logic parameter validation fails " +
+ "if security marking validation fails " +
+ "if auditing fails",
+ responseCode = "400",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if the user doesn't own the query " +
+ "if the user doesn't have access to the requested query logic",
+ responseCode = "401",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if the query cannot be found",
+ responseCode = "404",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if query lock acquisition fails " +
+ "if query storage fails " +
+ "if there is an unknown error",
+ responseCode = "500",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class)))})
+ @Parameters({
+ @Parameter(
+ name = QUERY_BEGIN,
+ in = ParameterIn.QUERY,
+ description = "The query begin date",
+ schema = @Schema(implementation = String.class),
+ example = "\"19660908 000000.000\""),
+ @Parameter(
+ name = QUERY_END,
+ in = ParameterIn.QUERY,
+ description = "The query end date",
+ schema = @Schema(implementation = String.class),
+ example = "\"20161002 235959.999\""),
+ @Parameter(
+ name = QUERY_NAME,
+ in = ParameterIn.QUERY,
+ description = "The query name",
+ schema = @Schema(implementation = String.class),
+ example = "Developer Test Query"),
+ @Parameter(
+ name = QUERY_STRING,
+ in = ParameterIn.QUERY,
+ description = "The query string",
+ schema = @Schema(implementation = String.class),
+ example = "GENRES:[Action to Western]"),
+ @Parameter(
+ name = QUERY_AUTHORIZATIONS,
+ in = ParameterIn.QUERY,
+ description = "The query auths",
+ schema = @Schema(implementation = String.class),
+ example = "PUBLIC,PRIVATE,BAR,FOO"),
+ @Parameter(
+ name = QUERY_VISIBILITY,
+ in = ParameterIn.QUERY,
+ description = "The visibility to use when storing metrics for this query",
+ schema = @Schema(implementation = String.class),
+ example = "PUBLIC"),
+ @Parameter(
+ name = QUERY_SYNTAX,
+ in = ParameterIn.QUERY,
+ description = "The syntax used in the query",
+ schema = @Schema(implementation = String.class),
+ example = "LUCENE"),
+ @Parameter(
+ name = QUERY_MAX_CONCURRENT_TASKS,
+ in = ParameterIn.QUERY,
+ description = "The max number of concurrent tasks to run for this query",
+ schema = @Schema(implementation = Integer.class),
+ example = "10"),
+ @Parameter(
+ name = QUERY_POOL,
+ in = ParameterIn.QUERY,
+ description = "The executor pool to run against",
+ schema = @Schema(implementation = String.class),
+ example = "pool1"),
+ @Parameter(
+ name = QUERY_PAGESIZE,
+ in = ParameterIn.QUERY,
+ description = "The requested page size",
+ schema = @Schema(implementation = Integer.class),
+ example = "10"),
+ @Parameter(
+ name = QUERY_PAGETIMEOUT,
+ in = ParameterIn.QUERY,
+ description = "The call timeout when requesting a page, in minutes",
+ schema = @Schema(implementation = Integer.class),
+ example = "60"),
+ @Parameter(
+ name = QUERY_MAX_RESULTS_OVERRIDE,
+ in = ParameterIn.QUERY,
+ description = "The max results override value",
+ schema = @Schema(implementation = Integer.class),
+ example = "5000"),
+ @Parameter(
+ name = QUERY_PARAMS,
+ in = ParameterIn.QUERY,
+ description = "Additional query parameters",
+ schema = @Schema(implementation = String.class),
+ example = "KEY_1:VALUE_1;KEY_2:VALUE_2")
+ })
+ // @formatter:on
+ @Timed(name = "dw.query.duplicate", absolute = true)
+ @RequestMapping(path = "{queryId}/duplicate", method = {RequestMethod.POST}, produces = {"application/xml", "text/xml", "application/json", "text/yaml",
+ "text/x-yaml", "application/x-yaml", "application/x-protobuf", "application/x-protostuff"})
+ public GenericResponse duplicate(@Parameter(description = "The query ID") @PathVariable String queryId,
+ @Parameter(hidden = true) @RequestParam MultiValueMap parameters, @AuthenticationPrincipal DatawaveUserDetails currentUser)
+ throws QueryException {
+ return queryManagementService.duplicate(queryId, parameters, currentUser);
+ }
+
+ /**
+ * @see QueryManagementService#list(String, String, DatawaveUserDetails)
+ */
+ // @formatter:off
+ @Operation(
+ summary = "Gets a list of queries for the calling user.",
+ description = "Returns all matching queries owned by the calling user, filtering by query id and query name.")
+ @ApiResponses({
+ @ApiResponse(
+ description = "if successful, returns a list response containing the matching queries",
+ responseCode = "200",
+ content = @Content(schema = @Schema(implementation = QueryImplListResponse.class))),
+ @ApiResponse(
+ description = "if there is an unknown error",
+ responseCode = "500",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class)))})
+ // @formatter:on
+ @Timed(name = "dw.query.list", absolute = true)
+ @RequestMapping(path = "list", method = {RequestMethod.GET}, produces = {"text/xml", "application/json", "text/yaml", "text/x-yaml", "application/x-yaml",
+ "application/x-protobuf", "application/x-protostuff"})
+ public QueryImplListResponse list(@Parameter(description = "The query ID") @RequestParam(required = false) String queryId,
+ @Parameter(description = "The query name") @RequestParam(required = false) String queryName,
+ @AuthenticationPrincipal DatawaveUserDetails currentUser) throws QueryException {
+ return queryManagementService.list(queryId, queryName, currentUser);
+ }
+
+ /**
+ * @see QueryManagementService#adminList(String, String, String, DatawaveUserDetails)
+ */
+ // @formatter:off
+ @Operation(
+ summary = "Gets a list of queries for the specified user using admin privileges.",
+ description = "Returns all matching queries owned by any user, filtered by user ID, query ID, and query name. " +
+ "Only admin users should be allowed to call this method.")
+ @ApiResponses({
+ @ApiResponse(
+ description = "if successful, returns a list response containing the matching queries",
+ responseCode = "200",
+ content = @Content(schema = @Schema(implementation = QueryImplListResponse.class))),
+ @ApiResponse(
+ description = "if there is an unknown error",
+ responseCode = "500",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class)))})
+ // @formatter:on
+ @Timed(name = "dw.query.adminList", absolute = true)
+ @Secured({"Administrator", "JBossAdministrator"})
+ @RequestMapping(path = "adminList", method = {RequestMethod.GET}, produces = {"application/xml", "text/xml", "application/json", "text/yaml", "text/x-yaml",
+ "application/x-yaml", "application/x-protobuf", "application/x-protostuff"})
+ public QueryImplListResponse adminList(@Parameter(description = "The query ID") @RequestParam(required = false) String queryId,
+ @Parameter(description = "The user id") @RequestParam(required = false) String user,
+ @Parameter(description = "The query name") @RequestParam(required = false) String queryName,
+ @AuthenticationPrincipal DatawaveUserDetails currentUser) throws QueryException {
+ return queryManagementService.adminList(queryId, queryName, user, currentUser);
+ }
+
+ /**
+ * @see QueryManagementService#list(String, String, DatawaveUserDetails)
+ */
+ // @formatter:off
+ @Operation(
+ summary = "Gets the matching query for the calling user.",
+ description = "Returns all matching queries owned by the calling user, filtering by query id.")
+ @ApiResponses({
+ @ApiResponse(
+ description = "if successful, returns a list response containing the matching query",
+ responseCode = "200",
+ content = @Content(schema = @Schema(implementation = QueryImplListResponse.class))),
+ @ApiResponse(
+ description = "if there is an unknown error",
+ responseCode = "500",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class)))})
+ // @formatter:on
+ @Timed(name = "dw.query.get", absolute = true)
+ @RequestMapping(path = "{queryId}", method = {RequestMethod.GET}, produces = {"application/xml", "text/xml", "application/json", "text/yaml", "text/x-yaml",
+ "application/x-yaml", "application/x-protobuf", "application/x-protostuff"})
+ public QueryImplListResponse get(@Parameter(description = "The query ID") @PathVariable String queryId,
+ @AuthenticationPrincipal DatawaveUserDetails currentUser) throws QueryException {
+ return queryManagementService.list(queryId, null, currentUser);
+ }
+
+ /**
+ * @see QueryManagementService#plan(String, DatawaveUserDetails)
+ */
+ // @formatter:off
+ @Operation(
+ summary = "Gets the plan for the given query for the calling user.")
+ @ApiResponses({
+ @ApiResponse(
+ description = "if successful, returns the query plan for the matching query",
+ responseCode = "200",
+ content = @Content(schema = @Schema(implementation = GenericResponse.class))),
+ @ApiResponse(
+ description = "if the user doesn't own the query",
+ responseCode = "401",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if the query cannot be found",
+ responseCode = "404",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if query lock acquisition fails " +
+ "if query storage fails " +
+ "if there is an unknown error",
+ responseCode = "500",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class)))})
+ // @formatter:on
+ @Timed(name = "dw.query.plan", absolute = true)
+ @RequestMapping(path = "{queryId}/plan", method = {RequestMethod.GET}, produces = {"application/xml", "text/xml", "application/json", "text/yaml",
+ "text/x-yaml", "application/x-yaml", "application/x-protobuf", "application/x-protostuff"})
+ public GenericResponse plan(@Parameter(description = "The query ID") @PathVariable String queryId,
+ @AuthenticationPrincipal DatawaveUserDetails currentUser) throws QueryException {
+ return queryManagementService.plan(queryId, currentUser);
+ }
+
+ /**
+ * @see QueryManagementService#predictions(String, DatawaveUserDetails)
+ */
+ // @formatter:off
+ @Operation(
+ summary = "Gets the predictions for the given query for the calling user.")
+ @ApiResponses({
+ @ApiResponse(
+ description = "if successful, returns the query predictions for the matching query",
+ responseCode = "200",
+ content = @Content(schema = @Schema(implementation = GenericResponse.class))),
+ @ApiResponse(
+ description = "if the user doesn't own the query",
+ responseCode = "401",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if the query cannot be found",
+ responseCode = "404",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if query lock acquisition fails " +
+ "if query storage fails " +
+ "if there is an unknown error",
+ responseCode = "500",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class)))})
+ // @formatter:on
+ @Timed(name = "dw.query.predictions", absolute = true)
+ @RequestMapping(path = "{queryId}/predictions", method = {RequestMethod.GET}, produces = {"application/xml", "text/xml", "application/json", "text/yaml",
+ "text/x-yaml", "application/x-yaml", "application/x-protobuf", "application/x-protostuff"})
+ public GenericResponse predictions(@Parameter(description = "The query ID") @PathVariable String queryId,
+ @AuthenticationPrincipal DatawaveUserDetails currentUser) throws QueryException {
+ return queryManagementService.predictions(queryId, currentUser);
+ }
+
+ /**
+ * @see QueryManagementService#adminCancelAll(DatawaveUserDetails)
+ */
+ // @formatter:off
+ @Operation(
+ summary = "Cancels all queries using admin privileges.",
+ description = "Cancel can only be called on a running query, or a query that is in the process of closing. " +
+ "Queries that are not running will be ignored by this method. " +
+ "Outstanding next calls will be stopped immediately, but will return partial results if applicable. " +
+ "Only admin users should be allowed to call this method.")
+ @ApiResponses({
+ @ApiResponse(
+ description = "if successful, returns a void response specifying which queries were canceled",
+ responseCode = "200",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if a query cannot be found",
+ responseCode = "404",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if query lock acquisition fails " +
+ "if the cancel call is interrupted " +
+ "if there is an unknown error",
+ responseCode = "500",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class)))})
+ // @formatter:on
+ @Timed(name = "dw.query.adminCancelAll", absolute = true)
+ @Secured({"Administrator", "JBossAdministrator"})
+ @RequestMapping(path = "adminCancelAll", method = {RequestMethod.PUT, RequestMethod.POST}, produces = {"application/xml", "text/xml", "application/json",
+ "text/yaml", "text/x-yaml", "application/x-yaml", "application/x-protobuf", "application/x-protostuff"})
+ public VoidResponse adminCancelAll(@AuthenticationPrincipal DatawaveUserDetails currentUser) throws QueryException {
+ return queryManagementService.adminCancelAll(currentUser);
+ }
+
+ /**
+ * @see QueryManagementService#adminCloseAll(DatawaveUserDetails)
+ */
+ // @formatter:off
+ @Operation(
+ summary = "Closes all queries using admin privileges.",
+ description = "Close can only be called on a running query. " +
+ "Queries that are not running will be ignored by this method. " +
+ "Outstanding next calls will be allowed to run until they can return a full page, or they timeout. " +
+ "Only admin users should be allowed to call this method.")
+ @ApiResponses({
+ @ApiResponse(
+ description = "if successful, returns a void response specifying which queries were closed",
+ responseCode = "200",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if a query cannot be found",
+ responseCode = "404",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if query lock acquisition fails " +
+ "if the close call is interrupted " +
+ "if there is an unknown error",
+ responseCode = "500",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class)))})
+ // @formatter:on
+ @Timed(name = "dw.query.adminCloseAll", absolute = true)
+ @Secured({"Administrator", "JBossAdministrator"})
+ @RequestMapping(path = "adminCloseAll", method = {RequestMethod.PUT, RequestMethod.POST}, produces = {"application/xml", "text/xml", "application/json",
+ "text/yaml", "text/x-yaml", "application/x-yaml", "application/x-protobuf", "application/x-protostuff"})
+ public VoidResponse adminCloseAll(@AuthenticationPrincipal DatawaveUserDetails currentUser) throws QueryException {
+ return queryManagementService.adminCloseAll(currentUser);
+ }
+
+ /**
+ * @see QueryManagementService#adminRemoveAll(DatawaveUserDetails)
+ */
+ // @formatter:off
+ @Operation(
+ summary = "Removes all queries from query storage using admin privileges.",
+ description = "Remove can only be called on a query that is not running. " +
+ "Queries that are running will be ignored by this method. " +
+ "Only admin users should be allowed to call this method.")
+ @ApiResponses({
+ @ApiResponse(
+ description = "if successful, returns a void response specifying which queries were removed",
+ responseCode = "200",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if a query cannot be found",
+ responseCode = "404",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if there is an unknown error",
+ responseCode = "500",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class)))})
+ // @formatter:on
+ @Timed(name = "dw.query.adminRemoveAll", absolute = true)
+ @Secured({"Administrator", "JBossAdministrator"})
+ @RequestMapping(path = "adminRemoveAll", method = {RequestMethod.DELETE}, produces = {"application/xml", "text/xml", "application/json", "text/yaml",
+ "text/x-yaml", "application/x-yaml", "application/x-protobuf", "application/x-protostuff"})
+ public VoidResponse adminRemoveAll(@AuthenticationPrincipal DatawaveUserDetails currentUser) throws QueryException {
+ return queryManagementService.adminRemoveAll(currentUser);
+ }
+
+ /**
+ * @see StreamingService#createAndExecute(String, MultiValueMap, String, DatawaveUserDetails, DatawaveUserDetails, StreamingResponseListener)
+ */
+ // @formatter:off
+ @Operation(
+ summary = "Creates a query using the given query logic and parameters, and streams back all pages of results.",
+ description = "Created queries will start running immediately. " +
+ "Auditing is performed before the query is started. " +
+ "Stop a running query gracefully using close or forcefully using cancel. " +
+ "Stop, and restart a running query using reset. " +
+ "Create a copy of a running query using duplicate. " +
+ "Aside from a limited set of admin actions, only the query owner can act on a running query.")
+ @ApiResponses({
+ @ApiResponse(
+ description = "if successful, returns multiple base query responses containing pages of results",
+ responseCode = "200",
+ content = @Content(array = @ArraySchema(schema = @Schema(implementation = BaseQueryResponse.class))),
+ headers = {
+ @Header(
+ name = "Pool",
+ description = "the executor pool to target",
+ schema = @Schema(defaultValue = "default"))}),
+ @ApiResponse(
+ description = "if no query results are found",
+ responseCode = "204",
+ content = @Content(schema = @Schema(hidden = true))),
+ @ApiResponse(
+ description = "if parameter validation fails " +
+ "if query logic parameter validation fails " +
+ "if security marking validation fails " +
+ "if auditing fails",
+ responseCode = "400",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if the user doesn't have access to the requested query logic",
+ responseCode = "401",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if query storage fails " +
+ "if there is an unknown error",
+ responseCode = "500",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class)))})
+ @Parameters({
+ @Parameter(
+ name = QUERY_BEGIN,
+ in = ParameterIn.QUERY,
+ description = "The query begin date",
+ required = true,
+ schema = @Schema(implementation = String.class),
+ example = "\"19660908 000000.000\""),
+ @Parameter(
+ name = QUERY_END,
+ in = ParameterIn.QUERY,
+ description = "The query end date",
+ required = true,
+ schema = @Schema(implementation = String.class),
+ example = "\"20161002 235959.999\""),
+ @Parameter(
+ name = QUERY_NAME,
+ in = ParameterIn.QUERY,
+ description = "The query name",
+ required = true,
+ schema = @Schema(implementation = String.class),
+ example = "Developer Test Query"),
+ @Parameter(
+ name = QUERY_STRING,
+ in = ParameterIn.QUERY,
+ description = "The query string",
+ required = true,
+ schema = @Schema(implementation = String.class),
+ example = "GENRES:[Action to Western]"),
+ @Parameter(
+ name = QUERY_AUTHORIZATIONS,
+ in = ParameterIn.QUERY,
+ description = "The query auths",
+ required = true,
+ schema = @Schema(implementation = String.class),
+ example = "PUBLIC,PRIVATE,BAR,FOO"),
+ @Parameter(
+ name = QUERY_VISIBILITY,
+ in = ParameterIn.QUERY,
+ description = "The visibility to use when storing metrics for this query",
+ required = true,
+ schema = @Schema(implementation = String.class),
+ example = "PUBLIC"),
+ @Parameter(
+ name = QUERY_SYNTAX,
+ in = ParameterIn.QUERY,
+ description = "The syntax used in the query",
+ schema = @Schema(implementation = String.class),
+ example = "LUCENE"),
+ @Parameter(
+ name = QUERY_MAX_CONCURRENT_TASKS,
+ in = ParameterIn.QUERY,
+ description = "The max number of concurrent tasks to run for this query",
+ schema = @Schema(implementation = Integer.class),
+ example = "10"),
+ @Parameter(
+ name = QUERY_POOL,
+ in = ParameterIn.QUERY,
+ description = "The executor pool to run against",
+ schema = @Schema(implementation = String.class),
+ example = "pool1"),
+ @Parameter(
+ name = QUERY_PAGESIZE,
+ in = ParameterIn.QUERY,
+ description = "The requested page size",
+ schema = @Schema(implementation = Integer.class),
+ example = "10"),
+ @Parameter(
+ name = QUERY_PAGETIMEOUT,
+ in = ParameterIn.QUERY,
+ description = "The call timeout when requesting a page, in minutes",
+ schema = @Schema(implementation = Integer.class),
+ example = "60"),
+ @Parameter(
+ name = QUERY_MAX_RESULTS_OVERRIDE,
+ in = ParameterIn.QUERY,
+ description = "The max results override value",
+ schema = @Schema(implementation = Integer.class),
+ example = "5000"),
+ @Parameter(
+ name = QUERY_PARAMS,
+ in = ParameterIn.QUERY,
+ description = "Additional query parameters",
+ schema = @Schema(implementation = String.class),
+ example = "KEY_1:VALUE_1;KEY_2:VALUE_2")
+ })
+ // @formatter:on
+ @Timed(name = "dw.query.createAndExecuteQuery", absolute = true)
+ @RequestMapping(path = "{queryLogic}/createAndExecute", method = {RequestMethod.POST}, produces = {"application/xml", "text/xml", "application/json",
+ "text/yaml", "text/x-yaml", "application/x-yaml", "application/x-protobuf", "application/x-protostuff"})
+ public ResponseEntity createAndExecute(
+ @Parameter(description = "The query logic", example = "EventQuery") @PathVariable String queryLogic,
+ @Parameter(hidden = true) @RequestParam MultiValueMap parameters, @RequestHeader HttpHeaders headers,
+ @AuthenticationPrincipal DatawaveUserDetails currentUser) throws QueryException {
+ MediaType contentType = determineContentType(headers.getAccept(), MediaType.parseMediaType(streamingProperties.getDefaultContentType()));
+ CountingResponseBodyEmitter emitter = baseMethodStatsContext.createCountingResponseBodyEmitter(streamingProperties.getCallTimeoutMillis());
+ String queryId = streamingService.createAndExecute(queryLogic, parameters, getPool(headers), currentUser, serverUserDetailsSupplier.get(),
+ new CountingResponseBodyEmitterListener(emitter, contentType));
+
+ // unfortunately this needs to be set manually. ResponseBodyAdvice does not run for streaming endpoints
+ queryMetricsEnrichmentContext.setMethodType(EnrichQueryMetrics.MethodType.CREATE);
+ queryMetricsEnrichmentContext.setQueryId(queryId);
+
+ return createStreamingResponse(emitter, contentType);
+ }
+
+ /**
+ * @see StreamingService#execute(String, DatawaveUserDetails, DatawaveUserDetails, StreamingResponseListener)
+ */
+ // @formatter:off
+ @Operation(
+ summary = "Gets all pages of results for the given query and streams them back.",
+ description = "Execute can only be called on a running query. " +
+ "Only the query owner can call execute on the specified query.")
+ @ApiResponses({
+ @ApiResponse(
+ description = "if successful, returns multiple base query responses containing pages of results",
+ responseCode = "200",
+ content = @Content(array = @ArraySchema(schema = @Schema(implementation = BaseQueryResponse.class)))),
+ @ApiResponse(
+ description = "if no query results are found",
+ responseCode = "204",
+ content = @Content(schema = @Schema(hidden = true))),
+ @ApiResponse(
+ description = "if the query is not running",
+ responseCode = "400",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if the user doesn't own the query",
+ responseCode = "401",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if the query cannot be found",
+ responseCode = "404",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))),
+ @ApiResponse(
+ description = "if query lock acquisition fails " +
+ "if the execute call is interrupted " +
+ "if the query times out " +
+ "if the next task is rejected by the executor " +
+ "if next call execution fails " +
+ "if query logic creation fails " +
+ "if there is an unknown error",
+ responseCode = "500",
+ content = @Content(schema = @Schema(implementation = VoidResponse.class)))})
+ // @formatter:on
+ @Timed(name = "dw.query.executeQuery", absolute = true)
+ @RequestMapping(path = "{queryId}/execute", method = {RequestMethod.GET}, produces = {"application/xml", "text/xml", "application/json", "text/yaml",
+ "text/x-yaml", "application/x-yaml", "application/x-protobuf", "application/x-protostuff"})
+ public ResponseEntity execute(@Parameter(description = "The query ID") @PathVariable String queryId,
+ @AuthenticationPrincipal DatawaveUserDetails currentUser, @RequestHeader HttpHeaders headers) {
+ MediaType contentType = determineContentType(headers.getAccept(), MediaType.parseMediaType(streamingProperties.getDefaultContentType()));
+ CountingResponseBodyEmitter emitter = baseMethodStatsContext.createCountingResponseBodyEmitter(streamingProperties.getCallTimeoutMillis());
+ streamingService.execute(queryId, currentUser, serverUserDetailsSupplier.get(), new CountingResponseBodyEmitterListener(emitter, contentType));
+
+ return createStreamingResponse(emitter, contentType);
+ }
+
+ private MediaType determineContentType(List acceptedMediaTypes, MediaType defaultMediaType) {
+ MediaType mediaType = null;
+
+ if (acceptedMediaTypes != null && !acceptedMediaTypes.isEmpty()) {
+ MediaType.sortBySpecificityAndQuality(acceptedMediaTypes);
+ mediaType = acceptedMediaTypes.get(0);
+ }
+
+ if (mediaType == null || MediaType.ALL.equals(mediaType)) {
+ mediaType = defaultMediaType;
+ }
+
+ return mediaType;
+ }
+
+ private String getPool(HttpHeaders headers) {
+ return headers.getFirst("Pool");
+ }
+
+ private ResponseEntity createStreamingResponse(ResponseBodyEmitter emitter, MediaType contentType) {
+ HttpHeaders responseHeaders = new HttpHeaders();
+ responseHeaders.setContentType(contentType);
+ return new ResponseEntity<>(emitter, responseHeaders, HttpStatus.OK);
+ }
+}
diff --git a/service/src/main/java/datawave/microservice/query/QueryManagementService.java b/service/src/main/java/datawave/microservice/query/QueryManagementService.java
new file mode 100644
index 00000000..07b9167f
--- /dev/null
+++ b/service/src/main/java/datawave/microservice/query/QueryManagementService.java
@@ -0,0 +1,2614 @@
+package datawave.microservice.query;
+
+import static datawave.microservice.query.QueryImpl.DN_LIST;
+import static datawave.microservice.query.QueryImpl.QUERY_ID;
+import static datawave.microservice.query.QueryImpl.USER_DN;
+import static datawave.microservice.query.QueryParameters.QUERY_LOGIC_NAME;
+import static datawave.microservice.query.storage.QueryStatus.QUERY_STATE.CANCEL;
+import static datawave.microservice.query.storage.QueryStatus.QUERY_STATE.CLOSE;
+import static datawave.microservice.query.storage.QueryStatus.QUERY_STATE.CREATE;
+import static datawave.microservice.query.storage.QueryStatus.QUERY_STATE.DEFINE;
+import static datawave.microservice.query.storage.QueryStatus.QUERY_STATE.FAIL;
+import static datawave.microservice.query.storage.QueryStatus.QUERY_STATE.PLAN;
+import static datawave.microservice.query.storage.QueryStatus.QUERY_STATE.PREDICT;
+
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+import org.apache.accumulo.core.security.Authorizations;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang.StringUtils;
+import org.apache.http.HttpStatus;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.cloud.bus.BusProperties;
+import org.springframework.cloud.bus.event.RemoteQueryRequestEvent;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.core.task.TaskRejectedException;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+import org.springframework.stereotype.Service;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import datawave.core.common.audit.PrivateAuditConstants;
+import datawave.core.query.cache.ResultsPage;
+import datawave.core.query.logic.QueryLogic;
+import datawave.core.query.logic.QueryLogicFactory;
+import datawave.core.query.util.QueryUtil;
+import datawave.marking.SecurityMarking;
+import datawave.microservice.audit.AuditClient;
+import datawave.microservice.authorization.federation.FederatedAuthorizationService;
+import datawave.microservice.authorization.user.DatawaveUserDetails;
+import datawave.microservice.authorization.util.AuthorizationsUtil;
+import datawave.microservice.query.config.QueryProperties;
+import datawave.microservice.query.messaging.QueryResultsManager;
+import datawave.microservice.query.remote.QueryRequest;
+import datawave.microservice.query.remote.QueryRequestHandler;
+import datawave.microservice.query.runner.NextCall;
+import datawave.microservice.query.storage.QueryStatus;
+import datawave.microservice.query.storage.QueryStorageCache;
+import datawave.microservice.query.storage.TaskKey;
+import datawave.microservice.query.util.QueryStatusUpdateUtil;
+import datawave.microservice.querymetric.BaseQueryMetric;
+import datawave.microservice.querymetric.QueryMetricClient;
+import datawave.microservice.querymetric.QueryMetricType;
+import datawave.security.util.ProxiedEntityUtils;
+import datawave.webservice.common.audit.AuditParameters;
+import datawave.webservice.common.audit.Auditor;
+import datawave.webservice.query.exception.BadRequestQueryException;
+import datawave.webservice.query.exception.DatawaveErrorCode;
+import datawave.webservice.query.exception.NoResultsQueryException;
+import datawave.webservice.query.exception.NotFoundQueryException;
+import datawave.webservice.query.exception.QueryCanceledQueryException;
+import datawave.webservice.query.exception.QueryException;
+import datawave.webservice.query.exception.TimeoutQueryException;
+import datawave.webservice.query.exception.UnauthorizedQueryException;
+import datawave.webservice.query.result.event.ResponseObjectFactory;
+import datawave.webservice.query.result.logic.QueryLogicDescription;
+import datawave.webservice.query.util.QueryUncaughtExceptionHandler;
+import datawave.webservice.result.BaseQueryResponse;
+import datawave.webservice.result.GenericResponse;
+import datawave.webservice.result.QueryImplListResponse;
+import datawave.webservice.result.QueryLogicResponse;
+import datawave.webservice.result.VoidResponse;
+
+@Service
+public class QueryManagementService implements QueryRequestHandler {
+ private final Logger log = LoggerFactory.getLogger(this.getClass());
+
+ private static final ObjectMapper mapper = new ObjectMapper();
+
+ private final QueryProperties queryProperties;
+
+ private final ApplicationEventPublisher eventPublisher;
+ private final BusProperties busProperties;
+
+ // Note: QueryParameters needs to be request scoped
+ private final QueryParameters queryParameters;
+ // Note: SecurityMarking needs to be request scoped
+ private final SecurityMarking securityMarking;
+ // Note: BaseQueryMetric needs to be request scoped
+ private final BaseQueryMetric baseQueryMetric;
+
+ private final QueryLogicFactory queryLogicFactory;
+ private final QueryMetricClient queryMetricClient;
+ private final ResponseObjectFactory responseObjectFactory;
+ private final QueryStorageCache queryStorageCache;
+ private final QueryResultsManager queryResultsManager;
+ private final AuditClient auditClient;
+ private final ThreadPoolTaskExecutor nextCallExecutor;
+
+ private final QueryStatusUpdateUtil queryStatusUpdateUtil;
+ private final MultiValueMap nextCallMap = new LinkedMultiValueMap<>();
+
+ private final String selfDestination;
+
+ // Note: for requests which don't originate with a rest call, provide ThreadLocal queryParameters
+ private final ThreadLocal queryParametersOverride;
+ // Note: for requests which don't originate with a rest call, provide ThreadLocal securityMarkings
+ private final ThreadLocal securityMarkingOverride;
+ // Note: for requests which don't originate with a rest call, provide ThreadLocal baseQueryMetric
+ private final ThreadLocal baseQueryMetricOverride;
+
+ private final Map queryLatchMap = new ConcurrentHashMap<>();
+
+ public QueryManagementService(QueryProperties queryProperties, ApplicationEventPublisher eventPublisher, BusProperties busProperties,
+ QueryParameters queryParameters, SecurityMarking securityMarking, BaseQueryMetric baseQueryMetric, QueryLogicFactory queryLogicFactory,
+ QueryMetricClient queryMetricClient, ResponseObjectFactory responseObjectFactory, QueryStorageCache queryStorageCache,
+ QueryResultsManager queryResultsManager, AuditClient auditClient, ThreadPoolTaskExecutor nextCallExecutor) {
+ this.queryProperties = queryProperties;
+ this.eventPublisher = eventPublisher;
+ this.busProperties = busProperties;
+ this.queryParameters = queryParameters;
+ this.securityMarking = securityMarking;
+ this.baseQueryMetric = baseQueryMetric;
+ this.queryLogicFactory = queryLogicFactory;
+ this.queryMetricClient = queryMetricClient;
+ this.responseObjectFactory = responseObjectFactory;
+ this.queryStorageCache = queryStorageCache;
+ this.queryResultsManager = queryResultsManager;
+ this.auditClient = auditClient;
+ this.nextCallExecutor = nextCallExecutor;
+ this.queryStatusUpdateUtil = new QueryStatusUpdateUtil(this.queryProperties, this.queryStorageCache);
+ this.selfDestination = getSelfDestination();
+ this.queryParametersOverride = new ThreadLocal<>();
+ this.securityMarkingOverride = new ThreadLocal<>();
+ this.baseQueryMetricOverride = new ThreadLocal<>();
+ }
+
+ /**
+ * Gets a list of descriptions for the configured query logics, sorted by query logic name.
+ *
+ * The descriptions include things like the audit type, optional and required parameters, required roles, and response class.
+ *
+ * @param currentUser
+ * the user who called this method, not null
+ * @return the query logic descriptions
+ */
+ public QueryLogicResponse listQueryLogic(DatawaveUserDetails currentUser) {
+ log.info("Request: listQueryLogic from {}", ProxiedEntityUtils.getShortName(currentUser.getPrimaryUser().getName()));
+
+ QueryLogicResponse response = new QueryLogicResponse();
+ List> queryLogicList = queryLogicFactory.getQueryLogicList();
+ List logicConfigurationList = new ArrayList<>();
+
+ // reference query necessary to avoid NPEs in getting the Transformer and BaseResponse
+ Query q = new QueryImpl();
+ Date now = new Date();
+ q.setExpirationDate(now);
+ q.setQuery("test");
+ q.setQueryAuthorizations("ALL");
+
+ for (QueryLogic> queryLogic : queryLogicList) {
+ try {
+ QueryLogicDescription logicDesc = new QueryLogicDescription(queryLogic.getLogicName());
+ logicDesc.setAuditType(queryLogic.getAuditType(null).toString());
+ logicDesc.setLogicDescription(queryLogic.getLogicDescription());
+
+ Set optionalQueryParameters = queryLogic.getOptionalQueryParameters();
+ if (optionalQueryParameters != null) {
+ logicDesc.setSupportedParams(new ArrayList<>(optionalQueryParameters));
+ }
+ Set requiredQueryParameters = queryLogic.getRequiredQueryParameters();
+ if (requiredQueryParameters != null) {
+ logicDesc.setRequiredParams(new ArrayList<>(requiredQueryParameters));
+ }
+ Set exampleQueries = queryLogic.getExampleQueries();
+ if (exampleQueries != null) {
+ logicDesc.setExampleQueries(new ArrayList<>(exampleQueries));
+ }
+ Set requiredRoles = queryLogic.getRequiredRoles();
+ if (requiredRoles != null) {
+ List requiredRolesList = new ArrayList<>(queryLogic.getRequiredRoles());
+ logicDesc.setRequiredRoles(requiredRolesList);
+ }
+
+ try {
+ logicDesc.setResponseClass(queryLogic.getResponseClass(q));
+ } catch (QueryException e) {
+ log.error("Unable to get response class for query logic: {}", queryLogic.getLogicName(), e);
+ response.addException(e);
+ logicDesc.setResponseClass("unknown");
+ }
+
+ List querySyntax = new ArrayList<>();
+ try {
+ Method m = queryLogic.getClass().getMethod("getQuerySyntaxParsers");
+ Object result = m.invoke(queryLogic);
+ if (result instanceof Map,?>) {
+ Map,?> map = (Map,?>) result;
+ for (Object o : map.keySet())
+ querySyntax.add(o.toString());
+ }
+ } catch (Exception e) {
+ log.warn("Unable to get query syntax for query logic: {}", queryLogic.getClass().getCanonicalName());
+ }
+ if (querySyntax.isEmpty()) {
+ querySyntax.add("CUSTOM");
+ }
+ logicDesc.setQuerySyntax(querySyntax);
+
+ logicConfigurationList.add(logicDesc);
+ } catch (Exception e) {
+ log.error("Error setting query logic description", e);
+ }
+ }
+ logicConfigurationList.sort(Comparator.comparing(QueryLogicDescription::getName));
+ response.setQueryLogicList(logicConfigurationList);
+
+ return response;
+ }
+
+ /**
+ * Defines a query using the given query logic and parameters.
+ *
+ * Defined queries cannot be started and run.
+ * Auditing is not performed when defining a query.
+ * Updates can be made to any parameter using {@link #update}.
+ * Create a runnable query from a defined query using {@link #duplicate} or {@link #reset}.
+ * Delete a defined query using {@link #remove}.
+ * Aside from a limited set of admin actions, only the query owner can act on a defined query.
+ *
+ * @param queryLogicName
+ * the requested query logic, not null
+ * @param parameters
+ * the query parameters, not null
+ * @param currentUser
+ * the user who called this method, not null
+ * @param pool
+ * the pool to target, may be null
+ * @return a generic response containing the query id
+ * @throws BadRequestQueryException
+ * if parameter validation fails
+ * @throws BadRequestQueryException
+ * if query logic parameter validation fails
+ * @throws UnauthorizedQueryException
+ * if the user doesn't have access to the requested query logic
+ * @throws BadRequestQueryException
+ * if security marking validation fails
+ * @throws QueryException
+ * if query storage fails
+ * @throws QueryException
+ * if there is an unknown error
+ */
+ public GenericResponse define(String queryLogicName, MultiValueMap parameters, String pool, DatawaveUserDetails currentUser)
+ throws QueryException {
+ String user = ProxiedEntityUtils.getShortName(currentUser.getPrimaryUser().getName());
+ if (log.isDebugEnabled()) {
+ log.info("Request: {}/define from {} with params: {}", queryLogicName, user, parameters);
+ } else {
+ log.info("Request: {}/define from {}", queryLogicName, user);
+ }
+
+ try {
+ TaskKey taskKey = storeQuery(queryLogicName, parameters, pool, currentUser, DEFINE);
+ GenericResponse response = new GenericResponse<>();
+ response.setResult(taskKey.getQueryId());
+ return response;
+ } catch (QueryException e) {
+ throw e;
+ } catch (Exception e) {
+ log.error("Unknown error defining query", e);
+ throw new QueryException(DatawaveErrorCode.QUERY_SETUP_ERROR, e, "Unknown error defining query.");
+ }
+ }
+
+ /**
+ * Creates a query using the given query logic and parameters.
+ *
+ * Created queries will start running immediately.
+ * Auditing is performed before the query is started.
+ * Query results can be retrieved using {@link #executeNext}.
+ * Updates can be made to any parameter which doesn't affect the scope of the query using {@link #update}.
+ * Stop a running query gracefully using {@link #close} or forcefully using {@link #cancel}.
+ * Stop, and restart a running query using {@link #reset}.
+ * Create a copy of a running query using {@link #duplicate}.
+ * Aside from a limited set of admin actions, only the query owner can act on a running query.
+ *
+ * @param queryLogicName
+ * the requested query logic, not null
+ * @param parameters
+ * the query parameters, not null
+ * @param pool
+ * the pool to target, may be null
+ * @param currentUser
+ * the user who called this method, not null
+ * @return a generic response containing the query id
+ * @throws BadRequestQueryException
+ * if parameter validation fails
+ * @throws BadRequestQueryException
+ * if query logic parameter validation fails
+ * @throws UnauthorizedQueryException
+ * if the user doesn't have access to the requested query logic
+ * @throws BadRequestQueryException
+ * if security marking validation fails
+ * @throws BadRequestQueryException
+ * if auditing fails
+ * @throws QueryException
+ * if query storage fails
+ * @throws QueryException
+ * if there is an unknown error
+ */
+ public GenericResponse create(String queryLogicName, MultiValueMap parameters, String pool, DatawaveUserDetails currentUser)
+ throws QueryException {
+ String user = ProxiedEntityUtils.getShortName(currentUser.getPrimaryUser().getName());
+ if (log.isDebugEnabled()) {
+ log.info("Request: {}/create from {} with params: {}", queryLogicName, user, parameters);
+ } else {
+ log.info("Request: {}/create from {}", queryLogicName, user);
+ }
+
+ try {
+ TaskKey taskKey = storeQuery(queryLogicName, parameters, pool, currentUser, CREATE);
+ GenericResponse response = new GenericResponse<>();
+ response.setResult(taskKey.getQueryId());
+ response.setHasResults(true);
+ return response;
+ } catch (QueryException e) {
+ throw e;
+ } catch (Exception e) {
+ log.error("Unknown error creating query", e);
+ throw new QueryException(DatawaveErrorCode.QUERY_SETUP_ERROR, e, "Unknown error creating query.");
+ }
+ }
+
+ /**
+ * Generates a query plan using the given query logic and parameters.
+ *
+ * Created queries will begin planning immediately.
+ * Auditing is performed if we are expanding indices.
+ * Query plan will be returned in the response.
+ * Updates can be made to any parameter which doesn't affect the scope of the query using {@link #update}.
+ *
+ * @param queryLogicName
+ * the requested query logic, not null
+ * @param parameters
+ * the query parameters, not null
+ * @param pool
+ * the pool to target, may be null
+ * @param currentUser
+ * the user who called this method, not null
+ * @return a generic response containing the query plan
+ * @throws BadRequestQueryException
+ * if parameter validation fails
+ * @throws BadRequestQueryException
+ * if query logic parameter validation fails
+ * @throws UnauthorizedQueryException
+ * if the user doesn't have access to the requested query logic
+ * @throws BadRequestQueryException
+ * if security marking validation fails
+ * @throws BadRequestQueryException
+ * if auditing fails
+ * @throws QueryException
+ * if query storage fails
+ * @throws QueryException
+ * if there is an unknown error
+ */
+ public GenericResponse plan(String queryLogicName, MultiValueMap parameters, String pool, DatawaveUserDetails currentUser)
+ throws QueryException {
+ String user = ProxiedEntityUtils.getShortName(currentUser.getPrimaryUser().getName());
+ if (log.isDebugEnabled()) {
+ log.info("Request: {}/plan from {} with params: {}", queryLogicName, user, parameters);
+ } else {
+ log.info("Request: {}/plan from {}", queryLogicName, user);
+ }
+
+ try {
+ TaskKey taskKey = storeQuery(queryLogicName, parameters, pool, currentUser, PLAN);
+ String queryPlan = queryStorageCache.getQueryStatus(taskKey.getQueryId()).getPlan();
+ queryStorageCache.deleteQuery(taskKey.getQueryId());
+ GenericResponse response = new GenericResponse<>();
+ response.setResult(queryPlan);
+ response.setHasResults(true);
+ return response;
+ } catch (QueryException e) {
+ throw e;
+ } catch (Exception e) {
+ log.error("Unknown error planning query", e);
+ throw new QueryException(DatawaveErrorCode.QUERY_SETUP_ERROR, e, "Unknown error planning query.");
+ }
+ }
+
+ /**
+ * Generates a query prediction using the given query logic and parameters.
+ *
+ * Created queries will begin predicting immediately.
+ * Auditing is not performed.
+ * Query prediction will be returned in the response.
+ * Updates can be made to any parameter which doesn't affect the scope of the query using {@link #update}.
+ *
+ * @param queryLogicName
+ * the requested query logic, not null
+ * @param parameters
+ * the query parameters, not null
+ * @param pool
+ * the pool to target, may be null
+ * @param currentUser
+ * the user who called this method, not null
+ * @return a generic response containing the query prediction
+ * @throws BadRequestQueryException
+ * if parameter validation fails
+ * @throws BadRequestQueryException
+ * if query logic parameter validation fails
+ * @throws UnauthorizedQueryException
+ * if the user doesn't have access to the requested query logic
+ * @throws BadRequestQueryException
+ * if security marking validation fails
+ * @throws BadRequestQueryException
+ * if auditing fails
+ * @throws QueryException
+ * if query storage fails
+ * @throws QueryException
+ * if there is an unknown error
+ */
+ public GenericResponse predict(String queryLogicName, MultiValueMap parameters, String pool, DatawaveUserDetails currentUser)
+ throws QueryException {
+ String user = ProxiedEntityUtils.getShortName(currentUser.getPrimaryUser().getName());
+ if (log.isDebugEnabled()) {
+ log.info("Request: {}/predict from {} with params: {}", queryLogicName, user, parameters);
+ } else {
+ log.info("Request: {}/predict from {}", queryLogicName, user);
+ }
+
+ try {
+ TaskKey taskKey = storeQuery(queryLogicName, parameters, pool, currentUser, PREDICT);
+ String queryPrediction = "no predictions";
+ QueryStatus status = queryStorageCache.getQueryStatus(taskKey.getQueryId());
+ if (status != null) {
+ Set predictions = status.getPredictions();
+ if (CollectionUtils.isNotEmpty(predictions)) {
+ queryPrediction = predictions.toString();
+ }
+ }
+ queryStorageCache.deleteQuery(taskKey.getQueryId());
+ GenericResponse response = new GenericResponse<>();
+ response.setResult(queryPrediction);
+ response.setHasResults(true);
+ return response;
+ } catch (QueryException e) {
+ throw e;
+ } catch (Exception e) {
+ log.error("Unknown error predicting query", e);
+ throw new QueryException(DatawaveErrorCode.QUERY_SETUP_ERROR, e, "Unknown error predicting query.");
+ }
+ }
+
+ private TaskKey storeQuery(String queryLogicName, MultiValueMap parameters, String pool, DatawaveUserDetails currentUser,
+ QueryStatus.QUERY_STATE queryType) throws QueryException {
+ return storeQuery(queryLogicName, parameters, pool, currentUser, queryType, null);
+ }
+
+ /**
+ * Validates the query request, creates an entry in the query storage cache, and publishes a create event to the executor service.
+ *
+ * Validation is run against the requested logic, the parameters, and the security markings in {@link #validateQuery}.
+ * Auditing is performed when {@code isCreateRequest} is true using {@link #audit}.
+ * If {@code queryId} is null, a query id will be generated automatically.
+ *
+ * @param queryLogicName
+ * the requested query logic, not null
+ * @param parameters
+ * the query parameters, not null
+ * @param pool
+ * the pool to target, may be null
+ * @param currentUser
+ * the user who called this method, not null
+ * @param queryType
+ * whether this is a define, create, or plan call
+ * @param queryId
+ * the desired query id, may be null
+ * @return the task key returned from query storage
+ * @throws BadRequestQueryException
+ * if parameter validation fails
+ * @throws BadRequestQueryException
+ * if query logic parameter validation fails
+ * @throws UnauthorizedQueryException
+ * if the user doesn't have access to the requested query logic
+ * @throws BadRequestQueryException
+ * if security marking validation fails
+ * @throws BadRequestQueryException
+ * if auditing fails
+ * @throws QueryException
+ * if query storage fails
+ * @throws QueryException
+ * if there is an unknown error
+ */
+ private TaskKey storeQuery(String queryLogicName, MultiValueMap parameters, String pool, DatawaveUserDetails currentUser,
+ QueryStatus.QUERY_STATE queryType, String queryId) throws BadRequestQueryException, QueryException {
+ long callStartTimeMillis = System.currentTimeMillis();
+
+ // validate query and get a query logic
+ QueryLogic> queryLogic = validateQuery(queryLogicName, parameters, currentUser);
+
+ String userId = ProxiedEntityUtils.getShortName(currentUser.getPrimaryUser().getName());
+ log.trace("{} has authorizations {}", userId, currentUser.getPrimaryUser().getAuths());
+
+ Query query = createQuery(queryLogicName, parameters, currentUser, queryId);
+
+ // if this is a create request, or a plan request where we are expanding values, send an audit record to the auditor
+ if (queryType == CREATE || (queryType == PLAN && queryParameters.isExpandValues())) {
+ audit(query, queryLogic, parameters, currentUser);
+ }
+
+ // downgrade the auths
+ QueryParameters queryParameters = getQueryParameters();
+ Set downgradedAuthorizations;
+ try {
+ if (queryParameters.getAuths() == null) {
+ // if no requested auths, then use the overall auths for any filtering of the query operations
+ queryLogic.preInitialize(query, AuthorizationsUtil.buildAuthorizations(currentUser.getAuthorizations()));
+ } else {
+ queryLogic.preInitialize(query,
+ AuthorizationsUtil.buildAuthorizations(Collections.singleton(AuthorizationsUtil.splitAuths(query.getQueryAuthorizations()))));
+ }
+ // the query principal is our local principal unless the query logic has a different user operations
+ DatawaveUserDetails queryUserDetails = (DatawaveUserDetails) ((queryLogic.getUserOperations() == null) ? currentUser
+ : queryLogic.getUserOperations().getRemoteUser(currentUser));
+ // the overall principal (the one with combined auths across remote user operations) is our own user operations (probably the UserOperationsBean)
+ // don't call remote user operations if it's asked not to
+ DatawaveUserDetails overallUserDetails = (queryLogic.getUserOperations() == null
+ || "false".equalsIgnoreCase(parameters.getFirst(FederatedAuthorizationService.INCLUDE_REMOTE_SERVICES))) ? queryUserDetails
+ : queryLogic.getUserOperations().getRemoteUser(queryUserDetails);
+ downgradedAuthorizations = AuthorizationsUtil.getDowngradedAuthorizations(queryParameters.getAuths(), overallUserDetails, queryUserDetails);
+ } catch (Exception e) {
+ throw new BadRequestQueryException("Unable to downgrade authorizations", e, HttpStatus.SC_BAD_REQUEST + "-1");
+ }
+
+ try {
+ String computedPool = getPoolName(pool, isAdminUser(currentUser));
+
+ // persist the query w/ query id in the query storage cache
+ TaskKey taskKey = null;
+ if (queryType == DEFINE) {
+ // @formatter:off
+ taskKey = queryStorageCache.defineQuery(
+ computedPool,
+ query,
+ currentUser,
+ downgradedAuthorizations,
+ getMaxConcurrentTasks(queryLogic));
+ // @formatter:on
+ } else if (queryType == CREATE) {
+ // @formatter:off
+ taskKey = queryStorageCache.createQuery(
+ computedPool,
+ query,
+ currentUser,
+ downgradedAuthorizations,
+ getMaxConcurrentTasks(queryLogic));
+
+ sendRequestAwaitResponse(
+ QueryRequest.create(taskKey.getQueryId()),
+ computedPool,
+ queryProperties.isAwaitExecutorCreateResponse(),
+ callStartTimeMillis);
+ // @formatter:on
+ } else if (queryType == PLAN) {
+ // @formatter:off
+ taskKey = queryStorageCache.planQuery(
+ computedPool,
+ query,
+ currentUser,
+ downgradedAuthorizations);
+
+ sendRequestAwaitResponse(
+ QueryRequest.plan(taskKey.getQueryId()),
+ computedPool,
+ true,
+ callStartTimeMillis);
+ // @formatter:on
+ } else if (queryType == PREDICT) {
+ // @formatter:off
+ taskKey = queryStorageCache.predictQuery(
+ computedPool,
+ query,
+ currentUser,
+ downgradedAuthorizations);
+
+ sendRequestAwaitResponse(
+ QueryRequest.predict(taskKey.getQueryId()),
+ computedPool,
+ true,
+ callStartTimeMillis);
+ // @formatter:on
+ }
+
+ if (taskKey == null) {
+ log.error("Task Key not created for query");
+ throw new QueryException(DatawaveErrorCode.RUNNING_QUERY_CACHE_ERROR);
+ }
+
+ // update the query metric
+ BaseQueryMetric baseQueryMetric = getBaseQueryMetric();
+ if (queryType == DEFINE || queryType == CREATE) {
+ baseQueryMetric.setQueryId(taskKey.getQueryId());
+ baseQueryMetric.setLifecycle(BaseQueryMetric.Lifecycle.DEFINED);
+ baseQueryMetric.populate(query);
+ baseQueryMetric.setProxyServers(getDNs(currentUser));
+ }
+
+ return taskKey;
+ } catch (Exception e) {
+ log.error("Unknown error storing query", e);
+ throw new QueryException(DatawaveErrorCode.RUNNING_QUERY_CACHE_ERROR, e);
+ }
+ }
+
+ private void sendRequestAwaitResponse(QueryRequest request, String computedPool, boolean isAwaitResponse, long startTimeMillis) throws QueryException {
+ if (isAwaitResponse) {
+ // before publishing the message, create a latch based on the query ID
+ queryLatchMap.put(request.getQueryId(), new CountDownLatch(1));
+ }
+
+ // publish an event to the executor pool
+ publishExecutorEvent(request, computedPool);
+
+ if (isAwaitResponse) {
+ log.info("Waiting on query {} response from the executor.", request.getMethod().name());
+
+ try {
+ boolean isFinished = false;
+ while (!isFinished && System.currentTimeMillis() < (startTimeMillis + queryProperties.getExpiration().getCallTimeoutMillis())) {
+ try {
+ // wait for the executor response
+ if (queryLatchMap.get(request.getQueryId()).await(queryProperties.getExpiration().getCallTimeoutInterval(),
+ queryProperties.getExpiration().getCallTimeoutIntervalUnit())) {
+ log.info("Received query {} response from the executor.", request.getMethod().name());
+ isFinished = true;
+ }
+
+ // did the request fail?
+ QueryStatus queryStatus = queryStorageCache.getQueryStatus(request.getQueryId());
+ if (queryStatus.getQueryState() == FAIL) {
+ log.error("Query {} failed for queryId {}: {}", request.getMethod().name(), request.getQueryId(), queryStatus.getFailureMessage());
+ throw new QueryException(queryStatus.getErrorCode(), "Query " + request.getMethod().name() + " failed for queryId "
+ + request.getQueryId() + ": " + queryStatus.getFailureMessage());
+ }
+ } catch (InterruptedException e) {
+ log.warn("Interrupted while waiting for query {} latch for queryId {}", request.getMethod().name(), request.getQueryId());
+ }
+ }
+ } finally {
+ queryLatchMap.remove(request.getQueryId());
+ }
+ }
+ }
+
+ /**
+ * Creates a query using the given query logic and parameters, and returns the first page of results.
+ *
+ * Created queries will start running immediately.
+ * Auditing is performed before the query is started.
+ * Subsequent query results can be retrieved using {@link #executeNext}.
+ * Updates can be made to any parameter which doesn't affect the scope of the query using {@link #update}.
+ * Stop a running query gracefully using {@link #close} or forcefully using {@link #cancel}.
+ * Stop, and restart a running query using {@link #reset}.
+ * Create a copy of a running query using {@link #duplicate}.
+ * Aside from a limited set of admin actions, only the query owner can act on a running query.
+ *
+ * @param queryLogicName
+ * the requested query logic, not null
+ * @param parameters
+ * the query parameters, not null
+ * @param pool
+ * the pool to target, may be null
+ * @param currentUser
+ * the user who called this method, not null
+ * @return a base query response containing the first page of results
+ * @throws BadRequestQueryException
+ * if parameter validation fails
+ * @throws BadRequestQueryException
+ * if query logic parameter validation fails
+ * @throws UnauthorizedQueryException
+ * if the user doesn't have access to the requested query logic
+ * @throws BadRequestQueryException
+ * if security marking validation fails
+ * @throws BadRequestQueryException
+ * if auditing fails
+ * @throws QueryException
+ * if query storage fails
+ * @throws TimeoutQueryException
+ * if the next call times out
+ * @throws NoResultsQueryException
+ * if no query results are found
+ * @throws QueryException
+ * if this next task is rejected by the executor
+ * @throws QueryException
+ * if there is an unknown error
+ */
+ public BaseQueryResponse createAndNext(String queryLogicName, MultiValueMap parameters, String pool, DatawaveUserDetails currentUser)
+ throws QueryException {
+ String user = ProxiedEntityUtils.getShortName(currentUser.getPrimaryUser().getName());
+ if (log.isDebugEnabled()) {
+ log.info("Request: {}/createAndNext from {} with params: {}", queryLogicName, user, parameters);
+ } else {
+ log.info("Request: {}/createAndNext from {}", queryLogicName, user);
+ }
+
+ String queryId = null;
+ try {
+ queryId = create(queryLogicName, parameters, pool, currentUser).getResult();
+ return executeNext(queryId, currentUser);
+ } catch (Exception e) {
+ QueryException qe;
+ if (!(e instanceof QueryException)) {
+ log.error("Unknown error calling create and next. {}", queryId, e);
+ qe = new QueryException(DatawaveErrorCode.QUERY_NEXT_ERROR, e, "Unknown error calling create and next. " + queryId);
+ } else {
+ qe = (QueryException) e;
+ }
+
+ if (queryId != null && !(qe instanceof NoResultsQueryException)) {
+ getBaseQueryMetric().setError(qe);
+ }
+
+ throw qe;
+ }
+ }
+
+ /**
+ * Gets the next page of results for the specified query.
+ *
+ * Next can only be called on a running query.
+ * If configuration allows, multiple next calls may be run concurrently for a query.
+ * Only the query owner can call next on the specified query.
+ *
+ * @param queryId
+ * the query id, not null
+ * @param currentUser
+ * the user who called this method, not null
+ * @return a base query response containing the next page of results
+ * @throws NotFoundQueryException
+ * if the query cannot be found
+ * @throws UnauthorizedQueryException
+ * if the user doesn't own the query
+ * @throws BadRequestQueryException
+ * if the query is not running
+ * @throws QueryException
+ * if query lock acquisition fails
+ * @throws QueryException
+ * if the next call is interrupted
+ * @throws TimeoutQueryException
+ * if the query times out
+ * @throws NoResultsQueryException
+ * if no query results are found
+ * @throws QueryException
+ * if this next task is rejected by the executor
+ * @throws QueryException
+ * if next call execution fails
+ * @throws QueryException
+ * if query logic creation fails
+ * @throws QueryException
+ * if there is an unknown error
+ */
+ public BaseQueryResponse next(String queryId, DatawaveUserDetails currentUser) throws QueryException {
+ log.info("Request: next from {} for {}", ProxiedEntityUtils.getShortName(currentUser.getPrimaryUser().getName()), queryId);
+
+ try {
+ // make sure the query is valid, and the user can act on it
+ QueryStatus queryStatus = validateRequest(queryId, currentUser);
+
+ // make sure the state is created
+ if (queryStatus.getQueryState() == CREATE) {
+ return executeNext(queryId, currentUser);
+ } else {
+ throw new BadRequestQueryException("Cannot call next on a query that is not running", HttpStatus.SC_BAD_REQUEST + "-1");
+ }
+ } catch (Exception e) {
+ QueryException qe;
+ if (!(e instanceof QueryException)) {
+ log.error("Unknown error getting next page for query {}", queryId, e);
+ qe = new QueryException(DatawaveErrorCode.QUERY_NEXT_ERROR, e, "Unknown error getting next page for query " + queryId);
+ } else {
+ qe = (QueryException) e;
+ }
+
+ if (!(qe instanceof NoResultsQueryException)) {
+ getBaseQueryMetric().setError(qe);
+ }
+
+ throw qe;
+ }
+ }
+
+ /**
+ * Gets the next page of results for the given query, and publishes a next event to the executor service.
+ *
+ * If configuration allows, multiple next calls may be run concurrently for a query.
+ *
+ * @param queryId
+ * the query id, not null
+ * @param currentUser
+ * the user who called this method, not null
+ * @return a base query response containing the next page of results
+ * @throws NotFoundQueryException
+ * if the query cannot be found
+ * @throws QueryException
+ * if query lock acquisition fails
+ * @throws InterruptedException
+ * if the next call is interrupted
+ * @throws TimeoutQueryException
+ * if the query times out
+ * @throws NoResultsQueryException
+ * if no query results are found
+ * @throws QueryException
+ * if this next task is rejected by the executor
+ * @throws QueryException
+ * if next call execution fails
+ * @throws QueryException
+ * if query logic creation fails
+ */
+ private BaseQueryResponse executeNext(String queryId, DatawaveUserDetails currentUser) throws InterruptedException, QueryException {
+ // before we spin up a separate thread, make sure we are allowed to call next
+ boolean success = false;
+ QueryStatus queryStatus = queryStatusUpdateUtil.lockedUpdate(queryId, queryStatusUpdateUtil::claimNextCall);
+ try {
+ // publish a next event to the executor pool
+ publishNextEvent(queryId, queryStatus.getQueryKey().getQueryPool());
+
+ // get the query logic
+ String queryLogicName = queryStatus.getQuery().getQueryLogicName();
+ QueryLogic> queryLogic = queryLogicFactory.getQueryLogic(queryStatus.getQuery().getQueryLogicName(), currentUser);
+
+ // update query metrics
+ BaseQueryMetric baseQueryMetric = getBaseQueryMetric();
+ baseQueryMetric.setQueryId(queryId);
+ baseQueryMetric.setQueryLogic(queryLogicName);
+
+ // @formatter:off
+ final NextCall nextCall = new NextCall.Builder()
+ .setQueryProperties(queryProperties)
+ .setResultsQueueManager(queryResultsManager)
+ .setQueryStorageCache(queryStorageCache)
+ .setQueryStatusUpdateUtil(queryStatusUpdateUtil)
+ .setQueryId(queryId)
+ .setQueryLogic(queryLogic)
+ .build();
+ // @formatter:on
+
+ nextCallMap.add(queryId, nextCall);
+ try {
+ // submit the next call to the executor
+ nextCall.setFuture(nextCallExecutor.submit(nextCall));
+
+ // wait for the results to be ready
+ ResultsPage