Added: 
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elasticsearch/index/ElasticsearchIndexWriter.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elasticsearch/index/ElasticsearchIndexWriter.java?rev=1863411&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elasticsearch/index/ElasticsearchIndexWriter.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elasticsearch/index/ElasticsearchIndexWriter.java
 Fri Jul 19 18:26:56 2019
@@ -0,0 +1,167 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.plugins.index.elasticsearch.index;
+
+import 
org.apache.jackrabbit.oak.plugins.index.elasticsearch.ElasticsearchIndexCoordinateFactory;
+import 
org.apache.jackrabbit.oak.plugins.index.elasticsearch.ElasticsearchIndexCoordinate;
+import org.apache.jackrabbit.oak.plugins.index.search.FieldNames;
+import org.apache.jackrabbit.oak.plugins.index.search.IndexDefinition;
+import 
org.apache.jackrabbit.oak.plugins.index.search.spi.editor.FulltextIndexWriter;
+import org.elasticsearch.action.delete.DeleteRequest;
+import org.elasticsearch.action.delete.DeleteResponse;
+import org.elasticsearch.action.index.IndexRequest;
+import org.elasticsearch.action.index.IndexResponse;
+import org.elasticsearch.client.RequestOptions;
+import org.elasticsearch.client.RestHighLevelClient;
+import org.elasticsearch.client.indices.CreateIndexRequest;
+import org.elasticsearch.client.indices.CreateIndexResponse;
+import org.elasticsearch.common.Strings;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentFactory;
+import org.elasticsearch.common.xcontent.XContentType;
+import org.jetbrains.annotations.NotNull;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+
+import static 
org.apache.jackrabbit.oak.plugins.index.elasticsearch.index.ElasticsearchDocument.pathToId;
+import static 
org.elasticsearch.action.support.WriteRequest.RefreshPolicy.IMMEDIATE;
+import static org.elasticsearch.action.support.WriteRequest.RefreshPolicy.NONE;
+import static org.elasticsearch.common.xcontent.ToXContent.EMPTY_PARAMS;
+import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
+
+public class ElasticsearchIndexWriter implements 
FulltextIndexWriter<ElasticsearchDocument> {
+    private static Logger LOG = 
LoggerFactory.getLogger(ElasticsearchIndexWriter.class);
+
+    private final ElasticsearchIndexCoordinate esIndexCoord;
+    private final RestHighLevelClient client;
+    private boolean shouldProvisionIndex;
+
+    private final boolean isAsync;
+
+    // TODO: use bulk API - 
https://www.elastic.co/guide/en/elasticsearch/client/java-api/current/java-docs-bulk-processor.html
+    ElasticsearchIndexWriter(@NotNull IndexDefinition indexDefinition,
+                             ElasticsearchIndexCoordinateFactory 
esIndexCoordFactory) {
+        esIndexCoord = 
esIndexCoordFactory.getElasticsearchIndexCoordinate(indexDefinition);
+        client = esIndexCoord.getClient();
+
+        // TODO: ES indexing put another bit delay before docs appear in 
search.
+        // For test without "async" indexing, we can use following hack BUT 
those where we
+        // would setup async, we'd need to find another way.
+        isAsync = 
indexDefinition.getDefinitionNodeState().getProperty("async") != null;
+
+        shouldProvisionIndex = false;
+    }
+
+    @Override
+    public void updateDocument(String path, ElasticsearchDocument doc) throws 
IOException {
+        provisionIndex();
+        IndexRequest request = new IndexRequest(esIndexCoord.getEsIndexName())
+                .id(pathToId(path))
+                // immediate refresh would slow indexing response such that 
next
+                // search would see the effect of this indexed doc. Must only 
get
+                // enabled in tests (hopefully there are no non-async indexes 
in real life)
+                .setRefreshPolicy(isAsync ? NONE : IMMEDIATE)
+                .source(doc.build(), XContentType.JSON);
+        IndexResponse response = client.index(request, RequestOptions.DEFAULT);
+        LOG.trace("update {} - {}. Response: {}", path, doc, response);
+    }
+
+    @Override
+    public void deleteDocuments(String path) throws IOException {
+        provisionIndex();
+        DeleteRequest request = new 
DeleteRequest(esIndexCoord.getEsIndexName())
+                .id(pathToId(path))
+                // immediate refresh would slow indexing response such that 
next
+                // search would see the effect of this indexed doc. Must only 
get
+                // enabled in tests (hopefully there are no non-async indexes 
in real life)
+                .setRefreshPolicy(isAsync ? NONE : IMMEDIATE);
+        DeleteResponse response = client.delete(request, 
RequestOptions.DEFAULT);
+        LOG.trace("delete {}. Response: {}", path, response);
+
+    }
+
+    @Override
+    public boolean close(long timestamp) throws IOException {
+        provisionIndex();
+        // TODO : track index updates and return accordingly
+        // TODO : if/when we do async push, this is where to wait for those 
ops to complete
+        return false;
+    }
+
+    /**
+     * This method <b>won't</b> immediately provision index. But, provision 
would be done <b>before</b>
+     * any updates are sent to the index
+     */
+    void setProvisioningRequired() {
+        shouldProvisionIndex = true;
+    }
+
+    private void provisionIndex() throws IOException {
+        if (!shouldProvisionIndex) {
+            return;
+        }
+
+        try {
+            CreateIndexRequest request = new 
CreateIndexRequest(esIndexCoord.getEsIndexName());
+
+            // provision settings
+            request.settings(Settings.builder()
+                            .put("analysis.analyzer.ancestor_analyzer.type", 
"custom")
+                            
.put("analysis.analyzer.ancestor_analyzer.tokenizer", "path_hierarchy"));
+
+            // provision mappings
+            XContentBuilder mappingBuilder = XContentFactory.jsonBuilder();
+            mappingBuilder.startObject();
+            {
+                mappingBuilder.startObject("properties");
+                {
+                    mappingBuilder.startObject(FieldNames.ANCESTORS)
+                            .field("type", "text")
+                            .field("analyzer", "ancestor_analyzer")
+                            .field("search_analyzer", "keyword")
+                            .field("search_quote_analyzer", "keyword")
+                            .endObject();
+                    mappingBuilder.startObject(FieldNames.PATH_DEPTH)
+                            .field("type", "integer")
+                            .endObject();
+                    mappingBuilder.startObject(FieldNames.SUGGEST)
+                            .field("type", "completion")
+                            .endObject();
+                    mappingBuilder.startObject(FieldNames.NOT_NULL_PROPS)
+                            .field("type", "keyword")
+                            .endObject();
+                    mappingBuilder.startObject(FieldNames.NULL_PROPS)
+                            .field("type", "keyword")
+                            .endObject();
+                }
+                mappingBuilder.endObject();
+            }
+            mappingBuilder.endObject();
+            request.mapping(mappingBuilder);
+
+            String requestMsg = 
Strings.toString(request.toXContent(jsonBuilder(), EMPTY_PARAMS));
+            CreateIndexResponse response = client.indices().create(request, 
RequestOptions.DEFAULT);
+
+            LOG.info("Updated settings {}. Response acknowledged: {}", 
requestMsg, response.isAcknowledged());
+        } finally {
+            shouldProvisionIndex = false;
+        }
+    }
+}

