Skip to content

Commit

Permalink
fix apache#2733 : Native support for kamelet.yaml discovery.
Browse files Browse the repository at this point in the history
  • Loading branch information
valdar committed Jun 16, 2021
1 parent 9251933 commit 0e198c8
Show file tree
Hide file tree
Showing 16 changed files with 126 additions and 168 deletions.
18 changes: 8 additions & 10 deletions docs/modules/ROOT/pages/reference/extensions/kamelet.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -42,24 +42,22 @@ Check the xref:user-guide/index.adoc[User guide] for more information about writ

=== Pre-load Kamelets at build-time

This extension allow to pre-load a set of Kamelets at build time through the following two options. The kamelet to pre-load can be listed using the `quarkus.camel.kamelet.names` property.
This extension allows to pre-load a set of Kamelets at build time using the `quarkus.camel.kamelet.identifiers` property.

=== Using the Kamelet Catalog

//TODO: does not yet exist

A set of pre-made Kamelets can be found on the https://camel.apache.org/camel-kamelets/latest[Kamelet Catalog].
To use the Kamelet from the catalog you either need to copy them on your project in the classpath location defined by the `quarkus.camel.kamelet.location` property, or you can add the `camel-quarkus-kamelets-catalog` artifact to your `pom.xml`:
To use the Kamelet from the catalog you need to copy their yaml definition (that you can find https://github.com/apache/camel-kamelets/[in the camel-kamelet repo]) on your project in the classpath. Alternatively you can add the `camel-quarkus-kamelets-catalog` artifact to your `pom.xml`:

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

This artifact makes all the kamelets available in the catalog available to Camel Quarkus for build time processing, the artefact is not part of the runtime classpath.
This artifact add all the kamelets available in the catalog to your Camel Quarkus application for build time processing. If you include it with the scope `provided` the artifact should not be part of the runtime classpath, but at build time, all the kamelets listed via `quarkus.camel.kamelet.identifiers` property should be preloaded.


== Additional Camel Quarkus configuration
Expand All @@ -69,10 +67,10 @@ This artifact makes all the kamelets available in the catalog available to Camel
| Configuration property | Type | Default


|icon:lock[title=Fixed at build time] [[quarkus.camel.kamelet.names]]`link:#quarkus.camel.kamelet.names[quarkus.camel.kamelet.names]`
|icon:lock[title=Fixed at build time] [[quarkus.camel.kamelet.identifiers]]`link:#quarkus.camel.kamelet.identifiers[quarkus.camel.kamelet.identifiers]`

List of kamelets names to pre-load at build time.
Each individual name is used to set the related `org.apache.camel.model.RouteTemplateDefinition` id. TODO: find a better name for this configuration option
List of kamelets identifiers to pre-load at build time.
Each individual identifier is used to set the related `org.apache.camel.model.RouteTemplateDefinition` id.
| `string`
|
|===
Expand Down
4 changes: 0 additions & 4 deletions extensions/kamelet/deployment/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,6 @@
<groupId>org.apache.camel.quarkus</groupId>
<artifactId>camel-quarkus-kamelet</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-yaml-dsl</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,35 +16,29 @@
*/
package org.apache.camel.quarkus.component.kamelet.deployment;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
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 org.apache.camel.CamelContext;
import org.apache.camel.ExtendedCamelContext;
import org.apache.camel.Ordered;
import org.apache.camel.RoutesBuilder;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.dsl.yaml.YamlRoutesBuilderLoader;
import org.apache.camel.impl.DefaultCamelContext;
import org.apache.camel.model.RouteTemplateDefinition;
import org.apache.camel.quarkus.component.kamelet.KameletConfiguration;
import org.apache.camel.quarkus.component.kamelet.KameletRecorder;
import org.apache.camel.quarkus.core.deployment.spi.CamelContextCustomizerBuildItem;
import org.apache.camel.spi.Resource;
import org.apache.camel.support.ResourceHelper;

class KameletProcessor {
private static final String FEATURE = "camel-kamelet";
Expand All @@ -58,10 +52,10 @@ FeatureBuildItem feature() {
KameletResolverBuildItem defaultResolver() {
return new KameletResolverBuildItem(new KameletResolver() {
@Override
public Optional<InputStream> resolve(String id) throws Exception {
public Optional<Resource> resolve(String id, CamelContext context) throws Exception {
ExtendedCamelContext ecc = context.adapt(ExtendedCamelContext.class);
return Optional.ofNullable(
Thread.currentThread().getContextClassLoader()
.getResourceAsStream("/kamelets/" + id + ".kamelet.yaml"));
ecc.getResourceLoader().resolveResource("/kamelets/" + id + ".kamelet.yaml"));
}
});
}
Expand All @@ -73,65 +67,39 @@ CamelContextCustomizerBuildItem configureTemplates(
KameletConfiguration configuration,
KameletRecorder recorder) throws Exception {

ObjectMapper mapper = new ObjectMapper(new YAMLFactory())
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

List<RouteTemplateDefinition> definitions = new ArrayList<>();
List<KameletResolver> kameletResolvers = resolvers.stream()
.map(KameletResolverBuildItem::getResolver)
.sorted(Comparator.comparingInt(Ordered::getOrder))
.collect(Collectors.toList());

try (YamlRoutesBuilderLoader ybl = new YamlRoutesBuilderLoader()) {
ybl.setCamelContext(new DefaultCamelContext());
ybl.start();
CamelContext context = new DefaultCamelContext();
ExtendedCamelContext ecc = context.adapt(ExtendedCamelContext.class);

for (String name : configuration.names.orElse(Collections.emptyList())) {
for (String id : configuration.identifiers.orElse(Collections.emptyList())) {
for (KameletResolver resolver : kameletResolvers) {
final Optional<Resource> resource = resolver.resolve(id, ecc);
if (!resource.isPresent()) {
continue;
}

for (KameletResolver resolver : kameletResolvers) {
final Optional<InputStream> is = resolver.resolve(name);
if (!is.isPresent()) {
continue;
Collection<RoutesBuilder> rbs = ecc.getRoutesLoader().findRoutesBuilders(resource.get());
for (RoutesBuilder rb : rbs) {
RouteBuilder routeBuilder = (RouteBuilder) rb;
routeBuilder.configure();
if (routeBuilder.getRouteTemplateCollection().getRouteTemplates().size() == 0) {
throw new IllegalStateException(
"No kamelet template was created for "
+ "kamelet:" + id + "might be the kamelet was malformed?");
} else if (routeBuilder.getRouteTemplateCollection().getRouteTemplates().size() > 1) {
throw new IllegalStateException(
"A kamelet is not supposed to create more than one route ("
+ "kamelet:" + id + ","
+ "routes: " + routeBuilder.getRouteTemplateCollection().getRouteTemplates().size()
+ ")");
}

try {
final ObjectNode definition = (ObjectNode) mapper.readTree(is.get());
final JsonNode properties = definition.requiredAt("/spec/definition/properties");
final JsonNode flow = mapper.createArrayNode().add(definition.requiredAt("/spec/flow"));
final Resource res = ResourceHelper.fromBytes(name + ".yaml", mapper.writeValueAsBytes(flow));

RouteTemplateDefinition rt = new RouteTemplateDefinition();
rt.setId(name);

Iterator<Map.Entry<String, JsonNode>> it = properties.fields();
while (it.hasNext()) {
final Map.Entry<String, JsonNode> property = it.next();
final String key = property.getKey();
final JsonNode def = property.getValue().at("/default");

if (def.isMissingNode()) {
rt.templateParameter(key);
} else {
rt.templateParameter(key, def.asText());
}
}

RouteBuilder rb = (RouteBuilder) ybl.loadRoutesBuilder(res);
rb.configure();
if (rb.getRouteCollection().getRoutes().size() != 1) {
throw new IllegalStateException(
"A kamelet is not supposed to create more than one route ("
+ "kamelet:" + name + ","
+ "routes: " + rb.getRouteCollection().getRoutes().size()
+ ")");
}

rt.setRoute(rb.getRouteCollection().getRoutes().get(0));

definitions.add(rt);
} finally {
is.get().close();
}
definitions.add(routeBuilder.getRouteTemplateCollection().getRouteTemplates().get(0));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,29 @@
/*
* 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.kamelet.deployment;

import java.io.InputStream;
import java.util.Optional;

import org.apache.camel.CamelContext;
import org.apache.camel.Ordered;
import org.apache.camel.spi.Resource;

public interface KameletResolver extends Ordered {
Optional<InputStream> resolve(String id) throws Exception;
Optional<Resource> resolve(String id, CamelContext context) throws Exception;

@Override
default int getOrder() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,27 @@
/*
* 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.kamelet.deployment;

import io.quarkus.builder.item.MultiBuildItem;

/**
* Build item used by kamelet providers to plug their own way of resolving kamelets giving a name. This could be
* leveraged by a future camel-quarkus-kamelet-catalog extension to resolve kamelets as they may have a different naming
* structure or location int the classpath.
* structure or location in the classpath.
*/
public final class KameletResolverBuildItem extends MultiBuildItem {
private final KameletResolver resolver;
Expand Down
12 changes: 5 additions & 7 deletions extensions/kamelet/runtime/src/main/doc/usage.adoc
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
=== Pre-load Kamelets at build-time

This extension allow to pre-load a set of Kamelets at build time through the following two options. The kamelet to pre-load can be listed using the `quarkus.camel.kamelet.names` property.
This extension allows to pre-load a set of Kamelets at build time using the `quarkus.camel.kamelet.identifiers` property.

=== Using the Kamelet Catalog

//TODO: does not yet exist

A set of pre-made Kamelets can be found on the https://camel.apache.org/camel-kamelets/latest[Kamelet Catalog].
To use the Kamelet from the catalog you either need to copy them on your project in the classpath location defined by the `quarkus.camel.kamelet.location` property, or you can add the `camel-quarkus-kamelets-catalog` artifact to your `pom.xml`:
To use the Kamelet from the catalog you need to copy their yaml definition (that you can find https://github.com/apache/camel-kamelets/[in the camel-kamelet repo]) on your project in the classpath. Alternatively you can add the `camel-quarkus-kamelets-catalog` artifact to your `pom.xml`:

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

This artifact makes all the kamelets available in the catalog available to Camel Quarkus for build time processing, the artefact is not part of the runtime classpath.
This artifact add all the kamelets available in the catalog to your Camel Quarkus application for build time processing. If you include it with the scope `provided` the artifact should not be part of the runtime classpath, but at build time, all the kamelets listed via `quarkus.camel.kamelet.identifiers` property should be preloaded.
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,10 @@
@ConfigRoot(name = "camel.kamelet", phase = ConfigPhase.BUILD_TIME)
public class KameletConfiguration {
/**
* List of kamelets names to pre-load at build time.
*
* <p/>
* Each individual name is used to set the related {@link org.apache.camel.model.RouteTemplateDefinition} id.
* TODO: find a better name for this configuration option
* List of kamelets identifiers to pre-load at build time.
* <p>
* Each individual identifier is used to set the related {@link org.apache.camel.model.RouteTemplateDefinition} id.
*/
@ConfigItem
public Optional<List<String>> names;
public Optional<List<String>> identifiers;
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,20 @@

import io.quarkus.runtime.RuntimeValue;
import io.quarkus.runtime.annotations.Recorder;
import io.quarkus.runtime.annotations.RelaxedValidation;
import org.apache.camel.CamelContext;
import org.apache.camel.model.Model;
import org.apache.camel.model.RouteTemplateDefinition;
import org.apache.camel.quarkus.core.CamelContextCustomizer;
import org.apache.camel.spi.CamelContextCustomizer;

@Recorder
public class KameletRecorder {
public RuntimeValue<CamelContextCustomizer> createTemplateLoaderCustomizer(List<RouteTemplateDefinition> definitions) {
public RuntimeValue<CamelContextCustomizer> createTemplateLoaderCustomizer(
@RelaxedValidation List<RouteTemplateDefinition> definitions) {

return new RuntimeValue<>(new CamelContextCustomizer() {
@Override
public void customize(CamelContext context) {
public void configure(CamelContext context) {
try {
context.getExtension(Model.class).addRouteTemplateDefinitions(definitions);
} catch (Exception e) {
Expand Down
13 changes: 13 additions & 0 deletions integration-tests/kamelet/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,19 @@
</dependency>

<!-- The following dependencies guarantee that this module is built after them. You can update them by running `mvn process-resources -Pformat -N` from the source tree root directory -->
<dependency>
<groupId>org.apache.camel.quarkus</groupId>
<artifactId>camel-quarkus-bean-deployment</artifactId>
<version>${project.version}</version>
<type>pom</type>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.camel.quarkus</groupId>
<artifactId>camel-quarkus-direct-deployment</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,16 +82,9 @@ public String invoke(@PathParam("name") String name, String message) throws Exce
return fluentProducerTemplate.toF("kamelet:%s", name).withBody(message).request(String.class);
}

@Path("/auto-discovery")
@POST
@Produces(MediaType.TEXT_PLAIN)
public String autoDiscovery(String message) {
return fluentProducerTemplate.toF("kamelet:auto-discovery?message=%s", message).request(String.class);
}

@Path("/list")
@GET
@Produces(MediaType.TEXT_PLAIN)
@Produces(MediaType.APPLICATION_JSON)
public JsonArray list() {
JsonArrayBuilder builder = Json.createArrayBuilder();

Expand Down
Loading

0 comments on commit 0e198c8

Please sign in to comment.