diff --git a/elide-core/src/main/java/com/yahoo/elide/core/datastore/inmemory/HashMapDataStore.java b/elide-core/src/main/java/com/yahoo/elide/core/datastore/inmemory/HashMapDataStore.java index 733f907261..6e39503024 100644 --- a/elide-core/src/main/java/com/yahoo/elide/core/datastore/inmemory/HashMapDataStore.java +++ b/elide-core/src/main/java/com/yahoo/elide/core/datastore/inmemory/HashMapDataStore.java @@ -18,6 +18,7 @@ import lombok.Getter; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; @@ -32,7 +33,6 @@ public class HashMapDataStore implements DataStore, DataStoreTestHarness { protected final Map, Map> dataStore = Collections.synchronizedMap(new HashMap<>()); @Getter protected EntityDictionary dictionary; - @Getter private final Set beanPackages; @Getter private final ConcurrentHashMap, AtomicLong> typeIds = new ConcurrentHashMap<>(); public HashMapDataStore(Package beanPackage) { @@ -40,8 +40,6 @@ public HashMapDataStore(Package beanPackage) { } public HashMapDataStore(Set beanPackages) { - this.beanPackages = beanPackages; - for (Package beanPackage : beanPackages) { ClassScanner.getAllClasses(beanPackage.getName()).stream() .map(ClassType::new) @@ -52,6 +50,15 @@ public HashMapDataStore(Set beanPackages) { } } + public HashMapDataStore(Collection> beanClasses) { + beanClasses.stream() + .map(ClassType::new) + .filter(modelType -> dictionary.getFirstAnnotation(modelType, + Arrays.asList(Include.class, Exclude.class)) instanceof Include) + .forEach(modelType -> dataStore.put(modelType, + Collections.synchronizedMap(new LinkedHashMap<>()))); + } + @Override public void populateEntityDictionary(EntityDictionary dictionary) { for (Type clazz : dataStore.keySet()) { diff --git a/elide-core/src/main/java/com/yahoo/elide/core/utils/ClassScanner.java b/elide-core/src/main/java/com/yahoo/elide/core/utils/ClassScanner.java index f5c52d7766..3d85dfebd7 100644 --- a/elide-core/src/main/java/com/yahoo/elide/core/utils/ClassScanner.java +++ b/elide-core/src/main/java/com/yahoo/elide/core/utils/ClassScanner.java @@ -11,8 +11,10 @@ import java.lang.annotation.Annotation; import java.util.Arrays; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.stream.Collectors; @@ -20,6 +22,39 @@ * Scans a package for classes by looking at files in the classpath. */ public class ClassScanner { + + /** + * Class Scanning is terribly costly for service boot, so we do all the scanning up front once to + * save on startup costs. All Annotations Elide scans for must be listed here: + */ + private static final String [] CACHE_ANNOTATIONS = { + //Elide Core Annotations + "com.yahoo.elide.annotation.Include", + "com.yahoo.elide.annotation.SecurityCheck", + "com.yahoo.elide.core.utils.coerce.converters.ElideTypeConverter", + + //Aggregation Store Annotations + "com.yahoo.elide.datastores.aggregation.queryengines.sql.annotation.FromTable", + "com.yahoo.elide.datastores.aggregation.queryengines.sql.annotation.FromSubquery", + "org.hibernate.annotations.Subselect", + "javax.persistence.Entity", + "javax.persistence.Table" + }; + + private static final Map>> STARTUP_CACHE = new HashMap<>(); + + static { + try (ScanResult scanResult = new ClassGraph().enableClassInfo().enableAnnotationInfo().scan()) { + for (String annotationName : CACHE_ANNOTATIONS) { + STARTUP_CACHE.put(annotationName, scanResult.getClassesWithAnnotation(annotationName) + .stream() + .map(ClassInfo::loadClass) + .collect(Collectors.toSet())); + + } + } + } + /** * Scans all classes accessible from the context class loader which belong to the given package and subpackages. * @@ -39,12 +74,11 @@ static public Set> getAnnotatedClasses(Package toScan, Class> getAnnotatedClasses(String packageName, Class annotation) { - try (ScanResult scanResult = new ClassGraph() - .enableClassInfo().enableAnnotationInfo().whitelistPackages(packageName).scan()) { - return scanResult.getClassesWithAnnotation(annotation.getCanonicalName()).stream() - .map((ClassInfo::loadClass)) - .collect(Collectors.toSet()); - } + return STARTUP_CACHE.get(annotation.getCanonicalName()).stream() + .filter(clazz -> + clazz.getPackage().getName().equals(packageName) + || clazz.getPackage().getName().startsWith(packageName + ".")) + .collect(Collectors.toSet()); } /** @@ -57,14 +91,13 @@ static public Set> getAnnotatedClasses(String packageName, Class> getAnnotatedClasses(List> annotations, FilterExpression filter) { Set> result = new HashSet<>(); - try (ScanResult scanResult = new ClassGraph().enableClassInfo().enableAnnotationInfo().scan()) { - for (Class annotation : annotations) { - result.addAll(scanResult.getClassesWithAnnotation(annotation.getCanonicalName()).stream() - .map(ClassInfo::loadClass) - .filter(filter::include) - .collect(Collectors.toSet())); - } + + for (Class annotation : annotations) { + result.addAll(STARTUP_CACHE.get(annotation.getCanonicalName()).stream() + .filter(filter::include) + .collect(Collectors.toSet())); } + return result; } diff --git a/elide-core/src/test/java/com/yahoo/elide/core/utils/ClassScannerTest.java b/elide-core/src/test/java/com/yahoo/elide/core/utils/ClassScannerTest.java index 767c545d47..4eb8cf1578 100644 --- a/elide-core/src/test/java/com/yahoo/elide/core/utils/ClassScannerTest.java +++ b/elide-core/src/test/java/com/yahoo/elide/core/utils/ClassScannerTest.java @@ -7,11 +7,11 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -import com.yahoo.elide.annotation.ReadPermission; -import com.yahoo.elide.annotation.UpdatePermission; +import com.yahoo.elide.annotation.Include; import org.junit.jupiter.api.Test; import java.util.Set; +import javax.persistence.Entity; public class ClassScannerTest { @@ -24,25 +24,25 @@ public void testGetAllClasses() { @Test public void testGetAnnotatedClasses() { - Set> classes = ClassScanner.getAnnotatedClasses("example", ReadPermission.class); - assertEquals(6, classes.size(), "Actual: " + classes); - classes.forEach(cls -> assertTrue(cls.isAnnotationPresent(ReadPermission.class))); + Set> classes = ClassScanner.getAnnotatedClasses("example", Include.class); + assertEquals(30, classes.size(), "Actual: " + classes); + classes.forEach(cls -> assertTrue(cls.isAnnotationPresent(Include.class))); } @Test public void testGetAllAnnotatedClasses() { - Set> classes = ClassScanner.getAnnotatedClasses(ReadPermission.class); - assertEquals(12, classes.size(), "Actual: " + classes); - classes.forEach(cls -> assertTrue(cls.isAnnotationPresent(ReadPermission.class))); + Set> classes = ClassScanner.getAnnotatedClasses(Include.class); + assertEquals(41, classes.size(), "Actual: " + classes); + classes.forEach(cls -> assertTrue(cls.isAnnotationPresent(Include.class))); } @Test public void testGetAnyAnnotatedClasses() { - Set> classes = ClassScanner.getAnnotatedClasses(ReadPermission.class, UpdatePermission.class); - assertEquals(17, classes.size()); + Set> classes = ClassScanner.getAnnotatedClasses(Include.class, Entity.class); + assertEquals(52, classes.size()); for (Class cls : classes) { - assertTrue(cls.isAnnotationPresent(ReadPermission.class) - || cls.isAnnotationPresent(UpdatePermission.class)); + assertTrue(cls.isAnnotationPresent(Include.class) + || cls.isAnnotationPresent(Entity.class)); } } } diff --git a/elide-datastore/elide-datastore-aggregation/src/main/java/com/yahoo/elide/datastores/aggregation/metadata/MetaDataStore.java b/elide-datastore/elide-datastore-aggregation/src/main/java/com/yahoo/elide/datastores/aggregation/metadata/MetaDataStore.java index 31f24b45c0..5a67441666 100644 --- a/elide-datastore/elide-datastore-aggregation/src/main/java/com/yahoo/elide/datastores/aggregation/metadata/MetaDataStore.java +++ b/elide-datastore/elide-datastore-aggregation/src/main/java/com/yahoo/elide/datastores/aggregation/metadata/MetaDataStore.java @@ -28,10 +28,14 @@ import com.yahoo.elide.datastores.aggregation.dynamic.TableType; import com.yahoo.elide.datastores.aggregation.metadata.models.ArgumentDefinition; import com.yahoo.elide.datastores.aggregation.metadata.models.Column; +import com.yahoo.elide.datastores.aggregation.metadata.models.Dimension; +import com.yahoo.elide.datastores.aggregation.metadata.models.Metric; import com.yahoo.elide.datastores.aggregation.metadata.models.Namespace; import com.yahoo.elide.datastores.aggregation.metadata.models.Table; +import com.yahoo.elide.datastores.aggregation.metadata.models.TableSource; import com.yahoo.elide.datastores.aggregation.metadata.models.TimeDimension; import com.yahoo.elide.datastores.aggregation.metadata.models.TimeDimensionGrain; +import com.yahoo.elide.datastores.aggregation.metadata.models.Versioned; import com.yahoo.elide.datastores.aggregation.queryengines.sql.annotation.FromSubquery; import com.yahoo.elide.datastores.aggregation.queryengines.sql.annotation.FromTable; import org.apache.commons.lang3.tuple.Pair; @@ -179,7 +183,21 @@ public Set> getDynamicTypes() { * @param enableMetaDataStore If Enable MetaDataStore */ public MetaDataStore(Set> modelsToBind, boolean enableMetaDataStore) { - this.metadataModelClasses = ClassScanner.getAllClasses(META_DATA_PACKAGE.getName()); + + //Hardcoded to avoid ClassGraph scan. + this.metadataModelClasses = new HashSet<>(Arrays.asList( + Column.class, + Metric.class, + ArgumentDefinition.class, + TableSource.class, + Dimension.class, + TimeDimension.class, + TimeDimensionGrain.class, + Table.class, + Versioned.class, + Namespace.class + )); + this.enableMetaDataStore = enableMetaDataStore; modelsToBind.forEach(cls -> { @@ -223,7 +241,7 @@ public void populateEntityDictionary(EntityDictionary dictionary) { private final Function getHashMapDataStoreInitializer() { return key -> { - HashMapDataStore hashMapDataStore = new HashMapDataStore(META_DATA_PACKAGE); + HashMapDataStore hashMapDataStore = new HashMapDataStore(metadataModelClasses); EntityDictionary dictionary = new EntityDictionary(new HashMap<>()); metadataModelClasses.forEach(dictionary::bindEntity); hashMapDataStore.populateEntityDictionary(dictionary);