How to cleanly undeploy Spring Boot/Jersey war in tomcat - java

Consider a very simple Spring Boot/Jersey application setup:
Generate a fresh Spring Boot application using Initializr and select the Jersey dependency (in my case, I prefer the Gradle setup).
Add simple Controller and Configuration classes:
JerseyConfig.java
package com.example.unload;
import org.glassfish.jersey.server.ResourceConfig;
import org.springframework.context.annotation.Configuration;
#Configuration
public class JerseyConfig extends ResourceConfig {
JerseyConfig() { register(TestController.class); }
}
TestController.java
package com.example.unload;
import org.springframework.stereotype.Component;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
#Component #Path("test")
public class TestController {
#GET public String test() {
return "test";
}
}
Run the war task and deploy the WAR to Tomcat (8.5 in my case).
Whenever I want to undeploy (e.g. for re-deployment) the WAR, I get the following error message
Oct 23, 2017 8:13:11 AM org.apache.catalina.startup.ExpandWar deleteDir
FATAL: […\apache-tomcat-8.5.20\webapps\unload-0.0.1-SNAPSHOT\WEB-INF\lib] could not be completely deleted. The presence of the remaining files may cause problems
The culprit is the jersey-server-2.25.1.jar in the lib directory, to which apparently some classloader must have a reference. I also cannot manually delete the file, because Java holds a lock on it.
Interestingly, after manually executing a GC run (externally through jvisualvm), I am able to delete the jersey-server.jar.
So, I suspect that a stream is not properly closed (but does get closed during finalization).
Of course, manually performing a GC run is not an option in a production environment.
I tried to call System.gc() in the contextDestroyed callback of the ServletContextListener, but this gets called to early when there are still live references.
I am also aware of the antiResourceLocking option in the context.xml file, but this creates copies of the whole WAR content in a temp directory, without ever removing it (which might be the job of a cron job). However, this feels more like a work-around and not a productive solution.
A third possible option is to extract all Spring, Jersey and related dependencies and move it to the Tomcat's shared lib directory, but I prefer to bundle all necessary dependencies to ease deployment for customers.
What other options do I have, and what is the root cause?
I have not found any reference to a bug in neither Tomcat, Spring, nor Jersey.
I could reproduce this issue with Jersey 2.26 and jersey-hk2 2.26.

Related

Can I use an instance of a WebMvcConfigurer in another module?

I've written an interceptor to generate service logs for a SpringBoot Java Rest API. I have the code below to define the custom WebMvcConfigurer:
package com.gem.common.interceptors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
#Configuration
public class InterceptorConfig implements WebMvcConfigurer {
#Autowired
LoggerInterceptor logInterceptor;
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(logInterceptor);
}
}
I'd like to use this InterceptorConfig across different modules. Can I package it and use it or do I need to define it in each module?
I suppose with "other modules" you are asking if you could make that code available to other spring boot applications too?
If that's the case - then: yes you can package it in a jar and add it as a dependency to all your other modules. I'll post the way to do this just below, however - just to warn you - if it's just for that simple class, the solution is not going to be worth it.
In short, what you'd need to do is to create your own external artifact (this usually is done via maven or gradle. You create a new maven project for your shared code. It will need to depend on some base libraries so that you have your #Configuration annotation available. Put your class as described in that project, and create a file src/main/resources/META-INF/spring.factories file. There you'll need to point to that class.
Then you build that project and upload the resulting jar to a package repository. Once that's done, you can add it as a dependency to your project. At startup, Spring boot will find the spring.factories file and automatically include the classes that are mentioned there in its initialization.
Please also note, that this is just a high level explanation and you will need more details. Spring has good documentation on this use case and they also have a demo project to show this extension mechanism.

Wildfly: web service deployed but unavailable

