Has anyone tried the plugin to build an executable war/jar using Tomcat 9?
I attempted to do so however ran into:
Exception in thread "main" java.lang.NoSuchMethodError: org.apache.catalina.startup.Catalina.setConfig(Ljava/lang/String;)V
at org.apache.tomcat.maven.runner.Tomcat7Runner.run(Tomcat7Runner.java:240)
at org.apache.tomcat.maven.runner.Tomcat7RunnerCli.main(Tomcat7RunnerCli.java:204)
I looked at the source and changed Catalina.setConfig() to Catalina.setConfigFile() based on docs here. After doing so the .extract dir is just empty:
use extractDirectory:.extract populateWebAppWarPerContext
warValue:ROOT.war|ROOT populateWebAppWarPerContext
contextValue/warFileName:ROOT/ROOT.war webappWarPerContext entry
key/value: ROOT/ROOT.war expand to file:.extract/webapps/ROOT.war
Exception in thread "main" java.lang.Exception: FATAL: impossible to
create directories:.extract/webapps at
org.apache.tomcat.maven.runner.Tomcat7Runner.extract(Tomcat7Runner.java:586)
at
org.apache.tomcat.maven.runner.Tomcat7Runner.run(Tomcat7Runner.java:204)
at
org.apache.tomcat.maven.runner.Tomcat7RunnerCli.main(Tomcat7RunnerCli.java:204)
.... although there is a ROOT.war, server.xml, web.xml in the *-exec-war.jar.
Is there a better way to be creating exec-jars with embedded tomcat 9?
For those looking for a solution it was fairly straight forward to checkout the code for the plugin and make a few changes to get this to work. Namely:
Update POM to change the depends to Tomcat 9
Fix compile errors which generally stem from deprecated methods. The lookup on these methods can be found here. For example:
- container.setConfig( serverXml.getAbsolutePath() );
+ container.setConfigFile( serverXml.getAbsolutePath() );
... and ...
- staticContext.addServletMapping( "/", "staticContent" );
+ staticContext.addServletMappingDecoded( "/", "staticContent" );
There are a few others but generally not difficult to resolve. After doing so I updated my app's pom to use the modified version and was able to generate a Tomcat 9 exec jar.
I would love to hear what others are doing here. I know some are programmatically initializing Tomcat via a new Tomcat() instance however curious what other solutions exist ready made. Thanks
For future searchs, one solution is to use the DirResourceSet or JarResourceSet.
String webAppMount = "/WEB-INF/classes";
WebResourceSet webResourceSet;
if (!isJar()) {
webResourceSet = new DirResourceSet(webResourceRoot, webAppMount, getResourceFromFs(), "/");
} else {
webResourceSet = new JarResourceSet(webResourceRoot, webAppMount, getResourceFromJarFile(), "/");
}
webResourceRoot.addJarResources(webResourceSet);
context.setResources(webResourceRoot);
public static boolean isJar() {
URL resource = Main.class.getResource("/");
return resource == null;
}
public static String getResourceFromJarFile() {
File jarFile = new File(System.getProperty("java.class.path"));
return jarFile.getAbsolutePath();
}
public static String getResourceFromFs() {
URL resource = Main.class.getResource("/");
return resource.getFile();
}
When add the webapp, use root path "/" for docBase:
tomcat.addWebapp("", "/")
Credits for:
https://nkonev.name/post/101
Related
please i need some help on the error. I have a javafx project with the following jar files
fontawesome-fx-8.1.jar
sqlite-jdbc-3.8.10.1.jar
controlsfx-8.40.11.jar
sqljdbc42.jar
jfoenix-1.0.0.jar
POI-3.17.jar
poi-examples-3.17.jar
poi-excelant-3.17.jar
poi-ooxml-3.17.jar
poi-ooxml-schemas-3.17.jar
poi-scratchpad-3.17.jar
And what i need is to import some excel data to it. I have imported the above jar files
But unfortunately when i try to run the project i get the error:
Caused by: java.lang.NoSuchMethodError: org.apache.poi.ss.usermodel.Workbook.sheetIterator()Ljava/util/Iterator;
at app.controllers.ContentAreaController.nextActionReport(ContentAreaController.java:734)
... 62 more
I have tried googling and what suggested is i change the versions of poi lib files but no luck .Can anyone suggest me the solution as i have spent enough time on the issue
Promoting some comments to an answer - you have older Apache POI jars on your classpath. As per this POI FAQ - mixing POI jars between versions is not supported
What you need to do is just remove the older POI jars. I say just, since you didn't know you had them... Luckily, if you follow the code in this Apache POI FAQ it'll help you find where the older jars are coming from. Something like this when run on your problematic system should print out the names and locations of the older jars:
ClassLoader classloader =
org.apache.poi.poifs.filesystem.POIFSFileSystem.class.getClassLoader();
URL res = classloader.getResource(
"org/apache/poi/poifs/filesystem/POIFSFileSystem.class");
String path = res.getPath();
System.out.println("POI Core came from " + path);
classloader = org.apache.poi.POIXMLDocument.class.getClassLoader();
res = classloader.getResource("org/apache/poi/POIXMLDocument.class");
path = res.getPath();
System.out.println("POI OOXML came from " + path);
classloader = org.apache.poi.hslf.usermodel.HSLFSlideShow.class.getClassLoader();
res = classloader.getResource("org/apache/poi/hslf/usermodel/HSLFSlideShow.class");
path = res.getPath();
System.out.println("POI Scratchpad came from " + path);
Just identify the older jars you don't want, remove, and you should be set!
Gagravarr's answer https://stackoverflow.com/a/50472754/1497139 pointed to http://poi.apache.org/help/faq.html#faq-N10006
I modified the code to be used the JUnit Test to fix the same base issue with the following error messages:
java.lang.NoSuchMethodError: org.apache.poi.xssf.usermodel.XSSFCell.getCellTypeEnum()Lorg/apache/poi/ss/usermodel/CellType;
Which if first thought was an issue of deprecation. After fixing the deprecation i got:
java.lang.NoSuchMethodError: org.apache.poi.xssf.usermodel.XSSFCell.getCellType()Lorg/apache/poi/ss/usermodel/CellType;
which was caused by mixing Apache POI 3.12 and Apache POI 4.0.1 jars in the same project.
To avoid this for the future I created the following Unit test. You might want to adapt the version to your needs or skip the assertion at all while still debugging the issue.
JUnit Test to check POI versions of used classes.
/**
* get the path the given class was loaded from
*
* #param clazz
* #return the path
*/
public String getClassLoaderPath(Class<?> clazz) {
ClassLoader classloader = clazz.getClassLoader();
String resource = clazz.getName().replaceAll("\\.", "/") + ".class";
URL res = classloader.getResource(resource);
String path = res.getPath();
return path;
}
#Test
public void testPOI() {
Class<?>[] classes = {
org.apache.poi.poifs.filesystem.POIFSFileSystem.class,
org.apache.poi.ooxml.POIXMLDocument.class,
org.apache.poi.hslf.usermodel.HSLFSlideShow.class,
org.apache.poi.xssf.usermodel.XSSFCell.class,
org.apache.poi.ss.usermodel.CellType.class};
for (Class<?> clazz : classes) {
String path = getClassLoaderPath(clazz);
if (debug)
System.out.println(
String.format("%s came from %s", clazz.getSimpleName(), path));
assertTrue(path.contains("4.0.1"));
}
}
Ok, I'm sure this should be pretty easy, but I'm fairly new to Java (I'm more a .NET boy :P) and after following every single recommendation I found here to no success, I think it's time to step back and ask.
I'm trying to start a simple rmi project with a client, a server and a common project where common interfaces are defined. I've just implemented my server code, and when I try to run it to check if everything is fine, I get struck on a java.lang.ClassNotFoundException.
After following several answers on similar issues, I'm fair sure that my problem comes from rmiregistry running on a different location than my project.
I use following code to set registry codebase:
public class Utils {
public static final String CODEBASE = "java.rmi.server.codebase";
public static void setCodeBase(Class<?> c) {
String ruta = c.getProtectionDomain().getCodeSource().getLocation().toString();
String path = System.getProperty(CODEBASE);
if (path != null && !path.isEmpty()) {
ruta = path + " " + ruta;
}
System.setProperty(CODEBASE, ruta);
}
}
Then, I try to start my server code with this main class:
public class MainRegulador {
public static void main(String[] args) throws AccessException, RemoteException, NotBoundException {
Utils.setCodeBase(IRegulador.class);
Registry registro = null;
Remote proxy = null;
try {
Regulador myReg = new Regulador();
proxy = UnicastRemoteObject.exportObject(myReg, 36510);
registro = LocateRegistry.getRegistry();
registro.rebind("Distribuidor", proxy); //this is the line where exception is thrown
System.out.println("El Regulador está corriendo. Pulse ENTER para finalizar el proceso.");
System.in.read();
} catch(Exception ex) {
System.out.println("No se ha logrado inicializar el Registrador");
System.out.println(ex.getMessage());
} finally {
if (registro != null && proxy != null) {
registro.unbind("Distribuidor");
UnicastRemoteObject.unexportObject(proxy, true);
}
}
}
}
But when I run it, always get a java.lang.ClassNotFoundException at IRegulador interface.
Now the fun part:
I've printed to console java.rmi.server.codebase value, and it's pointing to bin folder of project where IRegulador interface is defined. (file:/F:/Practicas%20y%20dem%c3%a1s/Sistemas%20Distribuidos/common/bin/)
Obviously, that project is also set in the classpath of server project
(Regulador)
Workspace and rmiregistry are on different disks
Despite all, it doesn't seem a global classpath problem, as Utils class is on the same project as IRegulador interface, and it runs before the exception is thrown (as java.rmi.server.codebase is correctly set).
I've tried to set the classpath of rmiregistry before calling it (although it is directly discouraged on some answers), but nothing changed.
I've also tried to start rmiregistry.exe from Regulador project bin folder, but also seemed to don't change anything.
Coming from a .NET background, I've always found these classpath issues confusing, and this one is starting to consume much more time than I suspect it deserves. I'm in desperate need of help.
UPDATE: I'm starting to think that the problem is within the url it's passed to the codebase from IRegulador.class. If I paste it into windows explorer, the SO is unable to locate it, so I supose that it's being built with some structure problem that prevents the registry to reach the route:
file:/F:/Practicas%20y%20dem%c3%a1s/Sistemas%20Distribuidos/common/bin/
UPDATE2: I thought path route could be too complex, so I decided to simplify it and strip it from any non-straight character. Now codebase value is
file:/F:/Practicas/SD/common/bin/
However the problem persists, I don't know why rmiregistry is unable to reach that folder.
Then I decided to move the whole project to the same disk where rmiregistry is executed, and see if it changes anything. But nothing changed, same problem.
Ok, finally I got it working...
I've just copied rmiregistry.exe into the common/bin folder and launch it directly from there (previously just had called from there).
This seems to fix the problem with the routes (actually it makes the route available to the registry as it's on the same folder, probably all my codebase writting code is superflous now).
I'm using GDAL native library (C++ and it is installed in /usr/lib/java/gdal). I found a trick short time ago, to allow Tomcat can load the web application and this library (cannot use System.load() or System.loadLibrary() as all will return error)
Caused by: java.lang.UnsatisfiedLinkError: org.gdal.osr.osrJNI.new_SpatialReference__SWIG_1()J
Then I need to use a trick to add the library path to JVM when application starts:
final Field usrPathsField = ClassLoader.class.getDeclaredField("usr_paths");
usrPathsField.setAccessible(true);
// get array of paths
final String[] paths = (String[]) usrPathsField.get(null);
// check if the path to add is already present
for (String path : paths) {
if (path.equals(pathToAdd)) {
return;
}
}
//add the new path
final String[] newPaths = Arrays.copyOf(paths, paths.length + 1);
newPaths[newPaths.length - 1] = pathToAdd;
usrPathsField.set(null, newPaths);
This works well when the Tomcat starts with application, however, if I redeploy the application, it will return error:
Caused by: java.lang.UnsatisfiedLinkError: Native Library /usr/lib/java/gdal/libgdaljni.so already loaded in another classloader
I could not find any solution in StackOverflow, so I ask here if anyone can give some information. I also cannot change or add library path to environment variable or Tomcat folder, all should be done in Java code only.
So to avoid to add library to Tomcat/lib folder, I copy all the GDAL native folder to a temp directory with time stamp (e.g: /tmp/gdal_native/date.time), then I use the code above normally, except when it checks for the previous path, it will override with the new one.
String tmpTargetNativeFolderPath = "/tmp/gdal_native" + "/" + current date time
int i = 0;
// check if the path to add is already present
for (String path : paths) {
String pathFolder = StringUtils.substringBeforeLast(path, "/");
if (pathFolder.equals("/tmp/gdal_native")) {
// Override the old path with the new one
paths[i] = tmpTargetNativeFolderPath;
usrPathsField.set(null, paths);
return;
}
i++;
}
Then Classloader will load the library from another folder when the web application is redeployed without the error and the usrPathsField only contains one folder path to /tmp/gdal_native/timestamp.
My current java project is using methods and variables from another project (same package). Right now the other project's jar has to be in the classpath to work correctly. My problem here is that the name of the jar can and will change because of increasing versions, and because you cannot use wildcards in the manifest classpath, it's impossible to add it to the classpath. So currently the only option of starting my application is using the -cp argument from the command line, manually adding the other jar my project depends on.
To improve this, I wanted to load the jar dynamically and read about using the ClassLoader. I read a lot of examples for it, however I still don't understand how to use it in my case.
What I want is it to load a jar file, lets say, myDependency-2.4.1-SNAPSHOT.jar, but it should be able to just search for a jar file starting with myDependency- because as I already said the version number can change at anytime. Then I should just be able to use it's methods and variables in my Code just like I do now (like ClassInMyDependency.exampleMethod()).
Can anyone help me with this, as I've been searching the web for a few hours now and still don't get how to use the ClassLoader to do what I just explained.
Many thanks in advance
(Applies to Java version 8 and earlier).
Indeed this is occasionally necessary. This is how I do this in production. It uses reflection to circumvent the encapsulation of addURL in the system class loader.
/*
* Adds the supplied Java Archive library to java.class.path. This is benign
* if the library is already loaded.
*/
public static synchronized void loadLibrary(java.io.File jar) throws MyException
{
try {
/*We are using reflection here to circumvent encapsulation; addURL is not public*/
java.net.URLClassLoader loader = (java.net.URLClassLoader)ClassLoader.getSystemClassLoader();
java.net.URL url = jar.toURI().toURL();
/*Disallow if already loaded*/
for (java.net.URL it : java.util.Arrays.asList(loader.getURLs())){
if (it.equals(url)){
return;
}
}
java.lang.reflect.Method method = java.net.URLClassLoader.class.getDeclaredMethod("addURL", new Class[]{java.net.URL.class});
method.setAccessible(true); /*promote the method to public access*/
method.invoke(loader, new Object[]{url});
} catch (final java.lang.NoSuchMethodException |
java.lang.IllegalAccessException |
java.net.MalformedURLException |
java.lang.reflect.InvocationTargetException e){
throw new MyException(e);
}
}
I needed to load a jar file at runtime for both java 8 and java 9+. Here is the method to do it (using Spring Boot 1.5.2 if it may relate).
public static synchronized void loadLibrary(java.io.File jar) {
try {
java.net.URL url = jar.toURI().toURL();
java.lang.reflect.Method method = java.net.URLClassLoader.class.getDeclaredMethod("addURL", new Class[]{java.net.URL.class});
method.setAccessible(true); /*promote the method to public access*/
method.invoke(Thread.currentThread().getContextClassLoader(), new Object[]{url});
} catch (Exception ex) {
throw new RuntimeException("Cannot load library from jar file '" + jar.getAbsolutePath() + "'. Reason: " + ex.getMessage());
}
}
I am starting to switch from a well-known Java build system to Gradle to build all my projects, and after barely two hours into it I have already been able to publish a new version of one of my projects without a problem -- a breeze.
But now I encounter a difficulty. In short, I need to replicate the functionality of this Maven plugin which generates the necessary files for a ServiceLoader-enabled service.
In short: given a base class foo.bar.MyClass, it generates a file named META-INF/services/foo.bar.MyClass whose content is a set of classes in the current project which implement that interface/extend that base class. Such a file would look like:
com.mycompany.MyClassImpl
org.othercompany.MyClassImpl
In order to do this, it uses I don't know what as a classloader, loads the Class objects for com.myCompany.MyClassImpl or whatever and checks whether this class implements the wanted interface.
I am trying to do the same in Gradle. Hours of googling led me to this plugin, but after discussing with its author a little, it appears this plugin is able to merge such files, not create them. So, I have to do that myself...
And I am a real beginner both with Gradle and Groovy, which does not help! Here is my current code, link to the full build.gradle here; output (which I managed to get somehow; doesn't work from a clean dir) shown below (and please bear with me... I do Java, and I am final happy; Groovy is totally new to me):
/*
* TEST CODE
*/
final int CLASS_SUFFIX = ".class".length();
final URLClassLoader classLoader = this.class.classLoader;
// Where the classes are: OK
final File classesDir = sourceSets.main.output.classesDir;
final String basePath = classesDir.getCanonicalPath();
// Add them to the classloader: OK
classLoader.addURL(classesDir.toURI().toURL())
// Recurse over each file
classesDir.eachFileRecurse {
// You "return" from a closure, you do not "continue"...
if (!isPotentialClass(it))
return;
// Transform into a class name
final String path = it.getAbsolutePath();
final String name = path.substring(basePath.length() + 1);
final String className = name.substring(0, name.length() - CLASS_SUFFIX)
.replace('/', '.');
// Try and load it
try {
classLoader.loadClass(className);
println(className);
} catch (NoClassDefFoundError ignored) {
println("failed to load " + className + ": " + ignored);
}
}
boolean isPotentialClass(final File file)
{
return file.isFile() && file.name.endsWith(".class")
}
The output:
com.github.fge.msgsimple.InternalBundle
failed to load com.github.fge.msgsimple.bundle.MessageBundle: java.lang.NoClassDefFoundError: com/github/fge/Frozen
failed to load com.github.fge.msgsimple.bundle.MessageBundleBuilder: java.lang.NoClassDefFoundError: com/github/fge/Thawed
com.github.fge.msgsimple.bundle.PropertiesBundle$1
com.github.fge.msgsimple.bundle.PropertiesBundle
com.github.fge.msgsimple.provider.MessageSourceProvider
com.github.fge.msgsimple.provider.LoadingMessageSourceProvider$1
com.github.fge.msgsimple.provider.LoadingMessageSourceProvider$2
com.github.fge.msgsimple.provider.LoadingMessageSourceProvider$3
com.github.fge.msgsimple.provider.LoadingMessageSourceProvider$Builder
com.github.fge.msgsimple.provider.LoadingMessageSourceProvider
com.github.fge.msgsimple.provider.MessageSourceLoader
com.github.fge.msgsimple.provider.StaticMessageSourceProvider$Builder
com.github.fge.msgsimple.provider.StaticMessageSourceProvider$1
com.github.fge.msgsimple.provider.StaticMessageSourceProvider
com.github.fge.msgsimple.source.MessageSource
com.github.fge.msgsimple.source.MapMessageSource$Builder
com.github.fge.msgsimple.source.MapMessageSource$1
com.github.fge.msgsimple.source.MapMessageSource
com.github.fge.msgsimple.source.PropertiesMessageSource
com.github.fge.msgsimple.locale.LocaleUtils
com.github.fge.msgsimple.serviceloader.MessageBundleFactory
com.github.fge.msgsimple.serviceloader.MessageBundleProvider
:compileJava UP-TO-DATE
The problem is in the two first lines: Frozen and Thawed are in a different project, which is in the compile classpath but not in the classpath I managed to grab so far... As such, these classes cannot even load.
How do I modify that code so as to have the full compile classpath availabe? Is my first question. Second question: how do I plug that code, when it works, into the build process?
Here are some hints:
Create a new URLClassLoader, rather than reusing an existing one.
Initialize the class loader with sourceSets.main.compileClasspath (which is an Iterable<File>) rather than classesDir.
Turn the code into a Gradle task class. For more information, see "Writing a simple task class" in the Gradle User Guide.
Ideally, you'd use a library like ASM to analyze the code, rather than using a class loader. To avoid the case where you cannot load a class because it internally references a class that's not on the compile class path, you may want to initialize the class loader with sourceSets.main.runtimeClasspath instead.