MediawikiEventStreamConfigLoader.java

package org.wikimedia.eventutilities.core.event;

import static java.nio.charset.StandardCharsets.UTF_8;

import java.net.URI;
import java.net.URLEncoder;
import java.util.List;
import java.util.Locale;

import org.wikimedia.eventutilities.core.json.JsonLoader;
import org.wikimedia.eventutilities.core.json.JsonLoadingException;

import com.fasterxml.jackson.databind.node.ObjectNode;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;

/**
 * EventStreamConfigLoader implementation that loads stream config
 * from a remote Mediawiki EventStreamConfig extension API endpoint.
 */
public class MediawikiEventStreamConfigLoader extends EventStreamConfigLoader {

    /**
     * Used to fetch JSON stream configs from the mediawikiApieEndpoint.
     */
    private final JsonLoader jsonLoader;

    /**
     * Base Mediawiki API endpoint, e.g https://meta.wikimedia.org/w/api.php.
     */
    protected String mediawikiApiEndpoint;

    /**
     * Key into the EventStreamConfig API response in which stream configs are located.
     */
    protected static final String RESPONSE_STREAMS_KEY = "streams";

    /**
     * Constructs a MediawikiEventStreamConfigLoader that loads from mediawikiApiEndpoint using jsonLoader.
     */
    public MediawikiEventStreamConfigLoader(String mediawikiApiEndpoint, JsonLoader jsonLoader) {
        this.mediawikiApiEndpoint = mediawikiApiEndpoint;
        this.jsonLoader = jsonLoader;
    }

    /**
     * EventStreamConfigLoader load implementation.
     */
    @SuppressFBWarnings(value = "EXS_EXCEPTION_SOFTENING_NO_CHECKED", justification = "This method is public.")
    public ObjectNode load(List<String> streamNames) {
        URI uri = makeMediawikiEventStreamConfigApiUri(mediawikiApiEndpoint, streamNames);
        try {
            return (ObjectNode) jsonLoader.load(uri).get(RESPONSE_STREAMS_KEY);
        } catch (JsonLoadingException e) {
            throw new IllegalArgumentException("Failed to load stream configuration", e);
        }
    }

    /**
     * Builds a Mediawiki EventStreamConfig extension API URI for the given streams.
     *
     * If streamNames is empty, this will attempt to request all streams from the API.
     */
    public static URI makeMediawikiEventStreamConfigApiUri(String mediawikiApiEndpoint, List<String> streamNames) {
        final String mediawikiStreamsParamFormat = "streams=%s";
        final String mediawikiStreamsDelimiter;

        try {
            // We need to support streams param delimiters like "|", which is what
            // MW API expects, but URI.create will throw a
            // java.lang.IllegalArgumentException: Illegal character
            // if we don't URL encode "|" first.
            mediawikiStreamsDelimiter = URLEncoder.encode(
                "|", UTF_8.name()
            );
        } catch (java.io.UnsupportedEncodingException e) {
            // This should never happen.
            throw new IllegalArgumentException(
                "Could not URL encode '|'. " + e.getMessage(), e
            );
        }

        String mediawikiEventStreamConfigUri = mediawikiApiEndpoint + "?format=json&action=streamconfigs&all_settings=true";

        // If no specified stream names, try to get them all.
        if (streamNames.isEmpty()) {
            return URI.create(mediawikiEventStreamConfigUri);
        } else {
            // else format the URI to request specific stream names.

            String streamsParam = String.format(
                    Locale.ROOT,
                    mediawikiStreamsParamFormat,
                    String.join(mediawikiStreamsDelimiter, streamNames)
            );

            return URI.create(
                mediawikiEventStreamConfigUri + streamsParam
            );
        }
    }

    public String toString() {
        return this.getClass().getName() + "(" + mediawikiApiEndpoint + ")";
    }

}