Skip to content

Commit

Permalink
Register MediaTypes through SPI (opensearch-project#8938)
Browse files Browse the repository at this point in the history
* Register MediaTypes through SPI

This commit provides a new SPI interface MediaContentProvider. Modules,
Plugins, Extensions, implement this interface and provide the concrete
MediaType implementations (and MIME aliases) through getMediaTypes and
getAdditionalMediaTypes, respectively. This enables downstream
extensions (e.g., serverless or cloud native implementations) to
register their own custom MediaType and define the serialization format
that is registered when the classloader loads the MediaTypeRegistry
instead of having to register the types explicitly in application code.

Signed-off-by: Nicholas Walter Knize <nknize@apache.org>

* pass the MediaTypeProvider classloader to the SPI ServiceLoader

Signed-off-by: Nicholas Walter Knize <nknize@apache.org>

---------

Signed-off-by: Nicholas Walter Knize <nknize@apache.org>
  • Loading branch information
nknize authored and baba-devv committed Jul 29, 2023
1 parent 671c7cc commit 2576fec
Show file tree
Hide file tree
Showing 8 changed files with 120 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,16 @@

package org.opensearch.core.xcontent;

import org.opensearch.core.xcontent.spi.MediaTypeProvider;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
* Parses supported internet media types
Expand All @@ -48,7 +55,25 @@ public final class MediaTypeRegistry {
// Default mediaType singleton
private static MediaType DEFAULT_MEDIA_TYPE;

public static void register(MediaType[] acceptedMediaTypes, Map<String, MediaType> additionalMediaTypes) {
// JSON is a core type, so we create a static instance for implementations that require JSON format (e.g., tests)
// todo we should explore moving the concrete JSON implementation from the xcontent library to core
public static final MediaType JSON;

static {
List<MediaType> mediaTypes = new ArrayList<>();
Map<String, MediaType> amt = new HashMap<>();
for (MediaTypeProvider provider : ServiceLoader.load(MediaTypeProvider.class, MediaTypeProvider.class.getClassLoader())) {
mediaTypes.addAll(provider.getMediaTypes());
amt = Stream.of(amt, provider.getAdditionalMediaTypes())
.flatMap(map -> map.entrySet().stream())
.collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, Map.Entry::getValue));
}
register(mediaTypes.toArray(new MediaType[0]), amt);
JSON = fromMediaType("application/json");
setDefaultMediaType(JSON);
}

private static void register(MediaType[] acceptedMediaTypes, Map<String, MediaType> additionalMediaTypes) {
// ensures the map is not overwritten:
Map<String, MediaType> typeMap = new HashMap<>(typeWithSubtypeToMediaType);
Map<String, MediaType> formatMap = new HashMap<>(formatToMediaType);
Expand Down Expand Up @@ -150,7 +175,7 @@ public Map<String, String> getParameters() {
}
}

public static void setDefaultMediaType(final MediaType mediaType) {
private static void setDefaultMediaType(final MediaType mediaType) {
if (DEFAULT_MEDIA_TYPE != null) {
throw new RuntimeException(
"unable to reset the default media type from current default [" + DEFAULT_MEDIA_TYPE + "] to [" + mediaType + "]"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/

package org.opensearch.core.xcontent.spi;

import org.opensearch.core.xcontent.MediaType;

import java.util.List;
import java.util.Map;

/**
* Service Provider Interface for plugins, modules, extensions providing
* their own Media Types
*
* @opensearch.experimental
* @opensearch.api
*/
public interface MediaTypeProvider {
/** Extensions that implement their own concrete {@link MediaType}s provide them through this interface method */
List<MediaType> getMediaTypes();

/** Extensions that implement additional {@link MediaType} aliases provide them through this interface method */
Map<String, MediaType> getAdditionalMediaTypes();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/

/** Service Provider Interface for extensible media types */
package org.opensearch.core.xcontent.spi;
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,9 @@
import org.opensearch.common.xcontent.yaml.YamlXContent;
import org.opensearch.core.common.io.stream.StreamOutput;
import org.opensearch.core.xcontent.MediaType;
import org.opensearch.core.xcontent.MediaTypeRegistry;
import org.opensearch.core.xcontent.XContent;

import java.io.IOException;
import java.util.Map;

/**
* The content type of {@link XContent}.
Expand Down Expand Up @@ -131,11 +129,6 @@ public XContent xContent() {
}
};

static {
/** a parser of media types */
MediaTypeRegistry.register(XContentType.values(), Map.of("application/*", JSON, "application/x-ndjson", JSON));
}

private int index;

XContentType(int index) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/

package org.opensearch.common.xcontent.spi;

import org.opensearch.common.xcontent.XContentType;
import org.opensearch.core.xcontent.MediaType;
import org.opensearch.core.xcontent.spi.MediaTypeProvider;

import java.util.List;
import java.util.Map;

/**
* Media Type implementations provided by xcontent library
*
* @opensearch.internal
*/
public class XContentProvider implements MediaTypeProvider {
/** Returns the concrete {@link MediaType} provided by the xcontent library */
@Override
public List<MediaType> getMediaTypes() {
return List.of(XContentType.values());
}

/** Returns the additional {@link MediaType} aliases provided by the xcontent library */
@Override
public Map<String, MediaType> getAdditionalMediaTypes() {
return Map.of("application/*", XContentType.JSON, "application/x-ndjson", XContentType.JSON);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/

/** SPI implementation for the xcontent library */
package org.opensearch.common.xcontent.spi;
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#
# SPDX-License-Identifier: Apache-2.0
#
# The OpenSearch Contributors require contributions made to
# this file be licensed under the Apache-2.0 license or a
# compatible open source license.
#

org.opensearch.common.xcontent.spi.XContentProvider
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,8 @@
import org.opensearch.common.util.concurrent.ThreadContext;
import org.opensearch.common.util.io.IOUtils;
import org.opensearch.common.lease.Releasable;
import org.opensearch.common.xcontent.XContentType;
import org.opensearch.core.common.Strings;
import org.opensearch.core.concurrency.OpenSearchRejectedExecutionException;
import org.opensearch.core.xcontent.MediaTypeRegistry;
import org.opensearch.node.NodeClosedException;
import org.opensearch.node.ReportingService;
import org.opensearch.tasks.Task;
Expand Down Expand Up @@ -173,8 +171,6 @@ public void close() {}
Streamables.registerStreamables();
/** Registers OpenSearch server specific exceptions (exceptions outside of core library) */
OpenSearchServerException.registerExceptions();
// set the default media type to JSON (fallback if a media type is not specified)
MediaTypeRegistry.setDefaultMediaType(XContentType.JSON);
}

/** does nothing. easy way to ensure class is loaded so the above static block is called to register the streamables */
Expand Down

0 comments on commit 2576fec

Please sign in to comment.