Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# See https://github.com/apache/solr/blob/main/dev-docs/changelog.adoc
title: Migrate Schema Designer API to JAX-RS. Fix bug preventing analysis of sample documents from running.
type: fixed # added, changed, fixed, deprecated, removed, dependency_update, security, other
authors:
- name: Eric Pugh
links:
- name: SOLR-18152
url: https://issues.apache.org/jira/browse/SOLR-18152
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
/*
* 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.api.endpoint;

import static org.apache.solr.client.api.util.Constants.RAW_OUTPUT_PROPERTY;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.extensions.Extension;
import io.swagger.v3.oas.annotations.extensions.ExtensionProperty;
import jakarta.ws.rs.DefaultValue;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.Response;
import java.util.List;
import org.apache.solr.client.api.model.FlexibleSolrJerseyResponse;
import org.apache.solr.client.api.model.SolrJerseyResponse;

/** V2 API definitions for the Solr Schema Designer. */
@Path("/schema-designer")
public interface SchemaDesignerApi {

@GET
@Path("/info")
@Operation(
summary = "Get info about a configSet being designed.",
tags = {"schema-designer"})
FlexibleSolrJerseyResponse getInfo(@QueryParam("configSet") String configSet) throws Exception;

@POST
@Path("/prep")
@Operation(
summary = "Prepare a mutable configSet copy for schema design.",
tags = {"schema-designer"})
FlexibleSolrJerseyResponse prepNewSchema(
@QueryParam("configSet") String configSet, @QueryParam("copyFrom") String copyFrom)
throws Exception;

@PUT
@Path("/cleanup")
@Operation(
summary = "Clean up temporary resources for a schema being designed.",
tags = {"schema-designer"})
SolrJerseyResponse cleanupTempSchema(@QueryParam("configSet") String configSet) throws Exception;

@GET
@Path("/file")
@Operation(
summary = "Get the contents of a file in a configSet being designed.",
tags = {"schema-designer"})
FlexibleSolrJerseyResponse getFileContents(
@QueryParam("configSet") String configSet, @QueryParam("file") String file) throws Exception;

@POST
@Path("/file")
@Operation(
summary = "Update the contents of a file in a configSet being designed.",
tags = {"schema-designer"})
FlexibleSolrJerseyResponse updateFileContents(
@QueryParam("configSet") String configSet, @QueryParam("file") String file) throws Exception;

@GET
@Path("/sample")
@Operation(
summary = "Get a sample value and analysis for a field.",
tags = {"schema-designer"})
FlexibleSolrJerseyResponse getSampleValue(
@QueryParam("configSet") String configSet,
@QueryParam("field") String fieldName,
@QueryParam("uniqueKeyField") String idField,
@QueryParam("docId") String docId)
throws Exception;

@GET
@Path("/collectionsForConfig")
@Operation(
summary = "List collections that use a given configSet.",
tags = {"schema-designer"})
FlexibleSolrJerseyResponse listCollectionsForConfig(@QueryParam("configSet") String configSet)
throws Exception;

@GET
@Path("/configs")
@Operation(
summary = "List all configSets available for schema design.",
tags = {"schema-designer"})
FlexibleSolrJerseyResponse listConfigs() throws Exception;

@GET
@Path("/download")
@Operation(
summary = "Download a configSet as a ZIP archive.",
tags = {"schema-designer"},
extensions = {
@Extension(properties = {@ExtensionProperty(name = RAW_OUTPUT_PROPERTY, value = "true")})
})
@Produces("application/zip")
Response downloadConfig(@QueryParam("configSet") String configSet) throws Exception;

@POST
@Path("/add")
@Operation(
summary = "Add a new field, field type, or dynamic field to the schema being designed.",
tags = {"schema-designer"})
FlexibleSolrJerseyResponse addSchemaObject(
@QueryParam("configSet") String configSet, @QueryParam("schemaVersion") Integer schemaVersion)
throws Exception;

@PUT
@Path("/update")
@Operation(
summary = "Update an existing field or field type in the schema being designed.",
tags = {"schema-designer"})
FlexibleSolrJerseyResponse updateSchemaObject(
@QueryParam("configSet") String configSet, @QueryParam("schemaVersion") Integer schemaVersion)
throws Exception;

@PUT
@Path("/publish")
@Operation(
summary = "Publish the designed schema to a live configSet.",
tags = {"schema-designer"})
FlexibleSolrJerseyResponse publish(
@QueryParam("configSet") String configSet,
@QueryParam("schemaVersion") Integer schemaVersion,
@QueryParam("newCollection") String newCollection,
@QueryParam("reloadCollections") @DefaultValue("false") Boolean reloadCollections,
@QueryParam("numShards") @DefaultValue("1") Integer numShards,
@QueryParam("replicationFactor") @DefaultValue("1") Integer replicationFactor,
@QueryParam("indexToCollection") @DefaultValue("false") Boolean indexToCollection,
@QueryParam("cleanupTemp") @DefaultValue("true") Boolean cleanupTempParam,
@QueryParam("disableDesigner") @DefaultValue("false") Boolean disableDesigner)
throws Exception;

@POST
@Path("/analyze")
@Operation(
summary = "Analyze sample documents and suggest a schema.",
tags = {"schema-designer"})
FlexibleSolrJerseyResponse analyze(
@QueryParam("configSet") String configSet,
@QueryParam("schemaVersion") Integer schemaVersion,
@QueryParam("copyFrom") String copyFrom,
@QueryParam("uniqueKeyField") String uniqueKeyField,
@QueryParam("languages") List<String> languages,
@QueryParam("enableDynamicFields") Boolean enableDynamicFields,
@QueryParam("enableFieldGuessing") Boolean enableFieldGuessing,
@QueryParam("enableNestedDocs") Boolean enableNestedDocs)
throws Exception;

@GET
@Path("/query")
@Operation(
summary = "Query the temporary collection used during schema design.",
tags = {"schema-designer"})
FlexibleSolrJerseyResponse query(@QueryParam("configSet") String configSet) throws Exception;

@GET
@Path("/diff")
@Operation(
summary = "Get the diff between the designed schema and the published schema.",
tags = {"schema-designer"})
FlexibleSolrJerseyResponse getSchemaDiff(@QueryParam("configSet") String configSet)
throws Exception;
}
2 changes: 1 addition & 1 deletion solr/core/src/java/org/apache/solr/core/CoreContainer.java
Original file line number Diff line number Diff line change
Expand Up @@ -870,7 +870,7 @@ private void loadInternal() {
registerV2ApiIfEnabled(clusterAPI.commands);

if (isZooKeeperAware()) {
registerV2ApiIfEnabled(new SchemaDesignerAPI(this));
registerV2ApiIfEnabled(SchemaDesignerAPI.class);
} // else Schema Designer not available in standalone (non-cloud) mode

/*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ public SampleDocuments parseDocsFromStream(
+ MAX_STREAM_SIZE
+ " bytes is the max upload size for sample documents.");
}
// use a byte stream for the parsers in case they need to re-parse using a different strategy
// use a byte stream for the parsers in case they need to reparse using a different strategy
// e.g. JSON vs. JSON lines or different CSV strategies ...
ContentStreamBase.ByteArrayStream byteStream =
new ContentStreamBase.ByteArrayStream(uploadedBytes, fileSource, contentType);
Expand Down Expand Up @@ -153,21 +153,14 @@ protected List<SolrInputDocument> loadCsvDocs(
.loadDocs(stream);
}

@SuppressWarnings("unchecked")
protected List<SolrInputDocument> loadJsonLines(
ContentStreamBase.ByteArrayStream stream, final int maxDocsToLoad) throws IOException {
List<Map<String, Object>> docs = new ArrayList<>();
try (Reader r = stream.getReader()) {
BufferedReader br = new BufferedReader(r);
String line;
while ((line = br.readLine()) != null) {
line = line.trim();
if (!line.isEmpty() && line.startsWith("{") && line.endsWith("}")) {
Object jsonLine = ObjectBuilder.getVal(new JSONParser(line));
if (jsonLine instanceof Map) {
docs.add((Map<String, Object>) jsonLine);
}
}
parseStringToJson(docs, line);
if (maxDocsToLoad > 0 && docs.size() == maxDocsToLoad) {
break;
}
Expand All @@ -177,6 +170,19 @@ protected List<SolrInputDocument> loadJsonLines(
return docs.stream().map(JsonLoader::buildDoc).collect(Collectors.toList());
}

private void parseStringToJson(List<Map<String, Object>> docs, String line) throws IOException {
line = line.trim();
if (line.startsWith("{") && line.endsWith("}")) {
Object jsonLine = ObjectBuilder.getVal(new JSONParser(line));
if (jsonLine instanceof Map<?, ?> rawMap) {
// JSON object keys are always Strings; the cast is safe
@SuppressWarnings("unchecked")
Map<String, Object> typedMap = (Map<String, Object>) rawMap;
docs.add(typedMap);
}
}
}

@SuppressWarnings("unchecked")
protected List<SolrInputDocument> loadJsonDocs(
ContentStreamBase.ByteArrayStream stream, final int maxDocsToLoad) throws IOException {
Expand Down Expand Up @@ -204,7 +210,7 @@ protected List<SolrInputDocument> loadJsonDocs(
if (lines.length > 1) {
for (String line : lines) {
line = line.trim();
if (!line.isEmpty() && line.startsWith("{") && line.endsWith("}")) {
if (line.startsWith("{") && line.endsWith("}")) {
isJsonLines = true;
break;
}
Expand Down Expand Up @@ -294,17 +300,10 @@ protected List<SolrInputDocument> parseXmlDocs(XMLStreamReader parser, final int
}
}

@SuppressWarnings("unchecked")
protected List<Map<String, Object>> loadJsonLines(String[] lines) throws IOException {
List<Map<String, Object>> docs = new ArrayList<>(lines.length);
for (String line : lines) {
line = line.trim();
if (!line.isEmpty() && line.startsWith("{") && line.endsWith("}")) {
Object jsonLine = ObjectBuilder.getVal(new JSONParser(line));
if (jsonLine instanceof Map) {
docs.add((Map<String, Object>) jsonLine);
}
}
parseStringToJson(docs, line);
}
return docs;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,18 +170,17 @@ public Optional<SchemaField> suggestField(
throw new IllegalStateException("FieldType '" + fieldTypeName + "' not found in the schema!");
}

Map<String, String> fieldProps =
guessFieldProps(fieldName, fieldType, sampleValues, isMV, schema);
Map<String, String> fieldProps = guessFieldProps(fieldName, fieldType, isMV, schema);
SchemaField schemaField = schema.newField(fieldName, fieldTypeName, fieldProps);
return Optional.of(schemaField);
}

@Override
public ManagedIndexSchema adaptExistingFieldToData(
SchemaField schemaField, List<Object> sampleValues, ManagedIndexSchema schema) {
// Promote a single-valued to multi-valued if needed
// Promote a single-valued to multivalued if needed
if (!schemaField.multiValued() && isMultiValued(sampleValues)) {
// this existing field needs to be promoted to multi-valued
// this existing field needs to be promoted to multivalued
SimpleOrderedMap<Object> fieldProps = schemaField.getNamedPropertyValues(false);
fieldProps.add("multiValued", true);
fieldProps.remove("name");
Expand Down Expand Up @@ -210,7 +209,7 @@ public Map<String, List<Object>> transposeDocs(List<SolrInputDocument> docs) {
Collection<Object> fieldValues = doc.getFieldValues(f);
if (fieldValues != null && !fieldValues.isEmpty()) {
if (fieldValues.size() == 1) {
// flatten so every field doesn't end up multi-valued
// flatten so every field doesn't end up multivalued
values.add(fieldValues.iterator().next());
} else {
// truly multi-valued
Expand Down Expand Up @@ -395,11 +394,7 @@ protected boolean isMultiValued(final List<Object> sampleValues) {
}

protected Map<String, String> guessFieldProps(
String fieldName,
FieldType fieldType,
List<Object> sampleValues,
boolean isMV,
IndexSchema schema) {
String fieldName, FieldType fieldType, boolean isMV, IndexSchema schema) {
Map<String, String> props = new HashMap<>();
props.put("indexed", "true");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public List<SolrInputDocument> appendDocs(
return id != null
&& !ids.contains(id); // doc has ID, and it's not already in the set
})
.collect(Collectors.toList());
.toList();
parsed.addAll(toAdd);
if (maxDocsToLoad > 0 && parsed.size() > maxDocsToLoad) {
parsed = parsed.subList(0, maxDocsToLoad);
Expand Down
Loading
Loading