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

com.yahoo.elide.standalone.config.ElideResourceConfig Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2017, Yahoo Inc.
 * Licensed under the Apache License, Version 2.0
 * See LICENSE file in project root for terms.
 */
package com.yahoo.elide.standalone.config;

import static com.yahoo.elide.annotation.LifeCycleHookBinding.Operation.CREATE;
import static com.yahoo.elide.annotation.LifeCycleHookBinding.TransactionPhase.POSTCOMMIT;
import static com.yahoo.elide.annotation.LifeCycleHookBinding.TransactionPhase.PREFLUSH;
import static com.yahoo.elide.annotation.LifeCycleHookBinding.TransactionPhase.PRESECURITY;

import com.yahoo.elide.Elide;
import com.yahoo.elide.ElideSettings;
import com.yahoo.elide.async.ResultTypeFileExtensionMapper;
import com.yahoo.elide.async.export.formatter.TableExportFormatter;
import com.yahoo.elide.async.hooks.AsyncQueryHook;
import com.yahoo.elide.async.hooks.TableExportHook;
import com.yahoo.elide.async.models.AsyncQuery;
import com.yahoo.elide.async.models.TableExport;
import com.yahoo.elide.async.resources.ExportApiEndpoint.ExportApiProperties;
import com.yahoo.elide.async.service.AsyncCleanerService;
import com.yahoo.elide.async.service.AsyncExecutorService;
import com.yahoo.elide.async.service.AsyncProviderService;
import com.yahoo.elide.async.service.dao.AsyncApiDao;
import com.yahoo.elide.async.service.dao.DefaultAsyncApiDao;
import com.yahoo.elide.async.service.storageengine.FileResultStorageEngine;
import com.yahoo.elide.async.service.storageengine.ResultStorageEngine;
import com.yahoo.elide.core.TransactionRegistry;
import com.yahoo.elide.core.datastore.DataStore;
import com.yahoo.elide.core.dictionary.EntityDictionary;
import com.yahoo.elide.core.dictionary.Injector;
import com.yahoo.elide.core.request.route.RouteResolver;
import com.yahoo.elide.core.utils.ClassScanner;
import com.yahoo.elide.datastores.aggregation.AggregationDataStore;
import com.yahoo.elide.datastores.aggregation.QueryEngine;
import com.yahoo.elide.datastores.aggregation.metadata.MetaDataStore;
import com.yahoo.elide.datastores.aggregation.queryengines.sql.ConnectionDetails;
import com.yahoo.elide.datastores.aggregation.queryengines.sql.dialects.SQLDialectFactory;
import com.yahoo.elide.graphql.QueryRunners;
import com.yahoo.elide.jsonapi.JsonApi;
import com.yahoo.elide.modelconfig.DynamicConfiguration;
import com.yahoo.elide.standalone.Util;
import com.yahoo.elide.swagger.resources.ApiDocsEndpoint;

import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.health.HealthCheckRegistry;
import org.glassfish.hk2.api.ServiceLocator;
import org.glassfish.hk2.api.TypeLiteral;
import org.glassfish.jersey.internal.inject.AbstractBinder;
import org.glassfish.jersey.server.ResourceConfig;
import graphql.execution.DataFetcherExceptionHandler;

import jakarta.inject.Inject;
import jakarta.persistence.EntityManagerFactory;
import jakarta.servlet.ServletContext;
import jakarta.ws.rs.core.Context;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.function.Consumer;

import javax.sql.DataSource;

/**
 * Elide application resource configuration file.
 */
@Slf4j
public class ElideResourceConfig extends ResourceConfig {
    private final ElideStandaloneSettings settings;
    private final ServiceLocator injector;

    public static final String ELIDE_STANDALONE_SETTINGS_ATTR = "elideStandaloneSettings";
    public static final String ASYNC_EXECUTOR_ATTR = "asyncExecutor";
    public static final String ASYNC_UPDATER_ATTR = "asyncUpdater";

    private static MetricRegistry metricRegistry = null;
    private static HealthCheckRegistry healthCheckRegistry = null;

    @AllArgsConstructor
    public class ElideBinder extends AbstractBinder {

        private ClassScanner classScanner;
        private Optional dynamicConfiguration;
        private ServletContext servletContext;

