Mod_Cluster LifecycleListeners Spring Boot - java

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!

Related

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

Set jvmRoute in spring boot 2.0.0

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

SpringBoot Tomcat Embedded Global JNDI Resource

I know there are a lot of questions about JNDI Resources in tomcat embedded, but I tried all the solutions I found without success.
I have an application that expose Rest API for my clients. Inside this app, we have a async solution using JMS and Amazon SQS. The App uses third-part libs, that uses JNDI to get sql.Datasource, because of that, I need use JNDI Datasource.
The problem is, when the App does a call to this libs in the same thread of Rest Controller, the JNDI Lookup works, and the datasource is getted.
When my #JmsListener calls this libs, I get a NamingNotFoungException.
I've used context.list("java") in 2 points of my code and confirmed that, inside JmsListener, there is no JNDI Context.
My tomcat factory class:
Configuration
public class CustomTomcatEmbeddedServletContainerFactory {
#Value("${spring.log.datasource.jndiName}")
private String logJndiName;
#Value("${spring.log.datasource.password}")
private String logPassword;
#Value("${spring.log.datasource.url}")
private String logUrl;
#Value("${spring.log.datasource.username}")
private String logUsername;
#Bean
public TomcatEmbeddedServletContainerFactory tomcatFactory() {
return new TomcatEmbeddedServletContainerFactory() {
#Override
protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(Tomcat tomcat) {
tomcat.enableNaming();
return super.getTomcatEmbeddedServletContainer(tomcat);
}
#Override
protected void postProcessContext(Context context) {
// LogDS
context.getNamingResources()
.addResource(
getContextResource(logJndiName, logUrl, logUsername, logPassword)
);
ContextResourceLink contextResourceLink = new
ContextResourceLink();
contextResourceLink.setGlobal(logJndiName);
contextResourceLink.setName(logJndiName);
contextResourceLink.setType("javax.sql.DataSource");
context.getNamingResources().addResourceLink(contextResourceLink);
}
private ContextResource getContextResource(
final String name
, final String url
, final String username
, final String password
) {
ContextResource resource = new ContextResource();
resource.setName(name);
resource.setType(DataSource.class.getName());
resource.setProperty("factory", "com.zaxxer.hikari.HikariJNDIFactory");
resource.setProperty("jdbcUrl", url);
resource.setProperty("dataSource.user", username);
resource.setProperty("dataSource.password", AESCrypto.decrypt(password));
resource.setScope("Sharable");
return resource;
}
};
}
}
Any idea of this problem?
-------Update---------
When I use the code below, the context in JMSListener solve but my RestController doesn't answer anymore, a 404 http status happen.
protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(Tomcat tomcat) {
tomcat.enableNaming();
TomcatEmbeddedServletContainer container = super.getTomcatEmbeddedServletContainer(tomcat);
for (Container child : container.getTomcat().getHost().findChildren()) {
if (child instanceof Context) {
ClassLoader contextClassLoader = ((Context) child).getLoader().getClassLoader();
Thread.currentThread().setContextClassLoader(contextClassLoader);
break;
}
}
return container;
}
-------Update2---------
My problem is fixed. Instead of returning "container", like I said above, I was returning super.getTomcatEmbeddedServletContainer(tomcat); The manual GlobalContext in my first update works well!
My problem is fixed. Instead of returning "container", like I said above, I was returning super.getTomcatEmbeddedServletContainer(tomcat); The manual GlobalContext in my first update works well!

Jersey + Spring standalone webservice #Autowire not working

