Java MemoryClassLoader (IllegalArgumentException) - java

I have a memory class loader (here) that I am using in a custom Minecraft launcher.
Memory Class Loader
Whenever I load up Minecraft (a Java LWJGL game), I am getting the following error:
27 achievements
182 recipes
Setting user
LWJGL Version: 2.4.2
java.lang.IllegalArgumentException: input == null!
at javax.imageio.ImageIO.read(Unknown Source)
at lc.<init>(SourceFile:21)
at gi.<init>(SourceFile:10)
at net.minecraft.client.Minecraft.a(SourceFile:254)
at net.minecraft.client.Minecraft.run(SourceFile:657)
at java.lang.Thread.run(Unknown Source)
I am creating the class loader like this:
Base.cLoader = new CLoader(
GameUpdater.classLoader,
new JarInputStream(new ByteArrayInputStream(jarFileBytes)));
As you can see, it manages to load up the first part then suddenly after LWJGL Version it crashes with "input == null".
Edit - Here is the new getResource method.
The error is on "URL()", as shown.
Code:
public URL getResource(final String name) {
URL url = new URL() { public InputStream openStream() {
return new ByteArrayInputStream((byte[])others.get(name));
}};
return url;
}

A wild guess... it could be this: Warning: URLs for this are not yet implemented! You cannot call getResource() or getResources()!
So your code expects to retrieve an image from the JAR using the unimplemented method. An equivalent of this is probably being executed:
ImageIO.read(memClassLoader.getResource(someString));
Except that, as we have seen, the Error thrown from getResource is getting ignored and null being used as the value. ImageIO.read goes like this:
public static BufferedImage read(URL input) throws IOException {
if (input == null) {
throw new IllegalArgumentException("input == null!");
}
InputStream istream = null;
try {
istream = input.openStream();
} catch (IOException e) {
throw new IIOException("Can't get input stream from URL!", e);
}
}
Sounds familiar? So, this is roughly what you need to implement:
public URL getResource(final String name) {
try {
return new URL("", "", 0, "",
new URLStreamHandler() { public URLConnection openConnection(URL url) {
return new URLConnection(url) {
public void connect() {}
public InputStream getInputStream() {
// return the appropriate InputStream, based on the name arg
}
};
}});
} catch (MalformedURLException e) { throw new RuntimeException(e); }
}

The MemoryClassLoader is pretty much broken. It does not implement getResource() (as stated in the comment in the source), and also it does not define Packages for the classes it loads (this may or may not break an application).
Most likely that ClassLoader was quickly hacked for testing purposes, leaving the more complicated methods out.
Implementing your own URL protocol to handle getResource() is not too difficult, in getResource() you return an URL that uses a custom protocol name (e.g. "myclassloader://resourcename"), and also a custom implementation of URLStreamHandler that handles that protocol.
That may not cover all the loopholes that might cause trouble in locating a resource, if the code loaded through the ClassLoader uses URL.toString() and converts it back it will still break.
Implementing a fully working ClassLoader that does not simple delegation to existing ClassLoaders, is not as simple as most examples make it look.

Related

How to use URLClassloader in an auto-update jar launcher?

