package org.apache.solr.rest.schema; /* * 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. */ 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.restlet.ext.servlet.ServerServlet; import java.io.File; import java.util.SortedMap; import java.util.TreeMap; import java.util.regex.Pattern; public class TestManagedSchemaFieldResource extends RestTestBase { private static File tmpSolrHome; private static File tmpConfDir; private static final String collection = "collection1"; private static final String confDir = collection + "/conf"; @Before public void before() throws Exception { createTempDir(); tmpSolrHome = new File( TEMP_DIR + File.separator + TestManagedSchemaFieldResource.class.getSimpleName() + System.currentTimeMillis()); tmpConfDir = new File(tmpSolrHome, confDir); 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/*"); // '/schema/*' matches '/schema', '/schema/', and '/schema/whatever...' 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 public void after() throws Exception { if (jetty != null) { jetty.stop(); jetty = null; } server = null; restTestHarness = null; } @Test public void testAddFieldBadFieldType() throws Exception { assertJPut("/schema/fields/newfield", json( "{'type':'not_in_there_at_all','stored':false}" ), "/error/msg==\"Field \\'newfield\\': Field type \\'not_in_there_at_all\\' not found.\""); } @Test public void testAddFieldMismatchedName() throws Exception { assertJPut("/schema/fields/newfield", json( "{'name':'something_else','type':'text','stored':false}" ), "/error/msg=='///regex:newfield///'"); } @Test public void testAddFieldBadProperty() throws Exception { assertJPut("/schema/fields/newfield", json( "{'type':'text','no_property_with_this_name':false}" ), "/error/msg==\"java.lang.IllegalArgumentException: Invalid field property: no_property_with_this_name\""); } @Test public void testAddField() throws Exception { assertQ("/schema/fields/newfield?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'"); assertJPut("/schema/fields/newfield", json("{'type':'text','stored':false}"), "/responseHeader/status==0"); assertQ("/schema/fields/newfield?indent=on&wt=xml", "count(/response/lst[@name='field']) = 1", "/response/lst[@name='responseHeader']/int[@name='status'] = '0'"); assertU(adoc("newfield", "value1 value2", "id", "123")); assertU(commit()); assertQ("/select?q=newfield:value1", "/response/lst[@name='responseHeader']/int[@name='status'] = '0'", "/response/result[@name='response'][@numFound='1']", "count(/response/result[@name='response']/doc/*) = 1", "/response/result[@name='response']/doc/str[@name='id'][.='123']"); } @Test public void testAddFieldWithMulipleOptions() throws Exception { assertQ("/schema/fields/newfield?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'"); assertJPut("/schema/fields/newfield", json("{'type':'text_en','stored':true,'indexed':false}"), "/responseHeader/status==0"); File managedSchemaFile = new File(tmpConfDir, "managed-schema"); assertTrue(managedSchemaFile.exists()); String managedSchemaContents = FileUtils.readFileToString(managedSchemaFile, "UTF-8"); Pattern newfieldStoredTrueIndexedFalsePattern = Pattern.compile( "<field name=\"newfield\" type=\"text_en\" " + "(?=.*stored=\"true\")(?=.*indexed=\"false\").*/>"); assertTrue(newfieldStoredTrueIndexedFalsePattern.matcher(managedSchemaContents).find()); assertQ("/schema/fields/newfield?indent=on&wt=xml", "count(/response/lst[@name='field']) = 1", "/response/lst[@name='responseHeader']/int[@name='status'] = '0'", "/response/lst[@name='field']/str[@name='name'] = 'newfield'", "/response/lst[@name='field']/str[@name='type'] = 'text_en'", "/response/lst[@name='field']/bool[@name='indexed'] = 'false'", "/response/lst[@name='field']/bool[@name='stored'] = 'true'"); assertU(adoc("newfield", "value1 value2", "id", "1234")); assertU(commit()); assertQ("/schema/fields/newfield2?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'"); assertJPut("/schema/fields/newfield2", json("{'type':'text_en','stored':true,'indexed':true,'multiValued':true}"), "/responseHeader/status==0"); managedSchemaContents = FileUtils.readFileToString(managedSchemaFile, "UTF-8"); Pattern newfield2StoredTrueIndexedTrueMultiValuedTruePattern = Pattern.compile( "<field name=\"newfield2\" type=\"text_en\" " + "(?=.*stored=\"true\")(?=.*indexed=\"true\")(?=.*multiValued=\"true\").*/>"); assertTrue(newfield2StoredTrueIndexedTrueMultiValuedTruePattern.matcher(managedSchemaContents).find()); assertQ("/schema/fields/newfield2?indent=on&wt=xml", "count(/response/lst[@name='field']) = 1", "/response/lst[@name='responseHeader']/int[@name='status'] = '0'", "/response/lst[@name='field']/str[@name='name'] = 'newfield2'", "/response/lst[@name='field']/str[@name='type'] = 'text_en'", "/response/lst[@name='field']/bool[@name='indexed'] = 'true'", "/response/lst[@name='field']/bool[@name='stored'] = 'true'", "/response/lst[@name='field']/bool[@name='multiValued'] = 'true'"); assertU(adoc("newfield2", "value1 value2", "newfield2", "value3 value4", "id", "5678")); assertU(commit()); assertQ("/select?q=newfield2:value3", "/response/lst[@name='responseHeader']/int[@name='status'] = '0'", "/response/result[@name='response'][@numFound='1']", "count(/response/result[@name='response']/doc) = 1", "/response/result[@name='response']/doc/str[@name='id'][.='5678']"); } @Test public void testAddFieldCollectionWithMultipleOptions() throws Exception { assertQ("/schema/fields?indent=on&wt=xml", "count(/response/arr[@name='fields']/lst/str[@name]) > 0", // there are fields "count(/response/arr[@name='fields']/lst/str[starts-with(@name,'newfield')]) = 0"); // but none named newfield* assertJPost("/schema/fields", json("[{'name':'newfield','type':'text_en','stored':true,'indexed':false}]"), "/responseHeader/status==0"); File managedSchemaFile = new File(tmpConfDir, "managed-schema"); assertTrue(managedSchemaFile.exists()); String managedSchemaContents = FileUtils.readFileToString(managedSchemaFile, "UTF-8"); Pattern newfieldStoredTrueIndexedFalsePattern = Pattern.compile( "<field name=\"newfield\" type=\"text_en\" " + "(?=.*stored=\"true\")(?=.*indexed=\"false\").*/>"); assertTrue(newfieldStoredTrueIndexedFalsePattern.matcher(managedSchemaContents).find()); assertQ("/schema/fields?indent=on&wt=xml", "/response/arr[@name='fields']/lst" + "[str[@name='name']='newfield' and str[@name='type']='text_en'" + " and bool[@name='stored']='true' and bool[@name='indexed']='false']"); assertU(adoc("newfield", "value1 value2", "id", "789")); assertU(commit()); assertJPost("/schema/fields", json("[{'name':'newfield2','type':'text_en','stored':true,'indexed':true,'multiValued':true}]"), "/responseHeader/status==0"); managedSchemaContents = FileUtils.readFileToString(managedSchemaFile, "UTF-8"); Pattern newfield2StoredTrueIndexedTrueMultiValuedTruePattern = Pattern.compile( "<field name=\"newfield2\" type=\"text_en\" " + "(?=.*stored=\"true\")(?=.*indexed=\"true\")(?=.*multiValued=\"true\").*/>"); assertTrue(newfield2StoredTrueIndexedTrueMultiValuedTruePattern.matcher(managedSchemaContents).find()); assertQ("/schema/fields?indent=on&wt=xml", "/response/arr[@name='fields']/lst" + "[str[@name='name']='newfield2' and str[@name='type']='text_en'" + " and bool[@name='stored']='true' and bool[@name='indexed']='true' and bool[@name='multiValued']='true']"); assertU(adoc("newfield2", "value1 value2", "newfield2", "value3 value4", "id", "790")); assertU(commit()); assertQ("/select?q=newfield2:value3", "/response/lst[@name='responseHeader']/int[@name='status'] = '0'", "/response/result[@name='response'][@numFound='1']", "count(/response/result[@name='response']/doc) = 1", "/response/result[@name='response']/doc/str[@name='id'][.='790']"); } @Test public void testAddCopyField() throws Exception { assertQ("/schema/fields/newfield2?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'"); assertJPut("/schema/fields/fieldA", json("{'type':'text','stored':false}"), "/responseHeader/status==0"); assertJPut("/schema/fields/fieldB", json("{'type':'text','stored':false, 'copyFields':['fieldA']}"), "/responseHeader/status==0"); assertJPut("/schema/fields/fieldC", json("{'type':'text','stored':false, 'copyFields':'fieldA'}"), "/responseHeader/status==0"); assertQ("/schema/fields/fieldB?indent=on&wt=xml", "count(/response/lst[@name='field']) = 1", "/response/lst[@name='responseHeader']/int[@name='status'] = '0'"); assertQ("/schema/copyfields/?indent=on&wt=xml&source.fl=fieldB", "count(/response/arr[@name='copyFields']/lst) = 1" ); assertQ("/schema/copyfields/?indent=on&wt=xml&source.fl=fieldC", "count(/response/arr[@name='copyFields']/lst) = 1" ); //fine to pass in empty list, just won't do anything assertJPut("/schema/fields/fieldD", json("{'type':'text','stored':false, 'copyFields':[]}"), "/responseHeader/status==0"); //some bad usages assertJPut("/schema/fields/fieldF", json("{'type':'text','stored':false, 'copyFields':['some_nonexistent_field_ignore_exception']}"), "/error/msg==\"copyField dest :\\'some_nonexistent_field_ignore_exception\\' is not an explicit field and doesn\\'t match a dynamicField.\""); } @Test public void testPostMultipleFields() throws Exception { assertQ("/schema/fields/newfield1?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'"); assertQ("/schema/fields/newfield2?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'"); assertJPost("/schema/fields", json( "[{'name':'newfield1','type':'text','stored':false}," + " {'name':'newfield2','type':'text','stored':false}]"), "/responseHeader/status==0"); assertQ("/schema/fields/newfield1?indent=on&wt=xml", "count(/response/lst[@name='field']) = 1", "/response/lst[@name='responseHeader']/int[@name='status'] = '0'"); assertQ("/schema/fields/newfield2?indent=on&wt=xml", "count(/response/lst[@name='field']) = 1", "/response/lst[@name='responseHeader']/int[@name='status'] = '0'"); assertU(adoc("newfield1", "value1 value2", "id", "123")); assertU(adoc("newfield2", "value3 value4", "id", "456")); assertU(commit()); assertQ("/select?q=newfield1:value1", "/response/lst[@name='responseHeader']/int[@name='status'] = '0'", "/response/result[@name='response'][@numFound='1']", "count(/response/result[@name='response']/doc/*) = 1", "/response/result[@name='response']/doc/str[@name='id'][.='123']"); assertQ("/select?q=newfield2:value3", "/response/lst[@name='responseHeader']/int[@name='status'] = '0'", "/response/result[@name='response'][@numFound='1']", "count(/response/result[@name='response']/doc/*) = 1", "/response/result[@name='response']/doc/str[@name='id'][.='456']"); } @Test public void testPostCopy() throws Exception { assertJPost("/schema/fields", json( "[{'name':'fieldA','type':'text','stored':false}," + " {'name':'fieldB','type':'text','stored':false}," + " {'name':'fieldC','type':'text','stored':false, 'copyFields':['fieldB']}]"), "/responseHeader/status==0"); assertQ("/schema/copyfields/?indent=on&wt=xml&source.fl=fieldC", "count(/response/arr[@name='copyFields']/lst) = 1" ); assertJPost("/schema/fields", json( "[{'name':'fieldD','type':'text','stored':false}," + " {'name':'fieldE','type':'text','stored':false}," + " {'name':'fieldF','type':'text','stored':false, 'copyFields':['fieldD','fieldE']}," + " {'name':'fieldG','type':'text','stored':false, 'copyFields':'fieldD'}]"),//single "/responseHeader/status==0"); assertQ("/schema/copyfields/?indent=on&wt=xml&source.fl=fieldF", "count(/response/arr[@name='copyFields']/lst) = 2" ); //passing in an empty list is perfectly acceptable, it just won't do anything assertJPost("/schema/fields", json( "[{'name':'fieldX','type':'text','stored':false}," + " {'name':'fieldY','type':'text','stored':false}," + " {'name':'fieldZ','type':'text','stored':false, 'copyFields':[]}]"), "/responseHeader/status==0"); //some bad usages assertJPost("/schema/fields", json( "[{'name':'fieldH','type':'text','stored':false}," + " {'name':'fieldI','type':'text','stored':false}," + " {'name':'fieldJ','type':'text','stored':false, 'copyFields':['some_nonexistent_field_ignore_exception']}]"), "/error/msg=='copyField dest :\\'some_nonexistent_field_ignore_exception\\' is not an explicit field and doesn\\'t match a dynamicField.'"); } @Test public void testPostCopyFields() throws Exception { assertJPost("/schema/fields", json( "[{'name':'fieldA','type':'text','stored':false}," + " {'name':'fieldB','type':'text','stored':false}," + " {'name':'fieldC','type':'text','stored':false}," + " {'name':'fieldD','type':'text','stored':false}," + " {'name':'fieldE','type':'text','stored':false}]"), "/responseHeader/status==0"); assertJPost("/schema/copyfields", json( "[{'source':'fieldA', 'dest':'fieldB'}," + " {'source':'fieldD', 'dest':['fieldC', 'fieldE']}]"), "/responseHeader/status==0"); assertQ("/schema/copyfields/?indent=on&wt=xml&source.fl=fieldA", "count(/response/arr[@name='copyFields']/lst) = 1"); assertQ("/schema/copyfields/?indent=on&wt=xml&source.fl=fieldD", "count(/response/arr[@name='copyFields']/lst) = 2"); assertJPost("/schema/copyfields", json("[{'source':'some_nonexistent_field_ignore_exception', 'dest':['fieldA']}]"), "/error/msg=='copyField source :\\'some_nonexistent_field_ignore_exception\\' is not a glob and doesn\\'t match any explicit field or dynamicField.'"); assertJPost("/schema/copyfields", json("[{'source':'fieldD', 'dest':['some_nonexistent_field_ignore_exception']}]"), "/error/msg=='copyField dest :\\'some_nonexistent_field_ignore_exception\\' is not an explicit field and doesn\\'t match a dynamicField.'"); } }