Propchange: 
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elasticsearch/index/ElasticsearchIndexWriter.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: 
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elasticsearch/index/ElasticsearchIndexWriterFactory.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elasticsearch/index/ElasticsearchIndexWriterFactory.java?rev=1863411&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elasticsearch/index/ElasticsearchIndexWriterFactory.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elasticsearch/index/ElasticsearchIndexWriterFactory.java
 Fri Jul 19 18:26:56 2019
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.plugins.index.elasticsearch.index;
+
+import 
org.apache.jackrabbit.oak.plugins.index.elasticsearch.ElasticsearchIndexCoordinateFactory;
+import org.apache.jackrabbit.oak.plugins.index.search.IndexDefinition;
+import 
org.apache.jackrabbit.oak.plugins.index.search.spi.editor.FulltextIndexWriterFactory;
+import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+import org.jetbrains.annotations.NotNull;
+
+public class ElasticsearchIndexWriterFactory implements 
FulltextIndexWriterFactory<ElasticsearchDocument> {
+    private final ElasticsearchIndexCoordinateFactory esIndexCoordFactory;
+
+    ElasticsearchIndexWriterFactory(@NotNull 
ElasticsearchIndexCoordinateFactory esIndexCoordFactory) {
+        this.esIndexCoordFactory = esIndexCoordFactory;
+    }
+
+    @Override
+    public ElasticsearchIndexWriter newInstance(IndexDefinition definition, 
NodeBuilder definitionBuilder, boolean reindex) {
+        return new ElasticsearchIndexWriter(definition, esIndexCoordFactory);
+    }
+}

Propchange: 
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elasticsearch/index/ElasticsearchIndexWriterFactory.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: 
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elasticsearch/query/ElasticsearchIndex.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elasticsearch/query/ElasticsearchIndex.java?rev=1863411&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elasticsearch/query/ElasticsearchIndex.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elasticsearch/query/ElasticsearchIndex.java
 Fri Jul 19 18:26:56 2019
@@ -0,0 +1,121 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.plugins.index.elasticsearch.query;
+
+import 
org.apache.jackrabbit.oak.plugins.index.elasticsearch.ElasticsearchIndexCoordinateFactory;
+import org.apache.jackrabbit.oak.plugins.index.search.IndexNode;
+import org.apache.jackrabbit.oak.plugins.index.search.SizeEstimator;
+import org.apache.jackrabbit.oak.plugins.index.search.spi.query.FulltextIndex;
+import 
org.apache.jackrabbit.oak.plugins.index.search.spi.query.FulltextIndexPlanner;
+import org.apache.jackrabbit.oak.spi.query.Cursor;
+import org.apache.jackrabbit.oak.spi.query.Filter;
+import org.apache.jackrabbit.oak.spi.query.QueryLimits;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+import org.elasticsearch.common.Strings;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Iterator;
+import java.util.function.Predicate;
+
+import static 
org.apache.jackrabbit.oak.plugins.index.IndexConstants.TYPE_PROPERTY_NAME;
+import static 
org.apache.jackrabbit.oak.plugins.index.elasticsearch.ElasticsearchIndexConstants.TYPE_ELASTICSEARCH;
+
+public class ElasticsearchIndex extends FulltextIndex {
+    private static final Predicate<NodeState> 
ELASTICSEARH_INDEX_DEFINITION_PREDICATE =
+            state -> 
TYPE_ELASTICSEARCH.equals(state.getString(TYPE_PROPERTY_NAME));
+
+    // higher than some threshold below which the query should rather be 
answered by something else if possible
+    private static double MIN_COST = 100.1;
+
+    private final ElasticsearchIndexCoordinateFactory esIndexCoordFactory;
+    private final NodeState root;
+
+    ElasticsearchIndex(@NotNull ElasticsearchIndexCoordinateFactory 
esIndexCoordFactory, @NotNull NodeState root) {
+        this.esIndexCoordFactory = esIndexCoordFactory;
+        this.root = root;
+    }
+
+    @Override
+    protected String getType() {
+        return TYPE_ELASTICSEARCH;
+    }
+
+    @Override
+    protected SizeEstimator getSizeEstimator(IndexPlan plan) {
+        return () -> {
+            // TODO: implement nicely - possible use solr impl's LMSEstimator
+            return 2L * (long)MIN_COST;
+        };
+    }
+
+    @Override
+    protected Predicate<NodeState> getIndexDefinitionPredicate() {
+        return ELASTICSEARH_INDEX_DEFINITION_PREDICATE;
+    }
+
+    @Override
+    public double getMinimumCost() {
+        return MIN_COST;
+    }
+
+    @Override
+    public String getIndexName() {
+        return "elasticsearch";
+    }
+
+    @Override
+    protected ElasticsearchIndexNode acquireIndexNode(IndexPlan plan) {
+        return (ElasticsearchIndexNode)super.acquireIndexNode(plan);
+    }
+
+    @Override
+    protected IndexNode acquireIndexNode(String indexPath) {
+        return ElasticsearchIndexNode.fromIndexPath(root, indexPath);
+    }
+
+    @Override
+    protected String getFulltextRequestString(IndexPlan plan, IndexNode 
indexNode) {
+        return 
Strings.toString(ElasticsearchResultRowIterator.getESRequest(plan, 
getPlanResult(plan)));
+    }
+
+    @Override
+    public Cursor query(IndexPlan plan, NodeState rootState) {
+        final Filter filter = plan.getFilter();
+
+        // TODO: sorting
+
+        final FulltextIndexPlanner.PlanResult pr = getPlanResult(plan);
+        QueryLimits settings = filter.getQueryLimits();
+
+        Iterator<FulltextResultRow> itr = new 
ElasticsearchResultRowIterator(esIndexCoordFactory,
+                filter, pr, plan,
+                acquireIndexNode(plan), FulltextIndex::shouldInclude);
+        SizeEstimator sizeEstimator = getSizeEstimator(plan);
+
+        /*
+        TODO: sync (nrt too??)
+        if (pr.hasPropertyIndexResult() || 
pr.evaluateSyncNodeTypeRestriction()) {
+            itr = mergePropertyIndexResult(plan, rootState, itr);
+        }
+        */
+
+        // no concept of rewound in ES (even if it might be doing it 
internally, we can't do much about it
+        IteratorRewoundStateProvider rewoundStateProvider = () -> 0;
+
+        return new FulltextPathCursor(itr, rewoundStateProvider, plan, 
settings, sizeEstimator);
+    }
+}

Propchange: 
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elasticsearch/query/ElasticsearchIndex.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: 
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elasticsearch/query/ElasticsearchIndexNode.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elasticsearch/query/ElasticsearchIndexNode.java?rev=1863411&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elasticsearch/query/ElasticsearchIndexNode.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elasticsearch/query/ElasticsearchIndexNode.java
 Fri Jul 19 18:26:56 2019
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.plugins.index.elasticsearch.query;
+
+import 
org.apache.jackrabbit.oak.plugins.index.elasticsearch.ElasticsearchIndexDefinition;
+import org.apache.jackrabbit.oak.plugins.index.search.IndexNode;
+import org.apache.jackrabbit.oak.plugins.index.search.IndexStatistics;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+import org.apache.jackrabbit.oak.spi.state.NodeStateUtils;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public class ElasticsearchIndexNode implements IndexNode {
+
+    private final ElasticsearchIndexDefinition indexDefinition;
+
+    static ElasticsearchIndexNode fromIndexPath(@NotNull NodeState root, 
@NotNull String indexPath) {
+        NodeState indexNS = NodeStateUtils.getNode(root, indexPath);
+        ElasticsearchIndexDefinition indexDefinition = new 
ElasticsearchIndexDefinition(root, indexNS, indexPath);
+        return new ElasticsearchIndexNode(indexDefinition);
+    }
+
+    private ElasticsearchIndexNode(ElasticsearchIndexDefinition 
indexDefinition) {
+        this.indexDefinition = indexDefinition;
+    }
+
+    @Override
+    public void release() {
+        // do nothing
+    }
+
+    @Override
+    public ElasticsearchIndexDefinition getDefinition() {
+        return indexDefinition;
+    }
+
+    @Override
+    public int getIndexNodeId() {
+        // TODO: does it matter that we simply return 0 as there's no 
observation based _refresh_ going on here
+        // and we always defer to ES to its own thing
+        return 0;
+    }
+
+    @Override
+    public @Nullable IndexStatistics getIndexStatistics() {
+        return new ElasticsearchIndexStatistics();
+    }
+}