I've come across many posts about these two topics: Auto-Updating and URLClassloaders. I'll start with the auto updating goal. I found this post here that talks about a 2 jar system. One jar that launches the main app jar: From Stephen C:
The launcher could be a Java application that creates a classloader for the new JAR, loads an entrypoint class and calls some method on it. If you do it this way, you have to watch for classloader storage leaks, but that's not difficult. (You just need to make sure that no objects with classes loaded from the JAR are reachable after you relaunch.)
This is the approach I'm taking, but I'm open to other ideas if they prove easier and/or more reliable. The Coordinator has posted some pretty cool launcher code to which I plan on incorporating some of this reload type code in my launcher, but first I need to get it to work.
My issue is that my main app jar has many other dependencies, and I cannot get some of those classes to load despite the fact that all the jars have been added to the URL's array. This brings up the second topic URLClassloader.
Side Note for future readers: When passing a URL to the URLClassloader that is a directory, a helpful note that would have saved me (an embarrassingly large) amount of time is that the contents of the directory must be .class files! I was originally pointing to my dependent jar directory, no good.
Context for the code below, my launcher jar resides in the same directory as my app jar, which is why I'm using user.dir. I will probably change this, but for now the code works and gets far enough into my app's code to request a connection to a sqlite database before failing.
Launcher:
public class Launcher {
public static void main(String[] args) {
try {
String userdir = System.getProperty("user.dir");
File parentDir = new File(userdir);
ArrayList<URL> urls = getJarURLs(parentDir);
URL[] jarURLs = new URL[urls.size()];
int index = 0;
for (URL u : urls) {
System.out.println(u.toString());
jarURLs[index] = u;
index ++;
}
URLClassLoader urlCL = new URLClassLoader(jarURLs);
Class<?> c = urlCL.loadClass("main.AppStart");
Object [] args2 = new Object[] {new String[] {}};
c.getMethod("main", String[].class).invoke(null, args2);
urlCL.close();
} catch (Exception e1) {
e1.printStackTrace();
}
}
public static ArrayList<URL> getJarURLs(File parentDir) throws MalformedURLException {
ArrayList<URL> list = new ArrayList<>();
for (File f : parentDir.listFiles()) {
if (f.isDirectory()) {
list.addAll(getJarURLs(f));
} else {
String name = f.getName();
if (name.endsWith(".jar")) {
list.add(f.toURI().toURL());
}
}
}
return list;
}
}
Here's an example of the URL output added to the array:
file:/C:/my/path/to/dependent/jars/sqlite-jdbc-3.32.3.2.jar
file:/C:/my/path/to/main/app.jar
file: ... [10 more]
The URLClassloader seems to work well enough to load my main method in app.jar. The main executes a some startup type stuff, before attempting to load a login screen. When the request is made to get the user info database, my message screen loads and displays (<-this is important for later)
the stacktrace containing:
java.sql.SQLException: No suitable driver found for jdbc:sqlite:C:\...\users.db
I understand that this is because that jar is not on the class path, but it's loaded via the class loader, so why can't it find the classes from the jar? From this post JamesB suggested adding Class.forName("org.sqlite.JDBC"); before the connection request. I rebuilt the app jar with this line of code and it worked!
The weird thing that happened next, is that my message screen class can no longer be found even though earlier it loaded and displayed correctly. The message screen is a class inside my main app.jar and not in a dependent jar, which is why I'm baffled. Am I going to have to add Class.forName before every instance of any of my classes? That seems rude..
So what could I be doing wrong with the class loader? Why does it load some classes and not others despite that fact that all the jars have been added to the URL array?
Some other relative info: My app works perfectly as intended when launched from windows command line when the classpath is specified: java -cp "main-app.jar;my/dependent/jar/directory/*" main.AppStart. It's only when I try launching the app via this classloader that I have these issues.
By the way, is this java command universal? Will it work on all operating systems with java installed? If so, could I not just scrap this launcher, and use a process builder to execute the above command? Bonus points for someone who can tell me how to execute the command from a jre packaged with my app, as that's what I plan on doing so the user does not have to download Java.
EDIT
I figured out one of the answers to one of the questions below. Turns out, I didn't need to do any of the code below. My main method loads a login screen but after it's loaded it returns back to the AppLauncher code, thus closing the URLClassLoader! Of course, at that point any requested class will not be found as the loader has been closed! What an oof! Hopefully I will save someone a headache in the future...
Original
Well, after more time, effort, research, and effective use of Eclipse's debugging tool, I was able to figure out what I needed to do to resolve my issues.
So the first issue was my JDBC driver was never registered when passing the jars to the URLClassloader. This is the part I sorta don't understand, so advisement would be welcomed, but there is a static block in the JDBC class that registers the driver so it can be used by DriverManager see code below. Loading the class is what executes that static block, hence why calling Class.forName works.
static {
try {
DriverManager.registerDriver(new JDBC());
} catch (SQLException e) {
e.printStackTrace();
}
}
What I don't understand, is how class loading works if jars are specified via the class path. The URLClassLoader doesn't load any of those classes until they are called, and I never directly work with the JDBC class, thus no suitable driver exception, but are all the classes specified via the classpath loaded initially? Seems that way for static blocks to execute.
Anyhow, to resolve my other issue with some of my app's classes not being found I had to implement my own classloader. I get what I did and how it works well, but still don't understand why I had to do it. All of my jars were loaded to the original URLClassloader so if I could find them and the files within, why couldn't it do it?
Basically, I had to override the findClass and findResource methods to return jarEntry information that I had to store. I hope this code helps someone!
public class SBURLClassLoader extends URLClassLoader {
private HashMap<String, Storage> map;
public SBURLClassLoader(URL[] urls) {
super(urls);
map = new HashMap<>();
try {
storeClasses(urls);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
private void storeClasses(URL[] urls) throws ClassNotFoundException {
for (URL u : urls) {
try {
JarFile jarFile = new JarFile(new File(u.getFile()));
Enumeration<JarEntry> e = jarFile.entries();
while (e.hasMoreElements()) {
JarEntry jar = e.nextElement();
String entryName = jar.getName();
if (jar.isDirectory()) continue;
if (!entryName.endsWith(".class")) {
//still need to store these non-class files as resources
//let code continue to store entry un-altered
} else {
entryName = entryName.replace(".class", "");
entryName = entryName.replace("/", ".");
}
map.put(entryName, new Storage(jarFile, jar));
System.out.println(entryName);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
#Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class<?> c = null;
try {
c = super.findClass(name);
} catch (ClassNotFoundException e) {
Storage s = map.get(name);
try {
InputStream in = s.jf.getInputStream(s.je);
int len = in.available();
c = defineClass(name, in.readAllBytes(), 0, len);
resolveClass(c);
} catch (IOException e1) {
e1.printStackTrace();
}
if (c == null) throw e;
}
return c;
}
#Override
public URL findResource(String name) {
URL url = super.findResource(name);
if (url == null) {
Storage s = map.get(name);
if (s != null) {
try {
url = new URL("jar:"+s.base.toString() + "!/" + name);
} catch (Exception e) {
e.printStackTrace();
}
}
}
return url;
}
private class Storage {
public JarFile jf;
public JarEntry je;
public URL base;
public Storage(JarFile jf, JarEntry je) {
this.jf = jf;
this.je = je;
try {
base = Path.of(jf.getName()).toUri().toURL();
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
}
}

How to copy a resource to a file in another location in Java

I'm trying to copy a resource in my project onto another location on the disk. So far, I have this code:
if (!file.exists()){
try {
file.createNewFile();
Files.copy(new InputSupplier<InputStream>() {
public InputStream getInput() throws IOException {
return Main.class.getResourceAsStream("/" + name);
}
}, file);
} catch (IOException e) {
file = null;
return null;
}
}
And it works fine, but the InputSupplier class is deprecated, so I was wondering if there was a better way to do what I'm trying to do.
See the documentation for the Guava InputSupplier class:
For InputSupplier<? extends InputStream>, use ByteSource instead. For InputSupplier<? extends Reader>, use CharSource. Implementations of InputSupplier that don't fall into one of those categories do not benefit from any of the methods in common.io and should use a different interface. This interface is scheduled for removal in December 2015.
So in your case, you're looking for ByteSource:
Resources.asByteSource(url).copyTo(Files.asByteSink(file));
See this section of the Guava Wiki for more information.
If you're looking for a pure Java (no external libraries) version, you can do the following:
try (InputStream is = this.getClass().getClassLoader().getResourceAsStream("/" + name)) {
Files.copy(is, Paths.get("C:\\some\\file.txt"));
} catch (IOException e) {
// An error occurred copying the resource
}
Note that this is only valid for Java 7 and higher.

Mocking/Testing HTTP Get Request

I'm trying to write unit tests for my program and use mock data. I'm a little confused on how to intercept an HTTP Get request to a URL.
My program calls a URL to our API and it is returned a simple XML file. I would like the test to instead of getting the XML file from the API online to receive a predetermined XML file from me so that I can compare the output to the expected output and determine if everything is working correctly.
I was pointed to Mockito and have been seeing many different examples such as this SO post, How to use mockito for testing a REST service? but it's not becoming clear to me how to set it all up and how to mock the data (i.e., return my own xml file whenever the call to the URL is made).
The only thing I can think of is having another program made that's running locally on Tomcat and in my test pass a special URL that calls the locally running program on Tomcat and then return the xml file that I want to test with. But that just seems like overkill and I don't think that would be acceptable. Could someone please point me in the right direction.
private static InputStream getContent(String uri) {
HttpURLConnection connection = null;
try {
URL url = new URL(uri);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setRequestProperty("Accept", "application/xml");
return connection.getInputStream();
} catch (MalformedURLException e) {
LOGGER.error("internal error", e);
} catch (IOException e) {
LOGGER.error("internal error", e);
} finally {
if (connection != null) {
connection.disconnect();
}
}
return null;
}
I am using Spring Boot and other parts of the Spring Framework if that helps.
Part of the problem is that you're not breaking things down into interfaces. You need to wrap getContent into an interface and provide a concrete class implementing the interface. This concrete class will then
need to be passed into any class that uses the original getContent. (This is essentially dependency inversion.) Your code will end up looking something like this.
public interface IUrlStreamSource {
InputStream getContent(String uri)
}
public class SimpleUrlStreamSource implements IUrlStreamSource {
protected final Logger LOGGER;
public SimpleUrlStreamSource(Logger LOGGER) {
this.LOGGER = LOGGER;
}
// pulled out to allow test classes to provide
// a version that returns mock objects
protected URL stringToUrl(String uri) throws MalformedURLException {
return new URL(uri);
}
public InputStream getContent(String uri) {
HttpURLConnection connection = null;
try {
Url url = stringToUrl(uri);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setRequestProperty("Accept", "application/xml");
return connection.getInputStream();
} catch (MalformedURLException e) {
LOGGER.error("internal error", e);
} catch (IOException e) {
LOGGER.error("internal error", e);
} finally {
if (connection != null) {
connection.disconnect();
}
}
return null;
}
}
Now code that was using the static getContent should go through a IUrlStreamSource instances getContent(). You then provide to the object that you want to test a mocked IUrlStreamSource rather than a SimpleUrlStreamSource.
If you want to test SimpleUrlStreamSource (but there's not much to test), then you can create a derived class that provides an implementation of stringToUrl that returns a mock (or throws an exception).
The other answers in here advise you to refactor your code to using a sort of provider which you can replace during your tests - which is the better approach.
If that isn't a possibility for whatever reason you can install a custom URLStreamHandlerFactory that intercepts the URLs you want to "mock" and falls back to the standard implementation for URLs that shouldn't be intercepted.
Note that this is irreversible, so you can't remove the InterceptingUrlStreamHandlerFactory once it's installed - the only way to get rid of it is to restart the JVM. You could implement a flag in it to disable it and return null for all lookups - which would produce the same results.
URLInterceptionDemo.java:
public class URLInterceptionDemo {
private static final String INTERCEPT_HOST = "dummy-host.com";
public static void main(String[] args) throws IOException {
// Install our own stream handler factory
URL.setURLStreamHandlerFactory(new InterceptingUrlStreamHandlerFactory());
// Fetch an intercepted URL
printUrlContents(new URL("http://dummy-host.com/message.txt"));
// Fetch another URL that shouldn't be intercepted
printUrlContents(new URL("http://httpbin.org/user-agent"));
}
private static void printUrlContents(URL url) throws IOException {
try(InputStream stream = url.openStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(stream))) {
String line;
while((line = reader.readLine()) != null) {
System.out.println(line);
}
}
}
private static class InterceptingUrlStreamHandlerFactory implements URLStreamHandlerFactory {
#Override
public URLStreamHandler createURLStreamHandler(final String protocol) {
if("http".equalsIgnoreCase(protocol)) {
// Intercept HTTP requests
return new InterceptingHttpUrlStreamHandler();
}
return null;
}
}
private static class InterceptingHttpUrlStreamHandler extends URLStreamHandler {
#Override
protected URLConnection openConnection(final URL u) throws IOException {
if(INTERCEPT_HOST.equals(u.getHost())) {
// This URL should be intercepted, return the file from the classpath
return URLInterceptionDemo.class.getResource(u.getHost() + "/" + u.getPath()).openConnection();
}
// Fall back to the default handler, by passing the default handler here we won't end up
// in the factory again - which would trigger infinite recursion
return new URL(null, u.toString(), new sun.net.www.protocol.http.Handler()).openConnection();
}
}
}
dummy-host.com/message.txt:
Hello World!
When run, this app will output:
Hello World!
{
"user-agent": "Java/1.8.0_45"
}
It's pretty easy to change the criteria of how you decide which URLs to intercept and what you return instead.
The answer depends on what you are testing.
If you need to test the processing of the InputStream
If getContent() is called by some code that processes the data returned by the InputStream, and you want to test how the processing code handles specific sets of input, then you need to create a seam to enable testing. I would simply move getContent() into a new class, and inject that class into the class that does the processing:
public interface ContentSource {
InputStream getContent(String uri);
}
You could create a HttpContentSource that uses URL.openConnection() (or, better yet, the Apache HttpClientcode).
Then you would inject the ContentSource into the processor:
public class Processor {
private final ContentSource contentSource;
#Inject
public Processor(ContentSource contentSource) {
this.contentSource = contentSource;
}
...
}
The code in Processor could be tested with a mock ContentSource.
If you need to test the fetching of the content
If you want to make sure that getContent() works, you could create a test that starts a lightweight in-memory HTTP server that serves the expected content, and have getContent() talk to that server. That does seem overkill.
If you need to test a large subset of the system with fake data
If you want to make sure things work end to end, write an end to end system test. Since you indicated you use Spring, you can use Spring to wire together parts of the system (or to wire the entire system, but with different properties). You have two choices
Have the system test start a local HTTP server, and when you have your test create your system, configure it to talk to that server. See the answers to this question for ways to start the HTTP server.
Configure spring to use a fake implementation of ContentSource. This gets you slightly less confidence that everything works end-to-end, but it will be faster and less flaky.

How to match catalog.xml entities with database?

I have to validate some xml files with .xsd files, which are listed in catalog.xml, but they are in database. So i need resolver, which will match systemId from catalog.xml with .xsd file stored as blob in database.
I found that XMLInputSource resolveEntity(XMLResourceIdentifier resourceIdentifier) method doing this, but I can't find how parser uses this method, so I'm not sure how to override it to do it propertly. I thought that it returns XMLInputStram which contains .xsd file in Stream, but it's not true because of "leaving resolution of the entity and opening of the input stream up to the caller", according to XMLInputSource documentation.
So my question is - how to map entities from catalog.xml with .xsd files stored in database?
I really hope that I explained problem clearly, but I know that my english is really poor - so feel free to ask for more details or better explaation.
Greetings,
Rzysia
Here's the resolver I wrote for the maven-jaxb2-plugin. This resolver resolves system ids to resources in Maven artifacts. This is somewhat similar task to yours.
Your task is, basically, to implement the resolveEntity method.
Normally it is practical to extend an existing CatalogResolver.
Then you can override the getResolvedEntity method.
Typically you first call the super method to resolve systemId/publicId.
Then you try to do you custom resolution.
systemId is normally the resource location URL (or logical URI).
publicId is often the namespace URI.
Here's a simple code snippet from another resolver which resolves classpath:com/acme/foo/schema.xsd in the classpath:
public static final String URI_SCHEME_CLASSPATH = "classpath";
#Override
public String getResolvedEntity(String publicId, String systemId) {
final String result = super.getResolvedEntity(publicId, systemId);
if (result == null) {
return null;
}
try {
final URI uri = new URI(result);
if (URI_SCHEME_CLASSPATH.equals(uri.getScheme())) {
final String schemeSpecificPart = uri.getSchemeSpecificPart();
final URL resource = Thread.currentThread()
.getContextClassLoader()
.getResource(schemeSpecificPart);
if (resource == null) {
return null;
} else {
return resource.toString();
}
} else {
return result;
}
} catch (URISyntaxException urisex) {
return result;
}
}
In your scenario, I'd do the following:
Define the URI schema like database:schema:table:value:id:schema.xsd.
Write a catalog resolver which is capable of resolving such URIs.
Define a catalog file which rewrites namespace URIs or absolute schema location URLs to your database:... URIs.
In simple notation this would be something like:
REWRITE_SYSTEM "http://example.com/schemas" "database:schemas:content:schema_id:example/schemas"
So the "base" catalog resolver would first resolve http://example.com/schemas/schema.xsd into database:schemas:content:schema_id:example/schemas/schema.xsd.
Then your code resolves database:schemas:content:schema_id:example/schemas/schema.xsd into a database resource.
Ok, i found solution - as I thought, method XMLInputSource resolveEntity(XMLResourceIdentifier resourceIdentifier) shoud return XMLInputSource with setted my own InputStream containing speciefied xsd schema.
My version of this overrided class:
public XMLInputSource resolveEntity(XMLResourceIdentifier resourceIdentifier)
throws XNIException, IOException {
String resolvedId = resolveIdentifier(resourceIdentifier);
if (resolvedId != null) {
XMLInputSource xmlis = new XMLInputSource(resourceIdentifier.getPublicId(),
resolvedId,
resourceIdentifier.getBaseSystemId());
try {
InputStream is = getXSDFromDb(resourceIdentifier.getLiteralSystemId());
xmlis.setByteStream(is);
} catch (SQLException ex) {
ex.printStackTrace();
}
return xmlis;
}
return null;

Can Xerces support XMLCatalogResolver and <xs:include/> at the same time?

Xerces claims to allow XML Catalog support to be added to a reader like this:
XMLCatalogResolver resolver = new XMLCatalogResolver();
resolver.setPreferPublic(true);
resolver.setCatalogList(catalogs);
XMLReader reader = XMLReaderFactory.createXMLReader(
"org.apache.xerces.parsers.SAXParser");
reader.setProperty("http://apache.org/xml/properties/internal/entity-resolver",
resolver);
But as soon as I do this then any <xs:include/> tags in my schemas are no longer processed. It seems like the XMLCatalogResolver becomes the only go-to place for entity resolution once it's added, so includes can't work anymore. Eclipse OTOH successfully validates using the same catalog, so it should be possilbe.
Is there a way around this, or are there any other Java based validators that support catalogs?
Thanks, Dominic.
I finally solved this by overriding the XMLCatalogResolver and logging the various calls made to the resolveEntity() method. I observed 3 types of call being made, only one of which made sense to be resolved using the XML catalog. So, I merely returned a FileInputStream directly for the other two call types.
Here is the code I used inside my custom XMLCatalogResolver class:
public XMLInputSource resolveEntity(XMLResourceIdentifier resourceIdentifier)
throws IOException
{
if(resourceIdentifier.getExpandedSystemId() != null)
{
return new XMLInputSource(resourceIdentifier.getPublicId(),
resourceIdentifier.getLiteralSystemId(),
resourceIdentifier.getBaseSystemId(),
new FileReader(getFile(resourceIdentifier.getExpandedSystemId())),
"UTF-8");
}
else if((resourceIdentifier.getBaseSystemId() != null) &&
(resourceIdentifier.getNamespace() == null))
{
return new XMLInputSource(resourceIdentifier.getPublicId(),
resourceIdentifier.getLiteralSystemId(),
resourceIdentifier.getBaseSystemId(),
new FileReader(getFile(resourceIdentifier.getBaseSystemId())),
"UTF-8");
}
else
{
return super.resolveEntity(resourceIdentifier);
}
}
private File getFile(String urlString) throws MalformedURLException
{
URL url = new URL(urlString);
return new File(url.toURI());
}
I'm not sure why this wouldn't be done by default within Xerces, but hopefully this helps the next person that encounters this problem.

Categories