Skip to content

Commit

Permalink
✨ Camel Spring Boot on Tomcat App/Product
Browse files Browse the repository at this point in the history
  • Loading branch information
Croway committed May 9, 2024
1 parent 0ac4f90 commit d8b6f09
Show file tree
Hide file tree
Showing 7 changed files with 410 additions and 1 deletion.
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 com.google.auto.service.AutoService;

import software.tnb.product.LocalProduct;
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;

@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();
}

@Override
public void teardownProduct() {
app.stop();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
package software.tnb.product.csb.application;

import software.tnb.common.config.TestConfiguration;
import software.tnb.common.utils.WaitUtils;
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;
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;
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.nio.file.Paths;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

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

private AbstractIntegrationBuilder<?> integrationBuilder;
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() {
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);
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);
}
}

@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);
}
""");
Files.write(mainPath, main.getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
throw new RuntimeException(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);
}
}

@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");
}

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

0 comments on commit d8b6f09

Please sign in to comment.