Problem description:
I want to deploy a web service through Wildfly and it gets deployed, but I cannot access it through a web browser.
This is what I do:
I run standalone.sh
I deploy the project using mvn clean package wildfly:deploy
Then I try to access my webservice through a web browser at http://localhost:8080/lab-ear/Hello?wsdl but I get only: "404 - Not Found"
Fragment of maven logs while deploying.
Here are logs from Wildfly server when it gets initialized.
And here Wildfly logs during deploy.
Other details:
I've done another project where the web service worked, but there were other problems, so I started everything from scratch.
This image is the comparison of structures of these two projects. On the left the old project and on the right the new project.
The important thing is that in the new project I don't get the web directory.
Maybe related problem:
Wildfly : application deployed but not running
Also, is it required to use Intellij IDEA in such projects?
EDIT
In my EJB module I have a class Hello in package pl.edu.agh.soa
I tried to change from this:
#Stateless
#WebService
public class Hello {
...
to:
#Stateless(name = "Hello")
#WebService(name = "HelloService")
public class Hello {
...
But it also doesn't work.
I was able to get a simple "HelloWorld" type JAX-WS service running with just the code:
import javax.ejb.Stateless;
import javax.jws.WebMethod;
import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;
#WebService
#SOAPBinding(style = SOAPBinding.Style.RPC)
#Stateless
public class HelloWorld {
#WebMethod
public String sayHello( String name ) {
return "Hello " + name;
}
}
From this I'm able to access the URL http://localhost:8080/<web-app-name>/HelloWorld?wsdl. This is in Wildfly 18.
The issue is that you don't specify a #WebMethod. While the class is marked correctly there isn't anything to "run" in it.
Additionally, while it does work with the #Stateless EJB annotation that isn't required for JAX-WS but may be required for your code.
Intellij IDEA is not required at all.
I think your issue is the missing web directory. Without WEB-INF/web.xml the app server won't know what it is supposed to serve.
Problem solved. I think I had unnecessarily changed too many dependencies' versions in pom.xml files.
Also, in main pom.xml file only <version.wildfly.maven.plugin> needed upgrading from 1.0.2.Final to 2.0.2.Final, but earlier I had also changed other plugins' versions.

What is best way to externalize error messages(resource bundles) out of src code in spring and springboot?

I am looking for best way to externalize my validation error messages out of my src code in spring and spring boot application, in order to avoid build/deployment on each time the error messages changes. Is there possibly any such ways to achieve it?
You can maintain all the validation error or success messages in a properties file. If you want to externalize, you can place the properties file outside the spring boot jar file. It is not necessary to put the configuration inside jar. I provide below the code snippet to achieve it.
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.annotation.PropertySources;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
#Configuration
#PropertySources({
#PropertySource("file:config/other-config.properties"),
#PropertySource("file:config/app-config.properties")
})
public class ExternalConfig {
#Bean
public static PropertySourcesPlaceholderConfigurer propertyConfigInDev() {
return new PropertySourcesPlaceholderConfigurer();
}
}
In the above code, in the line #PropertySource("file:config/app-config.properties"), config is the name of the folder or directory which contains many properties files like "app-config.properties". For better understanding, I provide below the image, external config file and spring boot jar will look like this finally.
The default resource bundle framework assumes your resource bundles are property files in your resources folder. So they are packaged inside your jar file as part of your build process.
However you can implement your own ResourceBundle loading:
https://docs.oracle.com/javase/tutorial/i18n/resbundle/control.html
You can then opt to use other mechanisms, including using a database instead of property files (with a TTL to cache messages for a specific period of time). This way you don't even have to restart the application when a message changes.

Spring Dependency Injection and Plugin Jar

