Fix #222: initial implementation of native cron support
diff --git a/camel-k-runtime-bom/pom.xml b/camel-k-runtime-bom/pom.xml
index 9bf0e2c..9c666aa 100644
--- a/camel-k-runtime-bom/pom.xml
+++ b/camel-k-runtime-bom/pom.xml
@@ -148,6 +148,11 @@
                 <artifactId>camel-k-runtime-knative</artifactId>
                 <version>${project.version}</version>
             </dependency>
+            <dependency>
+                <groupId>org.apache.camel.k</groupId>
+                <artifactId>camel-k-runtime-cron</artifactId>
+                <version>${project.version}</version>
+            </dependency>
 
             <!-- runtime - quarkus -->
             <dependency>
diff --git a/camel-k-runtime-cron/pom.xml b/camel-k-runtime-cron/pom.xml
new file mode 100644
index 0000000..b9611f1
--- /dev/null
+++ b/camel-k-runtime-cron/pom.xml
@@ -0,0 +1,152 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>org.apache.camel.k</groupId>
+        <artifactId>camel-k-runtime-parent</artifactId>
+        <version>1.0.10-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>camel-k-runtime-cron</artifactId>
+
+    <dependencies>
+
+        <!-- ****************************** -->
+        <!--                                -->
+        <!-- RUNTIME                        -->
+        <!--                                -->
+        <!-- ****************************** -->
+
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-core-engine</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-timer</artifactId>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-quartz</artifactId>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.camel.k</groupId>
+            <artifactId>camel-k-runtime-core</artifactId>
+        </dependency>
+
+        <!-- ****************************** -->
+        <!--                                -->
+        <!-- TESTS                          -->
+        <!--                                -->
+        <!-- ****************************** -->
+
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-endpointdsl</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-log</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-main</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.camel.k</groupId>
+            <artifactId>camel-k-loader-js</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.camel.k</groupId>
+            <artifactId>camel-k-runtime-main</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-api</artifactId>
+            <version>${junit.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-engine</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>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-params</artifactId>
+            <version>${junit.version}</version>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.logging.log4j</groupId>
+            <artifactId>log4j-core</artifactId>
+            <version>${log4j2.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.logging.log4j</groupId>
+            <artifactId>log4j-slf4j-impl</artifactId>
+            <version>${log4j2.version}</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.jboss.jandex</groupId>
+                <artifactId>jandex-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>make-index</id>
+                        <goals>
+                            <goal>jandex</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
diff --git a/camel-k-runtime-cron/src/main/java/org/apache/camel/k/cron/CronQuartzContextCustomizer.java b/camel-k-runtime-cron/src/main/java/org/apache/camel/k/cron/CronQuartzContextCustomizer.java
new file mode 100644
index 0000000..ad5a55f
--- /dev/null
+++ b/camel-k-runtime-cron/src/main/java/org/apache/camel/k/cron/CronQuartzContextCustomizer.java
@@ -0,0 +1,55 @@
+/*
+ * 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.cron;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Endpoint;
+import org.apache.camel.component.quartz.QuartzEndpoint;
+import org.apache.camel.k.ContextCustomizer;
+import org.apache.camel.support.LifecycleStrategySupport;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class CronQuartzContextCustomizer implements ContextCustomizer {
+
+    public CronQuartzContextCustomizer() {
+    }
+
+    @Override
+    public void apply(CamelContext camelContext) {
+        camelContext.addLifecycleStrategy(new CronQuartzLifecycleStrategy());
+        camelContext.addRoutePolicyFactory(new CronRoutePolicyFactory());
+    }
+
+    static class CronQuartzLifecycleStrategy extends LifecycleStrategySupport {
+
+        private static final Logger LOG = LoggerFactory.getLogger(CronQuartzLifecycleStrategy.class);
+
+        @Override
+        public void onEndpointAdd(Endpoint endpoint) {
+            if (endpoint instanceof QuartzEndpoint) {
+                LOG.info("Cron policy is configuring the quartz endpoint to startup immediately");
+                QuartzEndpoint qe = (QuartzEndpoint) endpoint;
+                qe.setCron(null);
+                qe.setFireNow(true);
+                qe.setAutoStartScheduler(true);
+                qe.setCustomCalendar(null);
+                qe.setStartDelayedSeconds(0);
+            }
+        }
+    }
+}
diff --git a/camel-k-runtime-cron/src/main/java/org/apache/camel/k/cron/CronRoutePolicyFactory.java b/camel-k-runtime-cron/src/main/java/org/apache/camel/k/cron/CronRoutePolicyFactory.java
new file mode 100644
index 0000000..d1dca0f
--- /dev/null
+++ b/camel-k-runtime-cron/src/main/java/org/apache/camel/k/cron/CronRoutePolicyFactory.java
@@ -0,0 +1,59 @@
+/*
+ * 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.cron;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Exchange;
+import org.apache.camel.NamedNode;
+import org.apache.camel.Route;
+import org.apache.camel.spi.RoutePolicy;
+import org.apache.camel.spi.RoutePolicyFactory;
+import org.apache.camel.support.RoutePolicySupport;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A RoutePolicyFactory that shuts the context down after the first exchange has been done.
+ */
+public class CronRoutePolicyFactory implements RoutePolicyFactory {
+
+    public CronRoutePolicyFactory() {
+    }
+
+    @Override
+    public RoutePolicy createRoutePolicy(CamelContext camelContext, String routeId, NamedNode route) {
+        return new CronRoutePolicy(camelContext);
+    }
+
+    private static class CronRoutePolicy extends RoutePolicySupport {
+
+        private static final Logger LOG = LoggerFactory.getLogger(CronRoutePolicy.class);
+
+        private final CamelContext context;
+
+        public CronRoutePolicy(CamelContext context) {
+            this.context = context;
+        }
+
+        @Override
+        public void onExchangeDone(Route route, Exchange exchange) {
+            LOG.info("Context shutdown started by cron policy");
+            context.getExecutorServiceManager().newThread("terminator", context::stop).start();
+        }
+
+    }
+}
diff --git a/camel-k-runtime-cron/src/main/java/org/apache/camel/k/cron/CronTimerContextCustomizer.java b/camel-k-runtime-cron/src/main/java/org/apache/camel/k/cron/CronTimerContextCustomizer.java
new file mode 100644
index 0000000..19ca64f
--- /dev/null
+++ b/camel-k-runtime-cron/src/main/java/org/apache/camel/k/cron/CronTimerContextCustomizer.java
@@ -0,0 +1,56 @@
+/*
+ * 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.cron;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Endpoint;
+import org.apache.camel.component.timer.TimerEndpoint;
+import org.apache.camel.k.ContextCustomizer;
+import org.apache.camel.support.LifecycleStrategySupport;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class CronTimerContextCustomizer implements ContextCustomizer {
+
+    public CronTimerContextCustomizer() {
+    }
+
+    @Override
+    public void apply(CamelContext camelContext) {
+        camelContext.addLifecycleStrategy(new CronTimerLifecycleStrategy());
+        camelContext.addRoutePolicyFactory(new CronRoutePolicyFactory());
+    }
+
+    static class CronTimerLifecycleStrategy extends LifecycleStrategySupport {
+
+        private static final Logger LOG = LoggerFactory.getLogger(CronTimerLifecycleStrategy.class);
+
+        @Override
+        public void onEndpointAdd(Endpoint endpoint) {
+            if (endpoint instanceof TimerEndpoint) {
+                LOG.info("Cron policy is configuring the timer endpoint to startup immediately");
+                TimerEndpoint te = (TimerEndpoint)endpoint;
+                te.setDelay(0);
+                te.setPeriod(1);
+                te.setRepeatCount(1);
+                te.setTime(null);
+                te.setFixedRate(false);
+                te.setPattern(null);
+            }
+        }
+    }
+}
diff --git a/camel-k-runtime-cron/src/main/resources/META-INF/services/org/apache/camel/k/customizer/cron-quartz b/camel-k-runtime-cron/src/main/resources/META-INF/services/org/apache/camel/k/customizer/cron-quartz
new file mode 100644
index 0000000..d567f81
--- /dev/null
+++ b/camel-k-runtime-cron/src/main/resources/META-INF/services/org/apache/camel/k/customizer/cron-quartz
@@ -0,0 +1,18 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+class=org.apache.camel.k.cron.CronQuartzContextCustomizer
diff --git a/camel-k-runtime-cron/src/main/resources/META-INF/services/org/apache/camel/k/customizer/cron-timer b/camel-k-runtime-cron/src/main/resources/META-INF/services/org/apache/camel/k/customizer/cron-timer
new file mode 100644
index 0000000..a29f31c
--- /dev/null
+++ b/camel-k-runtime-cron/src/main/resources/META-INF/services/org/apache/camel/k/customizer/cron-timer
@@ -0,0 +1,18 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+class=org.apache.camel.k.cron.CronTimerContextCustomizer
diff --git a/camel-k-runtime-cron/src/test/java/org/apache/camel/k/cron/CronTest.java b/camel-k-runtime-cron/src/test/java/org/apache/camel/k/cron/CronTest.java
new file mode 100644
index 0000000..d0b616f
--- /dev/null
+++ b/camel-k-runtime-cron/src/test/java/org/apache/camel/k/cron/CronTest.java
@@ -0,0 +1,78 @@
+/*
+ * 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.cron;
+
+import java.util.Properties;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Stream;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.k.listener.ContextConfigurer;
+import org.apache.camel.k.listener.RoutesConfigurer;
+import org.apache.camel.k.loader.js.JavaScriptSourceLoader;
+import org.apache.camel.k.main.ApplicationRuntime;
+import org.apache.camel.support.LifecycleStrategySupport;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class CronTest {
+
+    @ParameterizedTest
+    @MethodSource("parameters")
+    public void testCronTimerActivation(String routes, String customizer) throws Exception {
+        ApplicationRuntime runtime = new ApplicationRuntime();
+        runtime.addListener(RoutesConfigurer.forRoutes(routes));
+        runtime.addListener(new ContextConfigurer());
+
+        Properties properties = new Properties();
+        properties.setProperty("customizer." + customizer + ".enabled", "true");
+        runtime.setProperties(properties);
+
+        // To check auto-termination of Camel context
+        CountDownLatch termination = new CountDownLatch(1);
+        runtime.getCamelContext().addLifecycleStrategy(new LifecycleStrategySupport() {
+            @Override
+            public void onContextStop(CamelContext context) {
+                termination.countDown();
+            }
+        });
+
+        MockEndpoint mock = runtime.getCamelContext().getEndpoint("mock:result", MockEndpoint.class);
+        mock.expectedMessageCount(1);
+        mock.setResultWaitTime(10000);
+
+        runtime.run();
+        mock.assertIsSatisfied();
+
+        termination.await(10, TimeUnit.SECONDS);
+        assertThat(termination.getCount()).isEqualTo(0);
+    }
+
+    static Stream<Arguments> parameters() {
+        return Stream.of(
+            Arguments.arguments("classpath:routes-timer.js", "cron-timer"),
+            Arguments.arguments("classpath:routes-quartz.js", "cron-quartz")
+        );
+    }
+
+}
diff --git a/camel-k-runtime-cron/src/test/resources/log4j2-test.xml b/camel-k-runtime-cron/src/test/resources/log4j2-test.xml
new file mode 100644
index 0000000..3784c66
--- /dev/null
+++ b/camel-k-runtime-cron/src/test/resources/log4j2-test.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+<Configuration status="INFO">
+  <Appenders>
+    <Console name="STDOUT" target="SYSTEM_OUT">
+      <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS}|%-5level|%t|%c - %msg%n"/>
+    </Console>
+    <Null name="NONE"/>
+  </Appenders>
+
+  <Loggers>
+    <Logger name="io.netty" level="INFO"/>
+    <Logger name="io.netty.handler.logging" level="DEBUG"/>
+    <Root level="INFO">
+      <!--<AppenderRef ref="STDOUT"/>-->
+      <AppenderRef ref="NONE"/>
+    </Root>
+  </Loggers>
+
+</Configuration>
diff --git a/camel-k-runtime-cron/src/test/resources/routes-quartz.js b/camel-k-runtime-cron/src/test/resources/routes-quartz.js
new file mode 100644
index 0000000..995296a
--- /dev/null
+++ b/camel-k-runtime-cron/src/test/resources/routes-quartz.js
@@ -0,0 +1,19 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+from('quartz:tick?cron=0 0 0 * * ? 2099')
+    .setBody().simple('x')
+    .to('mock:result')
diff --git a/camel-k-runtime-cron/src/test/resources/routes-timer.js b/camel-k-runtime-cron/src/test/resources/routes-timer.js
new file mode 100644
index 0000000..29c26e1
--- /dev/null
+++ b/camel-k-runtime-cron/src/test/resources/routes-timer.js
@@ -0,0 +1,19 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+from('timer:tick?period=1&delay=600000')
+    .setBody().simple('x')
+    .to('mock:result')
diff --git a/pom.xml b/pom.xml
index aaefcf1..0a2cfcd 100644
--- a/pom.xml
+++ b/pom.xml
@@ -216,6 +216,7 @@
         <module>camel-k-loader-java</module>
         <module>camel-k-loader-knative</module>
 
+        <module>camel-k-runtime-cron</module>
         <module>camel-k-runtime-health</module>
         <module>camel-k-runtime-servlet</module>
         <module>camel-k-runtime-knative</module>