Jruby trying to load local ruby gems instead of packaged ones - java

I'm seeing a weird issue in my jruby jar and I'm sure it's due to some configuration but I can't quite figure out what. The behavior I'm seeing is when my jruby jar goes to load a gem it looks for it in my local ruby envrionment instead of inside the jar itself. If I move my jar to a system that does not have a ruby envrionment then it uses the gems packaged in the jar.
Here is my boot rb file.
require 'rubygems'
app_jar_root = File.expand_path(File.join(File.dirname(__FILE__), ".."))
Dir["#{app_jar_root}/Project/**/*/"].each do |foldername|
$LOAD_PATH.unshift foldername[-1] == '/' ? foldername[0..-2] : foldername
end
# All support libraries required to be included
[
'java',
'yaml'
].each do |require_name|
require require_name
end
# All java imported namespaces
[
# 'java.sql.DriverManager'
].each do |namespace|
java_import namespace
end
# base app directory requires
Dir["#{app_jar_root}/Project/app/**/*.rb"].reject do |filename|
%w|file_to_exclude1.rb file_to_exclude2|.include? File.basename(filename)
end.each do |filename|
require File.basename(filename)
end
# Debugger.start
I can also post my warble.rb if that would be of any use but I'm assuming the issue is some configuration in boot.rb.

Turns out the issue was that my jruby jar was referencing the local GEM_PATH and GEM_HOMEvariables if they existed on a system. To fix this I had to redirect the variables to point inside the jar by reassigning them in the boot.rb file.
Here's an example of how I achieved this where app_jar_root is the root of the app folder in the jar.
ENV['GEM_HOME'] = "#{app_jar_root}/gems"
ENV['GEM_PATH'] = "#{app_jar_root}/gems"

Related

Reading resources inside dependency JAR gives NullPointerException

I have the following situation:
JAR A has JAR B as dependency
JAR B is packed with some resources that are loaded when JAR A calls specific methods of JAR B (loaded once and for all the lifecycle of JAR B calls)
I am using Java SE 11 with IntelliJ 2021.1.3
JAR B resources tree is something like the following:
- resources
- data
- file.txt
- tariffs
- folder1
- file.xslx
Resources are loaded through the following method:
private InputStream getPath(String nomeFile) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
return classLoader.getResourceAsStream(DATA_FOLDER_NAME.concat(File.separator).concat(nomeFile));
}
And then managed through a BufferedReader.
Everything works fine when running mvn test (or application tests) withing JAR B project or when consuming JAR B from JAR A in a Unix environment.
When consuming JAR B from JAR A in a Windows 10 environment the getPath method returns a null InpuStream object thus a NullPointerException from the BufferedReader:
java.lang.NullPointerException: null
at java.base/java.io.Reader.<init>(Reader.java:167)
at java.base/java.io.InputStreamReader.<init>(InputStreamReader.java:72)
...
I tried to change the File.separator to hardcoded "/" in the method and seems like everything works also on Windows, but is failing in other places (where resources are managed) since I suppose Paths need to be hand-fixed.
I tried to change the loader to: this.getClass().getResourcesAsStream(...) and other workaround with no luck.
My question is: is there a way to make the program work as expected also on Windows without changing the above code?
Are there any settings I am missing?
Thank you,
Alberto
Just came over this issue.
The fact was (and is) the usage of File.separator in a Windows environment to access resources inside a JAR.
This is because (as pointed here) inside a JAR file paths are resolved UNIX-style.
The only way, then, to consume resources that are packed within a JAR file used as dependency is to specify resources paths UNIX-style.
In my case, this mean replacing the File.separator (and all occurrences) with a "/".
The other issues that were arising by this replacement were due to an incomplete replace-all of the File.separator directive across the code.
java -classpath A.jar;B.jar x.y.z.Main
Would be what your command to run would look like on Windows. Possibly better to use the absolute path to those jars for fail-proof ops

Running an uber jar from sbt assembly results in error: Could not find or load main class

I have a spark job packaged as an uber-jar using the sbt assembly plugin.
The build.sbt specifies a runnable main to be the target of the resulting uber-jar
mainClass in assembly := Some("com.foo.Bar")
After the assembly is correctly created, running the intended command:
java -jar assembly.jar
results in
Error: Could not find or load main class com.foo.Bar
Using the an alternative method, like java -cp assembly.jar com.foo.Bar gives the same error message.
Then, I extracted the contents of the uber-jar in a new directory. I can see my com/foo/ directory and the Bar.class file.
From the root of the extracted directory I tried:
java -cp . com.foo.Bar
and I get a correct result.
Further trying to find the reason of the error, I tried:
java -verbose -jar assembly.jar
I can see the java core classes being loaded, but I don't see any of my packaged classes being loaded.
What can possibly be wrong here?
After an extensive investigation (read: pulling hairs out), it turns out that this behavior is the result of a rogue INDEX.LIST from one of the flattened jar files landing in the META-INF directory of the resulting uber-jar.
Following the JAR file spec, the INDEX.LIST, if present, dictates what packages from the Jar file are to be loaded.
To avoid this, we updated the mergeStrategy with a rule to avoid any pollution of the resulting META-INF directory:
case PathList("META-INF", xs # _*) => MergeStrategy.discard
This fixed the issue and returned my sanity.
Update:
After some extra searching, it turns out that the default merge strategy takes proper care of INDEX.LIST. This answer applies when the customized merge strategy contains cases that handle the META-INF pathSpec

Eclipse: Setting an environment variable that references a folder contained in a plugin

I have some Java code that wraps an existing native application and performs the following:
Takes some input from the user
Executes a native application providing as parameters the input taken in step 1
Performs some more operations on the output files produced in step 2
The native application in step 2 requires some dynamic libraries. So, under Run Configurations -> Environment I have set the following variables to reference the libraries.
DYLD_LIBRARY_PATH = ${project_loc}/path/to/libs
DYLD_FALLBACK_LIBRARY_PATH = ${project_loc}/path/to/libs
And so far it all works. Now I have packaged my code and the existing native application as an Eclipse plugin. Whenever I try to run the code inside the plugin I get the following error:
dyld: Library not loaded: libsrcml.dylib
Referenced from: workspace/Project/src/nativeApp
Reason: image not found
To my understanding, this happens because the environment variables I had set previously reference {$project_loc}, which is the location where my Eclipse project was stored. Now, my code is no longer contained in that project, but it is contained inside a plugin, so the path for the variables no longer works. Question is, how can I set a path that references a folder inside my plugin? Alternatively, is it possible to, somehow, load those variables dynamically inside my Java code?
The path variables are used to specify a fixed location in the file system.
To identify a resource in a plugin, I would use its URL
Case 1: Platform.getBundle("").getEntry("")
Bundle bundle = Platform.getBundle("your.bundle.id");
URL url = bundle.getEntry("yourDir/yourFile.txt");
File f = new File(FileLocator.resolve(url).toURI());
Case 2 : Platform URL to your resource:
url = new URL("platform:/plugin/your.bundle.id/yourDir/yourFile.txt");
File f = new File(FileLocator.resolve(url).toURI());
Thanks to Vogella for this tip.
However, for libraries in your plug-in it is a little bit different, as System.loadLibrary("libname") must be able to resolve your lib.
If you ship and use native libraries in your plug-in, please package your plugin as a directory, and not as a compressed jar file.
So edit your plug-in's MANIFEST.MF and set your Eclipse-BundleShape: dir
Eclipse-BundleShape: dir
Then, your plug-in will be packaged as a folder, and then it is your responsibility to make your Native libraries interacting. Usually this depends on how the native libraries are linking each other, and on how your Java-to-native framework is setting the search paths.
My simple solution, is putting all the native libraries to the root folder of the Eclipse executable, which is the Java execution directory, so that I can get that path using the "user.dir" environment variable as follows:
System.getProperty("user.dir");
Then, when all the natives are in the same folder, they can reference each other without problems.
Please, also check these resources:
this StackOverflow answer
this eclipse forum answer

What exactly is a class path in java?

I wrote a program that works on my laptop perfectly, but I really want it to work on a server that I have. Using NetBeans, I've clean and built the project. I copied the contents of the folder dist on my server but I cannot seem to get to work by using command
java -jar nameOfFile.jar
I get the error
java.lang.NoClassDefFoundError: org/....
I have been doing some reading and from what I gather is that I need to pretty much specify where the libraries that I've used are located. Well they are located in a subfolder called lib.
Question:
So what would I need to do in order to be able to run my jar?
CLASSPATH is an environment variable that helps us to educate the Java Virtual Machine from where it will start searching for .class files.
We should store the root of the package hierarchies in the CLASSPATH environment variables.
In case of adding or using jar libraries in our project, we should put the location of the jar file in the CLASSPATH environment variable.
Example: If we are using jdbc mysql jar file in our java project, We have to update the location of the mysql jar file in the CLASSPATH environment variable. if our mysql.jar is in c:\driver\mysql.jar then
We can set the classpath through DOS in Windows
set CLASSPATH=%CLASSPATH%;c:\driver\mysql.jar
In Linux we can do
export CLASSPATH=$CLASSPATH:[path of the jar]
Hope it helps!
Try that:
java -classpath "$CLASSPATH:nameOfFile.jar:lib/*" path.to.your.MainClass
What this does is setting the classpath to the value of $CLASSPATH, plus nameOfFile.jar, plus all the .jar files in lib/.
Classpath
A compiler(e.g. javac) creates from .java - .class files and JVM uses these .class files.
classpath - local codebase[About] - points on the root of source. classpath + import_path = full path
For example for MacOS
//full path
/Users/Application.jar/my/package/MainClass
//classpath
/Users/Application.jar
//import_path
my.package.MainClass
Android classpath
ANDROID_HOME/platforms/android-<version>/android.jar
//e.g
/Users/alex/Library/Android/sdk/platforms/android-23/android.jar
When you use a META-INF/MANIFEST.MF file to specify the Main-Class dependencies must be specified in the manifest too.
The -jar switch ignores all other classpath information - see the tools docs for more.
You need to set class path using
The below works in bash .
This is temporary
set CLASSPATH=$CLASSPATH=[put the path here for lib]
If you want it permanent then you can add above lines in ~/.bashrc file
export CLASSPATH=$CLASSPATH:[put the path here for lib]:.
You have 2 questions, one is the "title question" and another is the "foot note question" after elaborating your problem.
Read this documentation bellow to get a better understanding of CLASSPATH.
https://docs.oracle.com/javase/tutorial/essential/environment/index.html
https://docs.oracle.com/javase/8/docs/technotes/tools/windows/classpath.html
This is fast and straight forward for what you need.
For your first question, this will do:
The documentation recommends us to set a classpath for every application we are running at the moment using (use in the command-line):
java -classpath C:\yourDirectoryPath myApp
For your second question, look this exercise in the java documentation. It seems to be the same problem:
https://docs.oracle.com/javase/tutorial/essential/environment/QandE/answers.html
Answers to Questions and Exercises: The Platform Environment
Question 1.A programmer installs a new library contained in a .jar file. In order to access the library from his code, he sets the CLASSPATH environment variable to point to the new .jar file. Now he finds that he gets an error message when he tries to launch simple applications:
java Hello
Exception in thread "main" java.lang.NoClassDefFoundError: Hello
In this case, the Hello class is compiled into a .class file in the current directory — yet the java command can't seem to find it. What's going wrong?
Answer 1. A class is only found if it appears in the class path. By default, the class path consists of the current directory. If the CLASSPATH environment variable is set, and doesn't include the current directory, the launcher can no longer find classes in the current directory. The solution is to change the CLASSPATH variable to include the current directory. For example, if the CLASSPATH value is c:\java\newLibrary.jar (Windows) or /home/me/newLibrary.jar (UNIX or Linux) it needs to be changed to .;c:\java\newLibrary.jar or .:/home/me/newLibrary.jar."

JRuby, Warbler, and Java's CLASSPATH

I've been developing applications in JRuby lately and really enjoying it, but I've been running into a wall when it comes to packaging my project into a JAR file when it includes external Java libraries. If the project does not depend on any external Java library JAR files, I run into no problems.
Below is an example application. This code works perfectly fine when running the ./bin/my_proj executable. But, when I package it into a JAR file, the external Java library cannot be loaded because it is not found on the CLASSPATH.
When I unpackage my application's JAR file, I can see that it includes all of my code as well as the vendor directory containing the external Java library. So, everything's where it should be.
lib/my_proj/application.rb
java_import 'com.somecompany.somejavalibrary.SomeJavaLibraryClass'
module MyProj
class Application < SomeJavaLibraryClass
# Some code implementing SomeJavaLibraryClass
end
end
lib/my_proj.rb
require 'pathname'
module MyProj
def root
Pathname.new(__FILE__).join('..', '..').expand_path
end
def start
setup_environment
Application.new
end
def setup_environment
#setup ||= false
unless #setup
#setup = true
require 'java'
$CLASSPATH << root.join('vendor').to_s # Setup Java CLASSPATH
$LOAD_PATH << root.join('lib').to_s # Setup Ruby LOAD_PATH
require 'some_java_library' # Load the external Java library from it's JAR
require 'my_proj/application'
end
end
extend self
end
bin/my_proj
#!/usr/bin/env ruby
$:.unshift File.expand_path( File.join('..', '..', 'lib'), __FILE__ )
require 'my_proj'
MyProj.start
config/warble.rb
Warbler::Config.new do |config|
config.features = %w(gemjar compiled)
config.autodeploy_dir = 'pkg'
config.dirs = %w(assets bin config lib)
config.java_libs += FileList['vendor/*.jar']
end
vendor/some_java_library.jar
# This is the external Java library
The external jars should be in the lib folder.
You can add them in code by doing something like
$CLASSPATH << "vendor/some_java_library.jar" #or loop the directory for all jars and add them
Or you can create a META-INF/MANIFEST.MF file and specifiy the CLASSPATH jars
and adding a line like
Class-Path: vendor/some_java_library.jar jar2-name directory-name/jar3-name
http://docs.oracle.com/javase/tutorial/deployment/jar/downman.html

Categories

Resources