From 0baf2fa33cef485df94649fd408c22e6430b68cf Mon Sep 17 00:00:00 2001 From: Mikhail Khludnev Date: Thu, 23 Feb 2017 00:40:40 +0300 Subject: [PATCH] SOLR-10134: EmbeddedSolrServer handles SchemaAPI requests --- solr/CHANGES.txt | 1 + .../solrj/embedded/EmbeddedSolrServer.java | 55 +++++---- .../solr/request/SolrQueryRequestBase.java | 17 ++- .../solr/servlet/SolrRequestParsers.java | 11 +- .../TestEmbeddedSolrServerSchemaAPI.java | 111 ++++++++++++++++++ .../java/org/apache/solr/SolrTestCaseJ4.java | 6 +- 6 files changed, 168 insertions(+), 33 deletions(-) create mode 100644 solr/core/src/test/org/apache/solr/client/solrj/embedded/TestEmbeddedSolrServerSchemaAPI.java diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt index 47f190bb415..db5e3e6b3d2 100644 --- a/solr/CHANGES.txt +++ b/solr/CHANGES.txt @@ -259,6 +259,7 @@ Other Changes * SOLR-10214: Remove unused HDFS BlockCache metrics and add storeFails, as well as adding total counts for lookups, hits, and evictions. (yonik) +* SOLR-10134: EmbeddedSolrServer responds on Schema API requests (Robert Alexandersson via Mikhail Khludnev) ================== 6.4.2 ================== diff --git a/solr/core/src/java/org/apache/solr/client/solrj/embedded/EmbeddedSolrServer.java b/solr/core/src/java/org/apache/solr/client/solrj/embedded/EmbeddedSolrServer.java index fc283f42ef6..8de5fc92faf 100644 --- a/solr/core/src/java/org/apache/solr/client/solrj/embedded/EmbeddedSolrServer.java +++ b/solr/core/src/java/org/apache/solr/client/solrj/embedded/EmbeddedSolrServer.java @@ -172,6 +172,7 @@ public class EmbeddedSolrServer extends SolrClient { req = _parser.buildRequestFrom(core, params, request.getContentStreams()); req.getContext().put(PATH, path); + req.getContext().put("httpMethod", request.getMethod().name()); SolrQueryResponse rsp = new SolrQueryResponse(); SolrRequestInfo.setRequestInfo(new SolrRequestInfo(req, rsp)); @@ -199,32 +200,13 @@ public class EmbeddedSolrServer extends SolrClient { }; - ByteArrayOutputStream out = new ByteArrayOutputStream(); - new JavaBinCodec(resolver) { + try(ByteArrayOutputStream out = new ByteArrayOutputStream()) { + createJavaBinCodec(callback, resolver).setWritableDocFields(resolver).marshal(rsp.getValues(), out); - @Override - public void writeSolrDocument(SolrDocument doc) { - callback.streamSolrDocument(doc); - //super.writeSolrDocument( doc, fields ); + try(InputStream in = out.toInputStream()){ + return (NamedList) new JavaBinCodec(resolver).unmarshal(in); } - - @Override - public void writeSolrDocumentList(SolrDocumentList docs) throws IOException { - if (docs.size() > 0) { - SolrDocumentList tmp = new SolrDocumentList(); - tmp.setMaxScore(docs.getMaxScore()); - tmp.setNumFound(docs.getNumFound()); - tmp.setStart(docs.getStart()); - docs = tmp; - } - callback.streamDocListInfo(docs.getNumFound(), docs.getStart(), docs.getMaxScore()); - super.writeSolrDocumentList(docs); - } - - }.setWritableDocFields(resolver). marshal(rsp.getValues(), out); - - InputStream in = out.toInputStream(); - return (NamedList) new JavaBinCodec(resolver).unmarshal(in); + } } catch (Exception ex) { throw new RuntimeException(ex); } @@ -243,6 +225,31 @@ public class EmbeddedSolrServer extends SolrClient { } } + private JavaBinCodec createJavaBinCodec(final StreamingResponseCallback callback, final BinaryResponseWriter.Resolver resolver) { + return new JavaBinCodec(resolver) { + + @Override + public void writeSolrDocument(SolrDocument doc) { + callback.streamSolrDocument(doc); + //super.writeSolrDocument( doc, fields ); + } + + @Override + public void writeSolrDocumentList(SolrDocumentList docs) throws IOException { + if (docs.size() > 0) { + SolrDocumentList tmp = new SolrDocumentList(); + tmp.setMaxScore(docs.getMaxScore()); + tmp.setNumFound(docs.getNumFound()); + tmp.setStart(docs.getStart()); + docs = tmp; + } + callback.streamDocListInfo(docs.getNumFound(), docs.getStart(), docs.getMaxScore()); + super.writeSolrDocumentList(docs); + } + + }; + } + private static void checkForExceptions(SolrQueryResponse rsp) throws Exception { if (rsp.getException() != null) { if (rsp.getException() instanceof SolrException) { diff --git a/solr/core/src/java/org/apache/solr/request/SolrQueryRequestBase.java b/solr/core/src/java/org/apache/solr/request/SolrQueryRequestBase.java index 4b0e4d62d8b..19350f01e3b 100644 --- a/solr/core/src/java/org/apache/solr/request/SolrQueryRequestBase.java +++ b/solr/core/src/java/org/apache/solr/request/SolrQueryRequestBase.java @@ -31,13 +31,14 @@ import org.apache.solr.common.util.ContentStream; import org.apache.solr.core.SolrCore; import java.io.Closeable; +import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.security.Principal; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.HashMap; import static java.nio.charset.StandardCharsets.UTF_8; @@ -202,7 +203,7 @@ public abstract class SolrQueryRequestBase implements SolrQueryRequest, Closeabl Iterable contentStreams = getContentStreams(); if (contentStreams == null) throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "No content stream"); for (ContentStream contentStream : contentStreams) { - parsedCommands = ApiBag.getCommandOperations(new InputStreamReader((InputStream) contentStream, UTF_8), + parsedCommands = ApiBag.getCommandOperations(getInputStream(contentStream), getValidators(), validateInput); } @@ -211,6 +212,18 @@ public abstract class SolrQueryRequestBase implements SolrQueryRequest, Closeabl } + private InputStreamReader getInputStream(ContentStream contentStream) { + if(contentStream instanceof InputStream) { + return new InputStreamReader((InputStream)contentStream, UTF_8); + } else { + try { + return new InputStreamReader(contentStream.getStream(), UTF_8); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + protected ValidatingJsonMap getSpec() { return null; } diff --git a/solr/core/src/java/org/apache/solr/servlet/SolrRequestParsers.java b/solr/core/src/java/org/apache/solr/servlet/SolrRequestParsers.java index c311d4aad4c..93baace3ce1 100644 --- a/solr/core/src/java/org/apache/solr/servlet/SolrRequestParsers.java +++ b/solr/core/src/java/org/apache/solr/servlet/SolrRequestParsers.java @@ -16,7 +16,7 @@ */ package org.apache.solr.servlet; -import javax.servlet.http.HttpServletRequest; +import static org.apache.solr.common.params.CommonParams.PATH; import java.io.ByteArrayOutputStream; import java.io.File; @@ -33,13 +33,14 @@ import java.security.Principal; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; +import javax.servlet.http.HttpServletRequest; + import org.apache.commons.fileupload.FileItem; import org.apache.commons.fileupload.disk.DiskFileItemFactory; import org.apache.commons.fileupload.servlet.ServletFileUpload; @@ -64,8 +65,6 @@ import org.apache.solr.util.CommandOperation; import org.apache.solr.util.RTimerTree; import org.apache.solr.util.SolrFileCleaningTracker; -import static org.apache.solr.common.params.CommonParams.PATH; - public class SolrRequestParsers { @@ -239,7 +238,7 @@ public class SolrRequestParsers if (httpSolrCall != null) { return httpSolrCall.getCommands(validateInput); } - return Collections.emptyList(); + return super.getCommands(validateInput); } @Override @@ -247,7 +246,7 @@ public class SolrRequestParsers if (httpSolrCall != null && httpSolrCall instanceof V2HttpCall) { return ((V2HttpCall) httpSolrCall).getUrlParts(); } - return Collections.EMPTY_MAP; + return super.getPathTemplateValues(); } @Override diff --git a/solr/core/src/test/org/apache/solr/client/solrj/embedded/TestEmbeddedSolrServerSchemaAPI.java b/solr/core/src/test/org/apache/solr/client/solrj/embedded/TestEmbeddedSolrServerSchemaAPI.java new file mode 100644 index 00000000000..f253831268c --- /dev/null +++ b/solr/core/src/test/org/apache/solr/client/solrj/embedded/TestEmbeddedSolrServerSchemaAPI.java @@ -0,0 +1,111 @@ +/* + * 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.client.solrj.embedded; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.apache.solr.SolrTestCaseJ4; +import org.apache.solr.client.solrj.SolrServerException; +import org.apache.solr.client.solrj.request.schema.SchemaRequest; +import org.apache.solr.client.solrj.response.schema.SchemaResponse; +import org.apache.solr.client.solrj.response.schema.SchemaResponse.FieldResponse; +import org.apache.solr.common.SolrException; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +public class TestEmbeddedSolrServerSchemaAPI extends SolrTestCaseJ4 { + + private String fieldName = "VerificationTest"; + private static EmbeddedSolrServer server; + private final Map fieldAttributes; + { + Map field = new LinkedHashMap<>(); + field.put("name", fieldName); + field.put("type", "string"); + field.put("stored", false); + field.put("indexed", true); + field.put("multiValued", true); + fieldAttributes = Collections.unmodifiableMap(field); + } + + @BeforeClass + public static void initClass() throws Exception { + assertNull("no system props clash please", System.getProperty("managed.schema.mutable")); + System.setProperty("managed.schema.mutable", ""+//true + random().nextBoolean() + ); + Path tmpHome = createTempDir("tmp-home"); + Path coreDir = tmpHome.resolve(DEFAULT_TEST_CORENAME); + copyMinConf(coreDir.toFile(), null, "solrconfig-managed-schema.xml"); + initCore("solrconfig.xml" /*it's renamed to to*/, "schema.xml", tmpHome.toAbsolutePath().toString()); + + server = new EmbeddedSolrServer(h.getCoreContainer(), DEFAULT_TEST_CORENAME); + } + + @AfterClass + public static void destroyClass() throws IOException { + server.close(); // doubtful + server = null; + System.clearProperty("managed.schema.mutable"); + } + + @Before + public void thereIsNoFieldYet() throws SolrServerException, IOException{ + try{ + FieldResponse process = new SchemaRequest.Field(fieldName) + .process(server); + fail(""+process); + }catch(SolrException e){ + assertTrue(e.getMessage().contains("No") + && e.getMessage().contains("VerificationTest")); + } + } + + @Test + public void testSchemaAddFieldAndVerifyExistence() throws Exception { + assumeTrue("it needs to ammend schema", Boolean.getBoolean("managed.schema.mutable")); + SchemaResponse.UpdateResponse addFieldResponse = new SchemaRequest.AddField(fieldAttributes).process(server); + + assertEquals(addFieldResponse.toString(), 0, addFieldResponse.getStatus()); + + // This asserts that the field was actually created + // this is due to the fact that the response gave OK but actually never created the field. + Map foundFieldAttributes = new SchemaRequest.Field(fieldName).process(server).getField(); + assertEquals(fieldAttributes, foundFieldAttributes); + + assertEquals("removing " + fieldName, 0, + new SchemaRequest.DeleteField(fieldName).process(server).getStatus()); + } + + @Test + public void testSchemaAddFieldAndFailOnImmutable() throws Exception { + assumeFalse("it needs a readonly schema", Boolean.getBoolean("managed.schema.mutable")); + + SchemaRequest.AddField addFieldUpdateSchemaRequest = new SchemaRequest.AddField(fieldAttributes); + SchemaResponse.UpdateResponse addFieldResponse = addFieldUpdateSchemaRequest.process(server); + // wt hell???? assertFalse(addFieldResponse.toString(), addFieldResponse.getStatus()==0); + assertTrue((""+addFieldResponse).contains("schema is not editable")); + + } + +} diff --git a/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java b/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java index 7d9d1f6a8f5..e5bd3849d52 100644 --- a/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java +++ b/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java @@ -2019,6 +2019,10 @@ public abstract class SolrTestCaseJ4 extends LuceneTestCase { // the string to write to the core.properties file may be null in which case nothing is done with it. // propertiesContent may be an empty string, which will actually work. public static void copyMinConf(File dstRoot, String propertiesContent) throws IOException { + copyMinConf(dstRoot, propertiesContent, "solrconfig-minimal.xml"); + } + + public static void copyMinConf(File dstRoot, String propertiesContent, String solrconfigXmlName) throws IOException { File subHome = new File(dstRoot, "conf"); if (! dstRoot.exists()) { @@ -2030,7 +2034,7 @@ public abstract class SolrTestCaseJ4 extends LuceneTestCase { } String top = SolrTestCaseJ4.TEST_HOME() + "/collection1/conf"; FileUtils.copyFile(new File(top, "schema-tiny.xml"), new File(subHome, "schema.xml")); - FileUtils.copyFile(new File(top, "solrconfig-minimal.xml"), new File(subHome, "solrconfig.xml")); + FileUtils.copyFile(new File(top, solrconfigXmlName), new File(subHome, "solrconfig.xml")); FileUtils.copyFile(new File(top, "solrconfig.snippet.randomindexconfig.xml"), new File(subHome, "solrconfig.snippet.randomindexconfig.xml")); }