This is an automated email from the ASF dual-hosted git repository. lburgazzoli pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/master by this push: new a481596 dsl: add support for JavaScript a481596 is described below commit a481596b49badb50d96cc064cc0cb1d73df4abd2 Author: Luca Burgazzoli <lburgazz...@gmail.com> AuthorDate: Tue Mar 16 10:47:17 2021 +0100 dsl: add support for JavaScript --- bom/camel-bom/pom.xml | 5 + camel-dependencies/pom.xml | 1 + .../main/java/org/apache/camel/spi/Resource.java | 26 +++- .../org/apache/camel/builder/RouteBuilder.java | 91 ++++++++--- .../builder/endpoint/EndpointRouteBuilder.java | 45 +++++- dsl/camel-js-dsl/pom.xml | 173 +++++++++++++++++++++ .../services/org/apache/camel/routes-loader/js | 2 + dsl/camel-js-dsl/src/main/docs/js-dsl.adoc | 13 ++ .../org/apache/camel/dsl/js/JavaScriptDSL.java | 99 ++++++++++++ .../dsl/js/JavaScriptRoutesBuilderLoader.java | 115 ++++++++++++++ .../dsl/js/JavaScriptRoutesBuilderLoaderTest.java | 125 +++++++++++++++ .../src/test/resources/log4j2-test.properties | 31 ++++ .../routes/routes-with-component-configuration.js | 5 + .../routes/routes-with-context-configuration.js | 18 +++ .../resources/routes/routes-with-endpoint-dsl.js | 22 +++ .../resources/routes/routes-with-processors.js | 15 ++ .../routes/routes-with-rest-configuration.js | 4 + .../test/resources/routes/routes-with-rest-dsl.js | 7 + .../src/test/resources/routes/routes.js | 18 +++ dsl/pom.xml | 1 + parent/pom.xml | 6 + 21 files changed, 796 insertions(+), 26 deletions(-) diff --git a/bom/camel-bom/pom.xml b/bom/camel-bom/pom.xml index a46f203..914c38d 100644 --- a/bom/camel-bom/pom.xml +++ b/bom/camel-bom/pom.xml @@ -1114,6 +1114,11 @@ </dependency> <dependency> <groupId>org.apache.camel</groupId> + <artifactId>camel-js-dsl</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.apache.camel</groupId> <artifactId>camel-jt400</artifactId> <version>${project.version}</version> </dependency> diff --git a/camel-dependencies/pom.xml b/camel-dependencies/pom.xml index 52ccc11..c2025a8 100644 --- a/camel-dependencies/pom.xml +++ b/camel-dependencies/pom.xml @@ -241,6 +241,7 @@ <google-guava-version>19.0</google-guava-version> <google-mail-guava-version>17.0</google-mail-guava-version> <google-maps-services-version>0.10.1</google-maps-services-version> + <graaljs-version>21.0.0.2</graaljs-version> <graphql-java-version>14.0</graphql-java-version> <grizzly-websockets-version>2.3.25</grizzly-websockets-version> <grpc-google-auth-library-version>0.19.0</grpc-google-auth-library-version> diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/Resource.java b/core/camel-api/src/main/java/org/apache/camel/spi/Resource.java index fb336c7..e56f6af 100644 --- a/core/camel-api/src/main/java/org/apache/camel/spi/Resource.java +++ b/core/camel-api/src/main/java/org/apache/camel/spi/Resource.java @@ -18,9 +18,13 @@ package org.apache.camel.spi; import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; import java.net.MalformedURLException; import java.net.URI; import java.net.URL; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; /** * Describe a resource, such as a file or class path resource. @@ -56,9 +60,29 @@ public interface Resource { } /** - * Returns an input stream that reads from the underlying resource. + * Returns an {@link InputStream} that reads from the underlying resource. * </p> * Each invocation must return a new {@link InputStream} instance. */ InputStream getInputStream() throws IOException; + + /** + * Returns a {@link Reader} that reads from the underlying resource using UTF-8 as charset. + * </p> + * Each invocation must return a new {@link Reader}, @see #getInputStream() + */ + default Reader getReader() throws Exception { + return getReader(StandardCharsets.UTF_8); + } + + /** + * Returns a {@link Reader} that reads from the underlying resource using the given {@link Charset} + * </p> + * Each invocation must return a new {@link Reader}, @see #getInputStream() + * + * @see #getInputStream() + */ + default Reader getReader(Charset charset) throws Exception { + return new InputStreamReader(getInputStream(), charset); + } } diff --git a/core/camel-core-model/src/main/java/org/apache/camel/builder/RouteBuilder.java b/core/camel-core-model/src/main/java/org/apache/camel/builder/RouteBuilder.java index 355fe43..9f3d919 100644 --- a/core/camel-core-model/src/main/java/org/apache/camel/builder/RouteBuilder.java +++ b/core/camel-core-model/src/main/java/org/apache/camel/builder/RouteBuilder.java @@ -16,12 +16,14 @@ */ package org.apache.camel.builder; +import java.io.Reader; import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.concurrent.atomic.AtomicBoolean; import org.apache.camel.CamelContext; +import org.apache.camel.CamelContextAware; import org.apache.camel.Endpoint; import org.apache.camel.ExtendedCamelContext; import org.apache.camel.Ordered; @@ -43,10 +45,13 @@ import org.apache.camel.model.rest.RestDefinition; import org.apache.camel.model.rest.RestsDefinition; import org.apache.camel.spi.OnCamelContextEvent; import org.apache.camel.spi.PropertiesComponent; +import org.apache.camel.spi.Resource; import org.apache.camel.spi.RestConfiguration; import org.apache.camel.support.LifecycleStrategySupport; import org.apache.camel.util.ObjectHelper; import org.apache.camel.util.StringHelper; +import org.apache.camel.util.function.ThrowingBiConsumer; +import org.apache.camel.util.function.ThrowingConsumer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -56,14 +61,16 @@ import org.slf4j.LoggerFactory; */ public abstract class RouteBuilder extends BuilderSupport implements RoutesBuilder, Ordered { protected Logger log = LoggerFactory.getLogger(getClass()); - private AtomicBoolean initialized = new AtomicBoolean(); + + private final AtomicBoolean initialized = new AtomicBoolean(); + private final List<RouteBuilderLifecycleStrategy> lifecycleInterceptors = new ArrayList<>(); + private final List<TransformerBuilder> transformerBuilders = new ArrayList<>(); + private final List<ValidatorBuilder> validatorBuilders = new ArrayList<>(); + private RestsDefinition restCollection = new RestsDefinition(); private RestConfigurationDefinition restConfiguration; - private List<TransformerBuilder> transformerBuilders = new ArrayList<>(); - private List<ValidatorBuilder> validatorBuilders = new ArrayList<>(); private RoutesDefinition routeCollection = new RoutesDefinition(); private RouteTemplatesDefinition routeTemplateCollection = new RouteTemplatesDefinition(); - private final List<RouteBuilderLifecycleStrategy> lifecycleInterceptors = new ArrayList<>(); public RouteBuilder() { this(null); @@ -74,19 +81,6 @@ public abstract class RouteBuilder extends BuilderSupport implements RoutesBuild } /** - * Override this method to define ordering of {@link RouteBuilder} classes that are added to Camel from various - * runtimes such as camel-main, camel-spring-boot. This allows end users to control the ordering if some routes must - * be added and started before others. - * <p/> - * Use low numbers for higher priority. Normally the sorting will start from 0 and move upwards. So if you want to - * be last then use {@link Integer#MAX_VALUE} or eg {@link #LOWEST}. - */ - @Override - public int getOrder() { - return LOWEST; - } - - /** * Add routes to a context using a lambda expression. It can be used as following: * * <pre> @@ -107,6 +101,56 @@ public abstract class RouteBuilder extends BuilderSupport implements RoutesBuild }); } + /** + * Loads {@link RoutesBuilder} from {@link Resource} using the given consumer to create a {@link RouteBuilder} + * instance. + * + * @param resource the resource to be loaded. + * @param consumer the function used to create a {@link RoutesBuilder} + * @return a {@link RoutesBuilder} + */ + public static RouteBuilder loadRoutesBuilder( + Resource resource, ThrowingBiConsumer<Reader, RouteBuilder, Exception> consumer) { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + CamelContextAware.trySetCamelContext(resource, getContext()); + + try (Reader reader = resource.getReader()) { + consumer.accept(reader, this); + } + } + }; + } + + /** + * Loads {@link RoutesBuilder} using the given consumer to create a {@link RouteBuilder} instance. + * + * @param consumer the function used to create a {@link RoutesBuilder} + * @return a {@link RoutesBuilder} + */ + public static RouteBuilder loadRoutesBuilder(ThrowingConsumer<RouteBuilder, Exception> consumer) { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + consumer.accept(this); + } + }; + } + + /** + * Override this method to define ordering of {@link RouteBuilder} classes that are added to Camel from various + * runtimes such as camel-main, camel-spring-boot. This allows end users to control the ordering if some routes must + * be added and started before others. + * <p/> + * Use low numbers for higher priority. Normally the sorting will start from 0 and move upwards. So if you want to + * be last then use {@link Integer#MAX_VALUE} or eg {@link #LOWEST}. + */ + @Override + public int getOrder() { + return LOWEST; + } + @Override public String toString() { return getRouteCollection().toString(); @@ -593,22 +637,22 @@ public abstract class RouteBuilder extends BuilderSupport implements RoutesBuild return restCollection; } - public RestConfigurationDefinition getRestConfiguration() { - return restConfiguration; - } - public void setRestCollection(RestsDefinition restCollection) { this.restCollection = restCollection; } - public void setRouteCollection(RoutesDefinition routeCollection) { - this.routeCollection = routeCollection; + public RestConfigurationDefinition getRestConfiguration() { + return restConfiguration; } public RoutesDefinition getRouteCollection() { return this.routeCollection; } + public void setRouteCollection(RoutesDefinition routeCollection) { + this.routeCollection = routeCollection; + } + public RouteTemplatesDefinition getRouteTemplateCollection() { return routeTemplateCollection; } @@ -628,5 +672,4 @@ public abstract class RouteBuilder extends BuilderSupport implements RoutesBuild protected void configureRouteTemplate(RouteTemplateDefinition routeTemplate) { // noop } - } diff --git a/core/camel-endpointdsl/src/main/java/org/apache/camel/builder/endpoint/EndpointRouteBuilder.java b/core/camel-endpointdsl/src/main/java/org/apache/camel/builder/endpoint/EndpointRouteBuilder.java index b8f3434..f9e9190 100644 --- a/core/camel-endpointdsl/src/main/java/org/apache/camel/builder/endpoint/EndpointRouteBuilder.java +++ b/core/camel-endpointdsl/src/main/java/org/apache/camel/builder/endpoint/EndpointRouteBuilder.java @@ -16,8 +16,13 @@ */ package org.apache.camel.builder.endpoint; +import java.io.Reader; + import org.apache.camel.CamelContext; +import org.apache.camel.CamelContextAware; import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.spi.Resource; +import org.apache.camel.util.function.ThrowingBiConsumer; import org.apache.camel.util.function.ThrowingConsumer; /** @@ -34,7 +39,7 @@ public abstract class EndpointRouteBuilder extends RouteBuilder implements Endpo /** * Add routes to a context using a lambda expression. It can be used as following: - * + * * <pre> * RouteBuilder.addRoutes(context, rb -> * rb.from("direct:inbound").bean(ProduceTemplateBean.class))); @@ -54,4 +59,42 @@ public abstract class EndpointRouteBuilder extends RouteBuilder implements Endpo }); } + /** + * Loads {@link EndpointRouteBuilder} from {@link Resource} using the given consumer to create an + * {@link EndpointRouteBuilder} instance. + * + * @param resource the resource to be loaded. + * @param consumer the function used to create a {@link EndpointRouteBuilder} + * @return a {@link EndpointRouteBuilder} + */ + public static EndpointRouteBuilder loadEndpointRoutesBuilder( + Resource resource, ThrowingBiConsumer<Reader, EndpointRouteBuilder, Exception> consumer) { + return new EndpointRouteBuilder() { + @Override + public void configure() throws Exception { + CamelContextAware.trySetCamelContext(resource, getContext()); + + try (Reader reader = resource.getReader()) { + consumer.accept(reader, this); + } + } + }; + } + + /** + * Loads {@link EndpointRouteBuilder} from {@link Resource} using the given consumer to create an + * {@link EndpointRouteBuilder} instance. + * + * @param consumer the function used to create a {@link EndpointRouteBuilder} + * @return a {@link EndpointRouteBuilder} + */ + public static EndpointRouteBuilder loadEndpointRoutesBuilder(ThrowingConsumer<EndpointRouteBuilder, Exception> consumer) { + return new EndpointRouteBuilder() { + @Override + public void configure() throws Exception { + consumer.accept(this); + } + }; + } + } diff --git a/dsl/camel-js-dsl/pom.xml b/dsl/camel-js-dsl/pom.xml new file mode 100644 index 0000000..21cdded --- /dev/null +++ b/dsl/camel-js-dsl/pom.xml @@ -0,0 +1,173 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + + 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. + +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.apache.camel</groupId> + <artifactId>dsl</artifactId> + <version>3.9.0-SNAPSHOT</version> + </parent> + + <artifactId>camel-js-dsl</artifactId> + <packaging>jar</packaging> + <name>Camel :: JavaScript DSL</name> + <description>Camel DSL with JavaScript</description> + + <properties> + <firstVersion>3.9.0</firstVersion> + <sourcecheckExcludes> + **/resources/**/My*.java + </sourcecheckExcludes> + <sourcecheckExcludesComma> + ${sourcecheckExcludes}, + </sourcecheckExcludesComma> + </properties> + + <dependencies> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-support</artifactId> + </dependency> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-core-model</artifactId> + </dependency> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-endpointdsl</artifactId> + </dependency> + + <!-- requires Java 11 or 14 --> + + <dependency> + <groupId>org.graalvm.js</groupId> + <artifactId>js</artifactId> + <version>${graaljs-version}</version> + </dependency> + + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-main</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-direct</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-rest</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-mock</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-core-languages</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-bean</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-log</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-telegram</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-seda</artifactId> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>org.assertj</groupId> + <artifactId>assertj-core</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-test-junit5</artifactId> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>org.apache.logging.log4j</groupId> + <artifactId>log4j-core</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.logging.log4j</groupId> + <artifactId>log4j-slf4j-impl</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.logging.log4j</groupId> + <artifactId>log4j-jcl</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + + + <build> + <plugins> + <plugin> + <groupId>org.apache.camel</groupId> + <artifactId>camel-package-maven-plugin</artifactId> + <executions> + <execution> + <id>generate-spi</id> + <goals> + <goal>generate-spi</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> + + <!-- skip testing on java 8 --> + <profiles> + <profile> + <id>jdk8</id> + <activation> + <jdk>[,8)</jdk> + </activation> + <properties> + <maven.test.skip.exec>true</maven.test.skip.exec> + </properties> + </profile> + </profiles> + +</project> \ No newline at end of file diff --git a/dsl/camel-js-dsl/src/generated/resources/META-INF/services/org/apache/camel/routes-loader/js b/dsl/camel-js-dsl/src/generated/resources/META-INF/services/org/apache/camel/routes-loader/js new file mode 100644 index 0000000..8ae3a3c --- /dev/null +++ b/dsl/camel-js-dsl/src/generated/resources/META-INF/services/org/apache/camel/routes-loader/js @@ -0,0 +1,2 @@ +# Generated by camel build tools - do NOT edit this file! +class=org.apache.camel.dsl.js.JavaScriptRoutesBuilderLoader diff --git a/dsl/camel-js-dsl/src/main/docs/js-dsl.adoc b/dsl/camel-js-dsl/src/main/docs/js-dsl.adoc new file mode 100644 index 0000000..595ce63 --- /dev/null +++ b/dsl/camel-js-dsl/src/main/docs/js-dsl.adoc @@ -0,0 +1,13 @@ +[[js-dsl-other]] += JavaScript DSL +:docTitle: JavaScript Dsl +:artifactId: camel-js-dsl +:description: Camel DSL with JavaScript +:supportLevel: Experimental +:since: 3. +:supportLevel: Preview +include::{cq-version}@camel-quarkus:ROOT:partial$reference/others/js-dsl.adoc[opts=optional] +//Manually maintained attributes +:group: DSL + +See https://camel.apache.org/manual/latest/dsl.html \ No newline at end of file diff --git a/dsl/camel-js-dsl/src/main/java/org/apache/camel/dsl/js/JavaScriptDSL.java b/dsl/camel-js-dsl/src/main/java/org/apache/camel/dsl/js/JavaScriptDSL.java new file mode 100644 index 0000000..295d82b --- /dev/null +++ b/dsl/camel-js-dsl/src/main/java/org/apache/camel/dsl/js/JavaScriptDSL.java @@ -0,0 +1,99 @@ +/* + * 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.dsl.js; + +import java.util.function.Consumer; + +import org.apache.camel.CamelContext; +import org.apache.camel.Exchange; +import org.apache.camel.Processor; +import org.apache.camel.builder.BuilderSupport; +import org.apache.camel.builder.EndpointConsumerBuilder; +import org.apache.camel.builder.ErrorHandlerBuilder; +import org.apache.camel.builder.endpoint.EndpointBuilderFactory; +import org.apache.camel.builder.endpoint.EndpointRouteBuilder; +import org.apache.camel.model.InterceptDefinition; +import org.apache.camel.model.InterceptFromDefinition; +import org.apache.camel.model.InterceptSendToEndpointDefinition; +import org.apache.camel.model.OnCompletionDefinition; +import org.apache.camel.model.OnExceptionDefinition; +import org.apache.camel.model.RouteDefinition; +import org.apache.camel.model.rest.RestConfigurationDefinition; +import org.apache.camel.model.rest.RestDefinition; +import org.apache.camel.spi.Registry; + +public class JavaScriptDSL extends BuilderSupport implements EndpointBuilderFactory { + public final Registry registry; + public final EndpointRouteBuilder builder; + public final CamelContext context; + + public JavaScriptDSL(EndpointRouteBuilder builder) { + super(builder.getContext()); + + this.registry = builder.getContext().getRegistry(); + this.builder = builder; + this.context = builder.getContext(); + } + + public RouteDefinition from(String endpoint) { + return builder.from(endpoint); + } + + public RouteDefinition from(EndpointConsumerBuilder endpoint) { + return builder.from(endpoint); + } + + public RestDefinition rest(String path) { + return builder.rest(path); + } + + public RestConfigurationDefinition restConfiguration() { + return builder.restConfiguration(); + } + + public OnExceptionDefinition onException(Class<? extends Throwable> exception) { + return builder.onException(exception); + } + + public OnCompletionDefinition onCompletion() { + return builder.onCompletion(); + } + + public InterceptDefinition intercept() { + return builder.intercept(); + } + + public InterceptFromDefinition interceptFrom() { + return builder.interceptFrom(); + } + + public InterceptFromDefinition interceptFrom(String uri) { + return builder.interceptFrom(uri); + } + + public InterceptSendToEndpointDefinition interceptSendToEndpoint(String uri) { + return builder.interceptSendToEndpoint(uri); + } + + public void errorHandler(ErrorHandlerBuilder handler) { + builder.errorHandler(handler); + } + + public Processor processor(Consumer<Exchange> consumer) { + return consumer::accept; + } +} diff --git a/dsl/camel-js-dsl/src/main/java/org/apache/camel/dsl/js/JavaScriptRoutesBuilderLoader.java b/dsl/camel-js-dsl/src/main/java/org/apache/camel/dsl/js/JavaScriptRoutesBuilderLoader.java new file mode 100644 index 0000000..6d9a052 --- /dev/null +++ b/dsl/camel-js-dsl/src/main/java/org/apache/camel/dsl/js/JavaScriptRoutesBuilderLoader.java @@ -0,0 +1,115 @@ +/* + * 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.dsl.js; + +import java.io.Reader; + +import org.apache.camel.CamelContext; +import org.apache.camel.ExtendedCamelContext; +import org.apache.camel.RoutesBuilder; +import org.apache.camel.StartupStep; +import org.apache.camel.api.management.ManagedAttribute; +import org.apache.camel.api.management.ManagedResource; +import org.apache.camel.builder.endpoint.EndpointRouteBuilder; +import org.apache.camel.spi.Resource; +import org.apache.camel.spi.StartupStepRecorder; +import org.apache.camel.spi.annotations.RoutesLoader; +import org.apache.camel.support.LifecycleStrategySupport; +import org.apache.camel.support.RoutesBuilderLoaderSupport; +import org.graalvm.polyglot.Context; +import org.graalvm.polyglot.Value; + +import static org.graalvm.polyglot.Source.newBuilder; + +@ManagedResource(description = "Managed JavaScriptRoutesBuilderLoader") +@RoutesLoader(JavaScriptRoutesBuilderLoader.EXTENSION) +public class JavaScriptRoutesBuilderLoader extends RoutesBuilderLoaderSupport { + public static final String EXTENSION = "js"; + public static final String LANGUAGE_ID = "js"; + + private StartupStepRecorder recorder; + + @Override + protected void doBuild() throws Exception { + super.doBuild(); + + if (getCamelContext() != null) { + this.recorder = getCamelContext().adapt(ExtendedCamelContext.class).getStartupStepRecorder(); + } + } + + @ManagedAttribute(description = "Supported file extension") + @Override + public String getSupportedExtension() { + return EXTENSION; + } + + @Override + public RoutesBuilder loadRoutesBuilder(Resource resource) throws Exception { + StartupStep step = recorder != null + ? recorder.beginStep(JavaScriptRoutesBuilderLoader.class, resource.getLocation(), "Compiling RouteBuilder") + : null; + + try { + return EndpointRouteBuilder.loadEndpointRoutesBuilder(resource, this::load); + } finally { + if (recorder != null) { + recorder.endStep(step); + } + } + } + + private void load(Reader reader, EndpointRouteBuilder builder) { + final Context context = Context.newBuilder(LANGUAGE_ID).allowAllAccess(true).build(); + final Value bindings = context.getBindings(LANGUAGE_ID); + + // configure bindings + bindings.putMember("__dsl", new JavaScriptDSL(builder)); + + // + // Expose JavaScriptDSL methods to global scope. + // + context.eval( + LANGUAGE_ID, + String.join( + "\n", + "Object.setPrototypeOf(globalThis, new Proxy(Object.prototype, {", + " has(target, key) {", + " return key in __dsl || key in target;", + " },", + " get(target, key, receiver) {", + " return Reflect.get((key in __dsl) ? __dsl : target, key, receiver);", + " }", + "}));")); + + // + // Run the script. + // + context.eval( + newBuilder(LANGUAGE_ID, reader, "Unnamed").buildLiteral()); + + // + // Close the polyglot context when the camel context stops + // + builder.getContext().addLifecycleStrategy(new LifecycleStrategySupport() { + @Override + public void onContextStopping(CamelContext camelContext) { + context.close(true); + } + }); + } +} diff --git a/dsl/camel-js-dsl/src/test/java/org/apache/camel/dsl/js/JavaScriptRoutesBuilderLoaderTest.java b/dsl/camel-js-dsl/src/test/java/org/apache/camel/dsl/js/JavaScriptRoutesBuilderLoaderTest.java new file mode 100644 index 0000000..5a2ae37 --- /dev/null +++ b/dsl/camel-js-dsl/src/test/java/org/apache/camel/dsl/js/JavaScriptRoutesBuilderLoaderTest.java @@ -0,0 +1,125 @@ +/* + * 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.dsl.js; + +import org.apache.camel.component.seda.SedaComponent; +import org.apache.camel.impl.DefaultCamelContext; +import org.apache.camel.model.FromDefinition; +import org.apache.camel.model.ToDefinition; +import org.apache.camel.model.TransformDefinition; +import org.apache.camel.spi.Resource; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.assertj.core.api.Assertions.assertThat; + +public class JavaScriptRoutesBuilderLoaderTest { + @ParameterizedTest + @ValueSource(strings = { + "/routes/routes.js", + "/routes/routes-with-endpoint-dsl.js", + }) + void routesCanBeLoaded(String location) throws Exception { + try (DefaultCamelContext context = new DefaultCamelContext()) { + Resource resource = context.getResourceLoader().resolveResource(location); + context.getRoutesLoader().loadRoutes(resource); + + assertThat(context.getRouteDefinitions()) + .hasSize(1) + .first() + .satisfies(rd -> { + assertThat(rd.getInput().getEndpointUri()).matches("timer:.*tick"); + assertThat(rd.getOutputs().get(0)).isInstanceOf(ToDefinition.class); + }); + } + } + + @Test + void componentsCanBeCustomized() throws Exception { + try (DefaultCamelContext context = new DefaultCamelContext()) { + Resource resource = context.getResourceLoader().resolveResource("/routes/routes-with-component-configuration.js"); + context.getRoutesLoader().loadRoutes(resource); + + assertThat(context.getComponent("seda", SedaComponent.class)).satisfies(c -> { + assertThat(c.getQueueSize()).isEqualTo(1234); + }); + } + } + + @Test + void contextCanBeCustomized() throws Exception { + try (DefaultCamelContext context = new DefaultCamelContext()) { + Resource resource = context.getResourceLoader().resolveResource("/routes/routes-with-context-configuration.js"); + context.getRoutesLoader().loadRoutes(resource); + + assertThat(context.isTypeConverterStatisticsEnabled()).isTrue(); + } + } + + @Test + void processorsCanBeCreated() throws Exception { + try (DefaultCamelContext context = new DefaultCamelContext()) { + Resource resource = context.getResourceLoader().resolveResource("/routes/routes-with-processors.js"); + context.getRoutesLoader().loadRoutes(resource); + + context.start(); + + assertThat(context.createFluentProducerTemplate().to("direct:arrow").request(String.class)) + .isEqualTo("arrow"); + assertThat(context.createFluentProducerTemplate().to("direct:wrapper").request(String.class)) + .isEqualTo("wrapper"); + assertThat(context.createFluentProducerTemplate().to("direct:function").request(String.class)) + .isEqualTo("function"); + } + } + + @Test + void restCanBeConfigured() throws Exception { + try (DefaultCamelContext context = new DefaultCamelContext()) { + Resource resource = context.getResourceLoader().resolveResource("/routes/routes-with-rest-configuration.js"); + context.getRoutesLoader().loadRoutes(resource); + + assertThat(context.getRestConfiguration()).satisfies(c -> { + assertThat(c.getComponent()).isEqualTo("undertow"); + assertThat(c.getPort()).isEqualTo(1234); + }); + } + } + + @Test + void restDslCanBeDefined() throws Exception { + try (DefaultCamelContext context = new DefaultCamelContext()) { + Resource resource = context.getResourceLoader().resolveResource("/routes/routes-with-rest-dsl.js"); + context.getRoutesLoader().loadRoutes(resource); + + assertThat(context.getRestDefinitions()).hasSize(1); + assertThat(context.getRouteDefinitions()).hasSize(1); + + assertThat(context.getRestDefinitions()).first().satisfies(d -> { + assertThat(d.getProduces()).isEqualTo("text/plain"); + assertThat(d.getVerbs()).first().satisfies(v -> { + assertThat(v.getUri()).isEqualTo("/say/hello"); + }); + }); + assertThat(context.getRouteDefinitions()).first().satisfies(d -> { + assertThat(d.getInput()).isInstanceOf(FromDefinition.class); + assertThat(d.getOutputs()).first().isInstanceOf(TransformDefinition.class); + }); + } + } +} diff --git a/dsl/camel-js-dsl/src/test/resources/log4j2-test.properties b/dsl/camel-js-dsl/src/test/resources/log4j2-test.properties new file mode 100644 index 0000000..10bac20 --- /dev/null +++ b/dsl/camel-js-dsl/src/test/resources/log4j2-test.properties @@ -0,0 +1,31 @@ +## --------------------------------------------------------------------------- +## 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. +## --------------------------------------------------------------------------- + +appender.file.type = File +appender.file.name = file +appender.file.fileName = target/camel-js-dsl-test.log +appender.file.layout.type = PatternLayout +appender.file.layout.pattern = %d [%-15.15t] %-5p %-30.30c{1} - %m%n + +appender.out.type = Console +appender.out.name = out +appender.out.layout.type = PatternLayout +appender.out.layout.pattern = [%30.30t] %-30.30c{1} %-5p %m%n + +rootLogger.level = INFO +rootLogger.appenderRef.file.ref = file +#rootLogger.appenderRef.out.ref = out diff --git a/dsl/camel-js-dsl/src/test/resources/routes/routes-with-component-configuration.js b/dsl/camel-js-dsl/src/test/resources/routes/routes-with-component-configuration.js new file mode 100644 index 0000000..efe5696 --- /dev/null +++ b/dsl/camel-js-dsl/src/test/resources/routes/routes-with-component-configuration.js @@ -0,0 +1,5 @@ + +const SedaType = Java.type("org.apache.camel.component.seda.SedaComponent"); + +s = context.getComponent('seda', SedaType) +s.setQueueSize(1234) \ No newline at end of file diff --git a/dsl/camel-js-dsl/src/test/resources/routes/routes-with-context-configuration.js b/dsl/camel-js-dsl/src/test/resources/routes/routes-with-context-configuration.js new file mode 100644 index 0000000..53817d7 --- /dev/null +++ b/dsl/camel-js-dsl/src/test/resources/routes/routes-with-context-configuration.js @@ -0,0 +1,18 @@ +/* + * 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. + */ + +context.setTypeConverterStatisticsEnabled(true) \ No newline at end of file diff --git a/dsl/camel-js-dsl/src/test/resources/routes/routes-with-endpoint-dsl.js b/dsl/camel-js-dsl/src/test/resources/routes/routes-with-endpoint-dsl.js new file mode 100644 index 0000000..fc8456d --- /dev/null +++ b/dsl/camel-js-dsl/src/test/resources/routes/routes-with-endpoint-dsl.js @@ -0,0 +1,22 @@ +/* + * 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. + */ + +f = timer("tick"); +t = log("info"); + +from(f) + .to(t); \ No newline at end of file diff --git a/dsl/camel-js-dsl/src/test/resources/routes/routes-with-processors.js b/dsl/camel-js-dsl/src/test/resources/routes/routes-with-processors.js new file mode 100644 index 0000000..a4fe408 --- /dev/null +++ b/dsl/camel-js-dsl/src/test/resources/routes/routes-with-processors.js @@ -0,0 +1,15 @@ +const Processor = Java.type("org.apache.camel.Processor"); +const p = Java.extend(Processor); +const f = new p(function(e) { e.getMessage().setBody('function') }); +const a = new p(e => { e.getMessage().setBody('arrow') }); +const w = processor(e => { e.getMessage().setBody('wrapper') }); + +from('direct:function') + .process(f); + +from('direct:arrow') + .process(a); + +from('direct:wrapper') + .process(w); + diff --git a/dsl/camel-js-dsl/src/test/resources/routes/routes-with-rest-configuration.js b/dsl/camel-js-dsl/src/test/resources/routes/routes-with-rest-configuration.js new file mode 100644 index 0000000..c54a31b --- /dev/null +++ b/dsl/camel-js-dsl/src/test/resources/routes/routes-with-rest-configuration.js @@ -0,0 +1,4 @@ + +c = restConfiguration() +c.setComponent('undertow') +c.setPort('1234') diff --git a/dsl/camel-js-dsl/src/test/resources/routes/routes-with-rest-dsl.js b/dsl/camel-js-dsl/src/test/resources/routes/routes-with-rest-dsl.js new file mode 100644 index 0000000..195fca6 --- /dev/null +++ b/dsl/camel-js-dsl/src/test/resources/routes/routes-with-rest-dsl.js @@ -0,0 +1,7 @@ + +rest('/') + .produces("text/plain") + .get('/say/hello') + .route() + .transform().constant("Hello World"); + diff --git a/dsl/camel-js-dsl/src/test/resources/routes/routes.js b/dsl/camel-js-dsl/src/test/resources/routes/routes.js new file mode 100644 index 0000000..ceac916 --- /dev/null +++ b/dsl/camel-js-dsl/src/test/resources/routes/routes.js @@ -0,0 +1,18 @@ +/* + * 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. + */ +from('timer:tick') + .to('log:info') \ No newline at end of file diff --git a/dsl/pom.xml b/dsl/pom.xml index 622cf3d..00e9a80 100644 --- a/dsl/pom.xml +++ b/dsl/pom.xml @@ -39,6 +39,7 @@ <module>camel-xml-jaxb-dsl</module> <module>camel-xml-jaxb-dsl-test</module> <module>camel-yaml-dsl</module> + <module>camel-js-dsl</module> </modules> <properties> diff --git a/parent/pom.xml b/parent/pom.xml index 9f67424..ed5a9de 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -220,6 +220,7 @@ <google-cloud-bom-version>16.3.0</google-cloud-bom-version> <google-cloud-guava-version>30.0-jre</google-cloud-guava-version> <google-mail-guava-version>17.0</google-mail-guava-version> + <graaljs-version>21.0.0.2</graaljs-version> <graphql-java-version>14.0</graphql-java-version> <grizzly-websockets-version>2.3.25</grizzly-websockets-version> <grpc-version>1.28.1</grpc-version> @@ -2702,6 +2703,11 @@ <artifactId>camel-yaml-dsl</artifactId> <version>${project.version}</version> </dependency> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-js-dsl</artifactId> + <version>${project.version}</version> + </dependency> <!-- camel catalog --> <dependency>