How to get the right manifest from project with multiple modules? [JAVA] - java

I want to get to read from the Manifest of my Jar file to get the SVNVersion info from there and show it in the UI.
I work on a multi-module project that includes a client, a server running on tomcat, some modules of the client and its dependencies.
I wrote the code to access the current thread's manifest and get it's attributes.
Manifest mf = new Manifest();
mf.read(Thread.currentThread().getContextClassLoader().getResourceAsStream("META-INF/MANIFEST.MF"));
Attributes atts = mf.getMainAttributes();
System.out.println(atts.values());
System.out.println("Version: " + atts.getClass());
System.out.println("Revision: " + atts.get("SCM-Revision"));
String scm = (String) atts.get("SCM-Revision");
String revision = (String) atts.get("Revision-Number");
return revision + scm;
}
The only thing is that it gets Null values returned because it get's the manifest of a dependency from the local repository that doesn't have the information i need inside of it.
Is there a solution to my problem? to specify a module from the project in a way so it know's to get IT's jar's Manifest and not the one from the dependency?
Thank you!

(I deleted my previous answer because it was buggy and unaccurate)
This is the procedure I used to have to get the proper manifest from any class in the classpath: It consists of getting it through a specific URL:
private static URL getManifestUrlForClass(Class<?> cl)
throws MalformedURLException
{
URL url=cl.getResource(cl.getSimpleName() + ".class");
String s=url.toString();
return new URL(s.substring(0, s.length() - (cl.getName() + ".class").length()) + "META-INF/MANIFEST.MF");
To use it from your code, just replace the first line:
mf.read(getManifestUrlForClass(MyClass.class).openStream());

You get a null because manifests are disabled in maven by default and what you get are values from the Jdk (jar containing the Thread-classfile). You either read
this.getClass().getResourceAsStream("/META-INF/.../pom.properties")
or add the capability to create a manifest (depends on the modul packaging you have to pick the correct build-plugin for that).
Warning: "/META-INF/.../pom.properties" might have the same path in different Jars. Choose the Class first to determin what pom.properties you like to read.

Related

How can I create executable jars with embedded tomcat 9?

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

How to load one of same named resources from different jars in a classpath?

Let's say there is a jar main.jar which depends on two other jars - dep1.jar and dep2.jar. Both dependencies are in a classpath in MANIFEST.MF of main.jar. Each of dependency jars has a directory foo inside with a file bar.txt within:
dep1.jar
|
\--foo/
|
\--bar.txt
dep2.jar
|
\--foo/
|
\--bar.txt
Here is a main class of main.jar:
public class App
{
public static void main( String[] args ) {
ApplicationContext ctx = new StaticApplicationContext();
Resource barResource = ctx.getResource("classpath:foo/bar.txt");
}
}
Which of two bar.txt files will be loaded? Is there a way to specify in a resource URL a jar the file should be loaded from?
Which one you get is undefined. However, you can use
Resource[] barResource = ctx.getResources("classpath*:foo/bar.txt");
to get them both (all). The URL in the Resource will tell you which jar they are in (though I don't recommend you start programming based on that information).
Flip a quarter, that's the one you'll get. Most likely, it will be the one highest alphabetically, so in your case the one inside dep1.jar. The files both have identical classpaths (foo.Bar), and while this should look to throw a compile time exception, it will not because it will just package both jars up and not try to compile/look at the (this specific file) file as it is a .txt file.
You wouldn't expect a compile time exception as resource loading is a run time process.
You can't specify which jar the resource will come from in code, and this is a common issue, particularly when someone bundles something like log4j.properties into a jar file.
What you can do is specify the order of jars in your classpath, and it will pick up the resource from the first one in the list. This is tricky in itself as when you are using something like ivy or maven for classpath dependencies, you are not in control of the ordering in the classpath (in the eclipse plugins at any rate).
The only reliable solution is to call the resources something different, or put them in separate packages.
The specification says that the first class/resource on the class path is taken (AFAIK).
However I would try:
Dep1Class.class.getResource("/foo/bar.txt");
Dep2Class.class.getResource("/foo/bar.txt");
As Class.getResource works cannot take resources from another jar, as opposed to the system class loader.
With a bit of luck, you will not need to play with ClassLoaders and hava a different class loader load dep2.jar.
As #Sotirios said, you can get all resources with the same name using ctx.getResources(...), code such as :
ApplicationContext ctx = new StaticApplicationContext();
Resource[] resources = ctx.getResources("classpath*:/foo/bar.txt");
for (Resource resource : resources) {
System.out.println("resource file: " + resource.getURL());
InputStream is = new FileInputStream(resource.getFile());
if (is == null) {
System.out.println("resource is null");
System.exit(-1);
}
Scanner scanner = new Scanner(is);
while(scanner.hasNextLine()) {
System.out.println(scanner.nextLine());
}
}

Building a ServiceLoader file with gradle: howto?

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.

From within a custom Maven plugin, is it possible to know if Maven was invoked with '-f'?

Maven can be invoked with a -f option which allows you to specify an arbitrary pom.xml file.
From within a custom Maven plugin mojo, is it possible to determine whether Maven was invoked this way or using the default pom.xml from the current working directory?
I'm looking for something along the lines of:
this.project.isInvokedWithNonDefaultPom()
The Maven source code related to this option are:
./maven-embedder/src/main/java/org/apache/maven/cli/CLIManager.java
./maven-embedder/src/main/java/org/apache/maven/cli/MavenCli.java
(Look for keyword ALTERNATE_POM_FILE)
The source code can be obtained via git:
git clone https://git-wip-us.apache.org/repos/asf/maven.git
One part of the code uses following
if (commandLine.hasOption(CLIManager.ALTERNATE_POM_FILE))
Never tried this before but maybe there's a way you can do the same via plugin Mojo
Further to gerrytan's answer, there is an object called MavenExecutionRequest which gets populated with commandline information by org.apache.maven.cli.MavenCli, and which is available inside a plugin Mojo, via MavenSession.getRequest().
However, as the following code snippet from inside org.apache.maven.cli.MavenCli shows, the fact of being invoked with '-f' or not is lost, as the Pom file gets fully resolved as part of populating the MavenExecutionRequest.
So the answer is: No, it is not possible to know, from within a Maven plugin mojo, how the pom.xml was specified.
private MavenExecutionRequest populateRequest( CliRequest cliRequest )
{
MavenExecutionRequest request = cliRequest.request;
.....
String alternatePomFile = null;
if ( commandLine.hasOption( CLIManager.ALTERNATE_POM_FILE ) )
{
alternatePomFile = commandLine.getOptionValue( CLIManager.ALTERNATE_POM_FILE );
}
.....
if ( alternatePomFile != null )
{
File pom = resolveFile( new File( alternatePomFile ), workingDirectory );
request.setPom( pom );
}
else
{
File pom = modelProcessor.locatePom( baseDirectory );
if ( pom.isFile() )
{
request.setPom( pom );
}
}
.....
}
Version is maven-embedder 3.0.x

SCons not picking up all class files when creating a jar

SCons neophyte here. I am using it(version 2.0) to create a jar as follows:
compiled_classes = env.Java \
(target = compiled_classes_dir,
source = source_tld,
JAVAVERSION='1.6',
JAVACLASSPATH=['source_tld/libs/' +
file.name
for file in
Glob('source_tld/' +
'libs/*.jar')])
new_jar = env.Jar(target = jar_name,
source = compiled_classes_dir)
I am seeing an issue wherein class files belonging to classes that have inner classes(which when compiled into class files have a $ in the name) are not being handled properly i.e. they do not get included in the generated JAR. Any suggestions to address this would be greatly appreciated. TIA.
PS: This suggestion to add JAVAVERSION didn't seem to help.
Since SCons is incorrectly calculating the output classes I would suggest this workaround.
compiled_classes = env.Java \
(target = compiled_classes_dir,
source = source_tld,
JAVAVERSION='1.6',
JAVACLASSPATH=['source_tld/libs/' +
file.name
for file in
Glob('source_tld/' +
'libs/*.jar')])
#workaround to make sure classes are cleaned
env.Clean(compiled_classes, env.Dir(compiled_classes_dir))
# its important to set the JARCHDIR or the Jar command will not be run
# from the correct location if you want an executable Jar add the manifest here
new_jar = env.Jar(target = jar_name,
source = [compiled_classes_dir], JARCHDIR='$SOURCE')

Categories