package org.rakam; import com.google.common.collect.ImmutableSet; import com.google.common.eventbus.EventBus; import com.google.common.eventbus.SubscriberExceptionContext; import com.google.common.eventbus.SubscriberExceptionHandler; import com.google.inject.Binder; import com.google.inject.Injector; import com.google.inject.Key; import com.google.inject.Module; import com.google.inject.Provider; import com.google.inject.Scopes; import com.google.inject.TypeLiteral; import com.google.inject.matcher.Matchers; import com.google.inject.multibindings.Multibinder; import com.google.inject.multibindings.OptionalBinder; import com.google.inject.name.Named; import com.google.inject.name.Names; import com.google.inject.spi.InjectionListener; import com.google.inject.spi.TypeEncounter; import com.google.inject.spi.TypeListener; import io.airlift.configuration.AbstractConfigurationAwareModule; import io.airlift.log.Logger; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.swagger.models.Tag; import org.flywaydb.core.Flyway; import org.flywaydb.core.api.FlywayException; import org.rakam.analysis.AdminHttpService; import org.rakam.analysis.ApiKeyService; import org.rakam.analysis.ContinuousQueryHttpService; import org.rakam.analysis.ContinuousQueryService; import org.rakam.analysis.CustomParameter; import org.rakam.analysis.JDBCPoolDataSource; import org.rakam.analysis.MaterializedViewHttpService; import org.rakam.analysis.ProjectHttpService; import org.rakam.analysis.QueryHttpService; import org.rakam.analysis.RequestPreProcessorItem; import org.rakam.analysis.metadata.SchemaChecker; import org.rakam.bootstrap.Bootstrap; import org.rakam.collection.EventCollectionHttpService; import org.rakam.collection.FieldDependencyBuilder; import org.rakam.collection.FieldDependencyBuilder.FieldDependency; import org.rakam.collection.WebHookHttpService; import org.rakam.config.EncryptionConfig; import org.rakam.config.JDBCConfig; import org.rakam.config.MetadataConfig; import org.rakam.config.ProjectConfig; import org.rakam.http.ForHttpServer; import org.rakam.http.HttpServerConfig; import org.rakam.http.OptionMethodHttpService; import org.rakam.http.WebServiceModule; import org.rakam.http.WebServiceModule.ProjectPermissionParameterFactory; import org.rakam.plugin.CopyEvent; import org.rakam.plugin.EventMapper; import org.rakam.plugin.InjectionHook; import org.rakam.plugin.RAsyncHttpClient; import org.rakam.plugin.RakamModule; import org.rakam.plugin.user.AbstractUserService; import org.rakam.plugin.user.UserStorage; import org.rakam.plugin.user.mailbox.UserMailboxStorage; import org.rakam.server.http.HttpRequestHandler; import org.rakam.server.http.HttpService; import org.rakam.server.http.WebSocketService; import org.rakam.ui.RakamUIModule; import org.rakam.util.NotFoundHandler; import javax.inject.Inject; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.time.Clock; import java.time.Duration; import java.util.Properties; import java.util.ServiceLoader; import java.util.Set; import static com.google.common.primitives.Chars.checkedCast; import static io.airlift.configuration.ConfigBinder.configBinder; import static java.lang.String.format; public final class ServiceStarter { public static String RAKAM_VERSION; private final static Logger LOGGER = Logger.get(ServiceStarter.class); static { Properties properties = new Properties(); InputStream inputStream; try { URL resource = ServiceStarter.class.getResource("/git.properties"); if (resource == null) { LOGGER.warn("git.properties doesn't exist."); } else { inputStream = resource.openStream(); properties.load(inputStream); } } catch (IOException e) { LOGGER.warn(e, "Error while reading git.properties"); } try { RAKAM_VERSION = properties.get("git.commit.id.describe-short").toString().split("-", 2)[0]; } catch (Exception e) { LOGGER.warn(e, "Error while parsing git.properties"); } } private ServiceStarter() throws InstantiationException { throw new InstantiationException("The class is not created for instantiation"); } public static void main(String[] args) throws Throwable { if (args.length > 0) { System.setProperty("config", args[0]); } Bootstrap app = new Bootstrap(getModules()); app.requireExplicitBindings(false); Injector injector = app.strictConfig().initialize(); Set<InjectionHook> hooks = injector.getInstance( Key.get(new TypeLiteral<Set<InjectionHook>>() {})); hooks.forEach(InjectionHook::call); HttpServerConfig httpConfig = injector.getInstance(HttpServerConfig.class); if (!httpConfig.getDisabled()) { WebServiceModule webServiceModule = injector.getInstance(WebServiceModule.class); injector.createChildInjector(webServiceModule); } LOGGER.info("======== SERVER STARTED ========"); } public static Set<Module> getModules() { ImmutableSet.Builder<Module> builder = ImmutableSet.builder(); ServiceLoader<RakamModule> modules = ServiceLoader.load(RakamModule.class); for (Module module : modules) { if (!(module instanceof RakamModule)) { throw new IllegalStateException(format("Modules must be subclasses of org.rakam.module.RakamModule: %s", module.getClass().getName())); } RakamModule rakamModule = (RakamModule) module; builder.add(rakamModule); } builder.add(new ServiceRecipe()); return builder.build(); } public static class FieldDependencyProvider implements Provider<FieldDependency> { private final Set<EventMapper> eventMappers; @Inject public FieldDependencyProvider(Set<EventMapper> eventMappers) { this.eventMappers = eventMappers; } @Override public FieldDependency get() { FieldDependencyBuilder builder = new FieldDependencyBuilder(); eventMappers.stream().forEach(mapper -> mapper.addFieldDependency(builder)); return builder.build(); } } public static class ServiceRecipe extends AbstractConfigurationAwareModule { @Override protected void setup(Binder binder) { binder.bind(Clock.class).toInstance(Clock.systemUTC()); // binder.bind(FlywayExecutor.class).asEagerSingleton(); binder.bind(FieldDependency.class).toProvider(FieldDependencyProvider.class).in(Scopes.SINGLETON); Multibinder.newSetBinder(binder, EventMapper.class); OptionalBinder.newOptionalBinder(binder, CopyEvent.class); Multibinder.newSetBinder(binder, InjectionHook.class); OptionalBinder.newOptionalBinder(binder, AbstractUserService.class); OptionalBinder.newOptionalBinder(binder, ContinuousQueryService.class); OptionalBinder.newOptionalBinder(binder, UserStorage.class); OptionalBinder.newOptionalBinder(binder, UserMailboxStorage.class); EventBus eventBus = new EventBus(new SubscriberExceptionHandler() { Logger logger = Logger.get("System Event Listener"); @Override public void handleException(Throwable exception, SubscriberExceptionContext context) { logger.error(exception, "Could not dispatch event: " + context.getSubscriber() + " to " + context.getSubscriberMethod(), exception.getCause()); } }); binder.bind(EventBus.class).toInstance(eventBus); binder.bindListener(Matchers.any(), new TypeListener() { public void hear(TypeLiteral typeLiteral, TypeEncounter typeEncounter) { typeEncounter.register((InjectionListener) i -> eventBus.register(i)); } }); Multibinder<Tag> tags = Multibinder.newSetBinder(binder, Tag.class); tags.addBinding().toInstance(new Tag().name("admin").description("System related actions").externalDocs(MetadataConfig.centralDocs)); tags.addBinding().toInstance(new Tag().name("collect").description("Collect data").externalDocs(MetadataConfig.centralDocs)); tags.addBinding().toInstance(new Tag().name("query").description("Analyze data").externalDocs(MetadataConfig.centralDocs)); tags.addBinding().toInstance(new Tag().name("materialized-view").description("Materialized view").externalDocs(MetadataConfig.centralDocs)); tags.addBinding().toInstance(new Tag().name("continuous-query").description("Continuous query").externalDocs(MetadataConfig.centralDocs)); // Register these interfaces to MultiBinder Multibinder.newSetBinder(binder, EventMapper.class); Multibinder.newSetBinder(binder, RequestPreProcessorItem.class); Multibinder<CustomParameter> customParameters = Multibinder.newSetBinder(binder, CustomParameter.class); customParameters.addBinding().toProvider(ProjectPermissionParameterProvider.class); Multibinder<HttpService> httpServices = Multibinder.newSetBinder(binder, HttpService.class); httpServices.addBinding().to(AdminHttpService.class); httpServices.addBinding().to(ProjectHttpService.class); httpServices.addBinding().to(MaterializedViewHttpService.class); httpServices.addBinding().to(EventCollectionHttpService.class); httpServices.addBinding().to(WebHookHttpService.class); httpServices.addBinding().to(ContinuousQueryHttpService.class); httpServices.addBinding().to(QueryHttpService.class); httpServices.addBinding().to(OptionMethodHttpService.class); Multibinder.newSetBinder(binder, WebSocketService.class); configBinder(binder).bindConfig(HttpServerConfig.class); configBinder(binder).bindConfig(ProjectConfig.class); configBinder(binder).bindConfig(EncryptionConfig.class); binder.bind(SchemaChecker.class).asEagerSingleton(); binder.bind(RAsyncHttpClient.class) .annotatedWith(Names.named("rakam-client")) .toProvider(() -> { return RAsyncHttpClient.create(1000 * 60 * 10, "rakam-custom-script"); }) .in(Scopes.SINGLETON); OptionalBinder.newOptionalBinder(binder, Key.get(HttpRequestHandler.class, NotFoundHandler.class)); binder.bind(EventLoopGroup.class) .annotatedWith(ForHttpServer.class) .to(NioEventLoopGroup.class) .in(Scopes.SINGLETON); binder.bind(WebServiceModule.class); } } public static class ProjectPermissionParameterProvider implements Provider<CustomParameter> { private final ApiKeyService apiKeyService; @Inject public ProjectPermissionParameterProvider(ApiKeyService apiKeyService) { this.apiKeyService = apiKeyService; } @Override public CustomParameter get() { return new CustomParameter("project", new ProjectPermissionParameterFactory(apiKeyService)); } } public static class FlywayExecutor { @Inject public FlywayExecutor(@Named("report.metadata.store.jdbc") JDBCPoolDataSource config) { Flyway flyway = new Flyway(); flyway.setBaselineOnMigrate(true); flyway.setDataSource(config); flyway.setLocations("db/migration/report"); flyway.setTable("schema_version_report"); try { flyway.migrate(); } catch (FlywayException e) { flyway.repair(); } } } }