Propchange: 
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elasticsearch/query/ElasticsearchIndexNode.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: 
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elasticsearch/query/ElasticsearchIndexProvider.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elasticsearch/query/ElasticsearchIndexProvider.java?rev=1863411&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elasticsearch/query/ElasticsearchIndexProvider.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elasticsearch/query/ElasticsearchIndexProvider.java
 Fri Jul 19 18:26:56 2019
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.plugins.index.elasticsearch.query;
+
+import com.google.common.collect.ImmutableList;
+import 
org.apache.jackrabbit.oak.plugins.index.elasticsearch.ElasticsearchIndexCoordinateFactory;
+import org.apache.jackrabbit.oak.spi.query.QueryIndex;
+import org.apache.jackrabbit.oak.spi.query.QueryIndexProvider;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+
+public class ElasticsearchIndexProvider implements QueryIndexProvider {
+    private final ElasticsearchIndexCoordinateFactory esIndexCoordFactory;
+
+    public ElasticsearchIndexProvider(@NotNull 
ElasticsearchIndexCoordinateFactory esIndexCoordFactory) {
+        this.esIndexCoordFactory = esIndexCoordFactory;
+    }
+
+    @Override
+    public @NotNull List<? extends QueryIndex> getQueryIndexes(NodeState 
nodeState) {
+        return ImmutableList.of(new ElasticsearchIndex(esIndexCoordFactory, 
nodeState));
+    }
+}

Propchange: 
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elasticsearch/query/ElasticsearchIndexProvider.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: 
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elasticsearch/query/ElasticsearchIndexStatistics.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elasticsearch/query/ElasticsearchIndexStatistics.java?rev=1863411&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elasticsearch/query/ElasticsearchIndexStatistics.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elasticsearch/query/ElasticsearchIndexStatistics.java
 Fri Jul 19 18:26:56 2019
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.plugins.index.elasticsearch.query;
+
+import org.apache.jackrabbit.oak.plugins.index.search.IndexStatistics;
+
+public class ElasticsearchIndexStatistics implements IndexStatistics {
+    @Override
+    public int numDocs() {
+        // TODO: some large value until we implement statistics
+        return 100000;
+    }
+
+    @Override
+    public int getDocCountFor(String key) {
+        // TODO: some large-ish value until we implement statistics
+        return 1000;
+    }
+}

Propchange: 
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elasticsearch/query/ElasticsearchIndexStatistics.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: 
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elasticsearch/query/ElasticsearchResultRowIterator.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elasticsearch/query/ElasticsearchResultRowIterator.java?rev=1863411&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elasticsearch/query/ElasticsearchResultRowIterator.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elasticsearch/query/ElasticsearchResultRowIterator.java
 Fri Jul 19 18:26:56 2019
