I am attempting to run my own custom in-memory James assembly inside of a Quarkus web application. I've been following along with the example here and gotten the server to run.
One of the issues I'm running into is that according to the docs I need to include custom hooks inside of a Jar file in a /conf/lib directory however I wish to use classes directly from inside the same project James is embedded in. Is there a way to modify the classloader so that I can use hooks that exist in the same jar where James is going to exist?
Edit: Adding handlerchain config. Sorry VonC I might have accidentally submitted this as an edit to your answer. It's been a day!
<handlerchain>
<handler class="org.apache.james.smtpserver.fastfail.ValidRcptHandler"/>
<handler class="org.apache.james.smtpserver.CoreCmdHandlerLoader"/>
<handler class="tech.gmork.oxprox.control.hooks.CustomAuthHook"/>
<handler class="tech.gmork.oxprox.control.hooks.CustomMessageHook"/>
</handlerchain>
"modify the classloader"? ... not without re-compiling the
apache/james-projectitself, I would presume.Apache James likely uses a dedicated component for classloading, which could be based on Java's URLClassLoader or a similar class.
I can see
org.apache.james.utils.ExtendedClassLoaderYou would need to modify it to check the current application classpath (where your Quarkus app and embedded James reside) in addition to or instead of the
conf/libdirectory.Then, after making the changes, you will need to recompile James and integrate the custom version with your Quarkus application.
The XML configurations you have for server implementations will not need to change because of these modifications. The XML file merely instructs James on what class to instantiate and possibly initialize. By modifying the classloading behavior, you are just changing where James looks for these classes. The XML stays the same because it references classes by their fully qualified names, not their locations.
I initially propose an updated code for
ExtendedClassLoader, but, as noted by Holger in the comments:In the constructor
ExtendedClassLoader(FileSystem fileSystem), you can retrieve the URLs from theextensions-jars/folder (just like the original code). And the URLs from the current application's classpath. The combined URLs are ensured to be unique to avoid duplication.These combined URLs are then fed into the
URLClassLoaderso that, when James tries to load a class, it will look in both the provided extension URLs and the current application's classpath.By making these changes, James will first look for hooks in the
extensions-jars/folder (if provided) and then in the current application's classpath. That means if you have packaged James and your custom hooks into the same jar (or same classpath), it should be able to find and load them without issues.See also "URLClassLoader can't find class thats in different directory".
True: the
extension-jarsdirectory is the default location where Apache James looks for additional modules and extensions.Instead of modifying the classloader, which would require a deep understanding of both the James server internals and Java's classloading mechanisms, placing the hooks in the
extension-jarsdirectory is a simpler solution.The OP's project is a Quarkus application, which may have its own classloading behaviors and constraints. Quarkus is designed for containerization and microservice architectures, so its classloading might be optimized for these environments, potentially complicating direct integration with embedded applications like Apache James.