I am running Restful web-service as standalone application using Jersey.
Below are my service classes which serve's the requests.
LoginServiceImpl.java
#Component
public class LoginServiceImpl implements LoginService {
#Value("${login.service.defaultmessage}")
private String defaultMessage;
#Autowired
private EmLoginDAO emLoginDAO;
#Override
public String defaultCall() {
return defaultMessage;
}
#Override
public String updatePassword(List<Login> userList) {
System.out.println(emLoginDAO + "\n" + userList);
emLoginDAO.save(userList);
return "Passwords Updated...";
}
#Override
public List<Login> getPasswords() {
System.out.println("OBJECT: " + emLoginDAO);
List<Login> userList = null;
userList = emLoginDAO.findAll();
return userList;
}
}
LoginService.java
#Component
#Path("/user")
public interface LoginService {
#GET
public String defaultCall();
#POST
#Path(value = "/print")
#Consumes(MediaType.APPLICATION_XML)
#Produces(MediaType.TEXT_PLAIN)
public String updatePassword(List<Login> userList);
#GET
#Path(value = "/getpassword")
#Produces(MediaType.APPLICATION_XML)
public List<Login> getPasswords();
}
Below is my spring configuration file.
<context:component-scan base-package="com.em.login" />
<context:annotation-config />
After starting the service when I call the respective method get called.
But my defaultMessage and emLoginDAO objects are null. So it is not referring to the properties and spring configuration files.
So can any one please help me to get this correct. Or to find a way to set the properties and spring configuration file paths to Jersey.
Update
Closeable server = null;
try {
DefaultResourceConfig resourceConfig = new DefaultResourceConfig(
LoginServiceImpl.class);
resourceConfig.getContainerResponseFilters().add(
new GZIPContentEncodingFilter());
server = SimpleServerFactory.create(serviceurl,
resourceConfig);
System.in.read();
} catch (IOException e) {
} finally {
if (server != null) {
try {
server.close();
} catch (IOException ex) {
}
}
}
I think this is the culprit:
DefaultResourceConfig resourceConfig = new DefaultResourceConfig(LoginServiceImpl.class);
You are using Spring's IOC to create the objects and do the autowiring, but you are not getting the instance from the Spring container. You need to get the LoginServiceImpl instance from the Spring container, and not have Jersey create it (Jersey does not know how to autowire your #Autowired properties.
You should use the Spring integration with Jersey, seen here.
Edit to respond to your comment, you posted this code:
LoginServiceImpl loginServiceImpl = (LoginServiceImpl) SpringApplicationContext.getBean("loginServiceImpl");
DefaultResourceConfig resourceConfig = new DefaultResourceConfig( loginServiceImpl.getClass());
You are creating a loginServiceImpl via the spring container, and I'll bet if you check your autowired fields will be there.
However, the second line where you use loginServiceImpl.getClass() this is going to create a new LoginServiceImpl, which is not the same one as the loginServiceImpl you got from the context, so you still are going to have the same problem.
You could take a look at Microserver, that will do all the wiring between Jersey and Spring for you (and setup a Grizzly webserver). From the tags I notice you are using Spring boot, with Microserver: micro-boot module you can do (in a class in package com.em.login):
public static void main(String[] args){
new MicrobootApp(()->"test-app").run();
}
And it should wire up Grizzly, Jersey & Spring with Spring-boot enabled for any backend (non-Jax-rs) dependencies.
Alternatively without Spring Boot (plain old Jersey and Spring)
public static void main(String[] args){
new MicroserverApp(()->"test-app").run();
}
To do it manually, you will need to add the Jersey-Spring integration jar to your classpath and make sure both are configured in a way that interoperates (i.e. I think a registering Spring ContextListener is essential). There is an example app here.
Have you configured those two in your spring configuration files?
I mean have you annotated EmLoginDAO also as stereotype Component?
I got this working.
Referred the this part of the Jersey documentation.
Below is the code I have used to make this working.
ResourceConfig resourceConfig = new ResourceConfig(LoginServiceImpl.class);
resourceConfig.register(org.glassfish.jersey.server.filter.UriConnegFilter.class);
resourceConfig.register(org.glassfish.jersey.server.spring.SpringComponentProvider.class);
resourceConfig.property(ServerProperties.METAINF_SERVICES_LOOKUP_DISABLE, true);
resourceConfig.property("contextConfigLocation", "classpath:/spring-config.xml");
URI serviceUri = UriBuilder.fromUri(serviceHost).port(servicePort).build();
server = SimpleContainerFactory.create(serviceUri, resourceConfig);
Thank you all for helping.

How to register attribute renderer in Apache Tiles?

on the Integration with FreeMarker page on the Apache Tiles site it has:
To access ".ftl" files as attributes, register FreeMarkerAttributeRenderer this way (only available in a servlet environment):
#Override
protected void registerAttributeRenderers(
BasicRendererFactory rendererFactory, TilesApplicationContext applicationContext,
TilesRequestContextFactory contextFactory,
TilesContainer container, AttributeEvaluator evaluator) {
super.registerAttributeRenderers(rendererFactory, applicationContext, contextFactory,
container, evaluator);
FreeMarkerAttributeRenderer freemarkerRenderer = new FreeMarkerAttributeRenderer();
freemarkerRenderer.setApplicationContext(applicationContext);
freemarkerRenderer.setEvaluator(evaluator);
freemarkerRenderer.setRequestContextFactory(contextFactory);
freemarkerRenderer.setParameter("TemplatePath", "/");
freemarkerRenderer.setParameter("NoCache", "true");
freemarkerRenderer.setParameter("ContentType", "text/html");
freemarkerRenderer.setParameter("template_update_delay", "0");
freemarkerRenderer.setParameter("default_encoding", "ISO-8859-1");
freemarkerRenderer.setParameter("number_format", "0.##########");
freemarkerRenderer.commit();
rendererFactory.registerRenderer("freemarker", freemarkerRenderer);
}
But I cannot figure out where I put this code. What is the normal spot you register this code on/which class do I extend and overwrite? Thanks for your help.
hi,I figured out just now
this is my project herichy, I use java based config, and webconfig is just like web.xml
#Bean
public TilesConfigurer tilesConfigurer() {
TilesConfigurer tilesConfigurer = new TilesConfigurer();
tilesConfigurer.setDefinitions("/WEB-INF/views/tiles/tiles.xml");
tilesConfigurer.setCheckRefresh(true);
tilesConfigurer.setTilesInitializer(new pringCompleteAutoloadTilesInitializer());
return tilesConfigurer;
}
notice tilesConfigurer.setTilesInitializer(new pringCompleteAutoloadTilesInitializer());
this is SpringCompleteAutoloadTilesInitializer
public class SpringCompleteAutoloadTilesInitializer extends CompleteAutoloadTilesInitializer
{
#Override
protected AbstractTilesContainerFactory createContainerFactory(ApplicationContext context)
{
return new SpringCompleteAutoloadTilesContainerFactory();
}
}
and that SpringCompleteAutoloadTilesContainerFactory is where you wanna put you code
you can check my project here git project
This code goes into your TilesContainerFactory implementation.
(which usually extends BasicTilesContainerFactory).

Categories