        @Override
        protected void configure() {
            ElideStandaloneAsyncSettings asyncProperties = settings.getAsyncProperties() == null
                    ? new ElideStandaloneAsyncSettings() { } : settings.getAsyncProperties();
            EntityManagerFactory entityManagerFactory = Util.getEntityManagerFactory(classScanner,
                    settings.getModelPackageName(), asyncProperties.enabled(), settings.getDatabaseProperties());


            EntityDictionary dictionary = settings.getEntityDictionary(injector, classScanner, dynamicConfiguration,
                    settings.getEntitiesToExclude(), settings.getIdObfuscator());

            DataStore dataStore;
            Injector inject = new Injector() {
                @Override
                public void inject(Object entity) {
                    injector.inject(entity);
                }

                @Override
                public  T instantiate(Class cls) {
                    return injector.create(cls);
                }
            };

            if (settings.getAnalyticProperties().enableAggregationDataStore()) {
                MetaDataStore metaDataStore = settings.getMetaDataStore(classScanner, inject, dynamicConfiguration);
                if (metaDataStore == null) {
                    throw new IllegalStateException("Aggregation Datastore is enabled but metaDataStore is null");
                }

                DataSource defaultDataSource = Util.getDataSource(settings.getDatabaseProperties());
                ConnectionDetails defaultConnectionDetails = new ConnectionDetails(defaultDataSource,
                        SQLDialectFactory.getDialect(settings.getAnalyticProperties().getDefaultDialect()));

                QueryEngine queryEngine = settings.getQueryEngine(metaDataStore, defaultConnectionDetails,
                        dynamicConfiguration, settings.getDataSourceConfiguration(),
                        settings.getAnalyticProperties().getDBPasswordExtractor());
                AggregationDataStore aggregationDataStore = settings.getAggregationDataStore(queryEngine);
                if (aggregationDataStore == null) {
                    throw new IllegalStateException(
                            "Aggregation Datastore is enabled but aggregationDataStore is null");
                }
                dataStore = settings.getDataStore(metaDataStore, aggregationDataStore, entityManagerFactory, inject);

            } else {
                dataStore = settings.getDataStore(entityManagerFactory);
            }

            ElideSettings elideSettings = settings.getElideSettings(dictionary, dataStore,
                    settings.getJsonApiMapper());
            Elide elide = new Elide(elideSettings, new TransactionRegistry(),
                    elideSettings.getEntityDictionary().getScanner(), false);

            // Bind elide instance for injection into endpoint
            bind(elide).to(Elide.class).named("elide");

            // Bind additional elements
            bind(elideSettings).to(ElideSettings.class);
            bind(elideSettings.getEntityDictionary()).to(EntityDictionary.class);
            bind(elideSettings.getDataStore()).to(DataStore.class).named("elideDataStore");

            // Binding async service
            if (asyncProperties.enabled()) {
                bindAsync(asyncProperties, elide, dictionary);
            }
        }

        protected void bindAsync(
                ElideStandaloneAsyncSettings asyncProperties,
                Elide elide,
                EntityDictionary dictionary
        ) {
            AsyncApiDao asyncApiDao = asyncProperties.getAsyncApiDao();
            if (asyncApiDao == null) {
                asyncApiDao = new DefaultAsyncApiDao(elide.getElideSettings(), elide.getDataStore());
            }
            bind(asyncApiDao).to(AsyncApiDao.class);

            ExecutorService executor = (ExecutorService) servletContext.getAttribute(ASYNC_EXECUTOR_ATTR);
            ExecutorService updater = (ExecutorService) servletContext.getAttribute(ASYNC_UPDATER_ATTR);

            AsyncProviderService asyncProviderService = AsyncProviderService.builder()
                    .provider(JsonApi.class, new JsonApi(elide)).provider(QueryRunners.class,
                            new QueryRunners(elide, Optional.of(settings.getDataFetcherExceptionHandler())))
                    .build();

            AsyncExecutorService asyncExecutorService = new AsyncExecutorService(elide, executor, updater, asyncApiDao,
                    asyncProviderService);
            bind(asyncExecutorService).to(AsyncExecutorService.class);

            if (asyncProperties.enableExport()) {
                ExportApiProperties exportApiProperties = new ExportApiProperties(
                        asyncProperties.getExportAsyncResponseExecutor(),
                        asyncProperties.getExportAsyncResponseTimeout());
                bind(exportApiProperties).to(ExportApiProperties.class).named("exportApiProperties");

                ResultStorageEngine resultStorageEngine = asyncProperties.getResultStorageEngine();
                if (resultStorageEngine == null) {
                    resultStorageEngine = new FileResultStorageEngine(asyncProperties.getStorageDestination());
                }
                bind(resultStorageEngine).to(ResultStorageEngine.class).named("resultStorageEngine");

                // Binding TableExport LifeCycleHook
                TableExportHook tableExportHook = getTableExportHook(asyncExecutorService,
                        asyncProperties, asyncProperties.getTableExportFormattersBuilder(elide).build(),
                        resultStorageEngine,
                        asyncProperties.appendFileExtension() ? asyncProperties.getResultTypeFileExtensionMapper()
                                : null);
                dictionary.bindTrigger(TableExport.class, CREATE, PREFLUSH, tableExportHook, false);
                dictionary.bindTrigger(TableExport.class, CREATE, POSTCOMMIT, tableExportHook, false);
                dictionary.bindTrigger(TableExport.class, CREATE, PRESECURITY, tableExportHook, false);
            }

            // Binding AsyncQuery LifeCycleHook
            AsyncQueryHook asyncQueryHook = new AsyncQueryHook(asyncExecutorService,
                    asyncProperties.getMaxAsyncAfter());

            dictionary.bindTrigger(AsyncQuery.class, CREATE, PREFLUSH, asyncQueryHook, false);
            dictionary.bindTrigger(AsyncQuery.class, CREATE, POSTCOMMIT, asyncQueryHook, false);
            dictionary.bindTrigger(AsyncQuery.class, CREATE, PRESECURITY, asyncQueryHook, false);

            // Binding async cleanup service
            if (asyncProperties.enableCleanup()) {
                AsyncCleanerService.init(elide, asyncProperties.getQueryMaxRunTime(),
                        asyncProperties.getQueryRetentionDuration(),
                        asyncProperties.getQueryCancellationCheckInterval(), asyncApiDao);
                bind(AsyncCleanerService.getInstance()).to(AsyncCleanerService.class);
            }
        }
    }

