-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Avro code generation with live reload
#fixes 15567
- Loading branch information
1 parent
660a652
commit 64c0b30
Showing
17 changed files
with
586 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
174 changes: 174 additions & 0 deletions
174
...ons/avro/deployment/src/main/java/io/quarkus/avro/deployment/AvroCodeGenProviderBase.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,174 @@ | ||
package io.quarkus.avro.deployment; | ||
|
||
import java.io.IOException; | ||
import java.nio.file.Files; | ||
import java.nio.file.Path; | ||
import java.nio.file.Paths; | ||
import java.util.Collection; | ||
import java.util.HashSet; | ||
import java.util.Locale; | ||
import java.util.Map; | ||
import java.util.Set; | ||
import java.util.stream.Collectors; | ||
|
||
import org.apache.avro.generic.GenericData; | ||
import org.jboss.logging.Logger; | ||
|
||
import io.quarkus.bootstrap.prebuild.CodeGenException; | ||
import io.quarkus.deployment.CodeGenContext; | ||
import io.quarkus.deployment.CodeGenProvider; | ||
|
||
public abstract class AvroCodeGenProviderBase implements CodeGenProvider { | ||
|
||
private static final Logger log = Logger.getLogger(AvroCodeGenProviderBase.class); | ||
|
||
/** | ||
* The directory (within the java classpath) that contains the velocity | ||
* templates to use for code generation. | ||
*/ | ||
static final String templateDirectory = "/org/apache/avro/compiler/specific/templates/java/classic/"; | ||
|
||
@Override | ||
public String inputDirectory() { | ||
return "avro"; | ||
} | ||
|
||
@Override | ||
public boolean trigger(CodeGenContext context) throws CodeGenException { | ||
init(); | ||
boolean filesGenerated = false; | ||
|
||
AvroOptions options = new AvroOptions(context.properties(), inputExtension()); | ||
Path input = context.inputDir(); | ||
Path outputDir = context.outDir(); | ||
|
||
Set<Path> importedPaths = new HashSet<>(); | ||
|
||
// compile the imports first | ||
for (String imprt : options.imports) { | ||
Path importPath = Paths.get(input.toAbsolutePath().toString(), imprt).toAbsolutePath(); | ||
if (Files.isDirectory(importPath)) { | ||
for (Path file : gatherAllFiles(importPath)) { | ||
compileSingleFile(file, outputDir, options); | ||
importedPaths.add(file); | ||
filesGenerated = true; | ||
} | ||
} else { | ||
compileSingleFile(importPath, outputDir, options); | ||
importedPaths.add(importPath); | ||
filesGenerated = true; | ||
} | ||
} | ||
|
||
// compile the rest of the files | ||
for (Path file : gatherAllFiles(input)) { | ||
if (!importedPaths.contains(file)) { | ||
compileSingleFile(file, outputDir, options); | ||
filesGenerated = true; | ||
} | ||
} | ||
|
||
return filesGenerated; | ||
} | ||
|
||
abstract void init(); | ||
|
||
private Collection<Path> gatherAllFiles(Path importPath) throws CodeGenException { | ||
try { | ||
return Files.find(importPath, 20, | ||
(path, ignored) -> Files.isRegularFile(path) && path.toString().endsWith("." + inputExtension())) | ||
.map(Path::toAbsolutePath) | ||
.collect(Collectors.toList()); | ||
} catch (IOException e) { | ||
throw new CodeGenException("Failed to list matching files in " + importPath, e); | ||
} | ||
} | ||
|
||
abstract void compileSingleFile(Path importPath, Path outputDir, AvroOptions options) throws CodeGenException; | ||
|
||
public static class AvroOptions { | ||
|
||
public static final String[] EMPTY = new String[0]; | ||
|
||
private final Map<String, String> properties; | ||
|
||
/** | ||
* A list of files or directories that should be compiled first thus making them | ||
* importable by subsequently compiled schemas. Note that imported files should | ||
* not reference each other. | ||
* <p> | ||
* All paths should be relative to the src/[main|test]/avro directory | ||
* <p> | ||
* Passed as a comma-separated list. | ||
*/ | ||
final String[] imports; | ||
|
||
/** | ||
* The Java type to use for Avro strings. May be one of CharSequence, String or | ||
* Utf8. CharSequence by default. | ||
*/ | ||
final GenericData.StringType stringType; | ||
|
||
/** | ||
* The createOptionalGetters parameter enables generating the getOptional... | ||
* methods that return an Optional of the requested type. This works ONLY on | ||
* Java 8+ | ||
*/ | ||
final boolean createOptionalGetters; | ||
|
||
/** | ||
* Determines whether or not to use Java classes for decimal types, defaults to false | ||
*/ | ||
final boolean enableDecimalLogicalType; | ||
|
||
/** | ||
* Determines whether or not to create setters for the fields of the record. The | ||
* default is to create setters. | ||
*/ | ||
final boolean createSetters; | ||
|
||
/** | ||
* The gettersReturnOptional parameter enables generating get... methods that | ||
* return an Optional of the requested type. This will replace the This works | ||
* ONLY on Java 8+ | ||
*/ | ||
final boolean gettersReturnOptional; | ||
|
||
/** | ||
* The optionalGettersForNullableFieldsOnly parameter works in conjunction with | ||
* gettersReturnOptional option. If it is set, Optional getters will be | ||
* generated only for fields that are nullable. If the field is mandatory, | ||
* regular getter will be generated. This works ONLY on Java 8+. | ||
*/ | ||
final boolean optionalGettersForNullableFieldsOnly; | ||
|
||
AvroOptions(Map<String, String> properties, String specificPropertyKey) { | ||
this.properties = properties; | ||
String imports = prop("avro.codegen." + specificPropertyKey + ".imports", ""); | ||
this.imports = "".equals(imports) ? EMPTY : imports.split(","); | ||
|
||
stringType = GenericData.StringType.valueOf(prop("avro.codegen.stringType", "String")); | ||
createOptionalGetters = getBooleanProperty("avro.codegen.createOptionalGetters", false); | ||
enableDecimalLogicalType = getBooleanProperty("avro.codegen.enableDecimalLogicalType", false); | ||
createSetters = getBooleanProperty("avro.codegen.createSetters", true); | ||
gettersReturnOptional = getBooleanProperty("avro.codegen.gettersReturnOptional", false); | ||
optionalGettersForNullableFieldsOnly = getBooleanProperty("avro.codegen.optionalGettersForNullableFieldsOnly", | ||
false); | ||
} | ||
|
||
private String prop(String propName, String defaultValue) { | ||
return properties.getOrDefault(propName, defaultValue); | ||
} | ||
|
||
private boolean getBooleanProperty(String propName, boolean defaultValue) { | ||
String value = prop(propName, String.valueOf(defaultValue)).toLowerCase(Locale.ROOT); | ||
if (Boolean.FALSE.toString().equals(value)) { | ||
return false; | ||
} | ||
if (Boolean.TRUE.toString().equals(value)) { | ||
return true; | ||
} | ||
return defaultValue; | ||
} | ||
} | ||
} |
54 changes: 54 additions & 0 deletions
54
...avro/deployment/src/main/java/io/quarkus/avro/deployment/AvroProtocolCodeGenProvider.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
package io.quarkus.avro.deployment; | ||
|
||
import java.io.IOException; | ||
import java.nio.file.Path; | ||
|
||
import org.apache.avro.Protocol; | ||
import org.apache.avro.compiler.specific.SpecificCompiler; | ||
|
||
import io.quarkus.bootstrap.prebuild.CodeGenException; | ||
import io.quarkus.deployment.CodeGenProvider; | ||
|
||
/** | ||
* Avro code generator for Avro Protocol, based on the avro-maven-plugin | ||
* | ||
* @see AvroCodeGenProviderBase | ||
*/ | ||
public class AvroProtocolCodeGenProvider extends AvroCodeGenProviderBase implements CodeGenProvider { | ||
|
||
@Override | ||
public String providerId() { | ||
return "avpr"; | ||
} | ||
|
||
@Override | ||
public String inputExtension() { | ||
return "avpr"; | ||
} | ||
|
||
@Override | ||
void init() { | ||
} | ||
|
||
void compileSingleFile(Path filePath, | ||
Path outputDirectory, | ||
AvroOptions options) throws CodeGenException { | ||
try { | ||
final Protocol protocol = Protocol.parse(filePath.toFile()); | ||
final SpecificCompiler compiler = new SpecificCompiler(protocol); | ||
compiler.setTemplateDir(templateDirectory); | ||
compiler.setStringType(options.stringType); | ||
compiler.setFieldVisibility(SpecificCompiler.FieldVisibility.PRIVATE); | ||
compiler.setCreateOptionalGetters(options.createOptionalGetters); | ||
compiler.setGettersReturnOptional(options.gettersReturnOptional); | ||
compiler.setOptionalGettersForNullableFieldsOnly(options.optionalGettersForNullableFieldsOnly); | ||
compiler.setCreateSetters(options.createSetters); | ||
compiler.setEnableDecimalLogicalType(options.enableDecimalLogicalType); | ||
|
||
compiler.setOutputCharacterEncoding("UTF-8"); | ||
compiler.compileToDestination(filePath.toFile(), outputDirectory.toFile()); | ||
} catch (IOException e) { | ||
new CodeGenException("Failed to compile avro protocole file: " + filePath.toString() + " to Java", e); | ||
} | ||
} | ||
} |
75 changes: 75 additions & 0 deletions
75
...s/avro/deployment/src/main/java/io/quarkus/avro/deployment/AvroSchemaCodeGenProvider.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
package io.quarkus.avro.deployment; | ||
|
||
import java.io.File; | ||
import java.io.IOException; | ||
import java.nio.file.Path; | ||
|
||
import org.apache.avro.Schema; | ||
import org.apache.avro.compiler.specific.SpecificCompiler; | ||
|
||
import io.quarkus.bootstrap.prebuild.CodeGenException; | ||
import io.quarkus.deployment.CodeGenProvider; | ||
|
||
/** | ||
* Avro code generator for Avro Schema, based on the avro-maven-plugin | ||
* | ||
* @see AvroCodeGenProviderBase | ||
*/ | ||
public class AvroSchemaCodeGenProvider extends AvroCodeGenProviderBase implements CodeGenProvider { | ||
|
||
Schema.Parser schemaParser; | ||
|
||
@Override | ||
public String providerId() { | ||
return "avsc"; | ||
} | ||
|
||
@Override | ||
public String inputExtension() { | ||
return "avsc"; | ||
} | ||
|
||
void init() { | ||
schemaParser = new Schema.Parser(); | ||
} | ||
|
||
@Override | ||
void compileSingleFile(Path filePath, | ||
Path outputDirectory, | ||
AvroOptions options) throws CodeGenException { | ||
final Schema schema; | ||
|
||
File file = filePath.toFile(); | ||
|
||
// This is necessary to maintain backward-compatibility. If there are | ||
// no imported files then isolate the schemas from each other, otherwise | ||
// allow them to share a single schema so reuse and sharing of schema | ||
// is possible. | ||
try { | ||
if (options.imports == null) { | ||
schema = new Schema.Parser().parse(file); | ||
} else { | ||
schema = schemaParser.parse(file); | ||
} | ||
} catch (IOException e) { | ||
throw new CodeGenException("", e); | ||
} | ||
|
||
final SpecificCompiler compiler = new SpecificCompiler(schema); | ||
compiler.setTemplateDir(templateDirectory); | ||
compiler.setStringType(options.stringType); | ||
compiler.setFieldVisibility(SpecificCompiler.FieldVisibility.PRIVATE); | ||
compiler.setCreateOptionalGetters(options.createOptionalGetters); | ||
compiler.setGettersReturnOptional(options.gettersReturnOptional); | ||
compiler.setOptionalGettersForNullableFieldsOnly(options.optionalGettersForNullableFieldsOnly); | ||
compiler.setCreateSetters(options.createSetters); | ||
compiler.setEnableDecimalLogicalType(options.enableDecimalLogicalType); | ||
compiler.setOutputCharacterEncoding("UTF-8"); | ||
try { | ||
compiler.compileToDestination(file, outputDirectory.toFile()); | ||
} catch (IOException e) { | ||
throw new CodeGenException("Failed to copy compiled files to output directory " + | ||
outputDirectory.toAbsolutePath(), e); | ||
} | ||
} | ||
} |
2 changes: 2 additions & 0 deletions
2
...vro/deployment/src/main/resources/META-INF/services/io.quarkus.deployment.CodeGenProvider
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
io.quarkus.avro.deployment.AvroSchemaCodeGenProvider | ||
io.quarkus.avro.deployment.AvroProtocolCodeGenProvider |
Oops, something went wrong.