bt.runtime.BtRuntimeBuilder Maven / Gradle / Ivy
Show all versions of bt-core Show documentation
/*
* Copyright (c) 2016—2021 Andrei Tomashpolskiy and individual contributors.
*
* Licensed 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 bt.runtime;
import bt.BtException;
import bt.module.BtModuleProvider;
import bt.module.ProtocolModule;
import bt.module.ServiceModule;
import bt.net.portmapping.impl.PortMappingModule;
import bt.peer.lan.LocalServiceDiscoveryModule;
import bt.peerexchange.PeerExchangeModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Module;
import com.google.inject.util.Modules;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.ServiceLoader;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
/**
* Runtime builder.
*
* @since 1.0
*/
public class BtRuntimeBuilder {
private static final Logger LOGGER = LoggerFactory.getLogger(BtRuntimeBuilder.class);
private Config config;
private Collection customProviders;
private boolean shouldDisableAutomaticShutdown;
private boolean shouldAutoLoadModules;
private boolean shouldDisableStandardExtensions;
private boolean shouldDisablePeerExchange = false;
private boolean shouldDisableLocalServiceDiscovery = false;
/**
* Create runtime builder with default config.
*
* @since 1.4
*/
public BtRuntimeBuilder() {
this.config = new Config();
}
/**
* Create runtime builder with provided config.
*
* @param config Runtime config
* @since 1.0
*/
public BtRuntimeBuilder(Config config) {
this.config = Objects.requireNonNull(config, "Missing runtime config");
}
/**
* Set runtime config.
*
* @since 1.4
*/
public BtRuntimeBuilder config(Config config) {
this.config = Objects.requireNonNull(config, "Missing runtime config");
return this;
}
/**
* Get this builder's config.
*
* @return Runtime config
* @since 1.0
*/
public Config getConfig() {
return config;
}
/**
* Contribute an extra module into the runtime.
*
* @since 1.0
*/
public BtRuntimeBuilder module(Module adapter) {
Objects.requireNonNull(adapter);
if (customProviders == null) {
customProviders = new ArrayList<>();
}
customProviders.add(() -> adapter);
return this;
}
/**
* Contribute an extra module into the runtime.
*
* @since 1.1
*/
public BtRuntimeBuilder module(Class extends Module> adapterType) {
Objects.requireNonNull(adapterType);
if (customProviders == null) {
customProviders = new ArrayList<>();
}
customProviders.add(() -> {
try {
return adapterType.newInstance();
} catch (Exception e) {
throw new BtException("Failed to instantiate custom module: " + adapterType.getName(), e);
}
});
return this;
}
/**
* Disable automatic runtime shutdown, when all clients have been stopped.
*
* @since 1.0
*/
public BtRuntimeBuilder disableAutomaticShutdown() {
this.shouldDisableAutomaticShutdown = true;
return this;
}
/**
* If this option is set, Bt will use the service loading mechanism
* to load any modules that are available on application's classpath.
*
* To support auto-loading a module should expose {@link BtModuleProvider} provider.
* Auto-loaded modules will be used in default configuration.
*
* @since 1.1
*/
public BtRuntimeBuilder autoLoadModules() {
this.shouldAutoLoadModules = true;
return this;
}
/**
* If this options is set, Bt will not automatically load standard extensions, such as PEX or LSD.
* May be used to selectively enable only a subset of standard extensions.
*
* @since 1.6
*/
public BtRuntimeBuilder disableStandardExtensions() {
this.shouldDisableStandardExtensions = true;
return this;
}
/**
* If this options is set, Bt will not automatically load standard extension {@link PeerExchangeModule}
*
* @since 1.10
*/
public BtRuntimeBuilder disablePeerExchange() {
this.shouldDisablePeerExchange = true;
return this;
}
/**
* If this options is set, Bt will not automatically load standard extension {@link LocalServiceDiscoveryModule}
*
* @since 1.10
*/
public BtRuntimeBuilder disableLocalServiceDiscovery() {
this.shouldDisableLocalServiceDiscovery = true;
return this;
}
/**
* @since 1.0
*/
public BtRuntime build() {
Injector injector = createInjector();
Config config = Objects.requireNonNull(injector.getInstance(Config.class), "Missing config binding");
BtRuntime runtime = new BtRuntime(injector, config);
if (shouldDisableAutomaticShutdown) {
runtime.disableAutomaticShutdown();
}
return runtime;
}
private Injector createInjector() {
Map, Module> standardModules = mapByClass(collectModules(standardProviders()));
Map, Module> standardExtensions;
if (shouldDisableStandardExtensions) {
standardExtensions = new HashMap<>();
} else {
standardExtensions = mapByClass(collectModules(standardExtensionProviders()));
}
Map, Module> autoLoadedModules;
if (shouldAutoLoadModules) {
autoLoadedModules = mapByClass(collectModules(autoLoadedProviders()));
} else {
autoLoadedModules = new HashMap<>();
}
Map, Module> customModules = mapByClass(collectModules(customProviders()));
standardExtensions.forEach((k, v) -> {
if (!autoLoadedModules.containsKey(k) && !customModules.containsKey(k)) {
LOGGER.info("Loading standard extension module {}", k.getName());
}
});
customModules.forEach((k, v) -> {
boolean overridesStandardModule = standardModules.containsKey(k);
boolean overridesStandardExtension = standardExtensions.containsKey(k);
if (autoLoadedModules.containsKey(k)) {
LOGGER.info("Overriding auto-loaded module {}", k.getName());
} else if (overridesStandardModule || overridesStandardExtension) {
LOGGER.info("Overriding standard " + (overridesStandardModule ? "module" : "extension") + " {}", k.getName());
} else {
LOGGER.info("Loading module {}", k.getName());
}
});
autoLoadedModules.forEach((k, v) -> {
boolean overridesStandardModule = standardModules.containsKey(k);
boolean overridesStandardExtension = standardExtensions.containsKey(k);
boolean overridenByCustomModule = customModules.containsKey(k);
if (!overridenByCustomModule) {
if (overridesStandardModule || overridesStandardExtension) {
// don't log if there is a custom module, that will override the auto-loaded version
LOGGER.info("Overriding standard " + (overridesStandardModule ? "module" : "extension") +
" {} with auto-loaded version", k.getName());
} else {
LOGGER.info("Auto-loading module {} with default configuration", k.getName());
}
}
});
return createInjector(
standardModules,
standardExtensions,
autoLoadedModules,
customModules);
}
private Injector createInjector(
Map, Module> standardModules,
Map, Module> standardExtensions,
Map, Module> autoLoadedModules,
Map, Module> customModules) {
Module combinedModule = Stream.of(standardModules, standardExtensions, autoLoadedModules, customModules)
.sequential()
.filter(map -> !map.isEmpty())
.map(map -> Modules.combine(map.values()))
.reduce(null, (m1, m2) -> (m1 == null) ? m2 : Modules.override(m1).with(m2));
return Guice.createInjector(combinedModule);
}
@SafeVarargs
private final Collection collectModules(Collection... providers) {
return Arrays.stream(providers)
.flatMap(Collection::stream)
.map(BtModuleProvider::module)
.collect(Collectors.toList());
}
private Map, T> mapByClass(Collection collection) {
return collection.stream().collect(HashMap::new, (m, o) -> {
@SuppressWarnings("unchecked")
Class moduleType = (Class) o.getClass();
if (m.containsKey(moduleType)) {
throw new IllegalStateException("Duplicate module: " + moduleType.getName());
}
m.put(moduleType, o);
}, Map::putAll);
}
private Collection standardProviders() {
Collection standardProviders = new ArrayList<>(4);
standardProviders.add(PortMappingModule::new);
standardProviders.add(() -> new ServiceModule(config));
standardProviders.add(ProtocolModule::new);
return standardProviders;
}
private Collection standardExtensionProviders() {
Collection standardExtensionProviders = new ArrayList<>();
if (!shouldDisablePeerExchange) {
standardExtensionProviders.add(PeerExchangeModule::new);
}
if (!shouldDisableLocalServiceDiscovery) {
standardExtensionProviders.add(LocalServiceDiscoveryModule::new);
}
return standardExtensionProviders;
}
private Collection customProviders() {
if (customProviders == null) {
return Collections.emptyList();
} else {
return customProviders.stream()
.map(BtRuntimeBuilder::nullCheckingProvider)
.collect(Collectors.toList());
}
}
private Collection autoLoadedProviders() {
return StreamSupport
.stream(ServiceLoader.load(BtModuleProvider.class).spliterator(), false)
.map(BtRuntimeBuilder::nullCheckingProvider)
.collect(Collectors.toList());
}
private static BtModuleProvider nullCheckingProvider(BtModuleProvider provider) {
return () -> Objects.requireNonNull(provider.module(), "Missing module in provider:" + provider.getClass().getName());
}
}