@@ -0,0 +1,717 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.plugins.index.elasticsearch.query;
+
+import com.google.common.collect.AbstractIterator;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Queues;
+import org.apache.jackrabbit.oak.api.Type;
+import org.apache.jackrabbit.oak.commons.PathUtils;
+import org.apache.jackrabbit.oak.commons.PerfLogger;
+import 
org.apache.jackrabbit.oak.plugins.index.elasticsearch.ElasticsearchIndexCoordinateFactory;
+import 
org.apache.jackrabbit.oak.plugins.index.elasticsearch.ElasticsearchIndexDefinition;
+import org.apache.jackrabbit.oak.plugins.index.search.FieldNames;
+import org.apache.jackrabbit.oak.plugins.index.search.IndexDefinition;
+import org.apache.jackrabbit.oak.plugins.index.search.PropertyDefinition;
+import org.apache.jackrabbit.oak.plugins.index.search.spi.query.FulltextIndex;
+import 
org.apache.jackrabbit.oak.plugins.index.search.spi.query.FulltextIndexPlanner.PlanResult;
+import org.apache.jackrabbit.oak.spi.query.Filter;
+import org.apache.jackrabbit.oak.spi.query.QueryConstants;
+import org.apache.jackrabbit.oak.spi.query.QueryIndex.IndexPlan;
+import org.apache.jackrabbit.oak.spi.query.fulltext.*;
+import org.elasticsearch.action.search.SearchResponse;
+import org.elasticsearch.index.query.BoolQueryBuilder;
+import org.elasticsearch.index.query.QueryBuilder;
+import org.elasticsearch.index.query.QueryBuilders;
+import org.elasticsearch.search.SearchHit;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jcr.PropertyType;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.util.ArrayList;
+import java.util.Deque;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+import static org.apache.jackrabbit.JcrConstants.JCR_MIXINTYPES;
+import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE;
+import static org.apache.jackrabbit.oak.api.Type.STRING;
+import static org.apache.jackrabbit.oak.commons.PathUtils.denotesRoot;
+import static org.apache.jackrabbit.oak.commons.PathUtils.getParentPath;
+import static 
org.apache.jackrabbit.oak.plugins.index.elasticsearch.util.TermQueryBuilderFactory.*;
+import static 
org.apache.jackrabbit.oak.plugins.index.search.spi.query.FulltextIndex.isNodePath;
+import static org.apache.jackrabbit.oak.spi.query.QueryConstants.JCR_PATH;
+import static org.apache.jackrabbit.util.ISO8601.parse;
+import static org.elasticsearch.index.query.QueryBuilders.*;
+
+public class ElasticsearchResultRowIterator extends 
AbstractIterator<FulltextIndex.FulltextResultRow> {
+    private static final Logger LOG = LoggerFactory
+            .getLogger(ElasticsearchResultRowIterator.class);
+    private static final PerfLogger PERF_LOGGER =
+            new 
PerfLogger(LoggerFactory.getLogger(ElasticsearchResultRowIterator.class.getName()
 + ".perf"));
+
+    // TODO: oak-lucene gets this via WildcardQuery class. See if ES also 
exposes these consts
+    private static final char WILDCARD_STRING = '*';
+    private static final char WILDCARD_CHAR = '?';
+
+    /**
+     * Batch size for fetching results from queries.
+     */
+    private static final int ELASTICSEARCH_QUERY_BATCH_SIZE = 1000;
+
+    private static final int ELASTICSEARCH_QUERY_MAX_BATCH_SIZE = 10000;
+
+
+    private final Deque<FulltextIndex.FulltextResultRow> queue = 
Queues.newArrayDeque();
+    // TODO : find if ES can return dup docs - if so how to avoid
+//    private final Set<String> seenPaths = Sets.newHashSet();
+    private SearchHit lastDoc;
+    private int nextBatchSize = ELASTICSEARCH_QUERY_BATCH_SIZE;
+    private boolean noDocs = false;
+
+    private final ElasticsearchIndexCoordinateFactory esIndexCoordFactory;
+    private final Filter filter;
+    private final PlanResult pr;
+    private final IndexPlan plan;
+    private final ElasticsearchIndexNode indexNode;
+    private final RowInclusionPredicate rowInclusionPredicate;
+
+    ElasticsearchResultRowIterator(@NotNull 
ElasticsearchIndexCoordinateFactory esIndexCoordFactory,
+                                   @NotNull Filter filter,
+                                   @NotNull PlanResult pr,
+                                   @NotNull IndexPlan plan,
+                                   ElasticsearchIndexNode indexNode,
+                                   RowInclusionPredicate rowInclusionPredicate
+    ) {
+        this.esIndexCoordFactory = esIndexCoordFactory;
+        this.filter = filter;
+        this.pr = pr;
+        this.plan = plan;
+        this.indexNode = indexNode;
+        this.rowInclusionPredicate = rowInclusionPredicate != null ? 
rowInclusionPredicate : RowInclusionPredicate.NOOP;
+    }
+
+    @Override
+    protected FulltextIndex.FulltextResultRow computeNext() {
+        if (!queue.isEmpty() || loadDocs()) {
+            return queue.remove();
+        } else {
+            return endOfData();
+        }
+    }
+
+    /**
+     * Loads the lucene documents in batches
+     * @return true if any document is loaded
+     */
+    private boolean loadDocs() {
+
+        if (noDocs) {
+            return false;
+        }
+
+        SearchHit lastDocToRecord = null;
+
+        checkState(indexNode != null);
+        try {
+            ElasticsearchSearcher searcher = getCurrentSearcher(indexNode);
+            QueryBuilder query = getESRequest(plan, pr);
+            // TODO: custom scoring
+
+            SearchResponse docs;
+            long start = PERF_LOGGER.start();
+            while (true) {
+                LOG.debug("loading {} entries for query {}", nextBatchSize, 
query);
+                docs = searcher.search(query, nextBatchSize);
+
+                SearchHit[] searchHits = docs.getHits().getHits();
+                PERF_LOGGER.end(start, -1, "{} ...", searchHits.length);
+
+                if (searchHits.length < nextBatchSize) {
+                    noDocs = true;
+                }
+
+                nextBatchSize = (int) Math.min(nextBatchSize * 2L, 
ELASTICSEARCH_QUERY_MAX_BATCH_SIZE);
+
+                // TODO: faceting
+
+                // TODO: excerpt
+
+                // TODO: explanation
+
+                // TODO: sim search
+
+                for (SearchHit doc : searchHits) {
+                    // TODO : excerpts
+
+                    FulltextIndex.FulltextResultRow row = convertToRow(doc);
+                    if (row != null) {
+                        queue.add(row);
+                    }
+                    lastDocToRecord = doc;
+                }
+
+                if (queue.isEmpty() && searchHits.length > 0) {
+                    //queue is still empty but more results can be fetched
+                    //from Lucene so still continue
+                    lastDoc = lastDocToRecord;
+                } else {
+                    break;
+                }
+            }
+
+            // TODO: spellcheck else if 
(luceneRequestFacade.getLuceneRequest() instanceof 
SpellcheckHelper.SpellcheckQuery) {
+            // TODO: suggest } else if (luceneRequestFacade.getLuceneRequest() 
instanceof SuggestHelper.SuggestQuery) {
+        } catch (Exception e) {
+            LOG.warn("query via {} failed.", this, e);
+        } finally {
+            indexNode.release();
+        }
+
+        if (lastDocToRecord != null) {
+            this.lastDoc = lastDocToRecord;
+        }
+
+        return !queue.isEmpty();
+    }
+
+    private ElasticsearchSearcher getCurrentSearcher(ElasticsearchIndexNode 
indexNode) {
+        return new ElasticsearchSearcher(esIndexCoordFactory, indexNode);
+    }
+
+    private FulltextIndex.FulltextResultRow convertToRow(SearchHit hit) throws 
IOException {
+        String id = hit.getId();
+        String path = idToPath(id);
+        if (path != null) {
+            if ("".equals(path)) {
+                path = "/";
+            }
+            if (pr.isPathTransformed()) {
+                String originalPath = path;
+                path = pr.transformPath(path);
+
+                if (path == null) {
+                    LOG.trace("Ignoring path {} : Transformation returned 
null", originalPath);
+                    return null;
+                }
+            }
+
+            boolean shouldIncludeForHierarchy = 
rowInclusionPredicate.shouldInclude(path, plan);
+            LOG.trace("Matched path {}; shouldIncludeForHierarchy: {}", path, 
shouldIncludeForHierarchy);
+            return shouldIncludeForHierarchy ? new 
FulltextIndex.FulltextResultRow(path, hit.getScore(), null,
+                    null, null)
+                    : null;
+        }
+        return null;
+    }
+
+    public interface RowInclusionPredicate {
+        boolean shouldInclude(@NotNull String path, @NotNull IndexPlan plan);
+
+        RowInclusionPredicate NOOP = (@NotNull String path, @NotNull IndexPlan 
plan) -> true;
+    }
+
+    /**
+     * Get the Elasticsearch query for the given filter.
+     *
+     * @param plan index plan containing filter details
+     * @param planResult
+     * @return the Lucene query
+     */
+    static QueryBuilder getESRequest(IndexPlan plan, PlanResult planResult) {
+        List<QueryBuilder> qs = new ArrayList<>();
+        Filter filter = plan.getFilter();
+        FullTextExpression ft = filter.getFullTextConstraint();
+        ElasticsearchIndexDefinition defn = (ElasticsearchIndexDefinition) 
planResult.indexDefinition;
+
+        if (ft != null) {
+            qs.add(getFullTextQuery(ft, planResult));
+        } else {
+            // there might be no full-text constraint
+            // when using the LowCostLuceneIndexProvider
+            // which is used for testing
+        }
+
+
+        //Check if native function is supported
+        Filter.PropertyRestriction pr = null;
+        if (defn.hasFunctionDefined()) {
+            pr = filter.getPropertyRestriction(defn.getFunctionName());
+        }
+
+        if (pr != null) {
+            String query = 
String.valueOf(pr.first.getValue(pr.first.getType()));
+            // TODO: more like this
+
+            // TODO: spellcheck
+
+            // TODO: suggest
+
+            qs.add(QueryBuilders.queryStringQuery(query));
+        } else if (planResult.evaluateNonFullTextConstraints()) {
+            addNonFullTextConstraints(qs, plan, planResult);
+        }
+
+        // TODO: sort with no other restriction
+
+        if (qs.size() == 0) {
+
+            // TODO: what happens here in planning mode (specially, apparently 
for things like rep:similar)
+
+            //For purely nodeType based queries all the documents would have to
+            //be returned (if the index definition has a single rule)
+            if (planResult.evaluateNodeTypeRestriction()) {
+                return matchAllQuery();
+            }
+
+            throw new IllegalStateException("No query created for filter " + 
filter);
+        }
+        return performAdditionalWraps(qs);
+    }
+
+    private static QueryBuilder getFullTextQuery(FullTextExpression ft, final 
PlanResult pr) {
+        // a reference to the query, so it can be set in the visitor
+        // (a "non-local return")
+        final AtomicReference<QueryBuilder> result = new AtomicReference<>();
+        ft.accept(new FullTextVisitor() {
+
+            @Override
+            public boolean visit(FullTextContains contains) {
+                visitTerm(contains.getPropertyName(), contains.getRawText(), 
null, contains.isNot());
+                return true;
+            }
+
+            @Override
+            public boolean visit(FullTextOr or) {
+                BoolQueryBuilder q = boolQuery();
+                for (FullTextExpression e : or.list) {
+                    QueryBuilder x = getFullTextQuery(e, pr);
+                    q.should(x);
+                }
+                result.set(q);
+                return true;
+            }
+
+            @Override
+            public boolean visit(FullTextAnd and) {
+                BoolQueryBuilder q = boolQuery();
+                for (FullTextExpression e : and.list) {
+                    QueryBuilder x = getFullTextQuery(e, pr);
+                    // TODO: see OAK-2434 and see if ES also can't work 
without unwrapping
+                    /* Only unwrap the clause if MUST_NOT(x) */
+                    boolean hasMustNot = false;
+                    if (x instanceof BoolQueryBuilder) {
+                        BoolQueryBuilder bq = (BoolQueryBuilder) x;
+                        if (bq.mustNot().size() == 1
+                            // no other clauses
+                                && bq.should().isEmpty() && 
bq.must().isEmpty() && bq.filter().isEmpty()) {
+                            hasMustNot = true;
+                            q.mustNot(bq.mustNot().get(0));
+                        }
+                    }
+
+                    if (!hasMustNot) {
+                        q.must(x);
+                    }
+                }
+                result.set(q);
+                return true;
+            }
+
+            @Override
+            public boolean visit(FullTextTerm term) {
+                return visitTerm(term.getPropertyName(), term.getText(), 
term.getBoost(), term.isNot());
+            }
+
+            private boolean visitTerm(String propertyName, String text, String 
boost, boolean not) {
+                String p = getLuceneFieldName(propertyName, pr);
+                QueryBuilder q = tokenToQuery(text, p, pr);
+                if (q == null) {
+                    return false;
+                }
+                if (boost != null) {
+                    q.boost(Float.parseFloat(boost));
+                }
+                if (not) {
+                    BoolQueryBuilder bq = boolQuery();
+                    bq.mustNot(q);
+                    result.set(bq);
+                } else {
+                    result.set(q);
+                }
+                return true;
+            }
+        });
+        return result.get();
+    }
+
+    private static QueryBuilder tokenToQuery(String text, String fieldName, 
PlanResult pr) {
+        QueryBuilder ret;
+        IndexDefinition.IndexingRule indexingRule = pr.indexingRule;
+        //Expand the query on fulltext field
+        if (FieldNames.FULLTEXT.equals(fieldName) &&
+                !indexingRule.getNodeScopeAnalyzedProps().isEmpty()) {
+            BoolQueryBuilder in = boolQuery();
+            for (PropertyDefinition pd : 
indexingRule.getNodeScopeAnalyzedProps()) {
+                QueryBuilder q = 
matchQuery(FieldNames.createAnalyzedFieldName(pd.name), text);
+                q.boost(pd.boost);
+                in.should(q);
+            }
+
+            //Add the query for actual fulltext field also. That query would
+            //not be boosted
+            in.should(matchQuery(fieldName, text));
+            ret = in;
+        } else {
+            ret = matchQuery(fieldName, text);
+        }
+
+        return ret;
+    }
+
+    private static String getLuceneFieldName(@Nullable String p, PlanResult 
pr) {
+        if (p == null) {
+            return FieldNames.FULLTEXT;
+        }
+
+        if (isNodePath(p)) {
+            if (pr.isPathTransformed()) {
+                p = PathUtils.getName(p);
+            } else {
+                //Get rid of /* as aggregated fulltext field name is the
+                //node relative path
+                p = 
FieldNames.createFulltextFieldName(PathUtils.getParentPath(p));
+            }
+        } else {
+            if (pr.isPathTransformed()) {
+                p = PathUtils.getName(p);
+            }
+            p = FieldNames.createAnalyzedFieldName(p);
+        }
+
+        if ("*".equals(p)) {
+            p = FieldNames.FULLTEXT;
+        }
+        return p;
+    }
+
+    /**
+     * Perform additional wraps on the list of queries to allow, for example, 
the NOT CONTAINS to
+     * play properly when sent to lucene.
+     *
+     * @param qs the list of queries. Cannot be null.
+     * @return the request facade
+     */
+    @NotNull
+    private static QueryBuilder performAdditionalWraps(@NotNull 
List<QueryBuilder> qs) {
+        checkNotNull(qs);
+        if (qs.size() == 1) {
+            // we don't need to worry about all-negatives in a bool query as
+            // BoolQueryBuilder.adjustPureNegative is on by default anyway
+            return qs.get(0);
+        }
+        BoolQueryBuilder bq = new BoolQueryBuilder();
+        // TODO: while I've attempted to translate oak-lucene code to 
corresponding ES one but I am
+        // unable to make sense of this code
+        for (QueryBuilder q : qs) {
+            boolean unwrapped = false;
+            if (q instanceof BoolQueryBuilder) {
+                unwrapped = unwrapMustNot((BoolQueryBuilder) q, bq);
+            }
+
+            if (!unwrapped) {
+                bq.must(q);
+            }
+        }
+        return bq;
+    }
+
+    /**
+     * unwraps any NOT clauses from the provided boolean query into another 
boolean query.
+     *
+     * @param input the query to be analysed for the existence of NOT clauses. 
Cannot be null.
+     * @param output the query where the unwrapped NOTs will be saved into. 
Cannot be null.
+     * @return true if there where at least one unwrapped NOT. false otherwise.
+     */
+    private static boolean unwrapMustNot(@NotNull BoolQueryBuilder input, 
@NotNull BoolQueryBuilder output) {
+        checkNotNull(input);
+        checkNotNull(output);
+        boolean unwrapped = false;
+        for (QueryBuilder mustNot : input.mustNot()) {
+            output.mustNot(mustNot);
+            unwrapped = true;
+        }
+        if (unwrapped) {
+            // if we have unwrapped "must not" conditions,
+            // then we need to unwrap "must" conditions as well
+            for (QueryBuilder must : input.must()) {
+                output.must(must);
+            }
+        }
+
+        return unwrapped;
+    }
+
+    private static void addNonFullTextConstraints(List<QueryBuilder> qs,
+                                                  IndexPlan plan, PlanResult 
planResult) {
+        Filter filter = plan.getFilter();
+        IndexDefinition defn = planResult.indexDefinition;
+        if (!filter.matchesAllTypes()) {
+            addNodeTypeConstraints(planResult.indexingRule, qs, filter);
+        }
+
+        String path = getPathRestriction(plan);
+        switch (filter.getPathRestriction()) {
+            case ALL_CHILDREN:
+                if (defn.evaluatePathRestrictions()) {
+                    if ("/".equals(path)) {
+                        break;
+                    }
+                    qs.add(newAncestorQuery(path));
+                }
+                break;
+            case DIRECT_CHILDREN:
+                if (defn.evaluatePathRestrictions()) {
+                    BoolQueryBuilder bq = boolQuery();
+                    bq.must(newAncestorQuery(path));
+                    bq.must(newDepthQuery(path, planResult));
+                    qs.add(bq);
+                }
+                break;
+            case EXACT:
+                // For transformed paths, we can only add path restriction if 
absolute path to property can be
+                // deduced
+                if (planResult.isPathTransformed()) {
+                    String parentPathSegment = 
planResult.getParentPathSegment();
+                    if ( ! 
Iterables.any(PathUtils.elements(parentPathSegment), "*"::equals)) {
+                        qs.add(newPathQuery(path + parentPathSegment));
+                    }
+                } else {
+                    qs.add(newPathQuery(path));
+                }
+                break;
+            case PARENT:
+                if (denotesRoot(path)) {
+                    // there's no parent of the root node
+                    // we add a path that can not possibly occur because there
+                    // is no way to say "match no documents" in Lucene
+                    qs.add(newPathQuery("///"));
+                } else {
+                    // For transformed paths, we can only add path restriction 
if absolute path to property can be
+                    // deduced
+                    if (planResult.isPathTransformed()) {
+                        String parentPathSegment = 
planResult.getParentPathSegment();
+                        if ( ! 
Iterables.any(PathUtils.elements(parentPathSegment), "*"::equals)) {
+                            qs.add(newPathQuery(getParentPath(path) + 
parentPathSegment));
+                        }
+                    } else {
+                        qs.add(newPathQuery(getParentPath(path)));
+                    }
+                }
+                break;
+            case NO_RESTRICTION:
+                break;
+        }
+
+        for (Filter.PropertyRestriction pr : filter.getPropertyRestrictions()) 
{
+            String name = pr.propertyName;
+
+            if (QueryConstants.REP_EXCERPT.equals(name) || 
QueryConstants.OAK_SCORE_EXPLANATION.equals(name)
+                    || QueryConstants.REP_FACET.equals(name)) {
+                continue;
+            }
+
+            if (QueryConstants.RESTRICTION_LOCAL_NAME.equals(name)) {
+                if (planResult.evaluateNodeNameRestriction()) {
+                    QueryBuilder q = createNodeNameQuery(pr);
+                    if (q != null) {
+                        qs.add(q);
+                    }
+                }
+                continue;
+            }
+
+            if (pr.first != null && pr.first.equals(pr.last) && 
pr.firstIncluding
+                    && pr.lastIncluding) {
+                String first = pr.first.getValue(STRING);
+                first = first.replace("\\", "");
+                if (JCR_PATH.equals(name)) {
+                    qs.add(newPathQuery(first));
+                    continue;
+                } else if ("*".equals(name)) {
+                    //TODO Revisit reference constraint. For performant impl
+                    //references need to be indexed in a different manner
+                    addReferenceConstraint(first, qs);
+                    continue;
+                }
+            }
+
+            PropertyDefinition pd = planResult.getPropDefn(pr);
+            if (pd == null) {
+                continue;
+            }
+
+            QueryBuilder q = createQuery(planResult.getPropertyName(pr), pr, 
pd);
+            if (q != null) {
+                qs.add(q);
+            }
+        }
+    }
+
+    private static void addNodeTypeConstraints(IndexDefinition.IndexingRule 
defn, List<QueryBuilder> qs, Filter filter) {
+        BoolQueryBuilder bq = boolQuery();
+        PropertyDefinition primaryType = defn.getConfig(JCR_PRIMARYTYPE);
+        //TODO OAK-2198 Add proper nodeType query support
+
+        if (primaryType != null && primaryType.propertyIndex) {
+            for (String type : filter.getPrimaryTypes()) {
+                bq.should(newNodeTypeQuery(type));
+            }
+        }
+
+        PropertyDefinition mixinType = defn.getConfig(JCR_MIXINTYPES);
+        if (mixinType != null && mixinType.propertyIndex) {
+            for (String type : filter.getMixinTypes()) {
+                bq.should(newMixinTypeQuery(type));
+            }
+        }
+
+        if (bq.hasClauses()) {
+            qs.add(bq);
+        }
+    }
+
+    // TODO: utilize FulltextIndex#getPathRestriction instead of copying it 
here
+    private static String getPathRestriction(IndexPlan plan) {
+        Filter f = plan.getFilter();
+        String pathPrefix = plan.getPathPrefix();
+        if (pathPrefix.isEmpty()) {
+            return f.getPath();
+        }
+        String relativePath = PathUtils.relativize(pathPrefix, f.getPath());
+        return "/" + relativePath;
+    }
+
+    private static QueryBuilder createNodeNameQuery(Filter.PropertyRestriction 
pr) {
+        String first = pr.first != null ? pr.first.getValue(STRING) : null;
+        if (pr.first != null && pr.first.equals(pr.last) && pr.firstIncluding
+                && pr.lastIncluding) {
+            // [property]=[value]
+            return termQuery(FieldNames.NODE_NAME, first);
+        }
+
+        if (pr.isLike) {
+            return createLikeQuery(FieldNames.NODE_NAME, first);
+        }
+
+        throw new IllegalStateException("For nodeName queries only EQUALS and 
LIKE are supported " + pr);
+    }
+
+    private static QueryBuilder createLikeQuery(String name, String first) {
+        first = first.replace('%', WILDCARD_STRING);
+        first = first.replace('_', WILDCARD_CHAR);
+
+        QueryBuilders.wildcardQuery(name, first);
+
+        int indexOfWS = first.indexOf(WILDCARD_STRING);
+        int indexOfWC = first.indexOf(WILDCARD_CHAR);
+        int len = first.length();
+
+        if (indexOfWS == len || indexOfWC == len) {
+            // remove trailing "*" for prefixquery
+            first = first.substring(0, first.length() - 1);
+            if (JCR_PATH.equals(name)) {
+                return newPrefixPathQuery(first);
+            } else {
+                return newPrefixQuery(name, first);
+            }
+        } else {
+            if (JCR_PATH.equals(name)) {
+                return newWildcardPathQuery(first);
+            } else {
+                return newWildcardQuery(name, first);
+            }
+        }
+    }
+
+    private static void addReferenceConstraint(String uuid, List<QueryBuilder> 
qs) {
+        // TODO: this seems very bad as a query - do we really want to support 
it. In fact, is it even used?
+        // reference query
+        qs.add(QueryBuilders.multiMatchQuery(uuid));
+    }
+
+    @Nullable
+    private static QueryBuilder createQuery(String propertyName, 
Filter.PropertyRestriction pr,
+                                     PropertyDefinition defn) {
+        int propType = determinePropertyType(defn, pr);
+
+        if (pr.isNullRestriction()) {
+            return newNullPropQuery(defn.name);
+        }
+
+        //If notNullCheckEnabled explicitly enabled use the simple TermQuery
+        //otherwise later fallback to range query
+        if (pr.isNotNullRestriction() && defn.notNullCheckEnabled) {
+            return newNotNullPropQuery(defn.name);
+        }
+
+        QueryBuilder in;
+        switch (propType) {
+            case PropertyType.DATE: {
+                in = newPropertyRestrictionQuery(propertyName, false, pr,
+                        value -> parse(value.getValue(Type.DATE)).getTime());
+                break;
+            }
+            case PropertyType.DOUBLE: {
+                in = newPropertyRestrictionQuery(propertyName, false, pr,
+                        value -> value.getValue(Type.DOUBLE));
+                break;
+            }
+            case PropertyType.LONG: {
+                in = newPropertyRestrictionQuery(propertyName, false, pr,
+                        value -> value.getValue(Type.LONG));
+                break;
+            }
+            default: {
+                if (pr.isLike) {
+                    return createLikeQuery(propertyName, 
pr.first.getValue(STRING));
+                }
+
+                //TODO Confirm that all other types can be treated as string
+                in = newPropertyRestrictionQuery(propertyName, true, pr,
+                        value -> value.getValue(Type.STRING));
+            }
+        }
+
+        if (in != null) {
+            return in;
+        }
+
+        throw new IllegalStateException("PropertyRestriction not handled " + 
pr + " for index " + defn);
+    }
+
+    private static String idToPath(String id) throws 
UnsupportedEncodingException {
+        return URLDecoder.decode(id, "UTF-8");
+    }
+}

Propchange: 
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elasticsearch/query/ElasticsearchResultRowIterator.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: 
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elasticsearch/query/ElasticsearchSearcher.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elasticsearch/query/ElasticsearchSearcher.java?rev=1863411&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elasticsearch/query/ElasticsearchSearcher.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elasticsearch/query/ElasticsearchSearcher.java
 Fri Jul 19 18:26:56 2019
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.plugins.index.elasticsearch.query;
+
+import 
org.apache.jackrabbit.oak.plugins.index.elasticsearch.ElasticsearchIndexCoordinateFactory;
+import 
org.apache.jackrabbit.oak.plugins.index.elasticsearch.ElasticsearchIndexCoordinate;
+import 
org.apache.jackrabbit.oak.plugins.index.elasticsearch.ElasticsearchIndexDefinition;
+import org.apache.jackrabbit.oak.plugins.index.search.FieldNames;
+import org.elasticsearch.action.search.SearchRequest;
+import org.elasticsearch.action.search.SearchResponse;
+import org.elasticsearch.client.RequestOptions;
+import org.elasticsearch.client.RestHighLevelClient;
+import org.elasticsearch.index.query.QueryBuilder;
+import org.elasticsearch.search.builder.SearchSourceBuilder;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.IOException;
+
+public class ElasticsearchSearcher {
+    private final ElasticsearchIndexCoordinate esIndexCoord;
+    private final RestHighLevelClient client;
+
+    ElasticsearchSearcher(@NotNull ElasticsearchIndexCoordinateFactory 
esIndexCoordFactory,
+                          @NotNull ElasticsearchIndexNode indexNode) {
+        ElasticsearchIndexDefinition defn = indexNode.getDefinition();
+        esIndexCoord = 
esIndexCoordFactory.getElasticsearchIndexCoordinate(defn);
+        client = esIndexCoord.getClient();
+    }
+
+    public SearchResponse search(QueryBuilder query, int batchSize) throws 
IOException {
+        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder()
+                .query(query)
+                .fetchSource(false)
+                .storedField(FieldNames.PATH)
+                .size(batchSize)
+                ;
+
+        SearchRequest request = new 
SearchRequest(esIndexCoord.getEsIndexName())
+                .source(searchSourceBuilder);
+
+        return client.search(request, RequestOptions.DEFAULT);
+    }
+}

Propchange: 
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elasticsearch/query/ElasticsearchSearcher.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: 
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elasticsearch/util/ElasticsearchIndexDefinitionBuilder.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elasticsearch/util/ElasticsearchIndexDefinitionBuilder.java?rev=1863411&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elasticsearch/util/ElasticsearchIndexDefinitionBuilder.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elasticsearch/util/ElasticsearchIndexDefinitionBuilder.java
 Fri Jul 19 18:26:56 2019
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.plugins.index.elasticsearch.util;
+
+import 
org.apache.jackrabbit.oak.plugins.index.search.util.IndexDefinitionBuilder;
+
+import static 
org.apache.jackrabbit.oak.plugins.index.elasticsearch.ElasticsearchIndexConstants.TYPE_ELASTICSEARCH;
+
+public class ElasticsearchIndexDefinitionBuilder extends 
IndexDefinitionBuilder {
+    @Override
+    protected String getIndexType() {
+        return TYPE_ELASTICSEARCH;
+    }
+}

Propchange: 
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elasticsearch/util/ElasticsearchIndexDefinitionBuilder.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: 
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elasticsearch/util/TermQueryBuilderFactory.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elasticsearch/util/TermQueryBuilderFactory.java?rev=1863411&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elasticsearch/util/TermQueryBuilderFactory.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elasticsearch/util/TermQueryBuilderFactory.java
 Fri Jul 19 18:26:56 2019
@@ -0,0 +1,191 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.plugins.index.elasticsearch.util;
+
+import org.apache.jackrabbit.oak.api.PropertyValue;
+import org.apache.jackrabbit.oak.api.Type;
+import org.apache.jackrabbit.oak.commons.PathUtils;
+import org.apache.jackrabbit.oak.plugins.index.search.FieldNames;
+import org.apache.jackrabbit.oak.plugins.index.search.PropertyDefinition;
+import 
org.apache.jackrabbit.oak.plugins.index.search.spi.query.FulltextIndexPlanner;
+import org.apache.jackrabbit.oak.spi.query.Filter;
+import org.elasticsearch.index.query.*;
+import org.jetbrains.annotations.NotNull;
+
+import javax.jcr.PropertyType;
+import java.util.List;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import static org.apache.jackrabbit.JcrConstants.JCR_MIXINTYPES;
+import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE;
+import static org.apache.jackrabbit.oak.plugins.index.search.FieldNames.PATH;
+import static 
org.apache.jackrabbit.oak.plugins.index.search.FieldNames.PATH_DEPTH;
+import static org.elasticsearch.index.query.QueryBuilders.*;
+
+public class TermQueryBuilderFactory {
+    /**
+     * Private constructor.
+     */
+    private TermQueryBuilderFactory() {
+    }
+
+    private static ExistsQueryBuilder newExistsQuery(String field) {
+        return QueryBuilders.existsQuery(field);
+    }
+
+    private static TermQueryBuilder newFulltextQuery(String ft) {
+        return termQuery(FieldNames.FULLTEXT, ft);
+    }
+
+    private static TermQueryBuilder newFulltextQuery(String ft, String field) {
+        if (field == null || "*".equals(field)) {
+            return newFulltextQuery(ft);
+        }
+        return termQuery(field, ft);
+    }
+
+    public static PrefixQueryBuilder newPrefixQuery(String field, @NotNull 
String value) {
+        return prefixQuery(keywordFieldName(field), value);
+    }
+
+    public static WildcardQueryBuilder newWildcardQuery(String field, @NotNull 
String value) {
+        return wildcardQuery(keywordFieldName(field), value);
+    }
+
+    public static TermQueryBuilder newPathQuery(String path) {
+        return termQuery(PATH, preparePath(path));
+    }
+
+    public static PrefixQueryBuilder newPrefixPathQuery(String path) {
+        return prefixQuery(PATH, preparePath(path));
+    }
+
+    public static WildcardQueryBuilder newWildcardPathQuery(@NotNull String 
value) {
+        return wildcardQuery(PATH, value);
+    }
+
+    public static TermQueryBuilder newAncestorQuery(String path){
+        return termQuery(FieldNames.ANCESTORS, preparePath(path));
+    }
+
+    public static TermQueryBuilder newDepthQuery(String path, 
FulltextIndexPlanner.PlanResult planResult) {
+        int depth = PathUtils.getDepth(path) + planResult.getParentDepth() + 1;
+        return QueryBuilders.termQuery(PATH_DEPTH, depth);
+    }
+
+    public static TermQueryBuilder newNodeTypeQuery(String type) {
+        return termQuery(keywordFieldName(JCR_PRIMARYTYPE), type);
+    }
+
+    public static TermQueryBuilder newMixinTypeQuery(String type) {
+        return termQuery(keywordFieldName(JCR_MIXINTYPES), type);
+    }
+
+    public static TermQueryBuilder newNotNullPropQuery(String propName) {
+        return termQuery(FieldNames.NOT_NULL_PROPS, propName);
+    }
+
+    public static TermQueryBuilder newNullPropQuery(String propName) {
+        return termQuery(FieldNames.NULL_PROPS, propName);
+    }
+
+    private static <R> RangeQueryBuilder newRangeQuery(String field,
+                                                       R first, R last, 
boolean firstIncluding, boolean lastIncluding) {
+        return QueryBuilders.rangeQuery(field)
+                .from(first).to(last)
+                .includeLower(firstIncluding).includeUpper(lastIncluding);
+    }
+
+    private static <R> BoolQueryBuilder newInQuery(String field, List<R> 
values) {
+        BoolQueryBuilder bq = boolQuery();
+        for (R value : values) {
+            bq.should(newRangeQuery(field, value, value, true, true));
+        }
+        return bq;
+    }
+
+    public static <R> QueryBuilder newPropertyRestrictionQuery(String 
propertyName, boolean isString,
+                                                               
Filter.PropertyRestriction pr,
+                                                               
Function<PropertyValue, R> propToObj) {
+        if (isString) {
+            propertyName = keywordFieldName(propertyName);
+        }
+
+        R first = pr.first != null ? propToObj.apply(pr.first) : null;
+        R last = pr.last != null ? propToObj.apply(pr.last) : null;
+        if (pr.first != null && pr.first.equals(pr.last) && pr.firstIncluding
+                && pr.lastIncluding) {
+            // [property]=[value]
+            return termQuery(propertyName, first);
+        } else if (pr.first != null && pr.last != null) {
+            return newRangeQuery(propertyName, first, last,
+                    pr.firstIncluding, pr.lastIncluding);
+        } else if (pr.first != null && pr.last == null) {
+            // '>' & '>=' use cases
+            return newRangeQuery(propertyName, first, null, pr.firstIncluding, 
true);
+        } else if (pr.last != null && !pr.last.equals(pr.first)) {
+            // '<' & '<='
+            return newRangeQuery(propertyName, null, last, true, 
pr.lastIncluding);
+        } else if (pr.list != null) {
+            return newInQuery(propertyName, pr.list.stream()
+                    .map(propToObj::apply)
+                    .collect(Collectors.toList()));
+        } else if (pr.isNotNullRestriction()) {
+            // not null. For date lower bound of zero can be used
+            return newExistsQuery(propertyName);
+        } else {
+            return null;
+        }
+    }
+
+    private static String preparePath(String path) {
+        if (!"/".equals(path) && !path.startsWith("/")) {
+            path = "/" + path;
+        }
+        return path;
+    }
+
+    // As per https://www.elastic.co/blog/strings-are-dead-long-live-strings
+    private static String keywordFieldName(String propName) {
+        return propName + "." + "keyword";
+    }
+
+    //TODO: figure out how to not duplicate these method from FulltextIndex
+    public static int determinePropertyType(PropertyDefinition defn, 
Filter.PropertyRestriction pr) {
+        int typeFromRestriction = pr.propertyType;
+        if (typeFromRestriction == PropertyType.UNDEFINED) {
+            //If no explicit type defined then determine the type from 
restriction
+            //value
+            if (pr.first != null && pr.first.getType() != Type.UNDEFINED) {
+                typeFromRestriction = pr.first.getType().tag();
+            } else if (pr.last != null && pr.last.getType() != Type.UNDEFINED) 
{
+                typeFromRestriction = pr.last.getType().tag();
+            } else if (pr.list != null && !pr.list.isEmpty()) {
+                typeFromRestriction = pr.list.get(0).getType().tag();
+            }
+        }
+        return getPropertyType(defn, pr.propertyName, typeFromRestriction);
+    }
+
+    private static int getPropertyType(PropertyDefinition defn, String name, 
int defaultVal) {
+        if (defn.isTypeDefined()) {
+            return defn.getType();
+        }
+        return defaultVal;
+    }
+}

Propchange: 
jackrabbit/oak/trunk/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elasticsearch/util/TermQueryBuilderFactory.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: 
jackrabbit/oak/trunk/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elasticsearch/ElasticsearchDockerRule.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elasticsearch/ElasticsearchDockerRule.java?rev=1863411&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elasticsearch/ElasticsearchDockerRule.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elasticsearch/ElasticsearchDockerRule.java
 Fri Jul 19 18:26:56 2019
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.plugins.index.elasticsearch;
+
+import com.arakelian.docker.junit.DockerRule;
+import com.arakelian.docker.junit.model.ImmutableDockerConfig;
+import com.spotify.docker.client.DefaultDockerClient;
+import com.spotify.docker.client.auth.FixedRegistryAuthSupplier;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * An Elasticsearch {@link DockerRule}.
+ */
+class ElasticsearchDockerRule extends DockerRule {
+
+    //Mimic following:
+    // docker run -p 9200:9200 -e "discovery.type=single-node" 
docker.elastic.co/elasticsearch/elasticsearch:7.1.1
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(ElasticsearchDockerRule.class);
+
+    private static final String CONFIG_NAME = "Elasticsearch";
+
+    private static final String VERSION = 
System.getProperty("elasticsearch.version", "7.1.1");
+
+    private static final String IMAGE = "elasticsearch:" + VERSION;
+
+    private static final boolean DOCKER_AVAILABLE;
+
+    static {
+        boolean available = false;
+        try (DefaultDockerClient client = DefaultDockerClient.fromEnv()
+                .connectTimeoutMillis(5000L).readTimeoutMillis(20000L)
+                .registryAuthSupplier(new FixedRegistryAuthSupplier())
+                .build()) {
+            client.ping();
+            client.pull(IMAGE);
+            available = true;
+        } catch (Throwable t) {
+            LOG.info("Cannot connect to docker or pull image", t);
+        }
+        DOCKER_AVAILABLE = available;
+    }
+
+    ElasticsearchDockerRule() {
+        super(ImmutableDockerConfig.builder()
+                .name(CONFIG_NAME)
+                .image(IMAGE)
+                .ports("9200")
+                .allowRunningBetweenUnitTests(true)
+                .alwaysRemoveContainer(true)
+                .addStartedListener(container -> 
container.waitForLog("LicenseService"))
+                .addContainerConfigurer(builder -> 
builder.env("discovery.type=single-node"))
+                .build());
+    }
+
+    int getPort() {
+        return getContainer().getPortBinding("9200/tcp").getPort();
+    }
+
+    boolean isDockerAvailable() {
+        return DOCKER_AVAILABLE;
+    }
+}

Propchange: 
jackrabbit/oak/trunk/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elasticsearch/ElasticsearchDockerRule.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: 
jackrabbit/oak/trunk/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elasticsearch/ElasticsearchManagementRule.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elasticsearch/ElasticsearchManagementRule.java?rev=1863411&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elasticsearch/ElasticsearchManagementRule.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elasticsearch/ElasticsearchManagementRule.java
 Fri Jul 19 18:26:56 2019
@@ -0,0 +1,101 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.plugins.index.elasticsearch;
+
+import com.google.common.collect.Sets;
+import org.apache.jackrabbit.oak.plugins.index.search.IndexDefinition;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
+import org.elasticsearch.client.RequestOptions;
+import org.junit.Assume;
+import org.junit.rules.ExternalResource;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.Set;
+
+public class ElasticsearchManagementRule extends ExternalResource
+        implements ElasticsearchIndexCoordinateFactory {
+    private static final Logger LOG = 
LoggerFactory.getLogger(ElasticsearchManagementRule.class);
+
+    private final ElasticsearchDockerRule elasticsearch = new 
ElasticsearchDockerRule();
+
+    private final ElasticsearchConnectionFactory connectionFactory = new 
ElasticsearchConnectionFactory();
+
+    private final Set<ElasticsearchIndexCoordinate> indices = 
Sets.newHashSet();
+
+    private boolean usingDocker;
+
+    @Override
+    public Statement apply(Statement base, Description description) {
+        Statement s = super.apply(base, description);
+        // see if local instance is available... initialize docker rule only 
if that's not the case
+        ElasticsearchCoordinate esCoord = 
ElasticsearchCoordinateImpl.construct(connectionFactory,
+                null, null);
+        if (!ElasticsearchTestUtils.isAvailable(esCoord) && 
elasticsearch.isDockerAvailable()) {
+            s = elasticsearch.apply(s, description);
+            usingDocker = true;
+        }
+
+        return s;
+    }
+
+    @Override
+    public ElasticsearchIndexCoordinate 
getElasticsearchIndexCoordinate(IndexDefinition indexDefinition) {
+        ElasticsearchCoordinate esCoord = 
getElasticsearchCoordinate(indexDefinition.getDefinitionNodeState());
+        ElasticsearchIndexCoordinate esIdxCoord = new 
ElasticsearchIndexCoordinateImpl(esCoord, indexDefinition);
+        indices.add(esIdxCoord);
+        return esIdxCoord;
+    }
+
+    @Override
+    protected void after() {
+        deletedIndices();
+        connectionFactory.close();
+    }
+
+    private ElasticsearchCoordinate getElasticsearchCoordinate(NodeState 
indexDefinition) {
+        ElasticsearchCoordinate esCoord = 
ElasticsearchCoordinateImpl.construct(connectionFactory,
+                indexDefinition, null);
+
+        if (!ElasticsearchTestUtils.isAvailable(esCoord) && usingDocker) {
+            int port = elasticsearch.getPort();
+            esCoord = new ElasticsearchCoordinateImpl(connectionFactory, 
"http", "localhost", port);
+        }
+
+        Assume.assumeTrue(ElasticsearchTestUtils.isAvailable(esCoord));
+
+        return esCoord;
+    }
+
+    private void deletedIndices() {
+        indices.forEach(idxCoord -> {
+            DeleteIndexRequest request = new 
DeleteIndexRequest(idxCoord.getEsIndexName());
+            try {
+                idxCoord.getClient().indices().delete(request, 
RequestOptions.DEFAULT);
+
+                LOG.info("Cleaned up index {}", idxCoord.getEsIndexName());
+
+            } catch (IOException e) {
+                LOG.warn("Failed to cleanup index {}", 
idxCoord.getEsIndexName());
+            }
+        });
+    }
+}
\ No newline at end of file

Propchange: 
jackrabbit/oak/trunk/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elasticsearch/ElasticsearchManagementRule.java
------------------------------------------------------------------------------
    svn:eol-style = native


Reply via email to