nicolaferraro closed pull request #133: runtime: support for kotlin runtime
URL: https://github.com/apache/camel-k/pull/133
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

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%
rename from deploy/platform-integration-context-core.yaml
rename 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-kotlin.yaml 
b/deploy/platform-integration-context-kotlin.yaml
new file mode 100644
index 0000000..822b4a4
--- /dev/null
+++ b/deploy/platform-integration-context-kotlin.yaml
@@ -0,0 +1,14 @@
+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
\ 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 String getName() {
 
     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 static Language fromResource(String resource) {
             }
         }
 
-        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 @@
 
 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.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.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 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 RouteBuilder load(String resource) throws Exception {
 
             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 void configure() throws Exception {
                         }
 
                         // 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 void configure() throws Exception {
 
                     // 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 void configure() throws Exception {
                 }
             };
         }
-    },
-    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 void configure() throws Exception {
                 }
             };
         }
-    };
+    }
 
-    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 static RoutesLoader loaderFor(String resource, 
String languageName) {
             ? 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 static RoutesLoader loaderFor(String resource, 
String languageName) {
 
         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 void testLoadClass() throws Exception {
         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 void testLoadJava() throws Exception {
         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 void testLoadJavaScript() throws Exception {
         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 void testLoadJavaScriptWithCustomExtension() throws 
Exception {
         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 void testLoadXml() throws Exception {
         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>


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
us...@infra.apache.org


With regards,
Apache Git Services

Reply via email to