diff --git a/warehouse/query-core/src/main/java/datawave/query/config/RemoteQueryConfiguration.java b/warehouse/query-core/src/main/java/datawave/query/config/RemoteQueryConfiguration.java
new file mode 100644
index 00000000000..5c8359c83bb
--- /dev/null
+++ b/warehouse/query-core/src/main/java/datawave/query/config/RemoteQueryConfiguration.java
@@ -0,0 +1,149 @@
+package datawave.query.config;
+
+import datawave.query.tables.RemoteEventQueryLogic;
+import datawave.webservice.query.Query;
+import datawave.webservice.query.configuration.GenericQueryConfiguration;
+
+import java.io.ObjectStreamException;
+import java.io.Serializable;
+import java.util.Objects;
+
+/**
+ *
+ * A GenericQueryConfiguration implementation that provides the additional logic on top of the traditional query that is needed to run a remote query logic
+ *
+ */
+public class RemoteQueryConfiguration extends GenericQueryConfiguration implements Serializable {
+
+ private static final long serialVersionUID = -4354990715046146110L;
+
+ // the id of the remote query
+ private String remoteId;
+
+ private String remoteQueryLogic;
+
+ private Query query;
+
+ /**
+ * Default constructor
+ */
+ public RemoteQueryConfiguration() {
+ super();
+ }
+
+ /**
+ * Performs a deep copy of the provided RemoteQueryConfiguration into a new instance
+ *
+ * @param other
+ * - another RemoteQueryConfiguration instance
+ */
+ public RemoteQueryConfiguration(RemoteQueryConfiguration other) {
+
+ // GenericQueryConfiguration copy first
+ super(other);
+
+ // RemoteQueryConfiguration copy
+ this.remoteId = other.getRemoteId();
+ this.remoteQueryLogic = other.getRemoteQueryLogic();
+ this.query = other.getQuery();
+ }
+
+ /**
+ * Delegates deep copy work to appropriate constructor, sets additional values specific to the provided RemoteRemoteQueryLogic
+ *
+ * @param logic
+ * - a RemoteQueryLogic instance or subclass
+ */
+ public RemoteQueryConfiguration(RemoteEventQueryLogic logic) {
+ this(logic.getConfig());
+ }
+
+ /**
+ * Factory method that instantiates an fresh RemoteQueryConfiguration
+ *
+ * @return - a clean RemoteQueryConfiguration
+ */
+ public static RemoteQueryConfiguration create() {
+ return new RemoteQueryConfiguration();
+ }
+
+ /**
+ * Factory method that returns a deep copy of the provided RemoteQueryConfiguration
+ *
+ * @param other
+ * - another instance of a RemoteQueryConfiguration
+ * @return - copy of provided RemoteQueryConfiguration
+ */
+ public static RemoteQueryConfiguration create(RemoteQueryConfiguration other) {
+ return new RemoteQueryConfiguration(other);
+ }
+
+ /**
+ * Factory method that creates a RemoteQueryConfiguration deep copy from a RemoteQueryLogic
+ *
+ * @param remoteQueryLogic
+ * - a configured RemoteQueryLogic
+ * @return - a RemoteQueryConfiguration
+ */
+ public static RemoteQueryConfiguration create(RemoteEventQueryLogic remoteQueryLogic) {
+ return create(remoteQueryLogic.getConfig());
+ }
+
+ /**
+ * Factory method that creates a RemoteQueryConfiguration from a RemoteQueryLogic and a Query
+ *
+ * @param remoteQueryLogic
+ * - a configured RemoteQueryLogic
+ * @param query
+ * - a configured Query object
+ * @return - a RemoteQueryConfiguration
+ */
+ public static RemoteQueryConfiguration create(RemoteEventQueryLogic remoteQueryLogic, Query query) {
+ RemoteQueryConfiguration config = create(remoteQueryLogic);
+ config.setQuery(query);
+ return config;
+ }
+
+ public String getRemoteId() {
+ return remoteId;
+ }
+
+ public void setRemoteId(String remoteId) {
+ this.remoteId = remoteId;
+ }
+
+ public String getRemoteQueryLogic() {
+ return remoteQueryLogic;
+ }
+
+ public void setRemoteQueryLogic(String remoteQueryLogic) {
+ this.remoteQueryLogic = remoteQueryLogic;
+ }
+
+ public Query getQuery() {
+ return query;
+ }
+
+ public void setQuery(Query query) {
+ this.query = query;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+ if (!super.equals(o))
+ return false;
+ RemoteQueryConfiguration that = (RemoteQueryConfiguration) o;
+ return Objects.equals(getRemoteId(), that.getRemoteId()) && Objects.equals(getRemoteQueryLogic(), that.getRemoteQueryLogic())
+ && Objects.equals(getQuery(), that.getQuery());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(super.hashCode(), getRemoteId(), getRemoteQueryLogic(), getQuery());
+ }
+
+}
diff --git a/warehouse/query-core/src/main/java/datawave/query/tables/RemoteEventQueryLogic.java b/warehouse/query-core/src/main/java/datawave/query/tables/RemoteEventQueryLogic.java
new file mode 100644
index 00000000000..18b866f32fc
--- /dev/null
+++ b/warehouse/query-core/src/main/java/datawave/query/tables/RemoteEventQueryLogic.java
@@ -0,0 +1,258 @@
+package datawave.query.tables;
+
+import datawave.marking.MarkingFunctions;
+import datawave.query.config.RemoteQueryConfiguration;
+import datawave.query.tables.remote.RemoteQueryLogic;
+import datawave.query.transformer.EventQueryTransformerSupport;
+import datawave.webservice.common.connection.AccumuloConnectionFactory;
+import datawave.webservice.common.logging.ThreadConfigurableLogger;
+import datawave.webservice.common.remote.RemoteQueryService;
+import datawave.webservice.query.Query;
+import datawave.webservice.query.configuration.GenericQueryConfiguration;
+import datawave.webservice.query.exception.EmptyObjectException;
+import datawave.webservice.query.exception.QueryException;
+import datawave.webservice.query.logic.BaseQueryLogic;
+import datawave.webservice.query.logic.QueryLogicTransformer;
+import datawave.webservice.query.result.event.DefaultEvent;
+import datawave.webservice.query.result.event.EventBase;
+import datawave.webservice.query.result.event.ResponseObjectFactory;
+import datawave.webservice.result.EventQueryResponseBase;
+import datawave.webservice.result.GenericResponse;
+import org.apache.accumulo.core.client.Connector;
+import org.apache.accumulo.core.data.Key;
+import org.apache.accumulo.core.data.Value;
+import org.apache.accumulo.core.security.Authorizations;
+import org.apache.log4j.Logger;
+
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Queue;
+import java.util.Set;
+
+/**
+ *
Overview
This is a query logic implementation that can handle delegating to a remote event query logic (i.e. one that returns an extension of
+ * EventQueryResponseBase).
+ */
+public class RemoteEventQueryLogic extends BaseQueryLogic implements RemoteQueryLogic {
+
+ protected static final Logger log = ThreadConfigurableLogger.getLogger(RemoteEventQueryLogic.class);
+
+ private RemoteQueryConfiguration config;
+
+ private RemoteQueryService remoteQueryService;
+
+ private QueryLogicTransformer transformerInstance = null;
+
+ /**
+ * Basic constructor
+ */
+ public RemoteEventQueryLogic() {
+ super();
+ if (log.isTraceEnabled())
+ log.trace("Creating RemoteQueryLogic: " + System.identityHashCode(this));
+ }
+
+ /**
+ * Copy constructor
+ *
+ * @param other
+ * - another ShardQueryLogic object
+ */
+ public RemoteEventQueryLogic(RemoteEventQueryLogic other) {
+ super(other);
+
+ if (log.isTraceEnabled())
+ log.trace("Creating Cloned RemoteQueryLogic: " + System.identityHashCode(this) + " from " + System.identityHashCode(other));
+
+ setRemoteQueryService(other.getRemoteQueryService());
+
+ // Set ShardQueryConfiguration variables
+ setConfig(RemoteQueryConfiguration.create(other));
+ }
+
+ public String getRemoteId() {
+ return getConfig().getRemoteId();
+ }
+
+ public void setRemoteId(String id) {
+ getConfig().setRemoteId(id);
+ }
+
+ public String getRemoteQueryLogic() {
+ return getConfig().getRemoteQueryLogic();
+ }
+
+ public void setRemoteQueryLogic(String remoteQueryLogic) {
+ getConfig().setRemoteQueryLogic(remoteQueryLogic);
+ }
+
+ public Object getCallerObject() {
+ return getPrincipal();
+ }
+
+ @Override
+ public GenericQueryConfiguration initialize(Connector connection, Query settings, Set auths) throws Exception {
+ GenericResponse createResponse = remoteQueryService.createQuery(getRemoteQueryLogic(), settings.toMap(), getCallerObject());
+ setRemoteId(createResponse.getResult());
+ return getConfig();
+ }
+
+ @Override
+ public String getPlan(Connector connection, Query settings, Set auths, boolean expandFields, boolean expandValues) throws Exception {
+ GenericResponse planResponse = remoteQueryService.planQuery(getRemoteQueryLogic(), settings.toMap());
+ return planResponse.getResult();
+ }
+
+ @Override
+ public void setupQuery(GenericQueryConfiguration genericConfig) throws Exception {
+ if (!RemoteQueryConfiguration.class.isAssignableFrom(genericConfig.getClass())) {
+ throw new QueryException("Did not receive a RemoteQueryConfiguration instance!!");
+ }
+
+ config = (RemoteQueryConfiguration) genericConfig;
+
+ // Create an iterator that returns a stream of EventBase objects
+ iterator = new RemoteQueryLogicIterator();
+ }
+
+ @Override
+ public QueryLogicTransformer getTransformer(Query settings) {
+ // a transformer that turns EventBase objects into a response
+ if (transformerInstance == null) {
+ transformerInstance = new EventBaseTransformer(settings, getMarkingFunctions(), getResponseObjectFactory());
+ }
+
+ return transformerInstance;
+ }
+
+ @Override
+ public RemoteEventQueryLogic clone() {
+ return new RemoteEventQueryLogic(this);
+ }
+
+ @Override
+ public void close() {
+
+ super.close();
+
+ log.debug("Closing RemoteQueryLogic: " + System.identityHashCode(this));
+
+ if (getRemoteId() != null) {
+ try {
+ remoteQueryService.close(getRemoteId(), getCallerObject());
+ } catch (Exception e) {
+ log.error("Failed to close remote query", e);
+ }
+ }
+ }
+
+ @Override
+ public RemoteQueryConfiguration getConfig() {
+ if (config == null) {
+ config = RemoteQueryConfiguration.create();
+ }
+
+ return config;
+ }
+
+ public void setConfig(RemoteQueryConfiguration config) {
+ this.config = config;
+ }
+
+ public RemoteQueryService getRemoteQueryService() {
+ return remoteQueryService;
+ }
+
+ public void setRemoteQueryService(RemoteQueryService remoteQueryService) {
+ this.remoteQueryService = remoteQueryService;
+ }
+
+ @Override
+ public AccumuloConnectionFactory.Priority getConnectionPriority() {
+ return AccumuloConnectionFactory.Priority.NORMAL;
+ }
+
+ @Override
+ public Set getOptionalQueryParameters() {
+ return new ShardQueryLogic().getOptionalQueryParameters();
+ }
+
+ @Override
+ public Set getRequiredQueryParameters() {
+ return new ShardQueryLogic().getRequiredQueryParameters();
+ }
+
+ @Override
+ public Set getExampleQueries() {
+ return new ShardQueryLogic().getExampleQueries();
+ }
+
+ public Query getSettings() {
+ return getConfig().getQuery();
+ }
+
+ public void setSettings(Query settings) {
+ getConfig().setQuery(settings);
+ }
+
+ private class RemoteQueryLogicIterator implements Iterator {
+ private Queue data = new LinkedList<>();
+ private boolean complete = false;
+
+ @Override
+ public boolean hasNext() {
+ if (data.isEmpty() && !complete) {
+ try {
+ EventQueryResponseBase response = (EventQueryResponseBase) remoteQueryService.next(getRemoteId(), getCallerObject());
+ if (response != null) {
+ if (response.getReturnedEvents() == 0) {
+ if (response.isPartialResults()) {
+ DefaultEvent e = new DefaultEvent();
+ e.setIntermediateResult(true);
+ data.add(e);
+ } else {
+ complete = true;
+ }
+ } else {
+ for (EventBase event : response.getEvents()) {
+ data.add(event);
+ }
+ }
+ } else {
+ // in this case we must have gotten a 204, so we are done
+ complete = true;
+ }
+ } catch (Exception e) {
+ complete = true;
+ throw new RuntimeException(e.getMessage(), e);
+ }
+ }
+ return !data.isEmpty();
+ }
+
+ @Override
+ public EventBase next() {
+ return data.poll();
+ }
+ }
+
+ private class EventBaseTransformer extends EventQueryTransformerSupport {
+
+ public EventBaseTransformer(Query settings, MarkingFunctions markingFunctions, ResponseObjectFactory responseObjectFactory) {
+ super("notable", settings, markingFunctions, responseObjectFactory);
+ }
+
+ public EventBaseTransformer(BaseQueryLogic> logic, Query settings, MarkingFunctions markingFunctions,
+ ResponseObjectFactory responseObjectFactory) {
+ super(logic, settings, markingFunctions, responseObjectFactory);
+ }
+
+ @Override
+ public EventBase transform(EventBase input) throws EmptyObjectException {
+ return input;
+ }
+
+ }
+
+}
diff --git a/warehouse/query-core/src/main/java/datawave/query/tables/remote/RemoteQueryLogic.java b/warehouse/query-core/src/main/java/datawave/query/tables/remote/RemoteQueryLogic.java
new file mode 100644
index 00000000000..18665e52d1b
--- /dev/null
+++ b/warehouse/query-core/src/main/java/datawave/query/tables/remote/RemoteQueryLogic.java
@@ -0,0 +1,11 @@
+package datawave.query.tables.remote;
+
+import datawave.webservice.common.remote.RemoteQueryService;
+import datawave.webservice.query.logic.QueryLogic;
+
+/**
+ * A remote query logic is is a query logic that uses a remote query service.
+ */
+public interface RemoteQueryLogic extends QueryLogic {
+ public void setRemoteQueryService(RemoteQueryService service);
+}
diff --git a/warehouse/query-core/src/test/java/datawave/query/tables/RemoteEventQueryLogicTest.java b/warehouse/query-core/src/test/java/datawave/query/tables/RemoteEventQueryLogicTest.java
new file mode 100644
index 00000000000..b4e75aa3118
--- /dev/null
+++ b/warehouse/query-core/src/test/java/datawave/query/tables/RemoteEventQueryLogicTest.java
@@ -0,0 +1,102 @@
+package datawave.query.tables;
+
+import datawave.webservice.common.remote.RemoteQueryService;
+import datawave.webservice.query.QueryImpl;
+import datawave.webservice.query.configuration.GenericQueryConfiguration;
+import datawave.webservice.query.result.event.DefaultEvent;
+import datawave.webservice.query.result.event.DefaultField;
+import datawave.webservice.query.result.event.EventBase;
+import datawave.webservice.result.BaseQueryResponse;
+import datawave.webservice.result.DefaultEventQueryResponse;
+import datawave.webservice.result.GenericResponse;
+import datawave.webservice.result.VoidResponse;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import static org.junit.Assert.assertEquals;
+
+public class RemoteEventQueryLogicTest {
+
+ RemoteEventQueryLogic logic = new RemoteEventQueryLogic();
+
+ @Before
+ public void setup() {
+ UUID uuid = UUID.randomUUID();
+ GenericResponse createResponse = new GenericResponse();
+ createResponse.setResult(uuid.toString());
+
+ DefaultEventQueryResponse response1 = new DefaultEventQueryResponse();
+ DefaultEvent event1 = new DefaultEvent();
+ event1.setFields(Collections.singletonList(new DefaultField("FOO1", "FOO|BAR", new HashMap(), -1L, "FOOBAR1")));
+ response1.setEvents(Collections.singletonList(event1));
+ response1.setReturnedEvents(1L);
+
+ DefaultEventQueryResponse response2 = new DefaultEventQueryResponse();
+ DefaultEvent event2 = new DefaultEvent();
+ event1.setFields(Collections.singletonList(new DefaultField("FOO2", "FOO|BAR", new HashMap(), -1L, "FOOBAR2")));
+ response2.setEvents(Collections.singletonList(event1));
+ response2.setReturnedEvents(1L);
+
+ // create a remote event query logic that has our own remote query service behind it
+ logic.setRemoteQueryService(new TestRemoteQueryService(createResponse, response1, response2));
+ logic.setRemoteQueryLogic("TestQuery");
+ }
+
+ @Test
+ public void testRemoteQuery() throws Exception {
+ GenericQueryConfiguration config = logic.initialize(null, new QueryImpl(), null);
+ logic.setupQuery(config);
+ Iterator t = logic.iterator();
+ List events = new ArrayList();
+ while (t.hasNext()) {
+ events.add(t.next());
+ }
+ assertEquals(2, events.size());
+ }
+
+ public static class TestRemoteQueryService implements RemoteQueryService {
+ GenericResponse createResponse;
+ LinkedList nextResponses;
+
+ public TestRemoteQueryService(GenericResponse createResponse, BaseQueryResponse response1, BaseQueryResponse response2) {
+ this.createResponse = createResponse;
+ this.nextResponses = new LinkedList<>();
+ nextResponses.add(response1);
+ nextResponses.add(response2);
+ }
+
+ @Override
+ public GenericResponse createQuery(String queryLogicName, Map> queryParameters, Object callerObject) {
+ return createResponse;
+ }
+
+ @Override
+ public BaseQueryResponse next(String id, Object callerObject) {
+ return nextResponses.poll();
+ }
+
+ @Override
+ public VoidResponse close(String id, Object callerObject) {
+ return new VoidResponse();
+ }
+
+ @Override
+ public GenericResponse planQuery(String queryLogicName, Map> queryParameters, Object callerObject) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public GenericResponse planQuery(String id, Object callerObject) {
+ throw new UnsupportedOperationException();
+ }
+ }
+}
diff --git a/web-services/client/src/main/java/datawave/webservice/query/QueryImpl.java b/web-services/client/src/main/java/datawave/webservice/query/QueryImpl.java
index b3ca9af3f40..f16294916e8 100644
--- a/web-services/client/src/main/java/datawave/webservice/query/QueryImpl.java
+++ b/web-services/client/src/main/java/datawave/webservice/query/QueryImpl.java
@@ -817,6 +817,8 @@ public Map> toMap() {
throw new RuntimeException("Error formatting date", e);
}
}
+ p.set(QueryParameters.QUERY_PAGETIMEOUT, Integer.toString(this.pageTimeout));
+
if (this.parameters != null) {
for (Parameter parameter : parameters) {
p.set(parameter.getParameterName(), parameter.getParameterValue());
diff --git a/web-services/common/pom.xml b/web-services/common/pom.xml
index a99dd109232..3c72ad2cf17 100644
--- a/web-services/common/pom.xml
+++ b/web-services/common/pom.xml
@@ -80,7 +80,11 @@