SpringBoot Tomcat Embedded Global JNDI Resource - java

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!

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

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!

AWS Lambda + Spring, how to load application.yml

I have problem with customizing API gateway domain, for my restful app deployed on AWS lambda. Customized domain, works this way, that depending on basePath it chooses different APIs which finally touches Lambda. For example:
api.mycustomdomain.com/view/ping -> goes to application view with path /view/ping
api.mycustomdomain.com/admin/ping -> goes to application admin with path /admin/ping
I am using this example as boilerplate: https://github.com/awslabs/aws-serverless-java-container/tree/master/samples/spring/pet-store
What I would like to achieve is handler which depending on Host header strips prefix from request path.
I have prepared following application.yml file:
server:
contextPath: "/view"
productionHost: "api.mycustomdomain.com"
The problem/question is. How can I now load those into my Lambda function? Here is my naive try:
public class LambdaHandler implements RequestHandler<AwsProxyRequest, AwsProxyResponse> {
SpringLambdaContainerHandler<AwsProxyRequest, AwsProxyResponse> handler;
boolean isinitialized = false;
#Value("${server.contextPath}")
private String prefix;
#Value("${server.productionHost}")
private String productionHost;
public AwsProxyResponse handleRequest(AwsProxyRequest awsProxyRequest, Context context) {
if(awsProxyRequest.getHeaders().get("Host").equals(productionHost))
awsProxyRequest.setPath(awsProxyRequest.getPath().substring(prefix.length()));
if (!isinitialized) {
isinitialized = true;
try {
handler = SpringLambdaContainerHandler.getAwsProxyHandler(PingPongApp.class);
} catch (ContainerInitializationException e) {
e.printStackTrace();
return null;
}
}
return handler.proxy(awsProxyRequest, context);
}
}
Obviously this doesn't work, LambdaHandler is working out of Spring context.
Any ideas how can I deal with that?
It seems you can not load those properties. Follow either of the 2 options given below.
1> You can add following bean in your configuration and that way you can autowire strings and use the way you are already using
#Bean
public static PropertySourcesPlaceholderConfigurer propertyConfigInDev() {
return new PropertySourcesPlaceholderConfigurer();
}
2>
public AwsProxyResponse..{
#Autowired
private Environment env;
..
public AwsProxyResponse handleRequest{
..
String contextPath = env.getRequiredProperty(“server.contextPath”));
...
}
}

How to deliver Realm with the war with Tomcat?

we have a simple web application running on Tomcat 7.0.56. Now we want to use our
own realm for authentication.
public class SpecialAuth extends DataSourceRealm{
#Override
public Principal authenticate(String username, String credentials){
....
}
}
This is defined in the /META-INF/context.xml inside the war
<Context>
<Realm className="some.package.SpecialAuth" dataSourceName="jdbc/MySQL" />
</Context>
Where to put the SpecialAuth.class?
What we expected was simply to have the SpecialAuth.class inside our war but then we're getting folling exception on startup
Caused by: java.lang.ClassNotFoundException: some.package.BackOfficeAuth
at java.net.URLClassLoader$1.run(URLClassLoader.java:366)
....
If we make a jar, putting it into $TOMCAT/lib everything works fine.
But this CAN'T be the solution! That would mean every time I work on this class(es) I have to touch my tomcat server and can't use the normal deployment.
How can I use the build-in authentication mechanism without touching the tomcat all the timeß
As you said I don't like your answers :) So what I did (and I'm 100% sure that you don't like it) was to set the realm on the dirties possible way BUT now I can run it on a ontouched tomcat. After 163 acceptance tests nothing seems to break.
public final class ContextListener implements ServletContextListener {
#Override
public void contextInitialized(ServletContextEvent event) {
ServletContext servletContext = event.getServletContext();
TomcatContextManipulator tomcat = new TomcatContextManipulator(servletContext);
tomcat.applyRealm(new MyOwnRealm());
}
}
.
public class TomcatContextManipulator {
private static final String EXPEXCTED_TOMCAT_VERSION = "7.0.52.0";
private ApplicationContextFacade servletContext;
/**
* #param servletContext must be of type {#link ApplicationContextFacade}
*/
public TomcatContextManipulator(ServletContext servletContext) {
checkTomcatVersion();
ensureEquals(servletContext.getClass(), ApplicationContextFacade.class, "class of servletContext");
this.servletContext = (ApplicationContextFacade) servletContext;
}
/**
* checks if the correct version of tomcat is in use, throws {#link IllegalStateException} if not
*/
private void checkTomcatVersion() {
// we use several internal parts of tomcat (for example with reflection)
// by doing this we bind ourself hardly to a explicit version
ensureEquals(EXPEXCTED_TOMCAT_VERSION, ServerInfo.getServerNumber(), "Tomcat-Server-Version");
}
/**
* overrides the existing realm with the given on
*/
public void applyRealm(Realm realm) {
ensureNotNull(realm, "realm");
ApplicationContext applicationContext = (ApplicationContext) ReflectionUtil.get(servletContext, "context");
StandardContext standardContext = (StandardContext) ReflectionUtil.get(applicationContext, "context");
standardContext.setRealm(realm);
}
}
Note:
Reflection.get() returns the value of the (private) instance variable of the given object
ensure...() is like assert... but it throws Exception

How to dynamically load OSGI fragment programmatically from within Java?

How can I load an OSGI fragment, along with all of the resources within its bundle (config files, etc), programmatically from Java?
I would like to access the resources via getResource() as if they were in the internal Jar.
First, you'll need a handle to bundle context. From thereon you can listen to bundle references, there get a Bundle instance and use its classloader to do what you want.
Example that uses a Servlet to hook into bundle context and ServiceTracker to listen for osgi bundles appearing
public class AnotherHelloWorldServlet extends HttpServlet {
private MessageService service = null;
private BundleContext context;
#Override
public void init(final ServletConfig config) throws ServletException {
super.init(config);
context = (BundleContext) config.getServletContext().getAttribute("osgi-bundlecontext");
final AnotherHelloWorldServlet servlet = this;
ServiceTracker tracker = new ServiceTracker(context,
MessageService.class.getName(), null) {
#Override
public Object addingService(final ServiceReference sref) {
log.infof("Adding service: %s to %s", sref, servlet);
service = (MessageService) super.addingService(sref);
return service;
}
#Override
public void removedService(final ServiceReference sref, final Object sinst) {
super.removedService(sref, service);
log.infof("Removing service: %s from %s", sref, servlet);
service = null;
}
};
tracker.open();
}
}
with a ServiceReference you can call getBundle() to get a bundle instance, and for that, getResource() to get what you asked for.
If you cannot use a servlet or similar (=if you are outside Java EE/servlet container), there aren't that many standard ways to hook into the bundle context that I know of. In that case you might need to resort to BundleContextProvider hack to get it.

Categories