Set jvmRoute in spring boot 2.0.0 - java

For sticky session i need to set the jvmRoute of the embedded tomcat.
Actually only a
System.setProperty("jvmRoute", "node1");
is required, but i want to set a via application.properties configurable property. I don't know how and when to set this with #Value annotated property.
With #PostConstruct as described here, it does not work (at least not in spring boot 2.0.0.RELEASE)
The only way i found so far is
#Component
public class TomcatInitializer implements ApplicationListener<ServletWebServerInitializedEvent> {
#Value("${tomcat.jvmroute}")
private String jvmRoute;
#Override
public void onApplicationEvent(final ServletWebServerInitializedEvent event) {
final WebServer ws = event.getApplicationContext().getWebServer();
if (ws instanceof TomcatWebServer) {
final TomcatWebServer tws = (TomcatWebServer) ws;
final Context context = (Context) tws.getTomcat().getHost().findChildren()[0];
context.getManager().getSessionIdGenerator().setJvmRoute(jvmRoute);
}
}
}
It works, but it does not look like much elegant...
Any suggestions are very appreciated.

You can customise Tomcat's Context a little more elegantly by using a context customiser. It's a functional interface so you can use a lambda:
#Bean
public WebServerFactoryCustomizer<TomcatServletWebServerFactory> tomcatCustomizer() {
return (tomcat) -> tomcat.addContextCustomizers((context) -> {
Manager manager = context.getManager();
if (manager == null) {
manager = new StandardManager();
context.setManager(manager);
}
manager.getSessionIdGenerator().setJvmRoute(jvmRoute);
});
}

I'm using Spring Boot 2.0.4. The above answer did not work for me all the way. I had to update it this way:
#Bean
public WebServerFactoryCustomizer<TomcatServletWebServerFactory> servletContainer() {
return (tomcat) -> {
tomcat.addContextCustomizers((context) -> {
Manager manager = context.getManager();
if (manager == null) {
manager = new StandardManager();
context.setManager(manager);
}
((ManagerBase) context.getManager()).getEngine().setJvmRoute("tomcatJvmRoute");
});
};
}

Related

Is it possible to edit schema and db name of org.testcontainers.containers.PostgreSQLContainer?

