diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index cf82501f9bd..6479c718645 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -63,6 +63,9 @@ New Features
* SOLR-9240: Support parallel ETL with the topic expression (Joel Bernstein)
+* SOLR-9275: XML QueryParser support (defType=xmlparser) now extensible via configuration.
+ (Christine Poerschke)
+
Bug Fixes
----------------------
diff --git a/solr/core/src/java/org/apache/solr/search/SolrCoreParser.java b/solr/core/src/java/org/apache/solr/search/SolrCoreParser.java
index cf3fb421954..1e0e5bd8365 100755
--- a/solr/core/src/java/org/apache/solr/search/SolrCoreParser.java
+++ b/solr/core/src/java/org/apache/solr/search/SolrCoreParser.java
@@ -16,23 +16,54 @@
*/
package org.apache.solr.search;
+import java.util.Map;
+
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.queryparser.xml.CoreParser;
+import org.apache.lucene.queryparser.xml.QueryBuilder;
+import org.apache.solr.common.util.NamedList;
+import org.apache.solr.core.SolrResourceLoader;
import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.util.plugin.NamedListInitializedPlugin;
/**
* Assembles a QueryBuilder which uses Query objects from Solr's search
module
* in addition to Query objects supported by the Lucene CoreParser
.
*/
-public class SolrCoreParser extends CoreParser {
+public class SolrCoreParser extends CoreParser implements NamedListInitializedPlugin {
+
+ protected final SolrQueryRequest req;
public SolrCoreParser(String defaultField, Analyzer analyzer,
SolrQueryRequest req) {
super(defaultField, analyzer);
+ this.req = req;
+ }
- // final IndexSchema schema = req.getSchema();
- // lucene_parser.addQueryBuilder("SomeOtherQuery", new SomeOtherQueryBuilder(schema));
+ @Override
+ public void init(NamedList initArgs) {
+ final SolrResourceLoader loader;
+ if (req == null) {
+ loader = new SolrResourceLoader();
+ } else {
+ loader = req.getCore().getResourceLoader();
+ }
+
+ final Iterable> args = initArgs;
+ for (final Map.Entry entry : args) {
+ final String queryName = entry.getKey();
+ final String queryBuilderClassName = (String)entry.getValue();
+
+ final SolrQueryBuilder queryBuilder = loader.newInstance(
+ queryBuilderClassName,
+ SolrQueryBuilder.class,
+ null,
+ new Class[] {String.class, Analyzer.class, SolrQueryRequest.class, QueryBuilder.class},
+ new Object[] {defaultField, analyzer, req, this});
+
+ this.queryFactory.addBuilder(queryName, queryBuilder);
+ }
}
}
diff --git a/solr/core/src/java/org/apache/solr/search/SolrQueryBuilder.java b/solr/core/src/java/org/apache/solr/search/SolrQueryBuilder.java
new file mode 100644
index 00000000000..e8135128337
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/search/SolrQueryBuilder.java
@@ -0,0 +1,34 @@
+/*
+ * 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.solr.search;
+
+import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.queryparser.xml.QueryBuilder;
+import org.apache.solr.request.SolrQueryRequest;
+
+public abstract class SolrQueryBuilder implements QueryBuilder {
+
+ protected final SolrQueryRequest req;
+ protected final QueryBuilder queryFactory;
+
+ public SolrQueryBuilder(String defaultField, Analyzer analyzer,
+ SolrQueryRequest req, QueryBuilder queryFactory) {
+ this.req = req;
+ this.queryFactory = queryFactory;
+ }
+
+}
diff --git a/solr/core/src/java/org/apache/solr/search/XmlQParserPlugin.java b/solr/core/src/java/org/apache/solr/search/XmlQParserPlugin.java
index cb6b45eae5f..ee8e062782a 100755
--- a/solr/core/src/java/org/apache/solr/search/XmlQParserPlugin.java
+++ b/solr/core/src/java/org/apache/solr/search/XmlQParserPlugin.java
@@ -25,12 +25,21 @@ import org.apache.lucene.search.Query;
import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.params.SolrParams;
+import org.apache.solr.common.util.NamedList;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.schema.IndexSchema;
public class XmlQParserPlugin extends QParserPlugin {
public static final String NAME = "xmlparser";
+ private NamedList args;
+
+ @Override
+ public void init( NamedList args ) {
+ super.init(args);
+ this.args = args;
+ }
+
private class XmlQParser extends QParser {
public XmlQParser(String qstr, SolrParams localParams,
@@ -46,7 +55,9 @@ public class XmlQParserPlugin extends QParserPlugin {
final IndexSchema schema = req.getSchema();
final String defaultField = QueryParsing.getDefaultField(schema, getParam(CommonParams.DF));
final Analyzer analyzer = schema.getQueryAnalyzer();
+
final SolrCoreParser solrParser = new SolrCoreParser(defaultField, analyzer, req);
+ solrParser.init(args);
try {
return solrParser.parse(new ByteArrayInputStream(qstr.getBytes(StandardCharsets.UTF_8)));
} catch (ParserException e) {
diff --git a/solr/core/src/test-files/solr/collection1/conf/solrconfig-testxmlparser.xml b/solr/core/src/test-files/solr/collection1/conf/solrconfig-testxmlparser.xml
new file mode 100644
index 00000000000..40c39a18984
--- /dev/null
+++ b/solr/core/src/test-files/solr/collection1/conf/solrconfig-testxmlparser.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+ ${tests.luceneMatchVersion:LATEST}
+ ${solr.data.dir:}
+
+
+
+
+
+ org.apache.solr.search.HandyQueryBuilder
+ org.apache.solr.search.HelloQueryBuilder
+ org.apache.solr.search.GoodbyeQueryBuilder
+
+
diff --git a/solr/core/src/test/org/apache/solr/search/GoodbyeQueryBuilder.java b/solr/core/src/test/org/apache/solr/search/GoodbyeQueryBuilder.java
new file mode 100644
index 00000000000..af258d49359
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/search/GoodbyeQueryBuilder.java
@@ -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.solr.search;
+
+import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.queryparser.xml.ParserException;
+import org.apache.lucene.queryparser.xml.QueryBuilder;
+import org.apache.lucene.search.MatchNoDocsQuery;
+import org.apache.lucene.search.Query;
+import org.apache.solr.request.SolrQueryRequest;
+import org.w3c.dom.Element;
+
+public class GoodbyeQueryBuilder extends SolrQueryBuilder {
+
+ public GoodbyeQueryBuilder(String defaultField, Analyzer analyzer,
+ SolrQueryRequest req, QueryBuilder queryFactory) {
+ super(defaultField, analyzer, req, queryFactory);
+ }
+
+ @Override
+ public Query getQuery(Element e) throws ParserException {
+ return new MatchNoDocsQuery();
+ }
+
+}
diff --git a/solr/core/src/test/org/apache/solr/search/HandyQueryBuilder.java b/solr/core/src/test/org/apache/solr/search/HandyQueryBuilder.java
new file mode 100644
index 00000000000..14a8aac66f5
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/search/HandyQueryBuilder.java
@@ -0,0 +1,53 @@
+/*
+ * 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.solr.search;
+
+import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.queryparser.xml.DOMUtils;
+import org.apache.lucene.queryparser.xml.ParserException;
+import org.apache.lucene.queryparser.xml.QueryBuilder;
+import org.apache.lucene.search.BooleanClause;
+import org.apache.lucene.search.BooleanQuery;
+import org.apache.lucene.search.Query;
+import org.apache.solr.request.SolrQueryRequest;
+import org.w3c.dom.Element;
+
+// A simple test query builder to demonstrate use of
+// SolrQueryBuilder's queryFactory constructor argument.
+public class HandyQueryBuilder extends SolrQueryBuilder {
+
+ public HandyQueryBuilder(String defaultField, Analyzer analyzer,
+ SolrQueryRequest req, QueryBuilder queryFactory) {
+ super(defaultField, analyzer, req, queryFactory);
+ }
+
+ @Override
+ public Query getQuery(Element e) throws ParserException {
+ final BooleanQuery.Builder bq = new BooleanQuery.Builder();
+ final Query lhsQ = getSubQuery(e, "Left");
+ final Query rhsQ = getSubQuery(e, "Right");
+ bq.add(new BooleanClause(lhsQ, BooleanClause.Occur.SHOULD));
+ bq.add(new BooleanClause(rhsQ, BooleanClause.Occur.SHOULD));
+ return bq.build();
+ }
+
+ private Query getSubQuery(Element e, String name) throws ParserException {
+ Element subE = DOMUtils.getChildByTagOrFail(e, name);
+ subE = DOMUtils.getFirstChildOrFail(subE);
+ return queryFactory.getQuery(subE);
+ }
+}
diff --git a/solr/core/src/test/org/apache/solr/search/HelloQueryBuilder.java b/solr/core/src/test/org/apache/solr/search/HelloQueryBuilder.java
new file mode 100644
index 00000000000..642047f32fa
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/search/HelloQueryBuilder.java
@@ -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.solr.search;
+
+import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.queryparser.xml.ParserException;
+import org.apache.lucene.queryparser.xml.QueryBuilder;
+import org.apache.lucene.search.MatchAllDocsQuery;
+import org.apache.lucene.search.Query;
+import org.apache.solr.request.SolrQueryRequest;
+import org.w3c.dom.Element;
+
+public class HelloQueryBuilder extends SolrQueryBuilder {
+
+ public HelloQueryBuilder(String defaultField, Analyzer analyzer,
+ SolrQueryRequest req, QueryBuilder queryFactory) {
+ super(defaultField, analyzer, req, queryFactory);
+ }
+
+ @Override
+ public Query getQuery(Element e) throws ParserException {
+ return new MatchAllDocsQuery();
+ }
+
+}
diff --git a/solr/core/src/test/org/apache/solr/search/TestXmlQParserPlugin.java b/solr/core/src/test/org/apache/solr/search/TestXmlQParserPlugin.java
new file mode 100644
index 00000000000..3c4edae0edc
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/search/TestXmlQParserPlugin.java
@@ -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.solr.search;
+
+import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.common.params.ModifiableSolrParams;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class TestXmlQParserPlugin extends SolrTestCaseJ4 {
+
+ @BeforeClass
+ public static void beforeClass() throws Exception {
+ initCore("solrconfig-testxmlparser.xml", "schema-minimal.xml");
+ }
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ // if you override setUp or tearDown, you better call
+ // the super classes version
+ super.setUp();
+ clearIndex();
+ assertU(commit());
+ }
+
+ @Test
+ public void testHelloQuery() throws Exception {
+ final int numDocs = random().nextInt(10);
+ implTestQuery(numDocs, "", numDocs);
+ }
+
+ @Test
+ public void testGoodbyeQuery() throws Exception {
+ final int numDocs = random().nextInt(10);
+ implTestQuery(numDocs, "", 0);
+ }
+
+ @Test
+ public void testHandyQuery() throws Exception {
+ final int numDocs = random().nextInt(10);
+ final String q = "";
+ implTestQuery(numDocs, q, numDocs);
+ }
+
+ public void implTestQuery(int numDocs, String q, int expectedCount) throws Exception {
+ // add some documents
+ for (int ii=1; ii<=numDocs; ++ii) {
+ String[] doc = {"id",ii+"0"};
+ assertU(adoc(doc));
+ if (random().nextBoolean()) {
+ assertU(commit());
+ }
+ }
+ assertU(commit());
+ // and then run the query
+ ModifiableSolrParams params = new ModifiableSolrParams();
+ params.add("defType", "testxmlparser");
+ params.add("q", q);
+ assertQ(req(params), "*[count(//doc)="+expectedCount+"]");
+ }
+
+}