diff --git a/elide-contrib/elide-dynamic-config-helpers/pom.xml b/elide-contrib/elide-dynamic-config-helpers/pom.xml index 6a54f55244..e775fc610d 100644 --- a/elide-contrib/elide-dynamic-config-helpers/pom.xml +++ b/elide-contrib/elide-dynamic-config-helpers/pom.xml @@ -40,7 +40,6 @@ 3.0.0 - 2.10.2 4.2.0 2.2.12 1.3.0 @@ -55,7 +54,7 @@ com.fasterxml.jackson.core jackson-annotations - ${jackson.version} + ${version.jackson} com.fasterxml.jackson.core @@ -65,11 +64,11 @@ org.apache.commons commons-lang3 - - commons-io - commons-io - ${commons-io.version} - + + commons-io + commons-io + ${commons-io.version} + org.slf4j slf4j-api @@ -117,7 +116,7 @@ com.google.guava guava - + diff --git a/elide-datastore/elide-datastore-aggregation/pom.xml b/elide-datastore/elide-datastore-aggregation/pom.xml index 4b5df78c05..25e36bc2ce 100644 --- a/elide-datastore/elide-datastore-aggregation/pom.xml +++ b/elide-datastore/elide-datastore-aggregation/pom.xml @@ -97,7 +97,6 @@ org.junit.jupiter junit-jupiter-api - ${junit.version} test @@ -111,7 +110,6 @@ org.junit.jupiter junit-jupiter-engine - ${junit.version} test diff --git a/elide-example/elide-blog-example/src/main/java/com/yahoo/elide/example/CommonElideSettings.java b/elide-example/elide-blog-example/src/main/java/com/yahoo/elide/example/CommonElideSettings.java index 9cd7062c27..de33206bdb 100644 --- a/elide-example/elide-blog-example/src/main/java/com/yahoo/elide/example/CommonElideSettings.java +++ b/elide-example/elide-blog-example/src/main/java/com/yahoo/elide/example/CommonElideSettings.java @@ -6,19 +6,8 @@ package com.yahoo.elide.example; -import com.yahoo.elide.contrib.swagger.SwaggerBuilder; -import com.yahoo.elide.contrib.swagger.resources.DocEndpoint; -import com.yahoo.elide.core.EntityDictionary; -import com.yahoo.elide.example.models.Comment; -import com.yahoo.elide.example.models.Post; -import com.yahoo.elide.example.models.User; import com.yahoo.elide.standalone.config.ElideStandaloneSettings; -import io.swagger.models.Info; -import io.swagger.models.Swagger; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; import java.util.Optional; /** @@ -35,20 +24,8 @@ public int getPort() { } @Override - public List enableSwagger() { - EntityDictionary dictionary = new EntityDictionary(new HashMap()); - - dictionary.bindEntity(User.class); - dictionary.bindEntity(Post.class); - dictionary.bindEntity(Comment.class); - Info info = new Info().title("Test Service").version("1.0"); - - SwaggerBuilder builder = new SwaggerBuilder(dictionary, info).withLegacyFilterDialect(false); - Swagger swagger = builder.build(); - - List docs = new ArrayList<>(); - docs.add(new DocEndpoint.SwaggerRegistration("test", swagger)); - return docs; + public boolean enableSwagger() { + return true; } @Override diff --git a/elide-standalone/pom.xml b/elide-standalone/pom.xml index 57bb3e601d..d58238c878 100644 --- a/elide-standalone/pom.xml +++ b/elide-standalone/pom.xml @@ -57,6 +57,12 @@ + + com.yahoo.elide + elide-datastore-aggregation + 5.0.0-pr10-SNAPSHOT + + com.yahoo.elide elide-datastore-jpa @@ -78,6 +84,12 @@ 5.0.0-pr10-SNAPSHOT + + com.yahoo.elide + elide-dynamic-config-helpers + 5.0.0-pr10-SNAPSHOT + + javax.persistence @@ -239,6 +251,10 @@ maven-jar-plugin 3.2.0 + + org.apache.maven.plugins + maven-checkstyle-plugin + diff --git a/elide-standalone/src/main/java/com/yahoo/elide/standalone/ElideStandalone.java b/elide-standalone/src/main/java/com/yahoo/elide/standalone/ElideStandalone.java index 2b3c1fbdba..11cedf3664 100644 --- a/elide-standalone/src/main/java/com/yahoo/elide/standalone/ElideStandalone.java +++ b/elide-standalone/src/main/java/com/yahoo/elide/standalone/ElideStandalone.java @@ -61,8 +61,9 @@ public Map> getCheckMappings() { } /** * Start the Elide service. - * * This method blocks until the server exits. + * + * @throws Exception Exception thrown */ public void start() throws Exception { start(true); @@ -72,6 +73,7 @@ public void start() throws Exception { * Start the Elide service. * * @param block - Whether or not to wait for the server to shutdown. + * @throws Exception Exception thrown */ public void start(boolean block) throws Exception { ServletContextHandler context = new ServletContextHandler(); @@ -115,7 +117,7 @@ public void start(boolean block) throws Exception { context.addServlet(AdminServlet.class, "/stats/*"); } - if (!elideStandaloneSettings.enableSwagger().isEmpty()) { + if (elideStandaloneSettings.enableSwagger()) { ServletHolder jerseyServlet = context.addServlet(ServletContainer.class, elideStandaloneSettings.getSwaggerPathSpec()); jerseyServlet.setInitOrder(0); @@ -144,6 +146,8 @@ public void start(boolean block) throws Exception { /** * Stop the Elide service. + * + * @throws Exception Exception thrown */ public void stop() throws Exception { jettyServer.stop(); diff --git a/elide-standalone/src/main/java/com/yahoo/elide/standalone/PersistenceUnitInfoImpl.java b/elide-standalone/src/main/java/com/yahoo/elide/standalone/PersistenceUnitInfoImpl.java deleted file mode 100644 index 1c16a1c1bd..0000000000 --- a/elide-standalone/src/main/java/com/yahoo/elide/standalone/PersistenceUnitInfoImpl.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2019, Yahoo Inc. - * Licensed under the Apache License, Version 2.0 - * See LICENSE file in project root for terms. - */ - -package com.yahoo.elide.standalone; - -import lombok.Data; - -import java.net.URL; -import java.util.List; -import java.util.Properties; - -import javax.persistence.SharedCacheMode; -import javax.persistence.ValidationMode; -import javax.persistence.spi.ClassTransformer; -import javax.persistence.spi.PersistenceUnitInfo; -import javax.persistence.spi.PersistenceUnitTransactionType; -import javax.sql.DataSource; - -/** - * Data object which allows programmatic configuration of everything inside a persistence.xml file. - */ -@Data -public class PersistenceUnitInfoImpl implements PersistenceUnitInfo { - - public PersistenceUnitInfoImpl(String persistenceUnitName, List managedClassNames, Properties properties) { - this.persistenceUnitName = persistenceUnitName; - this.managedClassNames = managedClassNames; - this.properties = properties; - } - - private String persistenceUnitName; - private String persistenceProviderClassName; - private PersistenceUnitTransactionType transactionType; - private DataSource jtaDataSource; - private DataSource nonJtaDataSource; - private List mappingFileNames; - private List jarFileUrls; - private URL persistenceUnitRootUrl; - private List managedClassNames; - private SharedCacheMode sharedCacheMode; - private ValidationMode validationMode; - private Properties properties; - private String persistenceXMLSchemaVersion; - private ClassLoader classLoader; - private ClassLoader newTempClassLoader; - - @Override - public boolean excludeUnlistedClasses() { - return false; - } - - public void addTransformer(ClassTransformer classTransformer) { } -} diff --git a/elide-standalone/src/main/java/com/yahoo/elide/standalone/Util.java b/elide-standalone/src/main/java/com/yahoo/elide/standalone/Util.java index c36cdda8f8..f479b187a6 100644 --- a/elide-standalone/src/main/java/com/yahoo/elide/standalone/Util.java +++ b/elide-standalone/src/main/java/com/yahoo/elide/standalone/Util.java @@ -6,12 +6,19 @@ package com.yahoo.elide.standalone; import com.yahoo.elide.async.models.AsyncQuery; +import com.yahoo.elide.contrib.dynamicconfighelpers.compile.ElideDynamicEntityCompiler; +import com.yahoo.elide.datastores.jpa.PersistenceUnitInfoImpl; import com.yahoo.elide.utils.ClassScanner; + +import org.hibernate.cfg.AvailableSettings; import org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl; import org.hibernate.jpa.boot.internal.PersistenceUnitInfoDescriptor; +import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.List; +import java.util.Optional; import java.util.Properties; import java.util.stream.Collectors; @@ -24,8 +31,8 @@ */ public class Util { - public static EntityManagerFactory getEntityManagerFactory(String modelPackageName, boolean includeAsyncModel, - Properties options) { + public static EntityManagerFactory getEntityManagerFactory(String modelPackageName, boolean includeAsyncModel, + Optional optionalCompiler, Properties options) { // Configure default options for example service if (options.isEmpty()) { @@ -53,28 +60,57 @@ public static EntityManagerFactory getEntityManagerFactory(String modelPackageNa options.put("javax.persistence.jdbc.password", "elide123"); } + ClassLoader classLoader = null; + //Bind entity classes from classpath to Persistence Unit + ArrayList loadedClasses = new ArrayList<>(); + + if (optionalCompiler.isPresent()) { + ElideDynamicEntityCompiler compiler = optionalCompiler.get(); + classLoader = compiler.getClassLoader(); + Collection classLoaders = new ArrayList<>(); + classLoaders.add(classLoader); + options.put(AvailableSettings.CLASSLOADERS, classLoaders); + + try { + loadedClasses.addAll(compiler.findAnnotatedClasses(Entity.class)); + } catch (ClassNotFoundException e) { + throw new IllegalStateException(e); + } + } + PersistenceUnitInfo persistenceUnitInfo = new PersistenceUnitInfoImpl("elide-stand-alone", - combineModelEntities(modelPackageName, includeAsyncModel), options); + combineModelEntities(optionalCompiler, modelPackageName, includeAsyncModel), + options, classLoader); return new EntityManagerFactoryBuilderImpl( - new PersistenceUnitInfoDescriptor(persistenceUnitInfo), new HashMap<>()) + new PersistenceUnitInfoDescriptor(persistenceUnitInfo), new HashMap<>(), classLoader) .build(); } /** - * Combine the model entities with Async model. + * Combine the model entities with Async and Dynamic models. * + * @param optionalCompiler optional dynamicCompiler * @param modelPackageName Package name * @param includeAsyncModel Include Async model package Name * @return All entities combined from both package. */ - public static List combineModelEntities(String modelPackageName, boolean includeAsyncModel) { + public static List combineModelEntities(Optional optionalCompiler, + String modelPackageName, boolean includeAsyncModel) { List modelEntities = getAllEntities(modelPackageName); if (includeAsyncModel) { modelEntities.addAll(getAllEntities(AsyncQuery.class.getPackage().getName())); } + + if (optionalCompiler.isPresent()) { + try { + modelEntities.addAll(optionalCompiler.get().findAnnotatedClassNames(Entity.class)); + } catch (ClassNotFoundException e) { + throw new IllegalStateException(e); + } + } return modelEntities; } diff --git a/elide-standalone/src/main/java/com/yahoo/elide/standalone/config/ElideResourceConfig.java b/elide-standalone/src/main/java/com/yahoo/elide/standalone/config/ElideResourceConfig.java index 8cdc89c9c2..1b671d234d 100644 --- a/elide-standalone/src/main/java/com/yahoo/elide/standalone/config/ElideResourceConfig.java +++ b/elide-standalone/src/main/java/com/yahoo/elide/standalone/config/ElideResourceConfig.java @@ -11,36 +11,37 @@ import com.yahoo.elide.Elide; import com.yahoo.elide.ElideSettings; -import com.yahoo.elide.contrib.swagger.resources.DocEndpoint; -import com.yahoo.elide.core.DataStore; -import com.yahoo.elide.core.EntityDictionary; -import com.yahoo.elide.standalone.Util; import com.yahoo.elide.async.hooks.ExecuteQueryHook; import com.yahoo.elide.async.hooks.UpdatePrincipalNameHook; import com.yahoo.elide.async.models.AsyncQuery; -import com.yahoo.elide.async.models.AsyncQueryResult; -import com.yahoo.elide.async.service.AsyncExecutorService; import com.yahoo.elide.async.service.AsyncCleanerService; +import com.yahoo.elide.async.service.AsyncExecutorService; import com.yahoo.elide.async.service.AsyncQueryDAO; import com.yahoo.elide.async.service.DefaultAsyncQueryDAO; -import com.yahoo.elide.contrib.swagger.SwaggerBuilder; +import com.yahoo.elide.contrib.dynamicconfighelpers.compile.ElideDynamicEntityCompiler; +import com.yahoo.elide.contrib.swagger.resources.DocEndpoint; +import com.yahoo.elide.core.DataStore; +import com.yahoo.elide.core.EntityDictionary; +import com.yahoo.elide.datastores.aggregation.QueryEngine; +import com.yahoo.elide.datastores.aggregation.metadata.MetaDataStore; +import com.yahoo.elide.standalone.Util; + 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 io.swagger.models.Info; -import io.swagger.models.Swagger; import lombok.extern.slf4j.Slf4j; -import java.util.HashMap; import java.util.List; -import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.function.Consumer; import javax.inject.Inject; +import javax.persistence.EntityManagerFactory; import javax.servlet.ServletContext; import javax.ws.rs.core.Context; @@ -58,9 +59,10 @@ public class ElideResourceConfig extends ResourceConfig { private static HealthCheckRegistry healthCheckRegistry = null; /** - * Constructor + * Constructor. * - * @param injector Injection instance for application + * @param injector Injection instance for application. + * @param servletContext servlet context instance. */ @Inject public ElideResourceConfig(ServiceLocator injector, @Context ServletContext servletContext) { @@ -68,12 +70,14 @@ public ElideResourceConfig(ServiceLocator injector, @Context ServletContext serv settings = (ElideStandaloneSettings) servletContext.getAttribute(ELIDE_STANDALONE_SETTINGS_ATTR); + Optional optionalCompiler = settings.getDynamicCompiler(); + // Bind things that should be injectable to the Settings class register(new AbstractBinder() { @Override protected void configure() { - bind(Util.combineModelEntities(settings.getModelPackageName(), settings.enableAsync())).to(Set.class) - .named("elideAllModels"); + bind(Util.combineModelEntities(optionalCompiler, settings.getModelPackageName(), + settings.enableAsync())).to(Set.class).named("elideAllModels"); } }); @@ -81,24 +85,36 @@ protected void configure() { register(new AbstractBinder() { @Override protected void configure() { - ElideSettings elideSettings = settings.getElideSettings(injector); + EntityManagerFactory entityManagerFactory = Util.getEntityManagerFactory(settings.getModelPackageName(), + settings.enableAsync(), optionalCompiler, settings.getDatabaseProperties()); + + EntityDictionary dictionary = settings.getEntityDictionary(injector, optionalCompiler); + + MetaDataStore metaDataStore = settings.getMetaDataStore(optionalCompiler); + + QueryEngine queryEngine = settings.getQueryEngine(metaDataStore, entityManagerFactory); + + DataStore dataStore = settings.getDataStore( + metaDataStore, + settings.getAggregationDataStore(queryEngine, optionalCompiler), + entityManagerFactory); + + ElideSettings elideSettings = settings.getElideSettings(dictionary, dataStore); Elide elide = new Elide(elideSettings); // Bind elide instance for injection into endpoint bind(elide).to(Elide.class).named("elide"); - EntityDictionary dictionary = elideSettings.getDictionary(); - // Bind additional elements bind(elideSettings).to(ElideSettings.class); - bind(dictionary).to(EntityDictionary.class); + bind(elideSettings.getDictionary()).to(EntityDictionary.class); bind(elideSettings.getDataStore()).to(DataStore.class).named("elideDataStore"); // Binding async service - if(settings.enableAsync()) { + if (settings.enableAsync()) { AsyncQueryDAO asyncQueryDao = settings.getAsyncQueryDAO(); - if(asyncQueryDao == null) { + if (asyncQueryDao == null) { asyncQueryDao = new DefaultAsyncQueryDAO(elide, elide.getDataStore()); } bind(asyncQueryDao).to(AsyncQueryDAO.class); @@ -115,7 +131,7 @@ protected void configure() { dictionary.bindTrigger(AsyncQuery.class, CREATE, PRESECURITY, updatePrincipalNameHook, false); // Binding async cleanup service - if(settings.enableAsyncCleanup()) { + if (settings.enableAsyncCleanup()) { AsyncCleanerService.init(elide, settings.getAsyncMaxRunTimeMinutes(), settings.getAsyncQueryCleanupDays(), asyncQueryDao); bind(AsyncCleanerService.getInstance()).to(AsyncCleanerService.class); @@ -128,25 +144,11 @@ protected void configure() { register(new org.glassfish.hk2.utilities.binding.AbstractBinder() { @Override protected void configure() { - List swaggerDocs = settings.enableSwagger(); - if (!swaggerDocs.isEmpty()) { - // Include the async models in swagger docs - if(settings.enableAsync()) { - EntityDictionary dictionary = new EntityDictionary(new HashMap()); - dictionary.bindEntity(AsyncQuery.class); - dictionary.bindEntity(AsyncQueryResult.class); - - Info info = new Info().title("Async Service"); - - SwaggerBuilder builder = new SwaggerBuilder(dictionary, info); - - //Default value of getJsonApiPathSpec() ends with /* at the end. need to remove. - String asyncBasePath = settings.getJsonApiPathSpec().replaceAll("/\\*", ""); - - Swagger swagger = builder.build().basePath(asyncBasePath); - - swaggerDocs.add(new DocEndpoint.SwaggerRegistration("async", swagger)); - } + EntityDictionary dictionary = injector.getService(EntityDictionary.class); + + if (settings.enableSwagger()) { + + List swaggerDocs = settings.buildSwagger(dictionary); bind(swaggerDocs).named("swagger").to(new TypeLiteral>() { }); } @@ -159,7 +161,7 @@ protected void configure() { } /** - * Init the supplemental resource config + * Init the supplemental resource config. */ private void additionalConfiguration(Consumer configurator) { // Inject into consumer if class is provided diff --git a/elide-standalone/src/main/java/com/yahoo/elide/standalone/config/ElideStandaloneSettings.java b/elide-standalone/src/main/java/com/yahoo/elide/standalone/config/ElideStandaloneSettings.java index 00b641dbbf..8bda402cd0 100644 --- a/elide-standalone/src/main/java/com/yahoo/elide/standalone/config/ElideStandaloneSettings.java +++ b/elide-standalone/src/main/java/com/yahoo/elide/standalone/config/ElideStandaloneSettings.java @@ -5,30 +5,48 @@ */ package com.yahoo.elide.standalone.config; +import static com.yahoo.elide.core.EntityDictionary.NO_VERSION; + import com.yahoo.elide.ElideSettings; import com.yahoo.elide.ElideSettingsBuilder; import com.yahoo.elide.Injector; +import com.yahoo.elide.annotation.SecurityCheck; +import com.yahoo.elide.async.service.AsyncQueryDAO; import com.yahoo.elide.audit.AuditLogger; import com.yahoo.elide.audit.Slf4jLogger; +import com.yahoo.elide.contrib.dynamicconfighelpers.compile.ElideDynamicEntityCompiler; +import com.yahoo.elide.contrib.swagger.SwaggerBuilder; import com.yahoo.elide.contrib.swagger.resources.DocEndpoint; import com.yahoo.elide.core.DataStore; import com.yahoo.elide.core.EntityDictionary; import com.yahoo.elide.core.filter.dialect.RSQLFilterDialect; +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.SQLQueryEngine; +import com.yahoo.elide.datastores.aggregation.queryengines.sql.annotation.FromSubquery; +import com.yahoo.elide.datastores.aggregation.queryengines.sql.annotation.FromTable; import com.yahoo.elide.datastores.jpa.JpaDataStore; import com.yahoo.elide.datastores.jpa.transaction.NonJtaTransaction; +import com.yahoo.elide.datastores.multiplex.MultiplexManager; import com.yahoo.elide.security.checks.Check; -import com.yahoo.elide.standalone.Util; -import com.yahoo.elide.async.service.AsyncQueryDAO; import org.eclipse.jetty.servlet.ServletContextHandler; import org.glassfish.hk2.api.ServiceLocator; import org.glassfish.jersey.server.ResourceConfig; +import io.swagger.models.Info; +import io.swagger.models.Swagger; + +import java.io.File; import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Properties; +import java.util.Set; import java.util.TimeZone; import java.util.function.Consumer; import javax.persistence.EntityManagerFactory; @@ -56,30 +74,11 @@ default Map> getCheckMappings() { * That is to say, if you intend to override this method, expect to fully configure the ElideSettings object to * your needs. * - * @param injector Service locator for web service for dependency injection. + * @param dictionary EntityDictionary object. + * @param dataStore Dastore object * @return Configured ElideSettings object. */ - default ElideSettings getElideSettings(ServiceLocator injector) { - EntityManagerFactory entityManagerFactory = Util.getEntityManagerFactory(getModelPackageName(), - enableAsync(), getDatabaseProperties()); - DataStore dataStore = new JpaDataStore( - () -> { return entityManagerFactory.createEntityManager(); }, - (em -> { return new NonJtaTransaction(em); })); - - EntityDictionary dictionary = new EntityDictionary(getCheckMappings(), - new Injector() { - @Override - public void inject(Object entity) { - injector.inject(entity); - } - - @Override - public T instantiate(Class cls) { - return injector.create(cls); - } - }); - - dictionary.scanForSecurityChecks(); + default ElideSettings getElideSettings(EntityDictionary dictionary, DataStore dataStore) { ElideSettingsBuilder builder = new ElideSettingsBuilder(dataStore) .withEntityDictionary(dictionary) @@ -120,8 +119,8 @@ default String getModelPackageName() { } /** - * API root path specification for JSON-API. Namely, this is the mount point of your API. By default it will look - * something like: + * API root path specification for JSON-API. Namely, this is the mount point of your API. + * By default it will look something like: * yourcompany.com/api/v1/YOUR_ENTITY * * @return Default: /api/v1/* @@ -165,7 +164,24 @@ default boolean enableJSONAPI() { default boolean enableGraphQL() { return true; } - + + /** + * Enable the support for Dynamic Model Configuration. If false, the feature will be disabled. + * + * @return Default: False + */ + default boolean enableDynamicModelConfig() { + return false; + } + + /** + * Base path to Hjson dynamic model configurations. + * @return Default: /models/ + */ + default String getDynamicConfigPath() { + return File.separator + "models" + File.separator; + } + /** * Enable the support for Async querying feature. If false, the async feature will be disabled. * @@ -222,7 +238,7 @@ default AsyncQueryDAO getAsyncQueryDAO() { /** * Whether Dates should be ISO8601 strings (true) or epochs (false). - * @return + * @return whether ISO8601Dates are enabled. */ default boolean enableISO8601Dates() { return true; @@ -231,18 +247,57 @@ default boolean enableISO8601Dates() { /** * Whether or not Codahale metrics, healthchecks, thread, ping, and admin servlet * should be enabled. - * @return + * @return whether ServiceMonitoring is enabled. */ default boolean enableServiceMonitoring() { return true; } /** - * Enable swagger documentation by returning non empty list. + * Enable swagger documentation. + * @return whether Swagger is enabled; + */ + default boolean enableSwagger() { + return false; + } + + /** + * Swagger documentation requires an API version. + * The models with the same version are included. + * @return swagger version; + */ + default String getSwaggerVersion() { + return NO_VERSION; + } + + /** + * Swagger documentation requires an API name. + * @return swagger service name; + */ + default String getSwaggerName() { + return "Elide Service"; + } + + /** + * Creates a singular swagger document for JSON-API. + * @param dictionary Contains the static metadata about Elide models. . * @return list of swagger registration objects. */ - default List enableSwagger() { - return new ArrayList<>(); + default List buildSwagger(EntityDictionary dictionary) { + Info info = new Info() + .title(getSwaggerName()) + .version(getSwaggerVersion()); + + SwaggerBuilder builder = new SwaggerBuilder(dictionary, info); + + String moduleBasePath = getJsonApiPathSpec().replaceAll("/\\*", ""); + + Swagger swagger = builder.build().basePath(moduleBasePath); + + List docs = new ArrayList<>(); + docs.add(new DocEndpoint.SwaggerRegistration("test", swagger)); + + return docs; } /** @@ -267,7 +322,7 @@ default Consumer getApplicationConfigurator() { } /** - * Gets properties to configure the database + * Gets properties to configure the database. * * @return Default: ./settings/hibernate.cfg.xml */ @@ -285,11 +340,150 @@ default void updateServletContextHandler(ServletContextHandler servletContextHan } /** - * Gets the audit logger for elide + * Gets the audit logger for elide. * * @return Default: Slf4jLogger */ default AuditLogger getAuditLogger() { return new Slf4jLogger(); } + + /** + * Gets the dynamic compiler for elide. + * + * @return Optional ElideDynamicEntityCompiler + */ + default Optional getDynamicCompiler() { + ElideDynamicEntityCompiler dynamicEntityCompiler = null; + + if (enableDynamicModelConfig()) { + try { + dynamicEntityCompiler = new ElideDynamicEntityCompiler(getDynamicConfigPath()); + } catch (Exception e) { // thrown by in memory compiler + throw new IllegalStateException(e); + } + } + + return Optional.ofNullable(dynamicEntityCompiler); + } + + /** + * Gets the DataStore for elide. + * @param metaDataStore MetaDataStore object. + * @param aggregationDataStore AggregationDataStore object. + * @param entityManagerFactory EntityManagerFactory object. + * @return EntityDictionary object initialized. + */ + default DataStore getDataStore(MetaDataStore metaDataStore, AggregationDataStore aggregationDataStore, + EntityManagerFactory entityManagerFactory) { + + DataStore jpaDataStore = new JpaDataStore( + () -> { return entityManagerFactory.createEntityManager(); }, + (em -> { return new NonJtaTransaction(em); })); + + DataStore dataStore = new MultiplexManager(jpaDataStore, metaDataStore, aggregationDataStore); + + return dataStore; + } + + /** + * Gets the AggregationDataStore for elide. + * @param queryEngine query engine object. + * @param optionalCompiler optional dynamic compiler object. + * @return AggregationDataStore object initialized. + */ + default AggregationDataStore getAggregationDataStore(QueryEngine queryEngine, + Optional optionalCompiler) { + AggregationDataStore aggregationDataStore = null; + + if (enableDynamicModelConfig()) { + Set> annotatedClasses = getDynamicClassesIfAvailable(optionalCompiler, FromTable.class); + annotatedClasses.addAll(getDynamicClassesIfAvailable(optionalCompiler, FromSubquery.class)); + aggregationDataStore = new AggregationDataStore(queryEngine, annotatedClasses); + } else { + aggregationDataStore = new AggregationDataStore(queryEngine); + } + + return aggregationDataStore; + } + + /** + * Gets the EntityDictionary for elide. + * @param injector Service locator for web service for dependency injection. + * @param optionalCompiler optional dynamic compiler object. + * @return EntityDictionary object initialized. + */ + default EntityDictionary getEntityDictionary(ServiceLocator injector, + Optional optionalCompiler) { + EntityDictionary dictionary = new EntityDictionary(getCheckMappings(), + new Injector() { + @Override + public void inject(Object entity) { + injector.inject(entity); + } + + @Override + public T instantiate(Class cls) { + return injector.create(cls); + } + }); + + dictionary.scanForSecurityChecks(); + + Set> annotatedSecurityClasses = getDynamicClassesIfAvailable(optionalCompiler, SecurityCheck.class); + + dictionary.addSecurityChecks(annotatedSecurityClasses); + + return dictionary; + } + + /** + * Gets the metadatastore for elide. + * @param optionalCompiler optional dynamic compiler object. + * @return MetaDataStore object initialized. + */ + default MetaDataStore getMetaDataStore(Optional optionalCompiler) { + MetaDataStore metaDataStore = null; + + if (optionalCompiler.isPresent()) { + try { + metaDataStore = new MetaDataStore(optionalCompiler.get()); + } catch (ClassNotFoundException e) { + throw new IllegalStateException(e); + } + } else { + metaDataStore = new MetaDataStore(); + } + + return metaDataStore; + } + + /** + * Gets the QueryEngine for elide. + * @param metaDataStore MetaDataStore object. + * @param entityManagerFactory EntityManagerFactory object. + * @return QueryEngine object initialized. + */ + default QueryEngine getQueryEngine(MetaDataStore metaDataStore, EntityManagerFactory entityManagerFactory) { + return new SQLQueryEngine(metaDataStore, entityManagerFactory, null); + } + + static Set> getDynamicClassesIfAvailable(Optional optionalCompiler, + Class classz) { + Set> annotatedClasses = new HashSet>(); + + if (!optionalCompiler.isPresent()) { + return annotatedClasses; + } + + ElideDynamicEntityCompiler compiler = optionalCompiler.get(); + + try { + annotatedClasses = compiler.findAnnotatedClasses(classz); + } catch (ClassNotFoundException e) { + throw new IllegalStateException(e); + } + + return annotatedClasses; + } } diff --git a/elide-standalone/src/main/java/com/yahoo/elide/standalone/interfaces/ElideSettingsProvider.java b/elide-standalone/src/main/java/com/yahoo/elide/standalone/interfaces/ElideSettingsProvider.java index 517af6c7a1..aa5bfd670b 100644 --- a/elide-standalone/src/main/java/com/yahoo/elide/standalone/interfaces/ElideSettingsProvider.java +++ b/elide-standalone/src/main/java/com/yahoo/elide/standalone/interfaces/ElideSettingsProvider.java @@ -8,12 +8,12 @@ import com.yahoo.elide.ElideSettings; /** - * Standalone ElideSettingsProvider + * Standalone ElideSettingsProvider. */ public interface ElideSettingsProvider { /** - * Provider for Elide settings + * Provider for Elide settings. * * @return Elide settings object. */ diff --git a/elide-standalone/src/test/java/example/ElideStandaloneTest.java b/elide-standalone/src/test/java/example/ElideStandaloneTest.java index 8f03594b6e..9e419d235a 100644 --- a/elide-standalone/src/test/java/example/ElideStandaloneTest.java +++ b/elide-standalone/src/test/java/example/ElideStandaloneTest.java @@ -17,36 +17,35 @@ import static com.yahoo.elide.contrib.testhelpers.jsonapi.JsonApiDSL.resource; import static com.yahoo.elide.contrib.testhelpers.jsonapi.JsonApiDSL.type; import static io.restassured.RestAssured.given; +import static io.restassured.RestAssured.when; import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.hasItems; import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.hasKey; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; -import com.google.common.collect.Maps; import com.yahoo.elide.async.service.AsyncQueryDAO; -import com.yahoo.elide.contrib.swagger.SwaggerBuilder; -import com.yahoo.elide.contrib.swagger.resources.DocEndpoint; -import com.yahoo.elide.core.EntityDictionary; import com.yahoo.elide.standalone.ElideStandalone; import com.yahoo.elide.standalone.config.ElideStandaloneSettings; + import example.models.Post; -import io.restassured.response.Response; -import io.swagger.models.Info; -import io.swagger.models.Swagger; + import org.apache.http.HttpStatus; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; -import javax.ws.rs.core.MediaType; -import java.util.ArrayList; -import java.util.List; +import io.restassured.response.Response; + import java.util.Properties; +import javax.ws.rs.core.MediaType; + /** - * Tests ElideStandalone starts and works + * Tests ElideStandalone starts and works. */ @TestInstance(TestInstance.Lifecycle.PER_CLASS) public class ElideStandaloneTest { @@ -79,26 +78,20 @@ public String getModelPackageName() { } @Override - public List enableSwagger() { - EntityDictionary dictionary = new EntityDictionary(Maps.newHashMap()); - - dictionary.bindEntity(Post.class); - Info info = new Info().title("Test Service"); - - SwaggerBuilder builder = new SwaggerBuilder(dictionary, info); - Swagger swagger = builder.build(); - - List docs = new ArrayList<>(); - docs.add(new DocEndpoint.SwaggerRegistration("test", swagger)); - return docs; + public boolean enableSwagger() { + return true; } @Override - public boolean enableGraphQL() { return true; } + public boolean enableGraphQL() { + return true; + } @Override - public boolean enableJSONAPI() { return true; } + public boolean enableJSONAPI() { + return true; + } @Override public boolean enableAsync() { @@ -129,6 +122,16 @@ public Integer getAsyncQueryCleanupDays() { public AsyncQueryDAO getAsyncQueryDAO() { return null; } + + @Override + public boolean enableDynamicModelConfig() { + return true; + } + + @Override + public String getDynamicConfigPath() { + return "src/test/resources/models/"; + } }); elide.start(false); } @@ -159,6 +162,16 @@ public void testJsonAPIPost() { .then() .statusCode(HttpStatus.SC_CREATED) .extract().body().asString(); + + // Test the Dynamic Generated Analytical Model is accessible + given() + .when() + .get("/api/v1/postView") + .then() + .statusCode(200) + .body("data.id", hasItems("0")) + .body("data.attributes.content", hasItems("This is my first post. woot.")); + } @Test @@ -221,7 +234,7 @@ public void testMetricsServlet() throws Exception { @Test public void testHealthCheckServlet() throws Exception { - given() + given() .when() .get("/stats/healthcheck") .then() @@ -237,6 +250,17 @@ public void testSwaggerEndpoint() throws Exception { .statusCode(200); } + @Test + public void swaggerDocumentTest() { + when() + .get("/swagger/doc/test") + .then() + .statusCode(200) + .body("tags.name", containsInAnyOrder("post", "functionArgument", "metric", + "metricFunction", "dimension", "column", "table", "asyncQuery", "asyncQueryResult", + "timeDimensionGrain", "timeDimension", "postView")); + } + @Test public void testAsyncApiEndpoint() throws InterruptedException { //Create Async Request @@ -341,4 +365,3 @@ public void testAsyncApiEndpoint() throws InterruptedException { } } } - diff --git a/elide-standalone/src/test/java/example/models/Post.java b/elide-standalone/src/test/java/example/models/Post.java index a4a3b2a2c3..f9aa75af3f 100644 --- a/elide-standalone/src/test/java/example/models/Post.java +++ b/elide-standalone/src/test/java/example/models/Post.java @@ -30,7 +30,7 @@ public class Post { @Column(nullable = false) private String content; - @Temporal( TemporalType.TIMESTAMP ) + @Temporal(TemporalType.TIMESTAMP) private Date date; @CreatePermission(expression = AdminCheck.USER_IS_ADMIN) diff --git a/elide-standalone/src/test/java/example/models/v2/PostV2.java b/elide-standalone/src/test/java/example/models/v2/PostV2.java index e98ddb4fe9..66cbba7d75 100644 --- a/elide-standalone/src/test/java/example/models/v2/PostV2.java +++ b/elide-standalone/src/test/java/example/models/v2/PostV2.java @@ -12,13 +12,14 @@ import example.checks.AdminCheck; import lombok.Data; +import java.util.Date; + import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; import javax.persistence.Temporal; import javax.persistence.TemporalType; -import java.util.Date; @Entity @Include(rootLevel = true, type = "post") @@ -31,7 +32,7 @@ public class PostV2 { @Column(nullable = false, name = "content") private String text; - @Temporal( TemporalType.TIMESTAMP ) + @Temporal(TemporalType.TIMESTAMP) private Date date; @CreatePermission(expression = AdminCheck.USER_IS_ADMIN) diff --git a/elide-standalone/src/test/resources/models/tables/PostView.hjson b/elide-standalone/src/test/resources/models/tables/PostView.hjson new file mode 100644 index 0000000000..2b7cf9a1ad --- /dev/null +++ b/elide-standalone/src/test/resources/models/tables/PostView.hjson @@ -0,0 +1,19 @@ +{ + tables: [{ + name: PostView + table: Post + description: + ''' + A long description + ''' + cardinality : large + readAccess : Prefab.Role.All + dimensions : [ + { + name : content + type : TEXT + definition : content + } + ] + }] +}