I'm using TestContainers' PostgreSQLContainer to test a Spring application, and I want to configure the DB to have a specific schema and DB name. The defaults are db_name=test and schema=public.
Is it possible to edit those? Of so, where and how?
I've an application.properties file:
spring.datasource.url=${DB_URL}
spring.datasource.username=${DB_USERNAME}
spring.datasource.password=${DB_PASSWORD}
My PostgreSQLContainer implementation of the start() method:
import org.testcontainers.containers.PostgreSQLContainer;
public class DefaultPostgresContainer extends PostgreSQLContainer<DefaultPostgresContainer> {
private static final String IMAGE_VERSION = "postgres:13";
private static DefaultPostgresContainer container;
private DefaultPostgresContainer() {
super(IMAGE_VERSION);
}
public static DefaultPostgresContainer getInstance() {
if (container == null) {
container = new DefaultPostgresContainer().withInitScript("init_postgresql.sql");
}
return container;
}
#Override
public void start() {
super.start();
System.setProperty("DB_URL", container.getJdbcUrl());
System.setProperty("DB_USERNAME", "my_test");
System.setProperty("DB_PASSWORD", "my_test");
}
#Override
public void stop() {
//do nothing, JVM handles shutdown
}
}
As was mentioned in the comments, the withDatabaseName() method will provide you with a way so set a name different from the default value. So in your case you could simple do the following:
public static DefaultPostgresContainer getInstance() {
if (container == null) {
container = new DefaultPostgresContainer()
.withInitScript("init_postgresql.sql")
.withDatabaseName("mydatabase");
}
return container;
}
The hint on working with schemas was also already given in the comments: custom schemas can easily be managed via JDBC (you'd have to create it before using it).
To select a custom schema as "default" in a JDBC connection to a PostgreSQLContainer you can do the following:
Properties properties = new Properties();
properties.setProperty("user", psqlContainer.getUsername());
properties.setProperty("password", psqlContainer.getPassword());
properties.setProperty("currentSchema", SCHEMA_NAME); // this sets the "default" schema for queries
final Connection connection = DriverManager.getConnection(psqlContainer.getJdbcUrl(), properties);
If you want to see it in action I've put together some simple test cases demonstrating the mechanisms explained here.

Spring Boot customize server.tomcat.threads.min-spare for management server only

I have an application with management.server enabled:
management.server.port=8081
When I start application, I have:
10 threads for 8080 HTTP nio connector
10 threads for 8081 HTTP nio connector
But I would like to reduce min-spare only for management (8081) and not for the web application (8080)
Looking at Spring code, it seems it's not possible, can someone confirm ?
EDIT: The approach below is not sufficient as the ManagementWebServerFactoryCustomizer is also a ConfigurableWebServerFactory and will thus be applied to the main server.
Adding logic to check againgst the management port is not helping as the management context has its very own wiring and won't pick up the bean.
Looks like it's not possible to hook into the management server configuration easily (would be easier if ServletManagementContextFactory were public).
You can look into ServletManagementChildContextConfiguration to see how the management server is wired.
You could hook into the management server configuration by providing a ManagementWebServerFactoryCustomizer like this (not sure if there's an easier way):
#Configuration
public class TomcatManagementCustomizerConfiguration {
#Bean
ManagementWebServerFactoryCustomizer<ConfigurableServletWebServerFactory> servletManagementWebServerFactoryCustomizer(
#Value("${management.server.threads.min-spare:5}") int managementMinSpareThreads,
ListableBeanFactory beanFactory) {
return new TomcatManagementCustomizer(beanFactory, managementMinSpareThreads);
}
static class TomcatManagementCustomizer extends ManagementWebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {
private final int managementMinSpareThreads;
protected TomcatManagementCustomizer(ListableBeanFactory beanFactory, int managementMinSpareThreads) {
super(beanFactory, TomcatWebServerFactoryCustomizer.class);
this.managementMinSpareThreads = managementMinSpareThreads;
}
#Override
#SuppressWarnings("rawtypes")
protected void customize(ConfigurableServletWebServerFactory factory, ManagementServerProperties managementServerProperties, ServerProperties serverProperties) {
super.customize(factory, managementServerProperties, serverProperties);
((TomcatServletWebServerFactory) factory).addConnectorCustomizers((connector) -> {
ProtocolHandler handler = connector.getProtocolHandler();
if (handler instanceof AbstractProtocol) {
AbstractProtocol protocol = (AbstractProtocol) handler;
protocol.setMinSpareThreads(managementMinSpareThreads);
}
});
}
}
}
Can you not just put the following in either properties file or YAML file?
Or is there something I misunderstood?
server.tomcat.threads.min-spare=2
(This is for properties file)
Just to verify (You don't need this as you have been checking the updated value in the log)
Put the following in either properties file or YAML file
management.endpoints.web.exposure.include=health,info,metrics,env
(This is for properties file)
And visit /actuator/env/server.tomcat.threads.min-spare
You need actuator dependency for the link above to work.
You can use #ManagementConfigurationContext and add the configuration class to to your META-INF/spring.properties file.
It is also important to place the configuration class in a package which is not the main package or sub-package of your main application context. This is so that this configuration only applies to the management context.
Below is the sampel configuration following #Holgzn's response.
#ManagementContextConfiguration
public class TomcatManagementCustomizerConfiguration {
#Bean
ManagementWebServerFactoryCustomizer<ConfigurableServletWebServerFactory> servletManagementWebServerFactoryCustomizer(
#Value("${management.server.threads.min-spare:5}") int managementMinSpareThreads,
ListableBeanFactory beanFactory) {
return new TomcatManagementCustomizer(beanFactory, managementMinSpareThreads);
}
static class TomcatManagementCustomizer extends ManagementWebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {
private final int managementMinSpareThreads;
protected TomcatManagementCustomizer(ListableBeanFactory beanFactory, int managementMinSpareThreads) {
super(beanFactory, TomcatWebServerFactoryCustomizer.class);
this.managementMinSpareThreads = managementMinSpareThreads;
}
#Override
#SuppressWarnings("rawtypes")
protected void customize(ConfigurableServletWebServerFactory factory, ManagementServerProperties managementServerProperties, ServerProperties serverProperties) {
super.customize(factory, managementServerProperties, serverProperties);
((TomcatServletWebServerFactory) factory).addConnectorCustomizers((connector) -> {
ProtocolHandler handler = connector.getProtocolHandler();
if (handler instanceof AbstractProtocol) {
AbstractProtocol protocol = (AbstractProtocol) handler;
protocol.setMinSpareThreads(managementMinSpareThreads);
}
});
}
}
}
The spring.properties file
org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration=<package>.TomcatManagementCustomizerConfiguration

How to create/customize Spring Boot Actuators for exposing application healthchecks and diagnostics for my Camunda Application?

I have been asked to create Spring Boot Actuators for my Camunda Engine/BPMN tool. SO that we can exposes various applications healthchecks and diagnostics via SpringActuator.
This should be extended to expose health, config and metrics for Camunda. Any idea how to proceed on this?
Assuming that you are using the camunda spring boot starter: Two default health indicators for ProcessEngine and JobExecutor are included with the starter, have a look at:
https://github.com/camunda/camunda-bpm-spring-boot-starter/tree/master/starter/src/main/java/org/camunda/bpm/spring/boot/starter/actuator
#Override
protected void doHealthCheck(Builder builder) throws Exception {
boolean active = jobExecutor.isActive();
if (active) {
builder = builder.up();
} else {
builder = builder.down();
}
builder.withDetail("jobExecutor", Details.from(jobExecutor));
}
which should give you an idea how to implement springs AbstractHealthIndicator, access camunda components and use them for health checks.
I have been able to customize the existing healthcheck actuator. Sharing the code below for people who want to customize in there projects.
#Component
public class X extends AbstractHealthIndicator {
#Autowired
public JobExecutor jobExecutor;
#Override
protected void doHealthCheck(Builder builder) throws Exception {
boolean active = jobExecutor.isActive();
if (active) {
builder = builder.up();
} else {
builder = builder.down();
}
builder.withDetail("jobExecutor", healthDetails(jobExecutor));
}
private Details healthDetails(JobExecutor jobExecutor) {
final DetailsBuilder builder = Details.builder().name(jobExecutor.getName())
.lockOwner(jobExecutor.getLockOwner()).lockTimeInMillis(jobExecutor.getLockTimeInMillis())
.maxJobsPerAcquisition(jobExecutor.getMaxJobsPerAcquisition())
.waitTimeInMillis(jobExecutor.getWaitTimeInMillis());
for (ProcessEngineImpl processEngineImpl : jobExecutor.getProcessEngines()) {
builder.processEngineName(processEngineImpl.getName());
}
return builder.build();
}
}

Mod_Cluster LifecycleListeners Spring Boot

I'm migrating my project Spring to Spring Boot. But I'm faced with a problem, we have a reverse proxy using apache2 and mod_cluster. In actual version we declare a Listerner in the server.xml.
<Listener className="org.jboss.modcluster.container.catalina.standalone.ModClusterListener" advertise="false" proxyList="${proxyList}" />
I put it like a Spring boot application.
private Connector ajpConnector() {
Connector connector = new Connector("AJP/1.3");
connector.setPort(8009);
connector.setRedirectPort(8443);
return connector;
}
private ModClusterListener modCluster() {
ModClusterListener modClusterListener = new ModClusterListener();
modClusterListener.setAdvertise(false);
modClusterListener.setProxyURL(proxyUrl);
return modClusterListener;
}
#Bean
public WebServerFactoryCustomizer<TomcatServletWebServerFactory> servletContainer() {
return server -> {
if (server != null) {
server.addContextLifecycleListeners(modCluster());
server.addAdditionalTomcatConnectors(ajpConnector());
}
};
}
But it don't work, the ModClusterListener want a LifecycleEvent of type Sever, but it never happen. Can anyone help me?
I posted the question on Gitter and Andy Wilkinson helped me.
"From what you've said, it sounds like ModClusterListener needs to be added to Tomcat's Server but the method you've used will add it to the Context. You could use a context customizer and navigate up from the Context till you find the Server or you could use a TomcatServletWebServerFactory sub-class instead:"
#Bean
public TomcatServletWebServerFactory tomcatFactory() {
return new TomcatServletWebServerFactory() {
#Override
protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
tomcat.getServer().addLifecycleListener(modCluster());
return new TomcatWebServer(tomcat);
}
};
}
It worked for me!

Spring bean creation conditionally #ConditionalOn

I would like to create a Bean only when httpPort value not equals -1. I tried below code but It is complaining something wrong with the expression. Could you please fix this? or how to achieve my requirements.
I am using Java 8 and Spring Boot 1.5.4
I tried below options
#ConditionalOnExpression("'${httpPort}' ne '-1'")
#ConditionalOnExpression("'${httpPort}' != '-1'")
#ConditionalOnExpression("!'${httpPort}'=='-1'")
#ConditionalOnExpression("!'${httpPort == -1}'")
#ConditionalOnExpression("!${httpPort == -1}")
Most of the cases error is EL1041E: After parsing a valid expression,
there is still more data in the expression: 'lcurly({)'
#Configuration
public class TomcatConfig {
#Value("${server.http.port:-1}")
private int httpPort;
#Bean
#ConditionalOnExpression("'${httpPort}' ne '-1'")
public EmbeddedServletContainerCustomizer containerCustomizer() {
return new EmbeddedServletContainerCustomizer() {
#Override
public void customize(ConfigurableEmbeddedServletContainer container) {
if (container instanceof TomcatEmbeddedServletContainerFactory) {
TomcatEmbeddedServletContainerFactory containerFactory =
(TomcatEmbeddedServletContainerFactory) container;
Connector connector = new Connector(TomcatEmbeddedServletContainerFactory.DEFAULT_PROTOCOL);
connector.setPort(httpPort);
containerFactory.addAdditionalTomcatConnectors(connector);
}
}
};
}
}
Solved this by #ConditionalOnProperty("server.http.port")
#ConditionalOnProperty("server.http.port")
public EmbeddedServletContainerCustomizer containerCustomizer() {
}

Categories