Skip to content

Commit

Permalink
Added native support for avro dataformat #1180
Browse files Browse the repository at this point in the history
  • Loading branch information
aldettinger committed May 19, 2020
1 parent ec50694 commit 4c4b8c4
Show file tree
Hide file tree
Showing 30 changed files with 896 additions and 121 deletions.
1 change: 1 addition & 0 deletions .github/test-categories.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ database:
- mongodb
- sql
dataformats:
- avro
- base64
- bindy
- csv
Expand Down
38 changes: 38 additions & 0 deletions docs/modules/ROOT/pages/extensions/avro.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
[[avro]]
= Avro Extension

*Since Camel Quarkus 1.0.0-M6*

The Avro extension provides link:https://avro.apache.org/[Avro schema based data serialization].

Maven users will need to add the following dependency to their `pom.xml` for this extension.

[source,xml]
------------------------------------------------------------
<dependency>
<groupId>org.apache.camel.quarkus</groupId>
<artifactId>camel-quarkus-avro</artifactId>
</dependency>
------------------------------------------------------------

== Usage

The extension provides support for the Camel https://camel.apache.org/components/latest/dataformats/avro-dataformat.html[Avro Data Format].

=== Configuration

Beyond standard usages described above, Camel Quarkus adds the possibility to parse the Avro schema at build time both in JVM and Native mode via the `@BuildTimeAvroDataFormat` annotation.

For instance below, in the first step the `user.avsc` schema resource is parsed at build time.
In the second step, an AvroDataFormat instance using the previously parsed schema is injected in the `buildTimeAvroDataFormat` field at runtime. At the end of the day, the injected data format is used
from the `configure()` method in order to marshal an incoming message.
[source,java]
----
@BuildTimeAvroDataFormat("user.avsc")
AvroDataFormat buildTimeAvroDataFormat;
@Override
public void configure() {
from("direct:marshalUsingBuildTimeAvroDataFormat").marshal(buildTimeAvroDataFormat);
}
----
4 changes: 2 additions & 2 deletions docs/modules/ROOT/pages/list-of-camel-quarkus-extensions.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -630,8 +630,8 @@ Number of Camel data formats: 26 in 21 JAR artifacts (0 deprecated)
| Data Format | Target +
Level | Since | Description

| link:https://camel.apache.org/components/latest/avro-dataformat.html[Avro] (camel-quarkus-avro) | JVM +
Preview | 1.0.0-M6 | Serialize and deserialize messages using Apache Avro binary data format.
| xref:extensions/avro.adoc[Avro] (camel-quarkus-avro) | Native +
Stable | 1.0.0-M6 | Serialize and deserialize messages using Apache Avro binary data format.

| link:https://camel.apache.org/components/latest/base64-dataformat.html[Base64] (camel-quarkus-base64) | Native +
Stable | 1.0.0-M1 | Encode and decode data using Base64.
Expand Down

This file was deleted.

This file was deleted.

1 change: 0 additions & 1 deletion extensions-jvm/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@

<modules>
<!-- extensions a..z; do not remove this comment, it is important when sorting via mvn process-resources -Pformat -->
<module>avro</module>
<module>avro-rpc</module>
<module>aws2-ddb</module>
<module>aws2-ec2</module>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@
<groupId>org.apache.camel.quarkus</groupId>
<artifactId>camel-quarkus-avro</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5-internal</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.camel.quarkus.component.avro.deployment;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

import javax.inject.Inject;

import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
import io.quarkus.arc.deployment.AnnotationsTransformerBuildItem;
import io.quarkus.arc.deployment.BeanArchiveIndexBuildItem;
import io.quarkus.arc.deployment.BeanContainerBuildItem;
import io.quarkus.arc.processor.AnnotationsTransformer;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.annotations.ExecutionTime;
import io.quarkus.deployment.annotations.Record;
import io.quarkus.deployment.builditem.FeatureBuildItem;
import io.quarkus.deployment.builditem.ObjectSubstitutionBuildItem;
import io.quarkus.deployment.builditem.ObjectSubstitutionBuildItem.Holder;
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
import org.apache.avro.Schema;
import org.apache.avro.SchemaParseException;
import org.apache.avro.generic.GenericContainer;
import org.apache.camel.quarkus.component.avro.AvroDataFormatProducer;
import org.apache.camel.quarkus.component.avro.AvroRecorder;
import org.apache.camel.quarkus.component.avro.AvroSchemaSubstitution;
import org.apache.camel.quarkus.component.avro.BuildTimeAvroDataFormat;
import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.DotName;
import org.jboss.jandex.FieldInfo;
import org.jboss.jandex.IndexView;
import org.jboss.logging.Logger;

