/**
* Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG
* 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 com.sixt.service.framework;
import com.google.common.annotations.VisibleForTesting;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Module;
import com.google.protobuf.Message;
import com.sixt.service.framework.annotation.RpcHandler;
import com.sixt.service.framework.configuration.ConfigurationManager;
import com.sixt.service.framework.configuration.ConfigurationProvider;
import com.sixt.service.framework.configuration.LogLevelChangeCallback;
import com.sixt.service.framework.database.SchemaMigrator;
import com.sixt.service.framework.health.HealthCheck;
import com.sixt.service.framework.health.HealthCheckContributor;
import com.sixt.service.framework.health.HealthCheckManager;
import com.sixt.service.framework.injection.*;
import com.sixt.service.framework.jetty.JettyComposer;
import com.sixt.service.framework.health.ReadinessCheckServer;
import com.sixt.service.framework.jetty.RpcServlet;
import com.sixt.service.framework.logging.SixtLogbackContext;
import com.sixt.service.framework.metrics.MetricsReporterProvider;
import com.sixt.service.framework.registry.ServiceDiscoveryProvider;
import com.sixt.service.framework.registry.ServiceRegistrationProvider;
import com.sixt.service.framework.rpc.LoadBalancerFactory;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
public abstract class AbstractService {
private static final Logger logger = LoggerFactory.getLogger(AbstractService.class);
protected MethodHandlerDictionary methodHandlers = new MethodHandlerDictionary();
protected ServiceProperties serviceProperties = new ServiceProperties();
protected Server jettyServer;
protected Injector injector = null;
protected AtomicBoolean startupComplete = new AtomicBoolean(false);
private ConfigurationManager configurationManager;
private List<String> serviceRegistryPlugins;
private List<String> configurationPlugins;
private List<String> metricsReporterPlugins;
private List<String> tracingPlugins;
@SuppressWarnings("unchecked")
public void registerMethodHandlers(List<String> rpcHandlers) {
for (String className : rpcHandlers) {
try {
Class clazz = Class.forName(className);
if ((clazz != ServiceMethodHandler.class) && ServiceMethodHandler.class.isAssignableFrom(clazz)) {
registerMethodHandlerFor(((RpcHandler) clazz.getAnnotation(RpcHandler.class)).value(), clazz);
} else {
throw new IllegalArgumentException(
String.format(
"RpcHandler annotation applied to class %s that does not implement ServiceMethodHandler",
clazz.getName()
)
);
}
} catch (ClassNotFoundException e) {
logger.error(e.getMessage(), e);
}
}
}
/**
* @deprecated please use displayHelp(PrintStream out) instead
*/
@Deprecated
public void displayHelp() {
displayHelp(System.out);
}
public abstract void displayHelp(PrintStream out);
/**
* Supplies additional modules specific to this service.
* Provide sub-class implementation to override
*/
protected List<Module> getGuiceModules() {
return null;
}
public void initializeGuice() throws Exception {
if (injector == null) {
InjectionModule mainModule = new InjectionModule(serviceProperties);
mainModule.setConfigurationManager(configurationManager);
mainModule.setMethodHandlerDictionary(methodHandlers);
ServiceRegistryModule registryModule = new ServiceRegistryModule(serviceProperties);
registryModule.setServiceRegistryPlugins(serviceRegistryPlugins);
MetricsReporterModule metricsModule = new MetricsReporterModule();
metricsModule.setPlugins(metricsReporterPlugins);
TracingModule tracingModule = new TracingModule(serviceProperties);
tracingModule.setPlugins(tracingPlugins);
List<Module> modules = getGuiceModules();
if (modules == null) {
modules = new ArrayList<>();
} else {
modules = new ArrayList<>(modules);
}
modules.add(0, new MessagingModule());
modules.add(0, new OrangeServletModule());
modules.add(0, registryModule);
modules.add(0, metricsModule);
modules.add(0, tracingModule);
modules.add(0, mainModule);
//^^^ the order of the modules is specifically controlled here
injector = Guice.createInjector((Module[]) modules.toArray(new Module[0]));
}
}
public void initProperties(String[] args) {
serviceProperties.initialize(args);
}
/**
* Override to verify any required command-line parameters or environment
* variables have been set.
*/
public void verifyEnvironment() {
}
public void startJettyContainer() throws Exception {
jettyServer = new Server(serviceProperties.getServicePort());
JettyComposer.compose(jettyServer);
jettyServer.start();
int port = ((ServerConnector) jettyServer.getConnectors()[0]).getLocalPort();
logger.info("Jetty has started on port {}", port);
serviceProperties.setServicePort(port);
}
public void bootstrapComplete() throws InterruptedException {
startupComplete.set(true);
injector.getInstance(RpcServlet.class).serveRequests();
injector.getInstance(ReadinessCheckServer.class).serveRequests();
jettyServer.join();
}
@SuppressWarnings("unchecked")
public void initializeHealthCheckManager(List<String> hcProviders) {
if (hcProviders != null && !hcProviders.isEmpty()) {
HealthCheckManager hcManager = injector.getInstance(HealthCheckManager.class);
for (String hcp : hcProviders) {
try {
Class<HealthCheckContributor> hcClass = (Class<HealthCheckContributor>) Class.forName(hcp);
logger.debug("Found HealthCheckContributor: {}", hcClass.getSimpleName());
HealthCheckContributor contrib = injector.getInstance(hcClass);
if (contrib.shouldRegister()) {
hcManager.registerPollingContributor(contrib);
} else {
logger.debug("HealthCheckContributor is choosing not to join");
}
} catch (ClassNotFoundException e) {
logger.error("Error loading HealthCheckProvider: " + hcp, e);
}
}
hcManager.registerOneShotContributor(new HealthCheckContributor() {
@Override
public HealthCheck getHealthCheck() {
if (startupComplete.get()) {
return new HealthCheck();
} else {
return new HealthCheck("service_startup_completion",
HealthCheck.Status.FAIL, "Service startup not complete");
}
}
@Override
public boolean shouldRegister() {
return true;
}
});
hcManager.initialize();
}
}
public void performDatabaseMigration() {
SchemaMigrator migrator = injector.getInstance(SchemaMigrator.class);
migrator.migrate();
}
/**
* Override this method to register method handlers at startup.
* Classpath-scanning is now also offered to find @RpcHandlers
*/
public void registerMethodHandlers() {
}
@SuppressWarnings("unchecked")
protected void registerMethodHandlerFor(String endpoint,
Class<? extends ServiceMethodHandler> handlerClass) {
methodHandlers.put(endpoint, injector.getInstance(handlerClass));
}
protected void registerPreMethodHandlerHookFor(String endpoint,
Class<? extends ServiceMethodPreHook<? extends Message>> handlerClass) {
methodHandlers.addPreHook(endpoint, injector.getInstance(handlerClass));
}
protected void registerPostMethodHandlerHookFor(String endpoint,
Class<? extends ServiceMethodPostHook<? extends Message>> handlerClass) {
methodHandlers.addPostHook(endpoint, injector.getInstance(handlerClass));
}
public Map<String, ServiceMethodHandler<? extends Message, ? extends Message>> getMethodHandlers() {
return methodHandlers.getMethodHandlers();
}
@VisibleForTesting
public void setInjector(Injector injector) {
this.injector = injector;
}
public ServiceProperties getServiceProperties() {
return serviceProperties;
}
public void enableJmx() {
System.setProperty("com.sun.management.jmxremote.ssl", "false");
System.setProperty("com.sun.management.jmxremote.authenticate", "false");
System.setProperty("com.sun.management.jmxremote.port", "1099");
}
public void initializeServiceDiscovery() {
initializeLoadBalancerFactory(injector);
}
public void initializeServiceRegistration() {
injector.getInstance(ServiceRegistrationProvider.class);
}
public void initializeConfigurationManager() {
if (StringUtils.equals(serviceProperties.getServiceName(), "com.sixt.service.configuration")) {
return;
}
InjectionModule configBaseModule = new InjectionModule(serviceProperties);
configurationManager = new ConfigurationManager(serviceProperties);
configBaseModule.setConfigurationManager(configurationManager);
ServiceRegistryModule serviceRegistryModule = new ServiceRegistryModule(serviceProperties);
serviceRegistryModule.setServiceRegistryPlugins(serviceRegistryPlugins);
ConfigurationModule configurationModule = new ConfigurationModule(serviceProperties);
configurationModule.setConfigurationPlugins(configurationPlugins);
Injector configInjector = Guice.createInjector(configBaseModule,
serviceRegistryModule,
configurationModule, new TracingModule(serviceProperties));
ConfigurationProvider configProvider = configInjector.getInstance(ConfigurationProvider.class);
if (configProvider == null) {
return;
}
initializeLoadBalancerFactory(configInjector);
configurationManager.registerChangeCallback(ServiceProperties.LOG_LEVEL_KEY,
new LogLevelChangeCallback(new SixtLogbackContext()));
configProvider.initialize();
configurationManager.waitForInitialConfiguration();
}
private void initializeLoadBalancerFactory(Injector localInjector) {
ServiceDiscoveryProvider provider = localInjector.getInstance(ServiceDiscoveryProvider.class);
if (provider != null) {
LoadBalancerFactory lbFactory = localInjector.getInstance(LoadBalancerFactory.class);
lbFactory.initialize(provider);
}
}
void setServiceRegistryPlugins(List<String> serviceRegistryPlugins) {
this.serviceRegistryPlugins = serviceRegistryPlugins;
}
void setConfigurationPlugins(List<String> configurationPlugins) {
this.configurationPlugins = configurationPlugins;
}
public void setMetricsReporterPlugins(List<String> metricsReporterPlugins) {
this.metricsReporterPlugins = metricsReporterPlugins;
}
public void initializeMetricsReporting() {
MetricsReporterProvider provider = injector.getInstance(MetricsReporterProvider.class);
if (provider != null) {
provider.initialize();
}
}
public void setTracingPlugins(List<String> tracingPlugins) {
this.tracingPlugins = tracingPlugins;
}
}