This is an automated email from the ASF dual-hosted git repository. nferraro pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/camel-k.git
The following commit(s) were added to refs/heads/master by this push: new 6d9d529 runtime: support for kotlin runtime 6d9d529 is described below commit 6d9d529bb4257036bad924be72f9e7f43e7cf218 Author: lburgazzoli <lburgazz...@gmail.com> AuthorDate: Tue Sep 25 10:32:40 2018 +0200 runtime: support for kotlin runtime --- README.adoc | 1 + deploy/platform-integration-context-groovy.yaml | 5 +- ....yaml => platform-integration-context-jvm.yaml} | 5 +- ...ml => platform-integration-context-kotlin.yaml} | 6 +- deploy/resources.go | 31 ++++- pkg/apis/camel/v1alpha1/types.go | 2 + pkg/build/assemble/maven_assembler.go | 4 + pkg/client/cmd/completion_bash.go | 22 +++- pkg/client/cmd/context_create.go | 11 ++ pkg/client/cmd/run.go | 23 +++- pkg/discover/dependencies.go | 7 +- pkg/discover/language.go | 6 +- pkg/install/operator.go | 2 +- pkg/stub/action/integration/initialize.go | 9 +- pkg/stub/action/platform/create.go | 8 +- pkg/util/util.go | 13 +++ runtime/examples/kotlin-routes.kts | 20 ++++ runtime/groovy/.gitignore | 10 ++ runtime/groovy/pom.xml | 45 ++++++++ .../apache/camel/k/groovy/GroovyRoutesLoader.java | 63 ++++++++++ .../services/org.apache.camel.k.jvm.RoutesLoader | 1 + .../apache/camel/k/groovy/RoutesLoadersTest.java | 47 ++++++++ .../src/test/resources/routes.groovy | 0 runtime/jvm/pom.xml | 24 +++- .../main/java/org/apache/camel/k/jvm/Language.java | 44 ++++--- .../java/org/apache/camel/k/jvm/RoutesLoaders.java | 127 ++++----------------- .../org/apache/camel/k/jvm/dsl/Components.java | 47 ++++++++ .../java/org/apache/camel/k/jvm/dsl/Scripting.java | 47 ++++++++ .../services/org.apache.camel.k.jvm.RoutesLoader | 4 + .../org/apache/camel/k/jvm/RoutesLoadersTest.java | 27 +---- runtime/kotlin/.gitignore | 10 ++ runtime/kotlin/pom.xml | 56 +++++++++ .../apache/camel/k/kotlin/KotlinRoutesLoader.java | 77 +++++++++++++ .../services/javax.script.ScriptEngineFactory | 1 + .../services/org.apache.camel.k.jvm.RoutesLoader | 1 + .../apache/camel/k/kotlin/RoutesLoadersTest.java | 50 ++++++++ runtime/kotlin/src/test/resources/routes.kts | 21 ++++ runtime/pom.xml | 3 + 38 files changed, 702 insertions(+), 178 deletions(-) diff --git a/README.adoc b/README.adoc index 24d0779..fadd6d1 100644 --- a/README.adoc +++ b/README.adoc @@ -114,6 +114,7 @@ Camel K supports multiple languages for writing integrations: | XML | Integrations written in plain XML DSL are supported (Spring XML or Blueprint not supported). | Groovy | Groovy `.groovy` files are supported (experimental). | JavaScript | JavaScript `.js` files are supported (experimental). +| Kotlin | Kotlin Script `.kts` files are supported (experimental). |======================= Integrations written in different languages are provided in the link:/runtime/examples[examples] directory. diff --git a/deploy/platform-integration-context-groovy.yaml b/deploy/platform-integration-context-groovy.yaml index cabe24a..31aaeff 100644 --- a/deploy/platform-integration-context-groovy.yaml +++ b/deploy/platform-integration-context-groovy.yaml @@ -9,5 +9,6 @@ metadata: camel.apache.org/context.type: platform spec: dependencies: - - camel:core - - camel:groovy \ No newline at end of file + - runtime:jvm + - runtime:groovy + - camel:core \ No newline at end of file diff --git a/deploy/platform-integration-context-core.yaml b/deploy/platform-integration-context-jvm.yaml similarity index 69% copy from deploy/platform-integration-context-core.yaml copy to deploy/platform-integration-context-jvm.yaml index d0d3289..28e8ff5 100644 --- a/deploy/platform-integration-context-core.yaml +++ b/deploy/platform-integration-context-jvm.yaml @@ -1,12 +1,13 @@ apiVersion: camel.apache.org/v1alpha1 kind: IntegrationContext metadata: - name: core + name: jvm labels: app: "camel-k" camel.apache.org/context.created.by.kind: Operator - camel.apache.org/context.created.by.name: core + camel.apache.org/context.created.by.name: jvm camel.apache.org/context.type: platform spec: dependencies: + - runtime:jvm - camel:core \ No newline at end of file diff --git a/deploy/platform-integration-context-core.yaml b/deploy/platform-integration-context-kotlin.yaml similarity index 65% rename from deploy/platform-integration-context-core.yaml rename to deploy/platform-integration-context-kotlin.yaml index d0d3289..822b4a4 100644 --- a/deploy/platform-integration-context-core.yaml +++ b/deploy/platform-integration-context-kotlin.yaml @@ -1,12 +1,14 @@ apiVersion: camel.apache.org/v1alpha1 kind: IntegrationContext metadata: - name: core + name: kotlin labels: app: "camel-k" camel.apache.org/context.created.by.kind: Operator - camel.apache.org/context.created.by.name: core + camel.apache.org/context.created.by.name: jvm camel.apache.org/context.type: platform spec: dependencies: + - runtime:jvm + - runtime:kotlin - camel:core \ No newline at end of file diff --git a/deploy/resources.go b/deploy/resources.go index 7749be3..a3274cb 100644 --- a/deploy/resources.go +++ b/deploy/resources.go @@ -2467,12 +2467,12 @@ metadata: app: "camel-k" ` - Resources["platform-integration-context-core.yaml"] = + Resources["platform-integration-context-groovy.yaml"] = ` apiVersion: camel.apache.org/v1alpha1 kind: IntegrationContext metadata: - name: core + name: groovy labels: app: "camel-k" camel.apache.org/context.created.by.kind: Operator @@ -2480,23 +2480,42 @@ metadata: camel.apache.org/context.type: platform spec: dependencies: + - runtime:jvm + - runtime:groovy - camel:core ` - Resources["platform-integration-context-groovy.yaml"] = + Resources["platform-integration-context-jvm.yaml"] = ` apiVersion: camel.apache.org/v1alpha1 kind: IntegrationContext metadata: - name: groovy + name: jvm labels: app: "camel-k" camel.apache.org/context.created.by.kind: Operator - camel.apache.org/context.created.by.name: core + camel.apache.org/context.created.by.name: jvm camel.apache.org/context.type: platform spec: dependencies: + - runtime:jvm + - camel:core +` + Resources["platform-integration-context-kotlin.yaml"] = + ` +apiVersion: camel.apache.org/v1alpha1 +kind: IntegrationContext +metadata: + name: kotlin + labels: + app: "camel-k" + camel.apache.org/context.created.by.kind: Operator + camel.apache.org/context.created.by.name: jvm + camel.apache.org/context.type: platform +spec: + dependencies: + - runtime:jvm + - runtime:kotlin - camel:core - - camel:groovy ` Resources["user-cluster-role.yaml"] = ` diff --git a/pkg/apis/camel/v1alpha1/types.go b/pkg/apis/camel/v1alpha1/types.go index fa27617..114942f 100644 --- a/pkg/apis/camel/v1alpha1/types.go +++ b/pkg/apis/camel/v1alpha1/types.go @@ -77,6 +77,8 @@ const ( LanguageJavaScript Language = "js" // LanguageXML -- LanguageXML Language = "xml" + // LanguageKotlin -- + LanguageKotlin Language = "kts" ) // IntegrationStatus -- diff --git a/pkg/build/assemble/maven_assembler.go b/pkg/build/assemble/maven_assembler.go index 9094181..9b2674e 100644 --- a/pkg/build/assemble/maven_assembler.go +++ b/pkg/build/assemble/maven_assembler.go @@ -162,6 +162,10 @@ func generateProject(source *build.Request) (maven.Project, error) { gav := strings.Replace(mid, "/", ":", -1) deps.AddEncodedGAV(gav) + } else if strings.HasPrefix(d, "runtime:") { + artifactID := strings.Replace(d, "runtime:", "camel-k-runtime-", 1) + + deps.AddGAV("org.apache.camel.k", artifactID, version.Version) } else { return maven.Project{}, fmt.Errorf("unknown dependency type: %s", d) } diff --git a/pkg/client/cmd/completion_bash.go b/pkg/client/cmd/completion_bash.go index c689f8e..513a821 100644 --- a/pkg/client/cmd/completion_bash.go +++ b/pkg/client/cmd/completion_bash.go @@ -50,24 +50,29 @@ __kamel_dependency_type() { COMPREPLY=( $( compgen -W "${type_list}" -- "$cur") ) ;; m*) - local type_list="mvn:"" + local type_list="mvn:" COMPREPLY=( $( compgen -W "${type_list}" -- "$cur") ) compopt -o nospace ;; f*) - local type_list="file:"" + local type_list="file:" COMPREPLY=( $( compgen -W "${type_list}" -- "$cur") ) compopt -o nospace ;; *) - local type_list="camel: mvn: file:" - COMPREPLY=( $( compgen -W "camel mvn: file:" -- "$cur") ) + local type_list="camel mvn: file:" + COMPREPLY=( $( compgen -W "${type_list}" -- "$cur") ) compopt -o nospace esac } __kamel_languages() { - local type_list="js groovy java xml" + local type_list="js groovy kotlin java xml" + COMPREPLY=( $( compgen -W "${type_list}" -- "$cur") ) +} + +__kamel_runtimes() { + local type_list="jvm groovy kotlin" COMPREPLY=( $( compgen -W "${type_list}" -- "$cur") ) } @@ -195,6 +200,13 @@ func configureKnownBashCompletions(command *cobra.Command) { cobra.BashCompCustom: {"__kamel_languages"}, }, ) + configureBashAnnotationForFlag( + command, + "runtime", + map[string][]string{ + cobra.BashCompCustom: {"__kamel_runtimes"}, + }, + ) } func configureBashAnnotationForFlag(command *cobra.Command, flagName string, annotations map[string][]string) { diff --git a/pkg/client/cmd/context_create.go b/pkg/client/cmd/context_create.go index e95ae73..739fc8d 100644 --- a/pkg/client/cmd/context_create.go +++ b/pkg/client/cmd/context_create.go @@ -23,6 +23,8 @@ import ( "strconv" "strings" + "github.com/apache/camel-k/pkg/util" + "github.com/operator-framework/operator-sdk/pkg/sdk" "github.com/apache/camel-k/pkg/apis/camel/v1alpha1" @@ -46,6 +48,7 @@ func newContextCreateCmd(rootCmdOptions *RootCmdOptions) *cobra.Command { RunE: impl.run, } + cmd.Flags().StringVarP(&impl.runtime, "runtime", "r", "jvm", "Runtime provided by the context") cmd.Flags().StringSliceVarP(&impl.dependencies, "dependency", "d", nil, "Add a dependency") cmd.Flags().StringSliceVarP(&impl.properties, "property", "p", nil, "Add a camel property") cmd.Flags().StringSliceVar(&impl.configmaps, "configmap", nil, "Add a ConfigMap") @@ -60,6 +63,7 @@ func newContextCreateCmd(rootCmdOptions *RootCmdOptions) *cobra.Command { type contextCreateCommand struct { *RootCmdOptions + runtime string dependencies []string properties []string configmaps []string @@ -105,6 +109,13 @@ func (command *contextCreateCommand) run(cmd *cobra.Command, args []string) erro } } + // jvm runtime required by default + util.StringSliceUniqueAdd(&ctx.Spec.Dependencies, "runtime:jvm") + + if command.runtime != "" { + util.StringSliceUniqueAdd(&ctx.Spec.Dependencies, "runtime:"+command.runtime) + } + for _, item := range command.properties { ctx.Spec.Configuration = append(ctx.Spec.Configuration, v1alpha1.ConfigurationSpec{ Type: "property", diff --git a/pkg/client/cmd/run.go b/pkg/client/cmd/run.go index ca4e52f..b9f40eb 100644 --- a/pkg/client/cmd/run.go +++ b/pkg/client/cmd/run.go @@ -56,6 +56,7 @@ func newCmdRun(rootCmdOptions *RootCmdOptions) *cobra.Command { } cmd.Flags().StringVarP(&options.Language, "language", "l", "", "Programming Language used to write the file") + cmd.Flags().StringVarP(&options.Runtime, "runtime", "r", "jvm", "Runtime used by the integration") cmd.Flags().StringVar(&options.IntegrationName, "name", "", "The integration name") cmd.Flags().StringSliceVarP(&options.Dependencies, "dependency", "d", nil, "The integration dependency") cmd.Flags().BoolVarP(&options.Wait, "wait", "w", false, "Waits for the integration to be running") @@ -78,6 +79,7 @@ type runCmdOptions struct { *RootCmdOptions IntegrationContext string Language string + Runtime string IntegrationName string Dependencies []string Properties []string @@ -269,13 +271,22 @@ func (o *runCmdOptions) updateIntegrationCode(filename string) (*v1alpha1.Integr } } - // special handling for groovy - // TODO: we should define handlers for languages and/or file extensions - if o.Language == "groovy" && !util.StringSliceExists(o.Dependencies, "camel:groovy") { - integration.Spec.Dependencies = append(integration.Spec.Dependencies, "camel:groovy") + if o.Language == "groovy" || strings.HasSuffix(filename, ".groovy") { + util.StringSliceUniqueAdd(&integration.Spec.Dependencies, "runtime:groovy") } - if o.Language == "" && strings.HasSuffix(filename, ".groovy") { - integration.Spec.Dependencies = append(integration.Spec.Dependencies, "camel:groovy") + if o.Language == "kotlin" || strings.HasSuffix(filename, ".kts") { + util.StringSliceUniqueAdd(&integration.Spec.Dependencies, "runtime:kotlin") + } + + // jvm runtime required by default + util.StringSliceUniqueAdd(&integration.Spec.Dependencies, "runtime:jvm") + + if o.Runtime != "" { + util.StringSliceUniqueAdd(&integration.Spec.Dependencies, "runtime:"+o.Runtime) + } + + switch o.Runtime { + } for _, item := range o.Properties { diff --git a/pkg/discover/dependencies.go b/pkg/discover/dependencies.go index 82ef757..b6daa63 100644 --- a/pkg/discover/dependencies.go +++ b/pkg/discover/dependencies.go @@ -18,11 +18,12 @@ limitations under the License. package discover import ( - "github.com/apache/camel-k/pkg/apis/camel/v1alpha1" - "github.com/apache/camel-k/pkg/util/camel" "regexp" "sort" "strings" + + "github.com/apache/camel-k/pkg/apis/camel/v1alpha1" + "github.com/apache/camel-k/pkg/util/camel" ) var ( @@ -65,6 +66,8 @@ func getRegexpsForLanguage(language v1alpha1.Language) []*regexp.Regexp { return []*regexp.Regexp{singleQuotedURI, doubleQuotedURI} case v1alpha1.LanguageJavaScript: return []*regexp.Regexp{singleQuotedURI, doubleQuotedURI} + case v1alpha1.LanguageKotlin: + return []*regexp.Regexp{doubleQuotedURI} } return []*regexp.Regexp{} } diff --git a/pkg/discover/language.go b/pkg/discover/language.go index e38d583..cafb6c3 100644 --- a/pkg/discover/language.go +++ b/pkg/discover/language.go @@ -19,8 +19,9 @@ limitations under the License. package discover import ( - "github.com/apache/camel-k/pkg/apis/camel/v1alpha1" "strings" + + "github.com/apache/camel-k/pkg/apis/camel/v1alpha1" ) // Language discovers the code language from file extension if not set @@ -33,7 +34,8 @@ func Language(source v1alpha1.SourceSpec) v1alpha1.Language { v1alpha1.LanguageJavaClass, v1alpha1.LanguageJavaScript, v1alpha1.LanguageGroovy, - v1alpha1.LanguageJavaScript} { + v1alpha1.LanguageJavaScript, + v1alpha1.LanguageKotlin} { if strings.HasSuffix(source.Name, "."+string(l)) { return l diff --git a/pkg/install/operator.go b/pkg/install/operator.go index 0f6039c..eb3ac4c 100644 --- a/pkg/install/operator.go +++ b/pkg/install/operator.go @@ -30,7 +30,7 @@ func Operator(namespace string) error { // Platform installs the platform custom resource func Platform(namespace string) error { - return Resource(namespace,"platform-cr.yaml") + return Resource(namespace, "platform-cr.yaml") } // Example -- diff --git a/pkg/stub/action/integration/initialize.go b/pkg/stub/action/integration/initialize.go index d9ab6c0..f0b5e7f 100644 --- a/pkg/stub/action/integration/initialize.go +++ b/pkg/stub/action/integration/initialize.go @@ -22,6 +22,8 @@ import ( "github.com/sirupsen/logrus" "sort" + "github.com/apache/camel-k/pkg/util" + "github.com/apache/camel-k/pkg/apis/camel/v1alpha1" "github.com/apache/camel-k/pkg/discover" "github.com/apache/camel-k/pkg/util/digest" @@ -46,7 +48,7 @@ func (action *initializeAction) CanHandle(integration *v1alpha1.Integration) boo return integration.Status.Phase == "" } -// Handle handles the integratios +// Handle handles the integrations func (action *initializeAction) Handle(integration *v1alpha1.Integration) error { // The integration platform needs to be ready before starting to create integrations if pl, err := platform.GetCurrentPlatform(integration.Namespace); err != nil || pl.Status.Phase != v1alpha1.IntegrationPlatformPhaseReady { @@ -63,6 +65,11 @@ func (action *initializeAction) Handle(integration *v1alpha1.Integration) error // set the correct language language := discover.Language(target.Spec.Source) target.Spec.Source.Language = language + + if !util.StringSliceExists(target.Spec.Dependencies, "camel:core") { + target.Spec.Dependencies = append(target.Spec.Dependencies, "camel:core") + } + // discover dependencies if target.Spec.DependenciesAutoDiscovery == nil { var autoDiscoveryDependencies = true diff --git a/pkg/stub/action/platform/create.go b/pkg/stub/action/platform/create.go index c313ccf..0933e2f 100644 --- a/pkg/stub/action/platform/create.go +++ b/pkg/stub/action/platform/create.go @@ -24,6 +24,12 @@ import ( "github.com/sirupsen/logrus" ) +var resources = []string{ + "platform-integration-context-jvm.yaml", + "platform-integration-context-groovy.yaml", + "platform-integration-context-kotlin.yaml", +} + // NewCreateAction returns a action that creates resources needed by the platform func NewCreateAction() Action { return &createAction{} @@ -41,7 +47,7 @@ func (action *createAction) CanHandle(platform *v1alpha1.IntegrationPlatform) bo } func (action *createAction) Handle(platform *v1alpha1.IntegrationPlatform) error { - err := install.Resources(platform.Namespace, "platform-integration-context-core.yaml", "platform-integration-context-groovy.yaml") + err := install.Resources(platform.Namespace, resources...) if err != nil { return err } diff --git a/pkg/util/util.go b/pkg/util/util.go index c2a2631..7792210 100644 --- a/pkg/util/util.go +++ b/pkg/util/util.go @@ -38,3 +38,16 @@ func StringSliceExists(slice []string, item string) bool { return false } + +// StringSliceUniqueAdd append the given item if not already present in the slice +func StringSliceUniqueAdd(slice *[]string, item string) bool { + for _, i := range *slice { + if i == item { + return false + } + } + + *slice = append(*slice, item) + + return true +} diff --git a/runtime/examples/kotlin-routes.kts b/runtime/examples/kotlin-routes.kts new file mode 100644 index 0000000..8c49d44 --- /dev/null +++ b/runtime/examples/kotlin-routes.kts @@ -0,0 +1,20 @@ +// +// To run this integrations use: +// +// kamel run --runtime kotlin runtime/examples/kotlin-routes.kts +// +// Or leveraging runtime detection +// +// kamel run runtime/examples/kotlin-routes.kts +// + +val rnd = java.util.Random() + +from("timer:kotlin?period=1s") + .routeId("kotlin") + .setBody() + .constant("Hello Camel K!") + .process().message { + m -> m.headers["RandomValue"] = rnd.nextInt() + } + .to("log:info?showHeaders=true") \ No newline at end of file diff --git a/runtime/groovy/.gitignore b/runtime/groovy/.gitignore new file mode 100644 index 0000000..ed92983 --- /dev/null +++ b/runtime/groovy/.gitignore @@ -0,0 +1,10 @@ +target + +*.iml + +.idea +.project +.metadata +.settings +.factorypath +.classpath diff --git a/runtime/groovy/pom.xml b/runtime/groovy/pom.xml new file mode 100644 index 0000000..a3fb478 --- /dev/null +++ b/runtime/groovy/pom.xml @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="UTF-8"?> +<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/xsd/maven-4.0.0.xsd"> + <parent> + <groupId>org.apache.camel.k</groupId> + <artifactId>camel-k-runtime-parent</artifactId> + <version>0.0.3-SNAPSHOT</version> + </parent> + <modelVersion>4.0.0</modelVersion> + + <artifactId>camel-k-runtime-groovy</artifactId> + + <dependencies> + <dependency> + <groupId>org.apache.camel.k</groupId> + <artifactId>camel-k-runtime-jvm</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-groovy</artifactId> + </dependency> + + <!-- ****************************** --> + <!-- --> + <!-- TESTS --> + <!-- --> + <!-- ****************************** --> + + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <version>${junit.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.assertj</groupId> + <artifactId>assertj-core</artifactId> + <version>${assertj.version}</version> + <scope>test</scope> + </dependency> + </dependencies> + +</project> diff --git a/runtime/groovy/src/main/java/org/apache/camel/k/groovy/GroovyRoutesLoader.java b/runtime/groovy/src/main/java/org/apache/camel/k/groovy/GroovyRoutesLoader.java new file mode 100644 index 0000000..ee8a512 --- /dev/null +++ b/runtime/groovy/src/main/java/org/apache/camel/k/groovy/GroovyRoutesLoader.java @@ -0,0 +1,63 @@ +/** + * 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.k.groovy; + +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.util.Collections; +import java.util.List; + +import groovy.lang.Binding; +import groovy.lang.GroovyShell; +import groovy.util.DelegatingScript; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.k.jvm.Language; +import org.apache.camel.k.jvm.RoutesLoader; +import org.apache.camel.k.jvm.dsl.Scripting; +import org.apache.camel.util.ResourceHelper; +import org.codehaus.groovy.control.CompilerConfiguration; + +public class GroovyRoutesLoader implements RoutesLoader { + @Override + public List<Language> getSupportedLanguages() { + return Collections.singletonList(Language.Groovy); + } + + @Override + public RouteBuilder load(String resource) throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + CompilerConfiguration cc = new CompilerConfiguration(); + cc.setScriptBaseClass(DelegatingScript.class.getName()); + + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + GroovyShell sh = new GroovyShell(cl, new Binding(), cc); + + try (InputStream is = ResourceHelper.resolveMandatoryResourceAsInputStream(getContext(), resource)) { + Reader reader = new InputStreamReader(is); + DelegatingScript script = (DelegatingScript) sh.parse(reader); + + // set the delegate target + script.setDelegate(new Scripting(this)); + script.run(); + } + } + }; + } +} diff --git a/runtime/groovy/src/main/resources/META-INF/services/org.apache.camel.k.jvm.RoutesLoader b/runtime/groovy/src/main/resources/META-INF/services/org.apache.camel.k.jvm.RoutesLoader new file mode 100644 index 0000000..db214e0 --- /dev/null +++ b/runtime/groovy/src/main/resources/META-INF/services/org.apache.camel.k.jvm.RoutesLoader @@ -0,0 +1 @@ +org.apache.camel.k.groovy.GroovyRoutesLoader \ No newline at end of file diff --git a/runtime/groovy/src/test/java/org/apache/camel/k/groovy/RoutesLoadersTest.java b/runtime/groovy/src/test/java/org/apache/camel/k/groovy/RoutesLoadersTest.java new file mode 100644 index 0000000..8a64faa --- /dev/null +++ b/runtime/groovy/src/test/java/org/apache/camel/k/groovy/RoutesLoadersTest.java @@ -0,0 +1,47 @@ +/** + * 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.k.groovy; + +import java.util.List; + +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.k.jvm.RoutesLoader; +import org.apache.camel.k.jvm.RoutesLoaders; +import org.apache.camel.model.RouteDefinition; +import org.apache.camel.model.ToDefinition; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class RoutesLoadersTest { + @Test + public void testLoadGroovy() throws Exception { + String resource = "classpath:routes.groovy"; + RoutesLoader loader = RoutesLoaders.loaderFor(resource, null); + RouteBuilder builder = loader.load(resource); + + assertThat(loader).isInstanceOf(GroovyRoutesLoader.class); + assertThat(builder).isNotNull(); + + builder.configure(); + + List<RouteDefinition> routes = builder.getRouteCollection().getRoutes(); + assertThat(routes).hasSize(1); + assertThat(routes.get(0).getInputs().get(0).getEndpointUri()).isEqualTo("timer:tick"); + assertThat(routes.get(0).getOutputs().get(0)).isInstanceOf(ToDefinition.class); + } +} diff --git a/runtime/jvm/src/test/resources/routes.groovy b/runtime/groovy/src/test/resources/routes.groovy similarity index 100% rename from runtime/jvm/src/test/resources/routes.groovy rename to runtime/groovy/src/test/resources/routes.groovy diff --git a/runtime/jvm/pom.xml b/runtime/jvm/pom.xml index 2ea58ff..9dc3918 100644 --- a/runtime/jvm/pom.xml +++ b/runtime/jvm/pom.xml @@ -11,7 +11,18 @@ <artifactId>camel-k-runtime-jvm</artifactId> + <properties> + <kotlin.version>1.2.71</kotlin.version> + </properties> + <dependencies> + + <!-- ****************************** --> + <!-- --> + <!-- RUNTIME --> + <!-- --> + <!-- ****************************** --> + <dependency> <groupId>org.apache.camel</groupId> <artifactId>camel-core</artifactId> @@ -25,6 +36,7 @@ <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-slf4j-impl</artifactId> <version>${log4j2.version}</version> + <scope>runtime</scope> </dependency> <dependency> <groupId>org.jooq</groupId> @@ -41,12 +53,12 @@ <artifactId>commons-lang3</artifactId> <version>${commons-lang.version}</version> </dependency> - <dependency> - <groupId>org.codehaus.groovy</groupId> - <artifactId>groovy</artifactId> - <version>${groovy.version}</version> - <optional>true</optional> - </dependency> + + <!-- ****************************** --> + <!-- --> + <!-- TESTS --> + <!-- --> + <!-- ****************************** --> <dependency> <groupId>junit</groupId> diff --git a/runtime/jvm/src/main/java/org/apache/camel/k/jvm/Language.java b/runtime/jvm/src/main/java/org/apache/camel/k/jvm/Language.java index a2a8377..d041faa 100644 --- a/runtime/jvm/src/main/java/org/apache/camel/k/jvm/Language.java +++ b/runtime/jvm/src/main/java/org/apache/camel/k/jvm/Language.java @@ -16,29 +16,45 @@ */ package org.apache.camel.k.jvm; +import java.util.Arrays; import java.util.Collections; import java.util.List; import org.apache.commons.lang3.StringUtils; public enum Language { - Unknow("", Collections.emptyList()), - JavaClass("class", Collections.singletonList("class")), - JavaSource("java", Collections.singletonList("java")), - JavaScript("js", Collections.singletonList("js")), - Groovy("groovy", Collections.singletonList("groovy")), - Xml("xml", Collections.singletonList("xml")); + Unknown( + Collections.emptyList(), + Collections.emptyList()), + JavaClass( + Collections.singletonList("class"), + Collections.singletonList("class")), + JavaSource( + Collections.singletonList("java"), + Collections.singletonList("java")), + JavaScript( + Arrays.asList("js", "javascript"), + Collections.singletonList("js")), + Groovy( + Collections.singletonList("groovy"), + Collections.singletonList("groovy")), + Xml( + Collections.singletonList("xml"), + Collections.singletonList("xml")), + Kotlin( + Arrays.asList("kotlin", "kts"), + Collections.singletonList("kts")); - private final String name; + private final List<String> names; private final List<String> extensions; - Language(String name, List<String> extensions) { - this.name = name; + Language(List<String> names, List<String> extensions) { + this.names = names; this.extensions = extensions; } - public String getName() { - return name; + public List<String> getNames() { + return names; } public List<String> getExtensions() { @@ -47,12 +63,12 @@ public enum Language { public static Language fromLanguageName(String name) { for (Language language: values()) { - if (language.getName().equals(name)) { + if (language.getNames().contains(name)) { return language; } } - return Unknow; + return Unknown; } public static Language fromResource(String resource) { @@ -66,6 +82,6 @@ public enum Language { } } - return Unknow; + return Unknown; } } diff --git a/runtime/jvm/src/main/java/org/apache/camel/k/jvm/RoutesLoaders.java b/runtime/jvm/src/main/java/org/apache/camel/k/jvm/RoutesLoaders.java index 448c2bb..2581d48 100644 --- a/runtime/jvm/src/main/java/org/apache/camel/k/jvm/RoutesLoaders.java +++ b/runtime/jvm/src/main/java/org/apache/camel/k/jvm/RoutesLoaders.java @@ -18,10 +18,10 @@ package org.apache.camel.k.jvm; import java.io.InputStream; import java.io.InputStreamReader; -import java.io.Reader; import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.List; +import java.util.ServiceLoader; import java.util.function.Function; import java.util.function.Supplier; import javax.script.Bindings; @@ -30,12 +30,9 @@ import javax.script.ScriptEngineManager; import javax.script.SimpleBindings; import javax.xml.bind.UnmarshalException; -import groovy.lang.Binding; -import groovy.lang.GroovyShell; -import groovy.util.DelegatingScript; import org.apache.camel.CamelContext; -import org.apache.camel.Component; import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.k.jvm.dsl.Components; import org.apache.camel.model.RouteDefinition; import org.apache.camel.model.rest.RestConfigurationDefinition; import org.apache.camel.model.rest.RestDefinition; @@ -43,7 +40,6 @@ import org.apache.camel.util.ObjectHelper; import org.apache.camel.util.ResourceHelper; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; -import org.codehaus.groovy.control.CompilerConfiguration; import org.joor.Reflect; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -51,8 +47,13 @@ import org.slf4j.LoggerFactory; import static org.apache.camel.k.jvm.Constants.SCHEME_CLASSPATH; import static org.apache.camel.k.jvm.Constants.SCHEME_FILE; -public enum RoutesLoaders implements RoutesLoader { - JavaClass { +public final class RoutesLoaders { + private static final Logger LOGGER = LoggerFactory.getLogger(RoutesLoaders.class); + + private RoutesLoaders() { + } + + public static class JavaClass implements RoutesLoader { @Override public List<Language> getSupportedLanguages() { return Collections.singletonList(Language.JavaClass); @@ -72,8 +73,9 @@ public enum RoutesLoaders implements RoutesLoader { return (RouteBuilder)type.newInstance(); } - }, - JavaSource { + } + + public static class JavaSource implements RoutesLoader { @Override public List<Language> getSupportedLanguages() { return Collections.singletonList(Language.JavaSource); @@ -93,15 +95,16 @@ public enum RoutesLoaders implements RoutesLoader { } // Wrap routes builder - includeRoutes( + addRoutes( Reflect.compile(name, IOUtils.toString(is, StandardCharsets.UTF_8)).create().get() ); } } }; } - }, - JavaScript { + } + + public static class JavaScript implements RoutesLoader { @Override public List<Language> getSupportedLanguages() { return Collections.singletonList(Language.JavaScript); @@ -119,6 +122,7 @@ public enum RoutesLoaders implements RoutesLoader { // Exposed to the underlying script, but maybe better to have // a nice dsl + bindings.put("builder", this); bindings.put("context", context); bindings.put("components", new Components(context)); bindings.put("from", (Function<String, RouteDefinition>) uri -> from(uri)); @@ -131,37 +135,9 @@ public enum RoutesLoaders implements RoutesLoader { } }; } - }, - Groovy { - @Override - public List<Language> getSupportedLanguages() { - return Collections.singletonList(Language.Groovy); - } - - @Override - public RouteBuilder load(String resource) throws Exception { - return new RouteBuilder() { - @Override - public void configure() throws Exception { - CompilerConfiguration cc = new CompilerConfiguration(); - cc.setScriptBaseClass(DelegatingScript.class.getName()); - - ClassLoader cl = Thread.currentThread().getContextClassLoader(); - GroovyShell sh = new GroovyShell(cl, new Binding(), cc); - - try (InputStream is = ResourceHelper.resolveMandatoryResourceAsInputStream(getContext(), resource)) { - Reader reader = new InputStreamReader(is); - DelegatingScript script = (DelegatingScript) sh.parse(reader); + } - // set the delegate target - script.setDelegate(new ScriptingDsl(this)); - script.run(); - } - } - }; - } - }, - Xml { + public static class Xml implements RoutesLoader { @Override public List<Language> getSupportedLanguages() { return Collections.singletonList(Language.Xml); @@ -192,9 +168,8 @@ public enum RoutesLoaders implements RoutesLoader { } }; } - }; + } - private static final Logger LOGGER = LoggerFactory.getLogger(RoutesLoaders.class); public static RoutesLoader loaderFor(String resource, String languageName) { if (!resource.startsWith(SCHEME_CLASSPATH) && !resource.startsWith(SCHEME_FILE)) { @@ -205,7 +180,7 @@ public enum RoutesLoaders implements RoutesLoader { ? Language.fromLanguageName(languageName) : Language.fromResource(resource); - for (RoutesLoader loader: RoutesLoaders.values()) { + for (RoutesLoader loader: ServiceLoader.load(RoutesLoader.class)) { if (loader.getSupportedLanguages().contains(language)) { return loader; } @@ -213,64 +188,4 @@ public enum RoutesLoaders implements RoutesLoader { throw new IllegalArgumentException("Unable to find loader for: resource=" + resource + " language=" + languageName); } - - // ******************************** - // - // Helpers - // - // TODO: move to a dedicate class - // ******************************** - - - public static class Components { - private CamelContext context; - - public Components(CamelContext context) { - this.context = context; - } - - public Component get(String scheme) { - return context.getComponent(scheme, true); - } - - public Component put(String scheme, Component instance) { - context.addComponent(scheme, instance); - - return instance; - } - - public Component make(String scheme, String type) { - final Class<?> clazz = context.getClassResolver().resolveClass(type); - final Component instance = (Component)context.getInjector().newInstance(clazz); - - context.addComponent(scheme, instance); - - return instance; - } - } - - private static class ScriptingDsl { - private final RouteBuilder builder; - - public final CamelContext context; - public final Components components; - - public ScriptingDsl(RouteBuilder builder) { - this.builder = builder; - this.context = builder.getContext(); - this.components = new Components(builder.getContext()); - } - - public RouteDefinition from(String endpoint) { - return builder.from(endpoint); - } - - public RestDefinition rest() { - return builder.rest(); - } - - public RestConfigurationDefinition restConfiguration() { - return builder.restConfiguration(); - } - } } diff --git a/runtime/jvm/src/main/java/org/apache/camel/k/jvm/dsl/Components.java b/runtime/jvm/src/main/java/org/apache/camel/k/jvm/dsl/Components.java new file mode 100644 index 0000000..bac1390 --- /dev/null +++ b/runtime/jvm/src/main/java/org/apache/camel/k/jvm/dsl/Components.java @@ -0,0 +1,47 @@ +/** + * 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.k.jvm.dsl; + +import org.apache.camel.CamelContext; +import org.apache.camel.Component; + +public class Components { + private CamelContext context; + + public Components(CamelContext context) { + this.context = context; + } + + public Component get(String scheme) { + return context.getComponent(scheme, true); + } + + public Component put(String scheme, Component instance) { + context.addComponent(scheme, instance); + + return instance; + } + + public Component make(String scheme, String type) { + final Class<?> clazz = context.getClassResolver().resolveClass(type); + final Component instance = (Component)context.getInjector().newInstance(clazz); + + context.addComponent(scheme, instance); + + return instance; + } +} diff --git a/runtime/jvm/src/main/java/org/apache/camel/k/jvm/dsl/Scripting.java b/runtime/jvm/src/main/java/org/apache/camel/k/jvm/dsl/Scripting.java new file mode 100644 index 0000000..3988290 --- /dev/null +++ b/runtime/jvm/src/main/java/org/apache/camel/k/jvm/dsl/Scripting.java @@ -0,0 +1,47 @@ +/** + * 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.k.jvm.dsl; + +import org.apache.camel.CamelContext; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.model.RouteDefinition; +import org.apache.camel.model.rest.RestConfigurationDefinition; +import org.apache.camel.model.rest.RestDefinition; + +public class Scripting { + public final RouteBuilder builder; + public final CamelContext context; + public final Components components; + + public Scripting(RouteBuilder builder) { + this.builder = builder; + this.context = builder.getContext(); + this.components = new Components(builder.getContext()); + } + + public RouteDefinition from(String endpoint) { + return builder.from(endpoint); + } + + public RestDefinition rest() { + return builder.rest(); + } + + public RestConfigurationDefinition restConfiguration() { + return builder.restConfiguration(); + } +} diff --git a/runtime/jvm/src/main/resources/META-INF/services/org.apache.camel.k.jvm.RoutesLoader b/runtime/jvm/src/main/resources/META-INF/services/org.apache.camel.k.jvm.RoutesLoader new file mode 100644 index 0000000..5a57927 --- /dev/null +++ b/runtime/jvm/src/main/resources/META-INF/services/org.apache.camel.k.jvm.RoutesLoader @@ -0,0 +1,4 @@ +org.apache.camel.k.jvm.RoutesLoaders$JavaClass +org.apache.camel.k.jvm.RoutesLoaders$JavaSource +org.apache.camel.k.jvm.RoutesLoaders$JavaScript +org.apache.camel.k.jvm.RoutesLoaders$Xml diff --git a/runtime/jvm/src/test/java/org/apache/camel/k/jvm/RoutesLoadersTest.java b/runtime/jvm/src/test/java/org/apache/camel/k/jvm/RoutesLoadersTest.java index 4b2090f..54b492b 100644 --- a/runtime/jvm/src/test/java/org/apache/camel/k/jvm/RoutesLoadersTest.java +++ b/runtime/jvm/src/test/java/org/apache/camel/k/jvm/RoutesLoadersTest.java @@ -33,7 +33,7 @@ public class RoutesLoadersTest { RoutesLoader loader = RoutesLoaders.loaderFor(resource, null); RouteBuilder builder = loader.load(resource); - assertThat(loader).isSameAs(RoutesLoaders.JavaClass); + assertThat(loader).isInstanceOf(RoutesLoaders.JavaClass.class); assertThat(builder).isNotNull(); builder.configure(); @@ -50,7 +50,7 @@ public class RoutesLoadersTest { RoutesLoader loader = RoutesLoaders.loaderFor(resource, null); RouteBuilder builder = loader.load(resource); - assertThat(loader).isSameAs(RoutesLoaders.JavaSource); + assertThat(loader).isInstanceOf(RoutesLoaders.JavaSource.class); assertThat(builder).isNotNull(); builder.configure(); @@ -67,7 +67,7 @@ public class RoutesLoadersTest { RoutesLoader loader = RoutesLoaders.loaderFor(resource, null); RouteBuilder builder = loader.load(resource); - assertThat(loader).isSameAs(RoutesLoaders.JavaScript); + assertThat(loader).isInstanceOf(RoutesLoaders.JavaScript.class); assertThat(builder).isNotNull(); builder.configure(); @@ -84,24 +84,7 @@ public class RoutesLoadersTest { RoutesLoader loader = RoutesLoaders.loaderFor(resource, "js"); RouteBuilder builder = loader.load(resource); - assertThat(loader).isSameAs(RoutesLoaders.JavaScript); - assertThat(builder).isNotNull(); - - builder.configure(); - - List<RouteDefinition> routes = builder.getRouteCollection().getRoutes(); - assertThat(routes).hasSize(1); - assertThat(routes.get(0).getInputs().get(0).getEndpointUri()).isEqualTo("timer:tick"); - assertThat(routes.get(0).getOutputs().get(0)).isInstanceOf(ToDefinition.class); - } - - @Test - public void testLoadGroovy() throws Exception { - String resource = "classpath:routes.groovy"; - RoutesLoader loader = RoutesLoaders.loaderFor(resource, null); - RouteBuilder builder = loader.load(resource); - - assertThat(loader).isSameAs(RoutesLoaders.Groovy); + assertThat(loader).isInstanceOf(RoutesLoaders.JavaScript.class); assertThat(builder).isNotNull(); builder.configure(); @@ -118,7 +101,7 @@ public class RoutesLoadersTest { RoutesLoader loader = RoutesLoaders.loaderFor(resource, null); RouteBuilder builder = loader.load(resource); - assertThat(loader).isSameAs(RoutesLoaders.Xml); + assertThat(loader).isInstanceOf(RoutesLoaders.Xml.class); assertThat(builder).isNotNull(); builder.configure(); diff --git a/runtime/kotlin/.gitignore b/runtime/kotlin/.gitignore new file mode 100644 index 0000000..ed92983 --- /dev/null +++ b/runtime/kotlin/.gitignore @@ -0,0 +1,10 @@ +target + +*.iml + +.idea +.project +.metadata +.settings +.factorypath +.classpath diff --git a/runtime/kotlin/pom.xml b/runtime/kotlin/pom.xml new file mode 100644 index 0000000..9036f76 --- /dev/null +++ b/runtime/kotlin/pom.xml @@ -0,0 +1,56 @@ +<?xml version="1.0" encoding="UTF-8"?> +<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/xsd/maven-4.0.0.xsd"> + <parent> + <groupId>org.apache.camel.k</groupId> + <artifactId>camel-k-runtime-parent</artifactId> + <version>0.0.3-SNAPSHOT</version> + </parent> + <modelVersion>4.0.0</modelVersion> + + <artifactId>camel-k-runtime-kotlin</artifactId> + + <dependencies> + <dependency> + <groupId>org.apache.camel.k</groupId> + <artifactId>camel-k-runtime-jvm</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.jetbrains.kotlin</groupId> + <artifactId>kotlin-stdlib-jdk8</artifactId> + <version>${kotlin.version}</version> + </dependency> + <dependency> + <groupId>org.jetbrains.kotlin</groupId> + <artifactId>kotlin-script-util</artifactId> + <version>${kotlin.version}</version> + </dependency> + <dependency> + <groupId>org.jetbrains.kotlin</groupId> + <artifactId>kotlin-compiler-embeddable</artifactId> + <version>${kotlin.version}</version> + </dependency> + + <!-- ****************************** --> + <!-- --> + <!-- TESTS --> + <!-- --> + <!-- ****************************** --> + + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <version>${junit.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.assertj</groupId> + <artifactId>assertj-core</artifactId> + <version>${assertj.version}</version> + <scope>test</scope> + </dependency> + </dependencies> + +</project> diff --git a/runtime/kotlin/src/main/java/org/apache/camel/k/kotlin/KotlinRoutesLoader.java b/runtime/kotlin/src/main/java/org/apache/camel/k/kotlin/KotlinRoutesLoader.java new file mode 100644 index 0000000..0b887ef --- /dev/null +++ b/runtime/kotlin/src/main/java/org/apache/camel/k/kotlin/KotlinRoutesLoader.java @@ -0,0 +1,77 @@ +/** + * 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.k.kotlin; + +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.Collections; +import java.util.List; +import javax.script.Bindings; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +import javax.script.SimpleBindings; + +import org.apache.camel.CamelContext; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.k.jvm.dsl.Components; +import org.apache.camel.k.jvm.Language; +import org.apache.camel.k.jvm.RoutesLoader; +import org.apache.camel.util.ResourceHelper; + +public class KotlinRoutesLoader implements RoutesLoader { + + @Override + public List<Language> getSupportedLanguages() { + return Collections.singletonList(Language.Kotlin); + } + + @Override + public RouteBuilder load(String resource) throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + final CamelContext context = getContext(); + final ScriptEngineManager manager = new ScriptEngineManager(); + final ScriptEngine engine = manager.getEngineByExtension("kts"); + final Bindings bindings = new SimpleBindings(); + + // Exposed to the underlying script, but maybe better to have + // a nice dsl + bindings.put("builder", this); + bindings.put("context", context); + bindings.put("components", new Components(context)); + + try (InputStream is = ResourceHelper.resolveMandatoryResourceAsInputStream(context, resource)) { + StringBuilder builder = new StringBuilder(); + + // extract global objects from 'bindings' + builder.append("val builder = bindings[\"builder\"] as org.apache.camel.builder.RouteBuilder").append('\n'); + builder.append("val context = bindings[\"context\"] as org.apache.camel.CamelContext").append('\n'); + builder.append("val components = bindings[\"components\"] as org.apache.camel.k.jvm.dsl.Components").append('\n'); + + // create aliases for common functions + builder.append("fun from(uri: String): org.apache.camel.model.RouteDefinition = builder.from(uri)").append('\n'); + builder.append("fun rest(): org.apache.camel.model.rest.RestDefinition = builder.rest()").append('\n'); + builder.append("fun restConfiguration(): org.apache.camel.model.rest.RestConfigurationDefinition = builder.restConfiguration()").append('\n'); + + engine.eval(builder.toString(), bindings); + engine.eval(new InputStreamReader(is), bindings); + } + } + }; + } +} diff --git a/runtime/kotlin/src/main/resources/META-INF/services/javax.script.ScriptEngineFactory b/runtime/kotlin/src/main/resources/META-INF/services/javax.script.ScriptEngineFactory new file mode 100644 index 0000000..f8f5900 --- /dev/null +++ b/runtime/kotlin/src/main/resources/META-INF/services/javax.script.ScriptEngineFactory @@ -0,0 +1 @@ +org.jetbrains.kotlin.script.jsr223.KotlinJsr223JvmLocalScriptEngineFactory \ No newline at end of file diff --git a/runtime/kotlin/src/main/resources/META-INF/services/org.apache.camel.k.jvm.RoutesLoader b/runtime/kotlin/src/main/resources/META-INF/services/org.apache.camel.k.jvm.RoutesLoader new file mode 100644 index 0000000..83c3f09 --- /dev/null +++ b/runtime/kotlin/src/main/resources/META-INF/services/org.apache.camel.k.jvm.RoutesLoader @@ -0,0 +1 @@ +org.apache.camel.k.kotlin.KotlinRoutesLoader \ No newline at end of file diff --git a/runtime/kotlin/src/test/java/org/apache/camel/k/kotlin/RoutesLoadersTest.java b/runtime/kotlin/src/test/java/org/apache/camel/k/kotlin/RoutesLoadersTest.java new file mode 100644 index 0000000..5c23d67 --- /dev/null +++ b/runtime/kotlin/src/test/java/org/apache/camel/k/kotlin/RoutesLoadersTest.java @@ -0,0 +1,50 @@ +/** + * 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.k.kotlin; + +import java.util.List; + +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.k.jvm.RoutesLoader; +import org.apache.camel.k.jvm.RoutesLoaders; +import org.apache.camel.model.ProcessDefinition; +import org.apache.camel.model.RouteDefinition; +import org.apache.camel.model.ToDefinition; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class RoutesLoadersTest { + + @Test + public void testLoadKotlin() throws Exception { + String resource = "classpath:routes.kts"; + RoutesLoader loader = RoutesLoaders.loaderFor(resource, null); + RouteBuilder builder = loader.load(resource); + + assertThat(loader).isInstanceOf(KotlinRoutesLoader.class); + assertThat(builder).isNotNull(); + + builder.configure(); + + List<RouteDefinition> routes = builder.getRouteCollection().getRoutes(); + assertThat(routes).hasSize(1); + assertThat(routes.get(0).getInputs().get(0).getEndpointUri()).isEqualTo("timer:tick"); + assertThat(routes.get(0).getOutputs().get(0)).isInstanceOf(ProcessDefinition.class); + assertThat(routes.get(0).getOutputs().get(1)).isInstanceOf(ToDefinition.class); + } +} diff --git a/runtime/kotlin/src/test/resources/routes.kts b/runtime/kotlin/src/test/resources/routes.kts new file mode 100644 index 0000000..22b0e14 --- /dev/null +++ b/runtime/kotlin/src/test/resources/routes.kts @@ -0,0 +1,21 @@ + +// ******************** +// +// setup +// +// ******************** + +//val builder = bindings["builder"] as org.apache.camel.builder.RouteBuilder +//fun from(uri: String): org.apache.camel.model.RouteDefinition = builder.from(uri) + +// ******************** +// +// routes +// +// ******************** + +from("timer:tick") + .process().message { + m -> m.headers["MyHeader"] = "MyHeaderValue" + } + .to("log:info") \ No newline at end of file diff --git a/runtime/pom.xml b/runtime/pom.xml index 0948ca3..8278b4f 100644 --- a/runtime/pom.xml +++ b/runtime/pom.xml @@ -24,6 +24,7 @@ <log4j2.version>2.11.0</log4j2.version> <slf4j.version>1.7.25</slf4j.version> <groovy.version>2.5.2</groovy.version> + <kotlin.version>1.2.71</kotlin.version> <snakeyaml.version>1.23</snakeyaml.version> <fabric8-maven-plugin.version>3.5.40</fabric8-maven-plugin.version> @@ -55,6 +56,8 @@ <modules> <module>jvm</module> + <module>groovy</module> + <module>kotlin</module> <module>catalog-builder</module> <module>dependency-lister</module> </modules>