I have web application running with a default impl of a backend service. One should be able to implement the interface and drop the jar into the plugins folder (which is not in the apps classpath). Once the server is restarted, the idea is to load the new jar into the classloader, and have it take part in dependency injection. I am using Spring DI using #Autowired. The new plugin service impl will have #Primary annotation. So given two impls of the interface, the primary should be loaded.
I got the jar loaded into the classloader and can invoke the impl manually. But I haven't been able to get to to participate in the Dependency Injection, and have it replace the default impl.
Here's a simplified example:
#Controller
public class MyController {
#Autowired
Service service;
}
//default.jar
#Service
DefaultService implements Service {
public void print() {
System.out.println("printing DefaultService.print()");
}
}
//plugin.jar not in classpath yet
#Service
#Primary
MyNewService implements Service {
public void print() {
System.out.println("printing MyNewService.print()");
}
}
//For lack of better place, I loaded the plugin jar from the ContextListener
public class PluginContextLoaderListener extends org.springframework.web.context.ContextLoaderListener {
#Override
protected void customizeContext(ServletContext servletContext,
ConfigurableWebApplicationContext wac) {
System.out.println("Init Plugin");
PluginManager pluginManager = PluginManagerFactory.createPluginManager("plugins");
pluginManager.init();
//Prints the MyNewService.print() method
Service service = (Service) pluginManager.getService("service");
service.print();
}
}
<listener>
<listener-class>com.plugin.PluginContextLoaderListener</listener-class>
</listener>
Even after I have loaded the jar into the classloader, DefaultService is still being injected as service. Any idea how I get the plugin jar to participate into the spring's DI lifecycle?
Edited:
To put it simply, I have a war file that has a few plugin jars in a plugins directory inside the war. Based on a value from a configuration file that the app looks at, when the app is started, I want to load that particular plugin jar and run the application with it. That way, I can distribute the war to anyone, and they can choose which plugin to run based on a config value without having to to repackage everything. This is the problem I am trying to solve.
It seems like all You need is to create the Spring ApplicationContext properly. I think it's possible without classpath mingling. What matters most are the locations of the Spring configuration files within the classpath. So put all Your plugin jar's into WEB-INF/lib and read on.
Let's start with the core module. We'll make it to create it's ApplicationContext from files located at classpath*:META-INF/spring/*-corecontext.xml.
Now we'll make all plugins to have their config files elsewhere. I.e. 'myplugin1' will have its config location like this: classpath*:META-INF/spring/*-myplugin1context.xml. And anotherplugin will have the configs at classpath*:META-INF/spring/*-anotherplugincontext.xml.
What You see is a convension. You can also use subdirectiries if You like:
core: classpath*:META-INF/spring/core/*.xml
myplugin1: classpath*:META-INF/spring/myplugin1/*.xml
anotherplugin: classpath*:META-INF/spring/anotherplugin/*.xml
What matters is that the locations have to be disjoint.
All that remains is to pass the right locations to the ApplicationContext creator. For web applications the right place for this would be to extend the ContextLoaderListener and override the method customizeContext(ServletContext, ConfigurableWebApplicationContext).
All that remains is to read Your config file (its location can be passed as servlet init parameter). Than You need to construct the list of config locations:
String locationPrefix = "classpath*:META-INF/spring/";
String locationSiffix = "/*.xml";
List<String> configLocations = new ArrayList<String>();
configLocations.add(locationPrefix + "core" + locationSiffix);
List<String> pluginsTurnedOn = getPluginsTurnedOnFromConfiguration();
for (String pluginName : pluginsTurnedOn) {
configLocations.add(locationPrefix + pluginName + locationSiffix);
}
applicationContext.setConfigLocations(configLocations.toArray(new String[configLocations.size()]));
This way You can easily manage what is and what is not loaded into Spring ApplicationContext.
Update:
To make it work there's one more hidden assumption I made that I'm about to explain now. The base package of the core module and each plugin should also be disjoint. That is i.e.:
com.mycompany.myapp.core
com.mycompany.myapp.myplugin1
com.mycompany.myapp.anotherplugin
This way each module can use <context:componet-scan /> (on equivalent in JavaConfig) easily to add classpath scanning for it's own classes only. The core module should not contain any package scanning of any plugin packages. The plugins should extend configuration of ApplicationContext to add their own packages to classpath scanning.
If you restart the server, I see no reason why you can't just add the JAR to the WEB-INF/lib and have it in the CLASSPATH. All the complication of a custom class loader and context listener goes away, because you treat it just like any other class under Spring's control.
If you do it this way because you don't want to open or modify a WAR, why not put it in the server /lib directory? Let the server class loader pick it up. This makes all plugin classes available to all deployed apps.
The answer depends on how important the separate /plugin directory is. If it's key to the solution, and you can't add the JAR to the server's /lib directory, then that's that. I've got nothing. But I think it'd be worthwhile to at least revisit the solution you have to make sure that it's the only way to accomplish what you want.

JAAS custom login module

I've got a custom login module in an ear on jboss. The ear's META-INF has a jboss-app.xml which points at a login-service.xml which contains an mbean that points to a login-config.xml which defines the custom login module.
The jboss.xml in the ear's META-INF uses the same security-domain as that defined for the login module in login-config.xml.
On making a call to an EJB within that ear I don't see my custom login module running the login and commit methods like I've seen when I've implemented this elsewhere in the past.
I have trace log4j setup for org.jboss.security and I see nothing on making the EJB call. The EJB call is successful even though I'm not authenticated.
I can't work out why my login module isn't being called or how to debug the JAAS decision process. Any ideas? Thanks.
What you've done so far is creating the login module, now you need to tell your EJB to use it:
import javax.annotation.security.RolesAllowed;
import javax.ejb.Stateless;
import org.jboss.ejb3.annotation.SecurityDomain;
#Stateless
#SecurityDomain("mySecurityDomain")
#RolesAllowed({"guestRole", "userRole", "adminRole"})
public class SecureBean implements Secure {
I found the problem, with some help.
My jboss.xml was in the META-INF folder for the ear, it should have been in the META-INF folder for the EJB jar.
Apparently the jboss file in the ear's META-INF folder would have been jboss-app.xml, which I guess would be the clue to remember for next time.

Categories