diff --git a/extensions-core/core/deployment/src/test/java/org/apache/camel/quarkus/core/runtime/CamelDevModeRouteHotReloadTest.java b/extensions-core/core/deployment/src/test/java/org/apache/camel/quarkus/core/runtime/CamelDevModeRouteHotReloadTest.java new file mode 100644 index 000000000000..e22ffe4d64b8 --- /dev/null +++ b/extensions-core/core/deployment/src/test/java/org/apache/camel/quarkus/core/runtime/CamelDevModeRouteHotReloadTest.java @@ -0,0 +1,86 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.quarkus.core.runtime; + +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; +import java.util.Properties; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; + +import io.quarkus.test.QuarkusDevModeTest; +import org.awaitility.Awaitility; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.Asset; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +/** + * Test that verifies Camel route hot reload works correctly with CamelHotReplacementSetup's own scanning, + * without other Quarkus extension HotReplacementSetup implementations involved. + *
+ * This test specifically avoids extensions like quarkus-vertx-http (which can also initiate restarts via + * + * VertxHttpHotReplacementSetup) to ensure Camel's timer-based scanning mechanism works independently. + *
+ * This also verifies the fix for race conditions (issue #8318) doesn't break Camel's own hot reload functionality. + */ +public class CamelDevModeRouteHotReloadTest { + + public static final String ORIGINAL_MESSAGE = "Original Route Message"; + public static final String MODIFIED_MESSAGE = "Modified Route Message"; + + @RegisterExtension + static final QuarkusDevModeTest TEST = new QuarkusDevModeTest() + .setLogRecordPredicate(record -> record.getLevel().equals(Level.INFO)) + .setArchiveProducer(() -> ShrinkWrap + .create(JavaArchive.class) + .addClass(HotReloadRoute.class) + .addAsResource(applicationProperties(), "application.properties")); + + public static Asset applicationProperties() { + Writer writer = new StringWriter(); + Properties props = new Properties(); + props.setProperty("quarkus.banner.enabled", "false"); + try { + props.store(writer, ""); + } catch (IOException e) { + throw new RuntimeException(e); + } + return new StringAsset(writer.toString()); + } + + @Test + public void testRouteHotReload() { + // Wait for original message to be logged + Awaitility.await().atMost(10, TimeUnit.SECONDS).until(() -> TEST.getLogRecords().stream() + .anyMatch(logRecord -> logRecord.getMessage().contains(ORIGINAL_MESSAGE))); + + // Modify the java route + TEST.modifySourceFile(HotReloadRoute.class, s -> s.replace( + ORIGINAL_MESSAGE, MODIFIED_MESSAGE)); + + // Wait for modified message to be logged after hot reload + Awaitility.await().atMost(10, TimeUnit.SECONDS).until(() -> TEST.getLogRecords().stream() + .anyMatch(logRecord -> logRecord.getMessage().contains(MODIFIED_MESSAGE))); + } + +} diff --git a/extensions-core/core/deployment/src/test/java/org/apache/camel/quarkus/core/runtime/HotReloadRoute.java b/extensions-core/core/deployment/src/test/java/org/apache/camel/quarkus/core/runtime/HotReloadRoute.java new file mode 100644 index 000000000000..3bfa72d8265e --- /dev/null +++ b/extensions-core/core/deployment/src/test/java/org/apache/camel/quarkus/core/runtime/HotReloadRoute.java @@ -0,0 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.quarkus.core.runtime; + +import org.apache.camel.builder.RouteBuilder; + +public class HotReloadRoute extends RouteBuilder { + @Override + public void configure() { + from("timer:hotReloadTest?repeatCount=1") + .log("Original Route Message"); + } +} diff --git a/extensions-core/core/runtime/src/main/java/org/apache/camel/quarkus/core/devmode/CamelHotReplacementSetup.java b/extensions-core/core/runtime/src/main/java/org/apache/camel/quarkus/core/devmode/CamelHotReplacementSetup.java index d257f68710b2..fe8c83588202 100644 --- a/extensions-core/core/runtime/src/main/java/org/apache/camel/quarkus/core/devmode/CamelHotReplacementSetup.java +++ b/extensions-core/core/runtime/src/main/java/org/apache/camel/quarkus/core/devmode/CamelHotReplacementSetup.java @@ -19,37 +19,85 @@ import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; import io.quarkus.dev.spi.HotReplacementContext; import io.quarkus.dev.spi.HotReplacementSetup; import org.jboss.logging.Logger; +/** + * Camel hot replacement setup for Quarkus dev mode. + *
+ * Provides periodic scanning for Camel-specific files that may not be detected by Quarkus's + * built-in file watchers. To avoid conflicts with concurrent restarts, this implementation + * tracks restart lifecycle events and skips scans while a restart is in progress. + *
+ * See: https://github.com/apache/camel-quarkus/issues/8318 + */ public class CamelHotReplacementSetup implements HotReplacementSetup { private static final long INITIAL_DELAY = TimeUnit.SECONDS.toMillis(5); private static final long TASK_DELAY = TimeUnit.SECONDS.toMillis(2); private static final Logger LOG = Logger.getLogger(CamelHotReplacementSetup.class); - private Timer timer; + + private volatile Timer timer; + private volatile boolean closed = false; + private final AtomicLong preRestartTime = new AtomicLong(0); + private final AtomicLong postRestartTime = new AtomicLong(0); @Override public void setupHotDeployment(HotReplacementContext context) { + LOG.debugf("Setting up Camel hot deployment - initial delay: %dms, task delay: %dms", + INITIAL_DELAY, TASK_DELAY); + + // Track when any restarts begin + context.addPreRestartStep(() -> { + preRestartTime.set(System.currentTimeMillis()); + LOG.trace("Restart starting - recording timestamp"); + }); + + // Track when any restart completes + context.addPostRestartStep(() -> { + postRestartTime.set(System.currentTimeMillis()); + LOG.trace("Restart completed - recording timestamp"); + }); + timer = new Timer("camel-live-reload", true); timer.schedule(new TimerTask() { @Override public void run() { + if (closed) { + LOG.trace("Skipping scan - HotReplacementSetup is closed"); + return; + } + + // Skip if a restart is currently in progress + // (preRestartTime > postRestartTime means restart hasn't completed yet) + if (preRestartTime.get() > postRestartTime.get()) { + LOG.trace("Skipping scan - restart currently in progress"); + return; + } + try { + LOG.trace("Starting Camel live reload scan"); context.doScan(false); } catch (Exception e) { LOG.warn("Camel live reload task failed", e); } } }, INITIAL_DELAY, TASK_DELAY); + + LOG.debug("Camel hot deployment timer scheduled successfully"); } @Override public void close() { + LOG.debug("Closing Camel hot replacement setup"); + closed = true; + if (timer != null) { timer.cancel(); timer = null; + LOG.debug("Canceled Camel live reload timer"); } } } diff --git a/test-framework/camel-quarkus-junit-tests/src/test/java/org/apache/camel/quarkus/test/extensions/doubeRouteBuilder/DoubleRoutesTest.java b/test-framework/camel-quarkus-junit-tests/src/test/java/org/apache/camel/quarkus/test/extensions/doubeRouteBuilder/DoubleRoutesTest.java index 1c331098ffb4..1f6dc0baf585 100644 --- a/test-framework/camel-quarkus-junit-tests/src/test/java/org/apache/camel/quarkus/test/extensions/doubeRouteBuilder/DoubleRoutesTest.java +++ b/test-framework/camel-quarkus-junit-tests/src/test/java/org/apache/camel/quarkus/test/extensions/doubeRouteBuilder/DoubleRoutesTest.java @@ -24,14 +24,12 @@ import org.jboss.shrinkwrap.api.asset.StringAsset; import org.jboss.shrinkwrap.api.spec.JavaArchive; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; /** * Test for https://github.com/apache/camel-quarkus/issues/4560 */ -@Disabled //https://github.com/apache/camel-quarkus/issues/8318 public class DoubleRoutesTest { @RegisterExtension diff --git a/test-framework/camel-quarkus-junit-tests/src/test/java/org/apache/camel/quarkus/test/extensions/producedRouteBuilder/ProducedRouteBuilderTest.java b/test-framework/camel-quarkus-junit-tests/src/test/java/org/apache/camel/quarkus/test/extensions/producedRouteBuilder/ProducedRouteBuilderTest.java index 8c73aadcc83c..6322288f9608 100644 --- a/test-framework/camel-quarkus-junit-tests/src/test/java/org/apache/camel/quarkus/test/extensions/producedRouteBuilder/ProducedRouteBuilderTest.java +++ b/test-framework/camel-quarkus-junit-tests/src/test/java/org/apache/camel/quarkus/test/extensions/producedRouteBuilder/ProducedRouteBuilderTest.java @@ -24,14 +24,12 @@ import org.jboss.shrinkwrap.api.asset.StringAsset; import org.jboss.shrinkwrap.api.spec.JavaArchive; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; /** * Test for https://github.com/apache/camel-quarkus/issues/4362 */ -@Disabled("https://github.com/apache/camel-quarkus/issues/8318") public class ProducedRouteBuilderTest { @RegisterExtension diff --git a/test-framework/camel-quarkus-junit-tests/src/test/java/org/apache/camel/quarkus/test/extensions/routeBuilder/RouteBuilderFailureTest.java b/test-framework/camel-quarkus-junit-tests/src/test/java/org/apache/camel/quarkus/test/extensions/routeBuilder/RouteBuilderFailureTest.java index edd0ce6a0874..bbb1b365ba31 100644 --- a/test-framework/camel-quarkus-junit-tests/src/test/java/org/apache/camel/quarkus/test/extensions/routeBuilder/RouteBuilderFailureTest.java +++ b/test-framework/camel-quarkus-junit-tests/src/test/java/org/apache/camel/quarkus/test/extensions/routeBuilder/RouteBuilderFailureTest.java @@ -25,14 +25,12 @@ import org.jboss.shrinkwrap.api.asset.StringAsset; import org.jboss.shrinkwrap.api.spec.JavaArchive; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; /** * Scenario when useRouteBuilder is FALSE and NO RouteBuilder is produced -> should fail. */ -@Disabled("https://github.com/apache/camel-quarkus/issues/8318") public class RouteBuilderFailureTest { @RegisterExtension diff --git a/test-framework/camel-quarkus-junit-tests/src/test/java/org/apache/camel/quarkus/test/extensions/routeBuilder/RouteBuilderWarningWithProducedBuilderTest.java b/test-framework/camel-quarkus-junit-tests/src/test/java/org/apache/camel/quarkus/test/extensions/routeBuilder/RouteBuilderWarningWithProducedBuilderTest.java index ba27dc37b7b6..87c552c79bc1 100644 --- a/test-framework/camel-quarkus-junit-tests/src/test/java/org/apache/camel/quarkus/test/extensions/routeBuilder/RouteBuilderWarningWithProducedBuilderTest.java +++ b/test-framework/camel-quarkus-junit-tests/src/test/java/org/apache/camel/quarkus/test/extensions/routeBuilder/RouteBuilderWarningWithProducedBuilderTest.java @@ -24,7 +24,6 @@ import org.jboss.shrinkwrap.api.asset.StringAsset; import org.jboss.shrinkwrap.api.spec.JavaArchive; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -32,7 +31,6 @@ * Scenario when useRouteBuilder is TRUE and RouteBuilder is created via HelloRouteBuilder -> should succeed with * warning. */ -@Disabled("https://github.com/apache/camel-quarkus/issues/8318") public class RouteBuilderWarningWithProducedBuilderTest { @RegisterExtension diff --git a/test-framework/camel-quarkus-junit-tests/src/test/java/org/apache/camel/quarkus/test/extensions/routeBuilder/RouteBuilderWarningWithoutProducedBuilderTest.java b/test-framework/camel-quarkus-junit-tests/src/test/java/org/apache/camel/quarkus/test/extensions/routeBuilder/RouteBuilderWarningWithoutProducedBuilderTest.java index 8e5639558481..dc612679696d 100644 --- a/test-framework/camel-quarkus-junit-tests/src/test/java/org/apache/camel/quarkus/test/extensions/routeBuilder/RouteBuilderWarningWithoutProducedBuilderTest.java +++ b/test-framework/camel-quarkus-junit-tests/src/test/java/org/apache/camel/quarkus/test/extensions/routeBuilder/RouteBuilderWarningWithoutProducedBuilderTest.java @@ -24,7 +24,6 @@ import org.jboss.shrinkwrap.api.asset.StringAsset; import org.jboss.shrinkwrap.api.spec.JavaArchive; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -32,7 +31,6 @@ * Scenario when useRouteBuilder is TRUE and RouteBuilder is created via HelloRouteBuilder -> should succeed without * warning. */ -@Disabled("https://github.com/apache/camel-quarkus/issues/8318") public class RouteBuilderWarningWithoutProducedBuilderTest { @RegisterExtension