Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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.
* <p>
* This test specifically avoids extensions like quarkus-vertx-http (which can also initiate restarts via
* <a href=
* "https://github.com/quarkusio/quarkus/blob/3.34.2/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/devmode/VertxHttpHotReplacementSetup.java#L171">
* VertxHttpHotReplacementSetup</a>) to ensure Camel's timer-based scanning mechanism works independently.
* <p>
* 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)));
}

}
Original file line number Diff line number Diff line change
@@ -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");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
* <p>
* 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.
* <p>
* 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");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,13 @@
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 TRUE and RouteBuilder is created via HelloRouteBuilder -> should succeed with
* warning.
*/
@Disabled("https://github.com/apache/camel-quarkus/issues/8318")
public class RouteBuilderWarningWithProducedBuilderTest {

@RegisterExtension
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,13 @@
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 TRUE and RouteBuilder is created via HelloRouteBuilder -> should succeed without
* warning.
*/
@Disabled("https://github.com/apache/camel-quarkus/issues/8318")
public class RouteBuilderWarningWithoutProducedBuilderTest {

@RegisterExtension
Expand Down