All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.elasticsearch.common.util.ExtensionPoint Maven / Gradle / Ivy

The newest version!
/*
 * Licensed to Elasticsearch under one or more contributor
 * license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright
 * ownership. Elasticsearch 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.elasticsearch.common.util;

import org.elasticsearch.common.inject.Binder;
import org.elasticsearch.common.inject.multibindings.MapBinder;
import org.elasticsearch.common.inject.multibindings.Multibinder;
import org.elasticsearch.common.settings.Settings;

import java.util.*;

/**
 * This class defines an official elasticsearch extension point. It registers
 * all extensions by a single name and ensures that extensions are not registered
 * more than once.
 */
public abstract class ExtensionPoint {
    protected final String name;
    protected final Class[] singletons;

    /**
     * Creates a new extension point
     *
     * @param name           the human readable underscore case name of the extension point. This is used in error messages etc.
     * @param singletons     a list of singletons to bind with this extension point - these are bound in {@link #bind(Binder)}
     */
    public ExtensionPoint(String name, Class... singletons) {
        this.name = name;
        this.singletons = singletons;
    }

    /**
     * Binds the extension as well as the singletons to the given guice binder.
     *
     * @param binder the binder to use
     */
    public final void bind(Binder binder) {
        for (Class c : singletons) {
            binder.bind(c).asEagerSingleton();
        }
        bindExtensions(binder);
    }

    /**
     * Subclasses can bind their type, map or set extensions here.
     */
    protected abstract void bindExtensions(Binder binder);

    /**
     * A map based extension point which allows to register keyed implementations ie. parsers or some kind of strategies.
     */
    public static class ClassMap extends ExtensionPoint {
        protected final Class extensionClass;
        private final Map> extensions = new HashMap<>();
        private final Set reservedKeys;

        /**
         * Creates a new {@link ClassMap}
         *
         * @param name           the human readable underscore case name of the extension poing. This is used in error messages etc.
         * @param extensionClass the base class that should be extended
         * @param singletons     a list of singletons to bind with this extension point - these are bound in {@link #bind(Binder)}
         * @param reservedKeys   a set of reserved keys by internal implementations
         */
        public ClassMap(String name, Class extensionClass, Set reservedKeys, Class... singletons) {
            super(name, singletons);
            this.extensionClass = extensionClass;
            this.reservedKeys = reservedKeys;
        }

        /**
         * Returns the extension for the given key or null
         */
        public Class getExtension(String type) {
            return extensions.get(type);
        }

        /**
         * Registers an extension class for a given key. This method will thr
         *
         * @param key       the extensions key
         * @param extension the extension
         * @throws IllegalArgumentException iff the key is already registered or if the key is a reserved key for an internal implementation
         */
        public final void registerExtension(String key, Class extension) {
            if (extensions.containsKey(key) || reservedKeys.contains(key)) {
                throw new IllegalArgumentException("Can't register the same [" + this.name + "] more than once for [" + key + "]");
            }
            extensions.put(key, extension);
        }

        @Override
        protected final void bindExtensions(Binder binder) {
            MapBinder parserMapBinder = MapBinder.newMapBinder(binder, String.class, extensionClass);
            for (Map.Entry> clazz : extensions.entrySet()) {
                parserMapBinder.addBinding(clazz.getKey()).to(clazz.getValue());
            }
        }
    }

    /**
     * A Type extension point which basically allows to registerd keyed extensions like {@link ClassMap}
     * but doesn't instantiate and bind all the registered key value pairs but instead replace a singleton based on a given setting via {@link #bindType(Binder, Settings, String, String)}
     * Note: {@link #bind(Binder)} is not supported by this class
     */
    public static final class SelectedType extends ClassMap {

        public SelectedType(String name, Class extensionClass) {
            super(name, extensionClass, Collections.EMPTY_SET);
        }

        /**
         * Binds the extension class to the class that is registered for the give configured for the settings key in
         * the settings object.
         *
         * @param binder       the binder to use
         * @param settings     the settings to look up the key to find the implementation to bind
         * @param settingsKey  the key to use with the settings
         * @param defaultValue the default value if the settings do not contain the key, or null if there is no default
         * @return the actual bound type key
         */
        public String bindType(Binder binder, Settings settings, String settingsKey, String defaultValue) {
            final String type = settings.get(settingsKey, defaultValue);
            if (type == null) {
                throw new IllegalArgumentException("Missing setting [" + settingsKey + "]");
            }
            final Class instance = getExtension(type);
            if (instance == null) {
                throw new IllegalArgumentException("Unknown [" + this.name + "] type [" + type + "]");
            }
            if (extensionClass == instance) {
                binder.bind(extensionClass).asEagerSingleton();
            } else {
                binder.bind(extensionClass).to(instance).asEagerSingleton();
            }
            return type;
        }

    }

    /**
     * A set based extension point which allows to register extended classes that might be used to chain additional functionality etc.
     */
    public final static class ClassSet extends ExtensionPoint {
        protected final Class extensionClass;
        private final Set> extensions = new HashSet<>();

        /**
         * Creates a new {@link ClassSet}
         *
         * @param name           the human readable underscore case name of the extension poing. This is used in error messages etc.
         * @param extensionClass the base class that should be extended
         * @param singletons     a list of singletons to bind with this extension point - these are bound in {@link #bind(Binder)}
         */
        public ClassSet(String name, Class extensionClass, Class... singletons) {
            super(name, singletons);
            this.extensionClass = extensionClass;
        }

        /**
         * Registers a new extension
         *
         * @param extension the extension to register
         * @throws IllegalArgumentException iff the class is already registered
         */
        public final void registerExtension(Class extension) {
            if (extensions.contains(extension)) {
                throw new IllegalArgumentException("Can't register the same [" + this.name + "] more than once for [" + extension.getName() + "]");
            }
            extensions.add(extension);
        }

        @Override
        protected final void bindExtensions(Binder binder) {
            Multibinder allocationMultibinder = Multibinder.newSetBinder(binder, extensionClass);
            for (Class clazz : extensions) {
                binder.bind(clazz).asEagerSingleton();
                allocationMultibinder.addBinding().to(clazz);
            }
        }
    }

    /**
     * A an instance of a map, mapping one instance value to another. Both key and value are instances, not classes
     * like with other extension points.
     */
    public final static class InstanceMap extends ExtensionPoint {
        private final Map map = new HashMap<>();
        private final Class keyType;
        private final Class valueType;

        /**
         * Creates a new {@link ClassSet}
         *
         * @param name           the human readable underscore case name of the extension point. This is used in error messages.
         * @param singletons     a list of singletons to bind with this extension point - these are bound in {@link #bind(Binder)}
         */
        public InstanceMap(String name, Class keyType, Class valueType, Class... singletons) {
            super(name, singletons);
            this.keyType = keyType;
            this.valueType = valueType;
        }

        /**
         * Registers a mapping from {@code key} to {@code value}
         *
         * @throws IllegalArgumentException iff the key is already registered
         */
        public final void registerExtension(K key, V value) {
            V old = map.put(key, value);
            if (old != null) {
                throw new IllegalArgumentException("Cannot register [" + this.name + "] with key [" + key + "] to [" + value + "], already registered to [" + old + "]");
            }
        }

        @Override
        protected void bindExtensions(Binder binder) {
            MapBinder mapBinder = MapBinder.newMapBinder(binder, keyType, valueType);
            for (Map.Entry entry : map.entrySet()) {
                mapBinder.addBinding(entry.getKey()).toInstance(entry.getValue());
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy