I have a simple structure: A data jar file which contains a batch of data, and a service jar file, which runs a service using the data. To make the data easy to replace, I have them separate, and service.jar's classpath contains the directory which data.jar is in.
Within service.jar, I use getResource to load the data files. This works if the data files are directly within the folder, but fails when they are inside data.jar;
This fails:
all
+ globalclasspath
| + data.jar
| + mine.properties
+ daemons
+ service.jar
jsvc -cp globalclasspath:daemons/service.jar (...)
MyClass.class.getClassLoader( ).getResource( "mine.properties" ); // <-- null
But this works:
all
+ globalclasspath
| + mine.properties
+ daemons
+ service.jar
jsvc -cp globalclasspath:daemons/service.jar (...)
MyClass.class.getClassLoader( ).getResource( "mine.properties" ); // <-- not null
I don't want to change the classpath (unless I can change it to something generic which doesn't depend on the name of the data jar file), but I'm fine with changing the getResource string (I've tried /data/mine.properties and /data.jar/mine.properties to no avail). Is there a change I can make so that the resources can be loaded from within the jar?
Solution 1
Use a classpath wildcard.
jsvc -cp globalclasspath/*:daemons/service.jar (...)
See "How to use a wildcard in the classpath to add multiple jars?"
Solution 2
To read data in JARs not on the classpath, use URLClassLoader. The general algorithm is this:
Find the list of JARs in the globalclasspath directory.
Create a URLClassLoader from this list of JARs.
Look up the resource you want from the URLClassLoader instance.
To find JARs on the classpath, I used ResourceList from the StackOverflow article "Get a list of resources from classpath directory."
public class MyClass {
/**
* Creates a {#code URLClassLoader} from JAR files found in the
* globalclasspath directory, assuming that globalclasspath is in
* {#code System.getProperty("java.class.path")}.
*/
private static URLClassLoader createURLClassLoader() {
Collection<String> resources = ResourceList.getResources(Pattern.compile(".*\\.jar"));
Collection<URL> urls = new ArrayList<URL>();
for (String resource : resources) {
File file = new File(resource);
// Ensure that the JAR exists
// and is in the globalclasspath directory.
if (file.isFile() && "globalclasspath".equals(file.getParentFile().getName())) {
try {
urls.add(file.toURI().toURL());
} catch (MalformedURLException e) {
// This should never happen.
e.printStackTrace();
}
}
}
return new URLClassLoader(urls.toArray(new URL[urls.size()]));
}
public static void main(String[] args) {
URLClassLoader classLoader = createURLClassLoader();
System.out.println(classLoader.getResource("mine.properties"));
}
}
I ran the following command:
java -cp globalclasspath:daemons/service.jar MyClass
The terminal output:
jar:file:/workspace/all/globalclasspath/data.jar!/mine.properties
Have you tried getResourceAsStream as suggested here:
how-to-a-read-file-from-jar-in-java
You can now use the maven-resources-plugin to share resources between your jars!
In the pom.xml file of the source module/jar, add the following plugin:
<artifactId>DATA_MODULE_NAME</artifactId>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
</plugin>
</plugins>
</build>
This will tell maven to copy the contents of the resource folder along with the jar.
In the module you want to access those resources, add this to the dependencies in the pom:
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>DATA_MODULE_NAME</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
When you build your final jar, the resources from the source module will be copied in and available using getResource()
Related
I want to run maven command to execute some tests based on environment like mvn test -env=qa
This properties file has some common variables for all environments
resource/application.properties
message = .......
loadPath = .....
This is a class to read the properties
public class PropertyFile {
private static Properties prop;
private static void getPropertyFile() {
String propFilePath = System.getProperty("user.dir") + "/src/test/resources/application.properties";
try (InputStream in = new FileInputStream(propFilePath)) {
prop = new Properties();
prop.load(in);
} catch (IOException e) {
e.printStackTrace();
}
}
public static String getProperty(String propertyName) {
if (prop == null) {
getPropertyFile();
}
return prop.getProperty(propertyName);
}
}
So now I want to create another two files
qa.properties
baseurl=http://qa.....
and
dev.properties
baseurl=http://dev....
When I run mvn test -env=qa. It will pass this qa variable and read application.properties for common properties and read qa.properties for specific environment properties.
I did some research to add the variables to pom file
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
<configuration>
<systemPropertyVariables>
<env>qa</env>
</systemPropertyVariables>
</configuration>
</plugin>
But not sure how to properly configure this. Any help would be appreciate.
First, this sounds like a bad idea to me - your build should be portable and environment agnostic. How is another developer going to know about the magic parameters to pass to the build?
Also unit tests (which is what the test phase should be running) should be local to the machine running the build, typically one unit test class testing one actual class. Unit tests should be run a lot and placing external dependencies in them is a prime cause of this not happening (because it make them too slow).
So with these warnings, to make the above work you can pass environment variables to maven via the command line simply by specifying a -D system property like you would with the jvm, for example:
mvn clean test -Denv=dev
Would set a system property called env with a value dev.
To use this in your code, you need to know that the resources directories are available via the classpath when running in Maven (test/resources is only visible from the tests I believe). This means that, unless you're doing something funky, you can get the InputStream for the properties file from the ClassLoader, like this:
//Get the environment, defaulting to dev if none is specified.
String environment = System.getProperty("env", "dev");
Properties properties = new Properties();
properties.load(ClassLoader.getSystemResourceAsStream("application-" + environment + ".properties"));
I want to access some information from the pom.xml to display in a Info dialog. So I googled and found this post:
public class MavenModelExample {
public static void main(String[] args) throws IOException, XmlPullParserException {
MavenXpp3Reader reader = new MavenXpp3Reader();
Model model = reader.read(new FileReader("pom.xml"));
System.out.println(model.getId());
System.out.println(model.getGroupId());
System.out.println(model.getArtifactId());
System.out.println(model.getVersion());
}
}
I implemented it in my tool, added
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-model</artifactId>
<version>3.3.9</version>
</dependency>
to my pom and was happy that everything ran as expected when I run the tool from the project root directory with java -jar target\mytool.jar.
When I move to any other directory, e.g. directly into target and execute my tool with java -jar mytool.jar, I get:
java.io.FileNotFoundException: pom.xml (The system cannot find the specified file)
at java.base/java.io.FileInputStream.open0(Native Method)
at java.base/java.io.FileInputStream.open(FileInputStream.java:213)
at java.base/java.io.FileInputStream.<init>(FileInputStream.java:155)
at java.base/java.io.FileInputStream.<init>(FileInputStream.java:110)
at java.base/java.io.FileReader.<init>(FileReader.java:60)
Which is kind of comprehensible. How should the code know, where the pom.xml is located, as it is not a resource. Is there any way to work around that?
In the mean time I use the approach from this thread to obtain the version and artifactID.
The problem is that
Model model = reader.read(new FileReader("pom.xml"));
tries to read the POM from the directory where your program is executed. Normally, pom.xml won't get copied to target, but it is embedded in the resulting artifact. You can override and force Maven to copy the POM to the target directory if you want to (for your own project), but it won't help you for other Maven artifacts.
Most of the time, a Maven artifact will have the POM coordinates included in the JAR/WAR/EAR output. If you unpack such a file, you'll notice that there are two files stored under META-INF/maven/<groupId>/<artifactId>: pom.xml and pom.properties where the latter is far easier to parse than pom.xml but it doesn't include the dependencies.
Parsing the embedded pom.xml from the classpath (and not from disk) should work better for you, especially if you always run your program with java -jar target\mytool.jar. In your program, try this:
try (InputStream is = MavenModelExample.class.getClassLoader().getResourceAsStream("META-INF/maven/<your groupId>/<your artifactId>/pom.xml")) {
MavenXpp3Reader reader = new MavenXpp3Reader();
Model model = reader.read(is);
System.out.println(model.getId());
System.out.println(model.getGroupId());
System.out.println(model.getArtifactId());
System.out.println(model.getVersion());
// If you want to get fancy:
model.getDependencies().stream().forEach(System.out::println);
}
catch (IOException e) {
// Do whatever you need to do if the operation fails.
}
<your groupId> and <your artifactId> should be fairly static, but if you do relocate your artifact's coordinates, then you need to change this in your code as well.
problem is that :
read(new FileReader("pom.xml"))
works fine when you start your application from STS or else, but when you build your application as JAR the path of the pom.xml file change to :
META- INF/maven/${groupId}/${artifactId}/pom.xml.
for that, try this code :
MavenXpp3Reader mavenXpp3Reader = new MavenXpp3Reader();
Model model;
if ((new File("pom.xml")).exists()) {
model = mavenXpp3Reader.read(new FileReader("pom.xml"));
}
else {
// Packaged artifacts contain a META- INF/maven/${groupId}/${artifactId}/pom.properties
model = mavenXpp3Reader.read(new
InputStreamReader(Application.class.getResourceAsStream(
"/META-INF/maven/groupId/artifactId/pom.xml")));
}
I am creating a contacts application using Maven in Netbeans. For the operation of the program I want users to add and store images (contact avatars) in a folder /avatars and access them on a listener event. I can access images from within the ProjectRoot/src/main/resources/images directory, but cannot access ProjectRoot/avatars. Note: I do not want to store avatars in the resources directory because these will be user-added during the programs operation.
I have tried using getClass().getResource(avatarPath); as suggested in similar questions, but it has not worked. I have also tried adding the "avatars" directory to the POM as its own resource directory, but that has not worked either.
How can I access files/folders in the root directory of my project when using Maven?
listviewContacts.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<Contact>() {
#Override
public void changed(ObservableValue<? extends Contact> observable, Contact oldValue, Contact newValue) {
String avatar = newValue.getAvatar();
String avatarPath = null;
if (avatar.isEmpty()) {
avatarPath = "/images/" + DEFAULT_AVATAR; // loads from ProjectRoot/src/main/resources/images
} else {
avatarPath = "/avatars/" + avatar; // won't load from ProjectRoot/avatars
}
try {
imageviewContact.setImage(new Image(avatarPath));
} catch (IllegalArgumentException ex) {
System.err.println("Could not locate " + avatarPath);
}
}
});
You are mixing 2 different things.
You can either have a classpath resource packed in jarfile along with your classes, or in a directory that is explicitly added java to java classpath(using java -cp commandline argument). That can be accessed via getClass().getResource.
Or you can load a file from arbitrary location, using java.io.File. Then your "projectRoot" is some folder in a filesystem, that you can either hardcode, configure by -DprojectRoot=C:/fun/with/files, or use some of relative paths.
Maven has nothing to do with it, as avatars "will be will be user-added during the programs operation".
Your users will not launch your IDE, right ?
The problem was in understanding where the different constructors of javafx.scene.image.Image begin their path.
When using the URL constructor for Image, the URL path will start in Maven's defined resources folder (/src/main/resources or whatever additional resource directories were added to the pom.xml file):
// the supplied string invokes the URL constructor
Image image = new Image("path/to/file");
When using the InputStream constructor for Image (via FileInputStream), the path will start at the root directory of the project/application:
// the FileInputStream invokes the InputStream constructor
Image image = new Image(new FileInputStream("path/to/file"));
It appears to me that JAR file indexing breaks the mechanics of ClassLoader.getResources(). Consider the following program:
import java.io.*;
import java.net.*;
import java.util.*;
public class TryIt {
public static void main(String[] args) throws Exception {
URL[] urls = {
(new File("a.jar")).getAbsoluteFile().toURI().toURL(),
(new File("b.jar")).getAbsoluteFile().toURI().toURL()
};
URLClassLoader cl = URLClassLoader.newInstance(urls);
String[] res = { "foo", "foo/", "foo/arb", "foo/bar", "foo/cab" };
for (String r: res) {
System.out.println("'" + r + "':");
for (URL u: Collections.list(cl.getResources(r)))
System.out.println(" " + u);
}
}
}
Now prepare the JAR files mentioned in that program:
mkdir a/foo b/foo
touch a/foo/arb a/foo/bar b/foo/bar b/foo/cab
echo "Class-Path: b.jar" > mf
jar cfm a.jar mf -C a foo
jar cf b.jar -C b foo
If you run java TryIt, you will get output like this:
'foo':
jar:file:…/a.jar!/foo
jar:file:…/b.jar!/foo
'foo/':
jar:file:…/a.jar!/foo/
jar:file:…/b.jar!/foo/
'foo/arb':
jar:file:…/a.jar!/foo/arb
'foo/bar':
jar:file:…/a.jar!/foo/bar
jar:file:…/b.jar!/foo/bar
'foo/cab':
jar:file:…/b.jar!/foo/cab
But if you run jar -i a.jar to create an index, then the same command as above prints this:
'foo':
jar:file:…/a.jar!/foo
'foo/':
jar:file:…/a.jar!/foo/
'foo/arb':
jar:file:…/a.jar!/foo/arb
'foo/bar':
jar:file:…/a.jar!/foo/bar
'foo/cab':
jar:file:…/b.jar!/foo/cab
The index itself looks like this:
JarIndex-Version: 1.0
a.jar
foo
b.jar
foo
Doesn't the contract of getResources imply that all available resources matching the given name should be returned?
Finds all the resources with the given name.
Doesn't the JAR File Specification allow indexed packages to span multiple JAR files?
Normally one package name is mapped to one jar file, but if a particular package spans more than one jar file, then the mapped value of this package will be a list of jar files.
Is there some specification somewhere which says that what I'm observing is indeed correct (or at least permissible) behavior?
Is there some workaround to get all named resources despite the index?
This appears to be a bug.
I've reported it to Oracle, and it's now in their bug database as bug 8150615.
I did some digging around in the OpenJDK sources and found the reson for this behavior in there.
The relevant class here is sun.misc.URLClassPath. It contains a (lazily constructed) list of loaders, and queries each loader in turn to assemble its result. However, if a JAR file contains an index, then the JAR files therein will explicitely be excluded from getting added to the list of loaders. Instead, the loader for the JAR containing the index will query said index for the name in question, and traversed the resulting list. But here is the catch: this happens in a method URLClassPath$JarLoader.getResource which returns a single Resource object. It is not possible for this method to return multiple resources. And as all objects in the index are handled by a single loader, a single resource is all we get.
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());
}
}