/* * 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.rest.schema.analysis; import java.io.File; import java.net.URLEncoder; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.SortedMap; import java.util.TreeMap; import org.apache.commons.io.FileUtils; import org.apache.solr.util.RestTestBase; import org.eclipse.jetty.servlet.ServletHolder; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.noggit.JSONUtil; import org.restlet.ext.servlet.ServerServlet; public class TestManagedSynonymFilterFactory extends RestTestBase { private static File tmpSolrHome; /** * Setup to make the schema mutable */ @Before public void before() throws Exception { tmpSolrHome = createTempDir().toFile(); FileUtils.copyDirectory(new File(TEST_HOME()), tmpSolrHome.getAbsoluteFile()); final SortedMap<ServletHolder,String> extraServlets = new TreeMap<>(); final ServletHolder solrRestApi = new ServletHolder("SolrSchemaRestApi", ServerServlet.class); solrRestApi.setInitParameter("org.restlet.application", "org.apache.solr.rest.SolrSchemaRestApi"); extraServlets.put(solrRestApi, "/schema/*"); System.setProperty("managed.schema.mutable", "true"); System.setProperty("enable.update.log", "false"); createJettyAndHarness(tmpSolrHome.getAbsolutePath(), "solrconfig-managed-schema.xml", "schema-rest.xml", "/solr", true, extraServlets); } @After private void after() throws Exception { jetty.stop(); jetty = null; FileUtils.deleteDirectory(tmpSolrHome); System.clearProperty("managed.schema.mutable"); System.clearProperty("enable.update.log"); if (restTestHarness != null) { restTestHarness.close(); } restTestHarness = null; } @Test public void testManagedSynonyms() throws Exception { // this endpoint depends on at least one field type containing the following // declaration in the schema-rest.xml: // // <filter class="solr.ManagedSynonymFilterFactory" managed="english" /> // String endpoint = "/schema/analysis/synonyms/english"; assertJQ(endpoint, "/synonymMappings/initArgs/ignoreCase==false", "/synonymMappings/managedMap=={}"); // put a new mapping into the synonyms Map<String,List<String>> syns = new HashMap<>(); syns.put("happy", Arrays.asList("glad","cheerful","joyful")); assertJPut(endpoint, JSONUtil.toJSON(syns), "/responseHeader/status==0"); assertJQ(endpoint, "/synonymMappings/managedMap/happy==['cheerful','glad','joyful']"); // request to a specific mapping assertJQ(endpoint+"/happy", "/happy==['cheerful','glad','joyful']"); // does not exist assertJQ(endpoint+"/sad", "/error/code==404"); // verify the user can update the ignoreCase initArg assertJPut(endpoint, json("{ 'initArgs':{ 'ignoreCase':true } }"), "responseHeader/status==0"); assertJQ(endpoint, "/synonymMappings/initArgs/ignoreCase==true"); syns = new HashMap<>(); syns.put("sad", Arrays.asList("unhappy")); syns.put("SAD", Arrays.asList("bummed")); assertJPut(endpoint, JSONUtil.toJSON(syns), "/responseHeader/status==0"); assertJQ(endpoint, "/synonymMappings/managedMap/sad==['unhappy']"); assertJQ(endpoint, "/synonymMappings/managedMap/SAD==['bummed']"); // expect a union of values when requesting the "sad" child assertJQ(endpoint+"/sad", "/sad==['bummed','unhappy']"); // verify delete works assertJDelete(endpoint+"/sad", "/responseHeader/status==0"); assertJQ(endpoint, "/synonymMappings/managedMap=={'happy':['cheerful','glad','joyful']}"); // should fail with 404 as foo doesn't exist assertJDelete(endpoint+"/foo", "/error/code==404"); // verify that a newly added synonym gets expanded on the query side after core reload String newFieldName = "managed_en_field"; // make sure the new field doesn't already exist assertQ("/schema/fields/" + newFieldName + "?indent=on&wt=xml", "count(/response/lst[@name='field']) = 0", "/response/lst[@name='responseHeader']/int[@name='status'] = '404'", "/response/lst[@name='error']/int[@name='code'] = '404'"); // add the new field assertJPost("/schema", "{ add-field : { name: managed_en_field, type : managed_en}}", "/responseHeader/status==0"); // make sure the new field exists now assertQ("/schema/fields/" + newFieldName + "?indent=on&wt=xml", "count(/response/lst[@name='field']) = 1", "/response/lst[@name='responseHeader']/int[@name='status'] = '0'"); // multi-term synonym logic - SOLR-10264 final String multiTermOrigin; final String multiTermSynonym; if (random().nextBoolean()) { multiTermOrigin = "hansestadt hamburg"; multiTermSynonym = "hh"; } else { multiTermOrigin = "hh"; multiTermSynonym = "hansestadt hamburg"; } // multi-term logic similar to the angry/mad logic (angry ~ origin, mad ~ synonym) assertU(adoc(newFieldName, "I am a happy test today but yesterday I was angry", "id", "5150")); assertU(adoc(newFieldName, multiTermOrigin+" is in North Germany.", "id", "040")); assertU(commit()); assertQ("/select?q=" + newFieldName + ":angry", "/response/lst[@name='responseHeader']/int[@name='status'] = '0'", "/response/result[@name='response'][@numFound='1']", "/response/result[@name='response']/doc/str[@name='id'][.='5150']"); assertQ("/select?q=" + newFieldName + ":"+URLEncoder.encode(multiTermOrigin, "UTF-8"), "/response/lst[@name='responseHeader']/int[@name='status'] = '0'", "/response/result[@name='response'][@numFound='1']", "/response/result[@name='response']/doc/str[@name='id'][.='040']"); // add a mapping that will expand a query for "mad" to match docs with "angry" syns = new HashMap<>(); syns.put("mad", Arrays.asList("angry")); assertJPut(endpoint, JSONUtil.toJSON(syns), "/responseHeader/status==0"); assertJQ(endpoint, "/synonymMappings/managedMap/mad==['angry']"); // add a mapping that will expand a query for "multi-term synonym" to match docs with "acronym" syns = new HashMap<>(); syns.put(multiTermSynonym, Arrays.asList(multiTermOrigin)); assertJPut(endpoint, JSONUtil.toJSON(syns), "/responseHeader/status==0"); assertJQ(endpoint+"/"+URLEncoder.encode(multiTermSynonym, "UTF-8"), "/"+multiTermSynonym+"==['"+multiTermOrigin+"']"); // should not match as the synonym mapping between mad and angry does not // get applied until core reload assertQ("/select?q=" + newFieldName + ":mad", "/response/lst[@name='responseHeader']/int[@name='status'] = '0'", "/response/result[@name='response'][@numFound='0']"); // should not match as the synonym mapping between "origin" and "synonym" // was not added before the document was indexed assertQ("/select?q=" + newFieldName + ":("+URLEncoder.encode(multiTermSynonym, "UTF-8") + ")&sow=false", "/response/lst[@name='responseHeader']/int[@name='status'] = '0'", "/response/result[@name='response'][@numFound='0']"); restTestHarness.reload(); // now query for mad and we should see our test doc assertQ("/select?q=" + newFieldName + ":mad", "/response/lst[@name='responseHeader']/int[@name='status'] = '0'", "/response/result[@name='response'][@numFound='1']", "/response/result[@name='response']/doc/str[@name='id'][.='5150']"); // now query for "synonym" and we should see our test doc with "origin" assertQ("/select?q=" + newFieldName + ":("+URLEncoder.encode(multiTermSynonym, "UTF-8") + ")&sow=false", "/response/lst[@name='responseHeader']/int[@name='status'] = '0'", "/response/result[@name='response'][@numFound='1']", "/response/result[@name='response']/doc/str[@name='id'][.='040']"); // test for SOLR-6015 syns = new HashMap<>(); syns.put("mb", Arrays.asList("megabyte")); assertJPut(endpoint, JSONUtil.toJSON(syns), "/responseHeader/status==0"); syns.put("MB", Arrays.asList("MiB", "Megabyte")); assertJPut(endpoint, JSONUtil.toJSON(syns), "/responseHeader/status==0"); assertJQ(endpoint + "/MB", "/MB==['Megabyte','MiB','megabyte']"); // test for SOLR-6878 - by default, expand is true, but only applies when sending in a list List<String> m2mSyns = new ArrayList<>(); m2mSyns.addAll(Arrays.asList("funny", "entertaining", "whimiscal", "jocular")); assertJPut(endpoint, JSONUtil.toJSON(m2mSyns), "/responseHeader/status==0"); assertJQ(endpoint + "/funny", "/funny==['entertaining','funny','jocular','whimiscal']"); assertJQ(endpoint + "/entertaining", "/entertaining==['entertaining','funny','jocular','whimiscal']"); assertJQ(endpoint + "/jocular", "/jocular==['entertaining','funny','jocular','whimiscal']"); assertJQ(endpoint + "/whimiscal", "/whimiscal==['entertaining','funny','jocular','whimiscal']"); } /** * Can we add and remove stopwords with umlauts */ @Test public void testCanHandleDecodingAndEncodingForSynonyms() throws Exception { String endpoint = "/schema/analysis/synonyms/german"; assertJQ(endpoint, "/synonymMappings/initArgs/ignoreCase==false", "/synonymMappings/managedMap=={}"); // does not exist assertJQ(endpoint+"/fröhlich", "/error/code==404"); Map<String,List<String>> syns = new HashMap<>(); // now put a synonym syns.put("fröhlich", Arrays.asList("glücklick")); assertJPut(endpoint, JSONUtil.toJSON(syns), "/responseHeader/status==0"); // and check if it exists assertJQ(endpoint, "/synonymMappings/managedMap/fröhlich==['glücklick']"); // verify delete works assertJDelete(endpoint+"/fröhlich", "/responseHeader/status==0"); // was it really deleted? assertJDelete(endpoint+"/fröhlich", "/error/code==404"); } }