    /**
     * Constructor.
     *
     * @param injector Injection instance for application.
     * @param servletContext servlet context instance.
     */
    @Inject
    public ElideResourceConfig(ServiceLocator injector, @Context ServletContext servletContext) {
        this.injector = injector;
        settings = (ElideStandaloneSettings) servletContext.getAttribute(ELIDE_STANDALONE_SETTINGS_ATTR);

        ClassScanner classScanner = settings.getClassScanner();

        // Bind things that should be injectable to the Settings class
        register(new AbstractBinder() {
            @Override
            protected void configure() {
                ElideStandaloneAsyncSettings asyncProperties = settings.getAsyncProperties();
                bind(Util.combineModelEntities(
                        classScanner,
                        settings.getModelPackageName(),
                        asyncProperties.enabled())).to(Set.class).named("elideAllModels");
                bind(settings.getDataFetcherExceptionHandler()).to(DataFetcherExceptionHandler.class)
                        .named("dataFetcherExceptionHandler");
                if (settings.getRouteResolver() != null) {
                    bind(settings.getRouteResolver()).to(RouteResolver.class).named("routeResolver");
                }
            }
        });

        Optional dynamicConfiguration;
        try {
            dynamicConfiguration = settings.getDynamicConfiguration(classScanner, new Injector() {
                @Override
                public void inject(Object entity) {
                    injector.inject(entity);
                }

                @Override
                public  T instantiate(Class cls) {
                    return injector.create(cls);
                }
            });
        } catch (IOException e) {
            throw new IllegalStateException(e);
        }

        // Bind to injector
        register(new ElideBinder(classScanner, dynamicConfiguration, servletContext));

        // Bind api docs to given endpoint
        //This looks strange, but Jersey binds its Abstract binders first, and then later it binds 'external'
        //binders (like this HK2 version).  This allows breaking dependency injection into two phases.
        //Everything bound in the first phase can be accessed in the second phase.
        register(new org.glassfish.hk2.utilities.binding.AbstractBinder() {
            @Override
            protected void configure() {
                Elide elide = injector.getService(Elide.class, "elide");

                elide.doScans();

                //Bind subscription hooks.
                if (settings.getSubscriptionProperties().publishingEnabled()) {
                    settings.getSubscriptionProperties().subscriptionScanner(elide,
                            settings.getSubscriptionProperties().getConnectionFactory());
                }

                EntityDictionary dictionary = elide.getElideSettings().getEntityDictionary();

                if (settings.enableApiDocs()) {
                    List apiDocs = settings.buildApiDocs(dictionary);
                    bind(apiDocs).named("apiDocs").to(new TypeLiteral>() {
                    });
                }
            }
        });

        registerFilters(settings.getFilters());

        additionalConfiguration(settings.getApplicationConfigurator());
    }

    /**
     * Init the supplemental resource config.
     */
    private void additionalConfiguration(Consumer configurator) {
        // Inject into consumer if class is provided
        injector.inject(configurator);
        configurator.accept(this);
    }

    /**
     * Register provided JAX-RS filters.
     */
    private void registerFilters(List> filters) {
        filters.forEach(this::register);
    }

    public static MetricRegistry getMetricRegistry() {
        if (metricRegistry == null) {
            metricRegistry = new MetricRegistry();
        }

        return metricRegistry;
    }

    public static HealthCheckRegistry getHealthCheckRegistry() {
        if (healthCheckRegistry == null) {
            healthCheckRegistry = new HealthCheckRegistry();
        }

        return healthCheckRegistry;
    }

    private TableExportHook getTableExportHook(AsyncExecutorService asyncExecutorService,
            ElideStandaloneAsyncSettings asyncProperties, Map supportedFormatters,
            ResultStorageEngine engine, ResultTypeFileExtensionMapper resultTypeFileExtensionMapper) {
        return new TableExportHook(asyncExecutorService, asyncProperties.getMaxAsyncAfter(),
                    supportedFormatters, engine, resultTypeFileExtensionMapper);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy