Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Camel Spring Boot on Tomcat App/Product #981

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
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
Expand Up @@ -2,6 +2,8 @@

import software.tnb.common.config.OpenshiftConfiguration;
import software.tnb.common.config.TestConfiguration;
import software.tnb.product.csb.TomcatCamelSpringBoot;
import software.tnb.product.csb.configuration.SpringBootConfiguration;

import java.util.Optional;
import java.util.ServiceLoader;
Expand All @@ -28,6 +30,7 @@ public static Product create() {
public static <P extends Product> P create(Class<P> clazz) {
final Optional<Product> product = StreamSupport.stream(ServiceLoader.load(Product.class).spliterator(), false)
.filter(p -> p.getClass().getSimpleName().toLowerCase().contains(TestConfiguration.product().getValue()))
.filter(p -> p instanceof TomcatCamelSpringBoot == SpringBootConfiguration.isTomcat())
.filter(p -> p instanceof OpenshiftProduct == OpenshiftConfiguration.isOpenshift())
.findFirst();
if (product.isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@
import software.tnb.common.config.TestConfiguration;
import software.tnb.common.product.ProductType;
import software.tnb.product.cq.configuration.QuarkusConfiguration;
import software.tnb.product.csb.configuration.SpringBootConfiguration;

public enum ProductConfiguration {
ALL,
QUARKUS, QUARKUS_JVM, QUARKUS_JVM_LOCAL, QUARKUS_JVM_OPENSHIFT, QUARKUS_NATIVE, QUARKUS_NATIVE_LOCAL, QUARKUS_NATIVE_OPENSHIFT,
CAMEL_K,
SPRINGBOOT, SPRINGBOOT_JVM_LOCAL, SPRINGBOOT_JVM_OPENSHIFT;
SPRINGBOOT, SPRINGBOOT_JVM_LOCAL, SPRINGBOOT_JVM_OPENSHIFT, SPRINGBOOT_TOMCAT;

public boolean isCurrentEnv() {
return switch (this) {
Expand All @@ -33,6 +34,8 @@ public boolean isCurrentEnv() {
&& !OpenshiftConfiguration.isOpenshift();
case SPRINGBOOT_JVM_OPENSHIFT -> TestConfiguration.product() == ProductType.CAMEL_SPRINGBOOT
&& OpenshiftConfiguration.isOpenshift();
case SPRINGBOOT_TOMCAT -> TestConfiguration.product() == ProductType.CAMEL_SPRINGBOOT
&& SpringBootConfiguration.isTomcat();
default -> true;
};
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package software.tnb.product.csb;

import software.tnb.product.LocalProduct;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [reviewdog] <com.puppycrawl.tools.checkstyle.checks.imports.ImportOrderCheck> reported by reviewdog 🐶
Wrong order for 'software.tnb.product.LocalProduct' import.

import software.tnb.product.Product;
import software.tnb.product.application.App;
import software.tnb.product.csb.application.TomcatSpringBootApp;
import software.tnb.product.integration.builder.AbstractIntegrationBuilder;

import com.google.auto.service.AutoService;

@AutoService(Product.class)
public class TomcatCamelSpringBoot extends LocalProduct {

private TomcatSpringBootApp app;

@Override
public App createIntegrationApp(AbstractIntegrationBuilder<?> integrationBuilder) {
app = new TomcatSpringBootApp(integrationBuilder);
return app;
}

@Override
public void setupProduct() {
super.setupProduct();
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not necessary here since it only calls super


@Override
public void teardownProduct() {
app.stop();
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

apps are stopped via Product#removeIntegrations

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
package software.tnb.product.csb.application;

import software.tnb.common.config.TestConfiguration;
import software.tnb.product.application.App;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [reviewdog] <com.puppycrawl.tools.checkstyle.checks.imports.UnusedImportsCheck> reported by reviewdog 🐶
Unused import - software.tnb.product.application.App.

import software.tnb.product.application.Phase;
import software.tnb.product.csb.configuration.SpringBootConfiguration;
import software.tnb.product.customizer.Customizer;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [reviewdog] <com.puppycrawl.tools.checkstyle.checks.imports.UnusedImportsCheck> reported by reviewdog 🐶
Unused import - software.tnb.product.customizer.Customizer.

import software.tnb.product.customizer.component.rest.RestCustomizer;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [reviewdog] <com.puppycrawl.tools.checkstyle.checks.imports.UnusedImportsCheck> reported by reviewdog 🐶
Unused import - software.tnb.product.customizer.component.rest.RestCustomizer.

import software.tnb.product.integration.builder.AbstractIntegrationBuilder;
import software.tnb.product.log.FileLog;
import software.tnb.product.log.Log;
import software.tnb.product.log.stream.LogStream;
import software.tnb.product.util.ZipUtils;
import software.tnb.product.util.maven.BuildRequest;
import software.tnb.product.util.maven.Maven;

import org.apache.commons.io.FileUtils;
import org.apache.maven.model.Dependency;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [reviewdog] <com.puppycrawl.tools.checkstyle.checks.imports.UnusedImportsCheck> reported by reviewdog 🐶
Unused import - org.apache.maven.model.Dependency.

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [reviewdog] <com.puppycrawl.tools.checkstyle.checks.imports.UnusedImportsCheck> reported by reviewdog 🐶
Unused import - java.util.List.

import java.util.Map;
import java.util.stream.Collectors;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [reviewdog] <com.puppycrawl.tools.checkstyle.checks.imports.UnusedImportsCheck> reported by reviewdog 🐶
Unused import - java.util.stream.Collectors.


public class TomcatSpringBootApp extends App {
private static final Logger LOG = LoggerFactory.getLogger(TomcatSpringBootApp.class);

private AbstractIntegrationBuilder<?> integrationBuilder;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not used for anything

private Path tomcatTmpDirectory;
private Path tomcatHome = null;
private Process tomcatProcess = null;
private static final String TOMCAT_PARENT_DIRECTORY = "tomcat";
private static final String TOMCAT_ARCHIVE_NAME = "tomcat-archive.zip";

@Override
public Log getLog() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

set the log variable after the app is started, instead of overriding this method

return new FileLog(getLogPath());
}

public TomcatSpringBootApp(AbstractIntegrationBuilder<?> integrationBuilder) {
super(integrationBuilder.getIntegrationName());

downloadTomcat();

this.integrationBuilder = integrationBuilder;
}

private void startTomcat() {
File[] file = tomcatTmpDirectory.resolve(TOMCAT_PARENT_DIRECTORY).toFile().listFiles();
for (File f : file) {
if (f.isDirectory()) {
if (f.getName().contains("apache-tomcat")) {
tomcatHome = f.toPath();
} else if (f.getName().contains("jws")) {
tomcatHome = f.toPath().resolve("tomcat"); // Case JWS
}
}
}

if (tomcatHome == null) {
throw new RuntimeException("Could not find Tomcat home in " + tomcatTmpDirectory.resolve(TOMCAT_PARENT_DIRECTORY));
}

Path logFile = getLogPath();
// startup.sh starts on another process, let's use catalina run so that we can control the lifecyle
ProcessBuilder processBuilder = new ProcessBuilder(tomcatHome + File.separator + "bin" + File.separator + "catalina.sh", "run")
.redirectError(logFile.toFile())
.redirectOutput(logFile.toFile());

try {
tomcatProcess = processBuilder.start();
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
if (tomcatProcess != null) {
tomcatProcess.destroyForcibly();
}
}));
} catch (IOException e) {
throw new RuntimeException(e);
}

LOG.info("Starting tomcat in {}", tomcatHome);
}

private void downloadTomcat() {
try {
tomcatTmpDirectory = Files.createTempDirectory("tnb-tomcat");
LOG.info("Downloading tomcat in {}", tomcatTmpDirectory);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

info -> debug

FileUtils.copyURLToFile(new URL(SpringBootConfiguration.tomcatZipUrl()), tomcatTmpDirectory.resolve(TOMCAT_ARCHIVE_NAME).toFile());

ZipUtils.unzip(tomcatTmpDirectory.resolve(TOMCAT_ARCHIVE_NAME), tomcatTmpDirectory.resolve(TOMCAT_PARENT_DIRECTORY));
} catch (IOException e) {
throw new RuntimeException(e);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

throw new RuntimeException("Unable to download tomcat", e);

}
}

@Override
public void start() {
startTomcat();

// Let's remove restcustomizer, since it exclude tomcat
List<Customizer> customizers = integrationBuilder.getCustomizers()
.stream().filter(customizer -> customizer instanceof RestCustomizer)
.collect(Collectors.toList());
integrationBuilder.getCustomizers().remove(customizers);

// spring web is needed and tomcat is provided
Dependency providedTomcatDependency = new Dependency();
providedTomcatDependency.setArtifactId("spring-boot-starter-tomcat");
providedTomcatDependency.setGroupId("org.springframework.boot");
providedTomcatDependency.setScope("provided");

integrationBuilder.dependencies(
Maven.createDependency("org.springframework.boot:spring-boot-starter-web"),
providedTomcatDependency);

// Create the integration
new SpringBootApp(integrationBuilder) {
@Override
public void start() {
// We just need the application to be generated, not run
}

@Override
public void stop() {
// do nothing
}

@Override
public boolean isReady() {
return true;
}

@Override
public boolean isFailed() {
return false;
}
}.start();

// replace packaging and extend SpringBootServletInitializer
try {
Path pomPath = TestConfiguration.appLocation().resolve(name).resolve("pom.xml");
String pom = Files.readString(pomPath);
pom = pom.replace("<artifactId>" + name + "</artifactId>",
"<artifactId>" + name + "</artifactId><packaging>war</packaging>");
Files.write(pomPath, pom.getBytes(StandardCharsets.UTF_8));

Path mainPath = TestConfiguration.appLocation().resolve(name)
.resolve("src")
.resolve("main")
.resolve("java")
.resolve("com")
.resolve("test")
.resolve("MySpringBootApplication.java");
String main = Files.readString(mainPath);

main = main.replace("class MySpringBootApplication {",
"""
class MySpringBootApplication extends org.springframework.boot.web.servlet.support.SpringBootServletInitializer {

@Override
protected org.springframework.boot.builder.SpringApplicationBuilder
configure(org.springframework.boot.builder.SpringApplicationBuilder application) {
return application.sources(MySpringBootApplication.class);
}
""");
Comment on lines +109 to +133
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if this is something that should be done for all tomcat apps, it should be done by a TomcatCustomizer

Files.write(mainPath, main.getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
throw new RuntimeException(e);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

throw new RuntimeException("Unable to start application", e);

}

// package application
BuildRequest.Builder requestBuilder = new BuildRequest.Builder()
.withBaseDirectory(TestConfiguration.appLocation().resolve(name))
.withGoals("clean", "package")
.withProperties(Map.of(
"skipTests", "true"
))
.withLogFile(getLogPath(Phase.BUILD))
.withLogMarker(LogStream.marker(name, Phase.BUILD));

LOG.info("Building {} application project for tomcat", name);
Maven.invoke(requestBuilder.build());

// copy generated WAR in tomcat
String warName = name + "-1.0.0-SNAPSHOT.war";
try {
Files.copy(TestConfiguration.appLocation().resolve(name).resolve("target").resolve(warName),
tomcatHome.resolve("webapps").resolve(warName));
} catch (IOException e) {
throw new RuntimeException(e);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

throw new RuntimeException("Unable to copy file", e);

}
}

@Override
public void stop() {
if (logStream != null) {
logStream.stop();
}

if (log != null) {
log.save();
}

if (tomcatProcess != null) {
try {
Process shutdown = new ProcessBuilder(tomcatHome + File.separator + "bin" + File.separator + "shutdown.sh")
.start();
shutdown.waitFor();

tomcatProcess.destroyForcibly();
} catch (IOException | InterruptedException e) {
throw new RuntimeException(e);
}
}
}

@Override
public boolean isReady() {
return tomcatProcess != null && tomcatProcess.isAlive();
}

@Override
public boolean isFailed() {
return tomcatProcess != null && !tomcatProcess.isAlive();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ public class SpringBootConfiguration extends CamelConfiguration {

public static final String OPENSHIFT_SB_RESULT_IMAGE_REPOSITORY = "openshift-sb.result-image.repo";

public static final String TOMCAT_ZIP_DOWNLOAD_URL = "tomcat.zip.download.url";
public static final String USE_TOMCAT = "test.use.tomcat";

public static String springBootVersion() {
return getProperty(SPRINGBOOT_VERSION, "2.6.1");
}
Expand Down Expand Up @@ -104,4 +107,12 @@ public static String openshiftBaseImage() {
public static String openshiftResultImageRepository() {
return getProperty(OPENSHIFT_SB_RESULT_IMAGE_REPOSITORY);
}

public static String tomcatZipUrl() {
return getProperty(TOMCAT_ZIP_DOWNLOAD_URL, "https://dlcdn.apache.org/tomcat/tomcat-10/v10.1.23/bin/apache-tomcat-10.1.23.zip");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't the version be configurable?

}

public static boolean isTomcat() {
return getBoolean(USE_TOMCAT, false);
}
}
Loading
Loading