class AvroProcessor {

private static final Logger LOG = Logger.getLogger(AvroProcessor.class);
private static final String FEATURE = "camel-avro";
private static DotName BUILD_TIME_AVRO_DATAFORMAT_ANNOTATION = DotName
.createSimple(BuildTimeAvroDataFormat.class.getName());

@BuildStep
FeatureBuildItem feature() {
return new FeatureBuildItem(FEATURE);
}

@BuildStep
List<ReflectiveClassBuildItem> reflectiveClasses() {
List<ReflectiveClassBuildItem> items = new ArrayList<ReflectiveClassBuildItem>();
items.add(new ReflectiveClassBuildItem(false, false, GenericContainer.class));
return items;
}

@BuildStep
void additionalBeanClasses(BuildProducer<AdditionalBeanBuildItem> additionalBeanProducer) {
additionalBeanProducer.produce(new AdditionalBeanBuildItem(AvroDataFormatProducer.class));
}

@BuildStep
AnnotationsTransformerBuildItem markFieldsAnnotatedWithBuildTimeAvroDataFormatAsInjectable() {
return new AnnotationsTransformerBuildItem(new AnnotationsTransformer() {

public boolean appliesTo(org.jboss.jandex.AnnotationTarget.Kind kind) {
return kind == org.jboss.jandex.AnnotationTarget.Kind.FIELD;
}

@Override
public void transform(TransformationContext ctx) {
FieldInfo fieldInfo = ctx.getTarget().asField();
if (fieldInfo.annotation(BUILD_TIME_AVRO_DATAFORMAT_ANNOTATION) != null) {
ctx.transform().add(Inject.class).done();
}
}
});
}

@BuildStep
void overrideAvroSchemasSerialization(BuildProducer<ObjectSubstitutionBuildItem> substitutions) {
Holder<Schema, byte[]> holder = new Holder(Schema.class, byte[].class, AvroSchemaSubstitution.class);
substitutions.produce(new ObjectSubstitutionBuildItem(holder));
}

@Record(ExecutionTime.STATIC_INIT)
@BuildStep
void recordAvroSchemasResigtration(BeanArchiveIndexBuildItem beanArchiveIndex,
BeanContainerBuildItem beanContainer, AvroRecorder avroRecorder) {
IndexView index = beanArchiveIndex.getIndex();
for (AnnotationInstance annotation : index.getAnnotations(BUILD_TIME_AVRO_DATAFORMAT_ANNOTATION)) {
String schemaResourceName = annotation.value().asString();
FieldInfo fieldInfo = annotation.target().asField();
String injectedFieldId = fieldInfo.declaringClass().name() + "." + fieldInfo.name();
try (InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(schemaResourceName)) {
Schema avroSchema = new Schema.Parser().parse(is);
avroRecorder.recordAvroSchemaResigtration(beanContainer.getValue(), injectedFieldId, avroSchema);
LOG.debug("Parsed the avro schema at build time from resource named " + schemaResourceName);
} catch (SchemaParseException | IOException ex) {
final String message = "An issue occured while parsing schema resource on field " + injectedFieldId;
throw new RuntimeException(message, ex);
}
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.camel.quarkus.component.avro.deployment;

import io.quarkus.test.QuarkusUnitTest;
import org.apache.avro.Schema;
import org.apache.camel.dataformat.avro.AvroDataFormat;
import org.apache.camel.quarkus.component.avro.BuildTimeAvroDataFormat;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class BuildTimeAvroDataFormatTest {

@RegisterExtension
static final QuarkusUnitTest CONFIG = new QuarkusUnitTest()
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class).addAsResource("schemas/a-user.avsc")
.addAsResource("schemas/another-user.avsc"));

@BuildTimeAvroDataFormat("schemas/a-user.avsc")
AvroDataFormat aUserBuildTimeAvroDataFormat;

@BuildTimeAvroDataFormat("schemas/a-user.avsc")
AvroDataFormat aUserBuildTimeAvroDataFormatBis;

@BuildTimeAvroDataFormat("schemas/another-user.avsc")
AvroDataFormat anotherUserBuildTimeAvroDataFormat;

@Test
void buildTimeAvroDataFormatAnnotationsShouldBeProcessed() {
assertNotNull(aUserBuildTimeAvroDataFormat);
Object aUserObjectSchema = aUserBuildTimeAvroDataFormat.getSchema();
assertNotNull(aUserObjectSchema);
assertTrue(aUserObjectSchema instanceof Schema);
Schema aUserSchema = (Schema) aUserObjectSchema;
assertEquals("a.user", aUserSchema.getNamespace());

Object aUserBisObjectSchema = aUserBuildTimeAvroDataFormatBis.getSchema();
assertNotNull(aUserBisObjectSchema);
assertNotSame(aUserObjectSchema, aUserBisObjectSchema);

assertNotNull(anotherUserBuildTimeAvroDataFormat);
Object anotherUserObjectSchema = anotherUserBuildTimeAvroDataFormat.getSchema();
assertNotNull(anotherUserObjectSchema);
assertTrue(anotherUserObjectSchema instanceof Schema);
Schema anotherUserSchema = (Schema) anotherUserObjectSchema;
assertEquals("another.user", anotherUserSchema.getNamespace());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{"namespace": "a.user",
"type": "record",
"name": "User",
"fields": [
{"name": "name", "type": "string"}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{"namespace": "another.user",
"type": "record",
"name": "User",
"fields": [
{"name": "name", "type": "string"}
]
}
1 change: 0 additions & 1 deletion extensions-jvm/avro/pom.xml → extensions/avro/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,5 @@
<modules>
<module>deployment</module>
<module>runtime</module>
<module>integration-test</module>
</modules>
</project>

0 comments on commit 4c4b8c4

Please sign in to comment.