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