From 7407879b604d7c065c7dd37936f27b930b3cfc3e Mon Sep 17 00:00:00 2001 From: AlexandruGhergut Date: Wed, 29 Jan 2020 20:08:22 +0200 Subject: [PATCH 1/4] Add more configurable properties for multi-language support --- .../nebula/hollow/ApiGeneratorExtension.java | 7 ++- .../nebula/hollow/ApiGeneratorTask.java | 52 +++++++++---------- 2 files changed, 32 insertions(+), 27 deletions(-) diff --git a/src/main/java/com/netflix/nebula/hollow/ApiGeneratorExtension.java b/src/main/java/com/netflix/nebula/hollow/ApiGeneratorExtension.java index 5033c5c..020cd3a 100644 --- a/src/main/java/com/netflix/nebula/hollow/ApiGeneratorExtension.java +++ b/src/main/java/com/netflix/nebula/hollow/ApiGeneratorExtension.java @@ -15,6 +15,7 @@ */ package com.netflix.nebula.hollow; +import java.util.Arrays; import java.util.List; public class ApiGeneratorExtension { @@ -24,7 +25,11 @@ public class ApiGeneratorExtension { public String apiPackageName; public String getterPrefix; public String classPostfix; - public String destinationPath; + public String sourcesExtension = ".java"; + public List filesToExclude = Arrays.asList("package-info.java", "module-info.java"); + public List relativeCompileClassPaths = Arrays.asList("/build/classes/main/", "/build/classes/java/main/"); + public String relativeDestinationPath = "/src/main/java/"; + public String relativeSourcesPath = "/src/main/java/"; public boolean parameterizeAllClassNames = false; public boolean useAggressiveSubstitutions = false; public boolean useErgonomicShortcuts = true; diff --git a/src/main/java/com/netflix/nebula/hollow/ApiGeneratorTask.java b/src/main/java/com/netflix/nebula/hollow/ApiGeneratorTask.java index fa9d9ad..9339dfb 100644 --- a/src/main/java/com/netflix/nebula/hollow/ApiGeneratorTask.java +++ b/src/main/java/com/netflix/nebula/hollow/ApiGeneratorTask.java @@ -33,13 +33,6 @@ public class ApiGeneratorTask extends DefaultTask { private final File projectDirFile = getProject().getProjectDir(); private final String projectDirPath = projectDirFile.getAbsolutePath(); - private final String relativeJavaSourcesPath = "/src/main/java/"; - private final String javaSourcesPath = projectDirPath + relativeJavaSourcesPath; - //TODO: perhaps allow the users to configure these paths? - private final String[] compiledClassesPaths = { - projectDirPath + "/build/classes/main/", - projectDirPath + "/build/classes/java/main/", - }; private URLClassLoader urlClassLoader; @@ -48,18 +41,23 @@ public void generateApi() throws IOException { ApiGeneratorExtension extension = getProject().getExtensions().getByType(ApiGeneratorExtension.class); validatePluginConfiguration(extension); - initClassLoader(); + initClassLoader(extension.relativeCompileClassPaths); + + String absoluteSourcesPath = projectDirFile.getAbsolutePath() + extension.relativeSourcesPath; + String absoluteDestinationPath = projectDirFile.getAbsolutePath() + extension.relativeDestinationPath; HollowWriteStateEngine writeEngine = new HollowWriteStateEngine(); HollowObjectMapper mapper = new HollowObjectMapper(writeEngine); - Collection> datamodelClasses = extractClasses(extension.packagesToScan); + Collection> datamodelClasses = extractClasses(extension.packagesToScan, absoluteSourcesPath, + extension.sourcesExtension, extension.filesToExclude); for (Class clazz : datamodelClasses) { getLogger().debug("Initialize schema for class {}", clazz.getName()); mapper.initializeTypeState(clazz); } - String apiTargetPath = extension.destinationPath != null && extension.destinationPath.isEmpty() ? extension.destinationPath : buildPathToApiTargetFolder(extension.apiPackageName); + String apiTargetPath = extension.relativeDestinationPath != null && extension.relativeDestinationPath.isEmpty() ? + extension.relativeDestinationPath : buildPathToApiTargetFolder(absoluteDestinationPath, extension.apiPackageName); HollowAPIGenerator generator = buildHollowAPIGenerator(extension, writeEngine, apiTargetPath); @@ -103,26 +101,28 @@ private HollowAPIGenerator buildHollowAPIGenerator(ApiGeneratorExtension extensi return builder.build(); } - private Collection> extractClasses(List packagesToScan) { + private Collection> extractClasses(List packagesToScan, String absoluteSourcesPath, + String sourcesExtension, List filesToExclude) { Set> classes = new HashSet<>(); for (String packageToScan : packagesToScan) { - File packageFile = buildPackageFile(packageToScan); + File packageFile = buildPackageFile(absoluteSourcesPath, packageToScan); List allFilesInPackage = findFilesRecursively(packageFile); List classNames = new ArrayList<>(); for (File file : allFilesInPackage) { String filePath = file.getAbsolutePath(); getLogger().debug("Candidate for schema initialization {}", filePath); - if (filePath.endsWith(".java") && - !filePath.endsWith("package-info.java") && - !filePath.endsWith("module-info.java") - ) { - String relativeFilePath = removeSubstrings(filePath, projectDirPath, relativeJavaSourcesPath); - classNames.add(convertFolderPathToPackageName(removeSubstrings(relativeFilePath, ".java"))); + if (filePath.endsWith(sourcesExtension) && filesToExclude.stream().noneMatch(filePath::endsWith)) { + String relativeFilePath = removeSubstrings(filePath, absoluteSourcesPath); + classNames.add(convertFolderPathToPackageName(removeSubstrings(relativeFilePath, sourcesExtension))); } } + if (classNames.isEmpty()) { + getLogger().warn("No data model classes have been found."); + } + for (String fqdn : classNames) { try { Class clazz = urlClassLoader.loadClass(fqdn); @@ -149,12 +149,12 @@ private List findFilesRecursively(File packageFile) { return foundFiles; } - private File buildPackageFile(String packageName) { - return new File(javaSourcesPath + convertPackageNameToFolderPath(packageName)); + private File buildPackageFile(String absoluteSourcesPath, String packageName) { + return new File(absoluteSourcesPath + convertPackageNameToFolderPath(packageName)); } - private String buildPathToApiTargetFolder(String apiPackageName) { - return javaSourcesPath + convertPackageNameToFolderPath(apiPackageName); + private String buildPathToApiTargetFolder(String destinationPath, String apiPackageName) { + return destinationPath + convertPackageNameToFolderPath(apiPackageName); } private String convertPackageNameToFolderPath(String packageName) { @@ -180,10 +180,10 @@ private void cleanupAndCreateFolders(String generatedApiTarget) { } } - private void initClassLoader() throws MalformedURLException { - URL[] urls = new URL[compiledClassesPaths.length]; - for (int i=0; i < compiledClassesPaths.length; i++){ - urls[i]= new File(compiledClassesPaths[i]).toURI().toURL() ; + private void initClassLoader(List relativeCompileClassPaths) throws MalformedURLException { + URL[] urls = new URL[relativeCompileClassPaths.size()]; + for (int i=0; i < relativeCompileClassPaths.size(); i++){ + urls[i]= new File(projectDirPath + relativeCompileClassPaths.get(i)).toURI().toURL() ; } urlClassLoader = new URLClassLoader(urls, Thread.currentThread().getContextClassLoader()); } From 08668fc97c35df7d818a6d02476e2d8efb5d518b Mon Sep 17 00:00:00 2001 From: AlexandruGhergut Date: Thu, 30 Jan 2020 12:49:50 +0200 Subject: [PATCH 2/4] Add test --- .../hollow/ApiGeneratorIntegrationSpec.groovy | 97 +++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/src/test/groovy/com/netflix/nebula/hollow/ApiGeneratorIntegrationSpec.groovy b/src/test/groovy/com/netflix/nebula/hollow/ApiGeneratorIntegrationSpec.groovy index ed79f9a..c2b1710 100644 --- a/src/test/groovy/com/netflix/nebula/hollow/ApiGeneratorIntegrationSpec.groovy +++ b/src/test/groovy/com/netflix/nebula/hollow/ApiGeneratorIntegrationSpec.groovy @@ -117,6 +117,103 @@ public class Actor { this.name = name; } } + """.stripIndent() + + when: + ExecutionResult result = runTasks('generateHollowConsumerApi') + + then: + result.success + + and: + [ + '/Movie.java', + '/Actor.java', + '/MovieAPI.java', + '/accessor/ActorDataAccessor.java', + '/accessor/MovieDataAccessor.java', + '/collections/SetOfActor.java', + '/core/ActorDelegate.java', + '/core/ActorDelegateCachedImpl.java', + '/core/ActorHollowFactory.java', + '/core/ActorTypeAPI.java', + '/core/MovieAPIFactory.java', + '/core/MovieDelegate.java', + '/core/MovieDelegateCachedImpl.java', + '/core/MovieDelegateLookupImpl.java', + '/core/MovieHollowFactory.java', + '/core/MovieTypeAPI.java', + '/core/SetOfActorHollowFactory.java', + '/core/SetOfActorTypeAPI.java', + '/core/ActorDelegateLookupImpl.java', + '/core/MovieAPIFactory.java', + '/index/MovieAPIHashIndex.java', + '/index/MoviePrimaryKeyIndex.java', + '/index/MovieUniqueKeyIndex.java' + ].forEach { fileName -> + assert getFile(destinationSrcFolder, fileName).exists() + } + } + + def 'execution of generator task is successful for Kotlin model classes'() { + given: + String destinationSrcFolder = '/src/main/java/com/netflix/nebula/hollow/test/api' + buildFile << """ + buildscript { + ext.kotlin_version = '1.3.61' + + repositories { + mavenCentral() + } + + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:\$kotlin_version" + } + } + + apply plugin: 'java' + apply plugin: 'kotlin' + apply plugin: 'nebula.hollow' + + sourceCompatibility = 1.8 + + hollow { + packagesToScan = ['com.netflix.nebula.hollow.test'] + apiClassName = 'MovieAPI' + apiPackageName = 'com.netflix.nebula.hollow.test.api' + + sourcesExtension = '.kt' + filesToExclude = [] + relativeCompileClassPaths = ['/build/classes/main/', '/build/classes/kotlin/main/'] + relativeSourcesPath = '/src/main/kotlin/' + } + + repositories { + jcenter() + } + + dependencies { + compile "com.netflix.hollow:hollow:3.+" + compile "org.jetbrains.kotlin:kotlin-stdlib:\$kotlin_version" + } + """.stripIndent() + + def moviefile = createFile('src/main/kotlin/com/netflix/nebula/hollow/test/Movie.kt') + moviefile << """package com.netflix.nebula.hollow.test; + +import com.netflix.hollow.core.write.objectmapper.HollowPrimaryKey; + +@HollowPrimaryKey(fields=["id"]) +data class Movie(val id: Long, + val title: String, + val releaseYear: Int, + val actors: Set) + """.stripIndent() + + def authorFile = createFile('src/main/kotlin/com/netflix/nebula/hollow/test/Actor.kt') + authorFile << """package com.netflix.nebula.hollow.test; + +data class Actor(val name: String) """.stripIndent() when: From d8dc637d1f6eb5f41a781f364dc1e0910f15aa0a Mon Sep 17 00:00:00 2001 From: AlexandruGhergut Date: Thu, 30 Jan 2020 12:57:09 +0200 Subject: [PATCH 3/4] Update README.md --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 6d7dc77..4ff8c88 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,11 @@ optional values: - `useHollowPrimitiveTypes` - specify to use Hollow Primitive Types instead of generating them per project. defaults to `true` - `restrictApiToFieldType` - api code only generates `get` with return type as per schema. defaults to `true` - `useVerboseToString` - will implement `toString()` method for hollow objects with `HollowRecordStringifier().stringify(this)`. defaults to `true` +- `sourcesExtension` - the extension of your data classes. defaults to `".java"` +- `filesToExclude` - files to exclude when scanning for data classes. defaults to `["package-info.java", "module-info.java"]` +- `relativeCompileClassPaths` - the relative classpath of your compiled classes. defaults to `["/build/classes/main/", "/build/classes/java/main/"]` +- `relativeDestinationPath` - the relative path to the api-related sources will be generated. defaults to `"/src/main/java/"` +- `relativeSourcesPath` - the relative path to your data classes. defaults to `"/src/main/java/"` For more information, please refer to [`AbstractHollowAPIGeneratorBuilder`](https://github.com/Netflix/hollow/blob/master/hollow/src/main/java/com/netflix/hollow/api/codegen/AbstractHollowAPIGeneratorBuilder.java) launch task: From 3e42dbdabcf4ea4afa34166a6d68159bd312a1e4 Mon Sep 17 00:00:00 2001 From: AlexandruGhergut Date: Thu, 30 Jan 2020 14:22:38 +0200 Subject: [PATCH 4/4] Update apiTargetPath --- src/main/java/com/netflix/nebula/hollow/ApiGeneratorTask.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/com/netflix/nebula/hollow/ApiGeneratorTask.java b/src/main/java/com/netflix/nebula/hollow/ApiGeneratorTask.java index 9339dfb..bc2b871 100644 --- a/src/main/java/com/netflix/nebula/hollow/ApiGeneratorTask.java +++ b/src/main/java/com/netflix/nebula/hollow/ApiGeneratorTask.java @@ -56,8 +56,7 @@ public void generateApi() throws IOException { mapper.initializeTypeState(clazz); } - String apiTargetPath = extension.relativeDestinationPath != null && extension.relativeDestinationPath.isEmpty() ? - extension.relativeDestinationPath : buildPathToApiTargetFolder(absoluteDestinationPath, extension.apiPackageName); + String apiTargetPath = buildPathToApiTargetFolder(absoluteDestinationPath, extension.apiPackageName); HollowAPIGenerator generator = buildHollowAPIGenerator(extension, writeEngine, apiTargetPath);