Java classloaders in a war application ( jar/classloader hell ) quartz - java

I have a maven project setup such as
parent
...pom.xml
...servlet-app
......pom.xml ( specifies simple-lib as a dependency )
...simple-lib
......src/main/resources/config.properties
......src/main/java/package1/Config.java
......src/main/java/package1/HelloQuartzJob.java
basically the servlet application servlet-app specifies simple-lib as a dependency. The config file config.properties is packaged at the top/level of the simple-lib.jar. And when i unpack the servelet-app.war i can see the WEB_INF/lib/simple-lib.jar. So, everything is good.
The Config.java looks like this:
public class Config{
static final String PROPERTILES_FILE = "config.properties";
static Properties props;
static{
log.info("Loading Config properties from {}", PROPERTILES_FILE);
props = new Properties();
try {
InputStream is = ClassLoader.getSystemResourceAsStream(PROPERTILES_FILE);
if (is == null) {
log.info("Loading through ClassLoader as root");
is = ClassLoader.getSystemResourceAsStream("/"+PROPERTILES_FILE);
}
if (is == null) {
log.info("Loading through Config.class. root");
is = Config.class.getResourceAsStream("/"+PROPERTILES_FILE);
}
if (is == null) {
log.info("Loading through Config.class. relative");
is = Config.class.getResourceAsStream(PROPERTILES_FILE);
}
if( is == null ) {
log.info("Thread class loader root");
is = Thread.currentThread().getContextClassLoader().getResourceAsStream("/"+PROPERTILES_FILE);
}
props.load(is);
} catch (Throwable e) {
log.error("Config properties loading error ", e);
throw new RuntimeException(e);
}
}
}
But, When I deploy it in tomcat7 I get a NoClassDefFound/Could not initialize package1.Config error. I believe this is because of the classLoaders.
Also of interest is that this package1.Config is used by HelloQuartzJob.java which is a quartz job that is run by the scheduler instance running within this servlet application.
Any pointers?

You should replace this line:
InputStream is = ClassLoader.getSystemResourceAsStream(PROPERTILES_FILE);
with:
InputStream is = Config.class.getClassLoader().getResourceAsStream(PROPERTILES_FILE);
in order to use the current webapp classloader with its root at the root of war..

I see two problems:
is = ClassLoader.getSystemResourceAsStream("/"+PROPERTILES_FILE);
and
is = Thread.currentThread().getContextClassLoader().getResourceAsStream("/"+PROPERTILES_FILE);
don't use a leading '/' character when loading resources directly from a classloader.
From Class.getResourceAsStream() use a leading '/' (as the resource is loaded relative to the class's package otherwise) but for ClassLoader.getResourceAsStream() and getSystemResourceAsStream() do not use a leading '/'.

You nead to ask for a resource from the classloader instance.
From a static context:
String url = Config.class.getClassLoader().getResource("config.properties");
InputStream is = Config.class.getClassLoader().getResourceAsStream("config.properties");
From an instance:
String url = getClass().getClassLoader().getResource("config.properties");
InputStream is = getClass().getClassLoader().getResourceAsStream("config.properties");
You should only use relative path and never absolute path (starting with "/") for accessing jar resource in webapp context.
Yesterday I had the same trouble and this way it was easy to solve it :-)
It is just a simle classLoader issue but it is very important. If you use absolute path the loader only looks into the WEB-INF/classes folder.

Related

Load an external library inside a jar

I have a jar file that I load dynamically,
inside it there is in lib/ another jar (external library) that I use in main (import it.xxx.xx).
How do I load also this external library dynamically in classpath?
My code doesn't work:
public static void runOne(String jar, String class_name, Optional<String> test_name,
TestExecutionListener listener) throws Exception {
Launcher launcher = LauncherFactory.create();
ClassLoader loader = URLClassLoader.newInstance(
new URL[] { new File(pathJars+"/"+jar).toURI().toURL() },
ServiceUtil.class.getClassLoader()
);
loader.getClass();
addURL(loader); <--here i want add a jar to classpath!
Class cls=loader.loadClass(class_name);
Constructor constructor = cls.getConstructor();
constructor.newInstance();
LauncherDiscoveryRequest request;
if (test_name.isPresent()) {
Method m = cls.getMethod(test_name.get());
request = LauncherDiscoveryRequestBuilder.request()
.selectors(selectMethod(cls,m))
.build();
}
else{
request = LauncherDiscoveryRequestBuilder.request()
.selectors(selectClass(cls))
.build();
}
TestPlan testPlan = launcher.discover(request);
launcher.registerTestExecutionListeners(listener);
launcher.execute(request);
//launcher.execute(request);
loader=null;
System.gc();
}
public static void addURL(ClassLoader loader) throws IOException {
URL u=loader.getResource("lib/sem-platform-sdk-1.0-SNAPSHOT.jar");
Class[] parameters = new Class[]{URL.class};
URLClassLoader sysloader = (URLClassLoader) ClassLoader.getSystemClassLoader();
Class sysclass = URLClassLoader.class;
try {
Method method = sysclass.getDeclaredMethod("addURL", parameters);
method.setAccessible(true);
method.invoke(sysloader, new Object[]{u});
} catch (Throwable t) {
t.printStackTrace();
throw new IOException("Error, could not add URL to system classloader");
}//end try catch
}//end method
Thanks
This is generally done with a build tool (e.g. maven or gradle). I don't know if you are using one of these. They make life so much easier.
We use Maven with the Apache Shade plugin to do exactly this. Maven has commands to set up the configuration for you automatically, then you add the Shade plugin to the resulting configuration file (pom.xml).
https://maven.apache.org/plugins/maven-shade-plugin/index.html
If I understand your problem correctly, you want the classes loaded from the jar file to be able to access the classes in the nested jar file. You can accomplish this by creating a ClassLoader with one entry for the jar file and another entry for the nested jar file.
Java has a special URL scheme, jar:, for referring to a jar entry directly. (This scheme and syntax is described in the documentation of JarURLConnection.) So you can construct your ClassLoader this way:
URL jarURL = new File(pathJars+"/"+jar).toURI().toURL();
URL semURL = new URL("jar:" + jarURL + "!/"
+ "lib/sem-platform-sdk-1.0-SNAPSHOT.jar");
ClassLoader loader = URLClassLoader.newInstance(
new URL[] { jarURL, semURL },
ServiceUtil.class.getClassLoader()
);

Tomcat 8 new Resource implementation to map Jar files in a separate directory

With tomcat 8 I have extend the WebAppClassLoader and add some jar filed from a shared location to the classloader path using addRepository() method. With tomcat 8 addRepository have been removed and new resource implementation have been introduced. I'm still able to use the addUrl method to add jar files. But I would like to implement the new resource based implementation.
I've tried with
DirResourceSet dirResourceSet = new DirResourceSet(getContext().getResources(), "/WEB-INF/lib", "/home/thusitha/lib/runtimes/cxf", "/");
WebResourceRoot webResourceRoot = getContext().getResources();
webResourceRoot.getContext().getResources().addPreResources(dirResourceSet);
But this is not working and still it throws classnotfoundexception
Can someone tell me how to map a directory which contains jars to a particular webapp using Tomcat new resource implementation?
A solution to this problem is to register your resources by overriding the ContextConfig class (org.apache.catalina.startup.ContextConfig). Catalina enters a starting state immediately after it scans your document path for resources. Most of the processing of those resources, such as annotations, is handled by the ContextConfig LifecycleListener. To ensure the resources are added before the context configuration takes place, override the ContextConfig.
final Context currentContext = ctx;
ContextConfig ctxCfg = new ContextConfig() {
#Override
public void lifecycleEvent(LifecycleEvent event) {
if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) {
WebResourceRoot webResourcesRoot = currentContext.getResources();
String baseDir = Platform.getBaseDir(); // Server Base Directory
File libDir = new File(baseDir + File.separator + "lib");
DirResourceSet dirResourceSet = null;
try {
dirResourceSet = new DirResourceSet(webResourcesRoot, "/WEB-INF/lib", libDir.getCanonicalPath(), "/");
} catch (IOException e) {
throw new RuntimeException(e);
}
webResourcesRoot.addPostResources(dirResourceSet);
String[] possibleJars = dirResourceSet.list("/WEB-INF/lib");
for(String libfile : possibleJars) {
WebResource possibleJar = dirResourceSet.getResource("/WEB-INF/lib/"+libfile);
System.err.println(String.format("Loading possible jar %s",possibleJar.getCanonicalPath())); // Just checking...
if (possibleJar.isFile() && possibleJar.getName().endsWith(".jar")) {
WebResourceSet resourceSet = new JarResourceSet(webResourcesRoot, "/WEB-INF/classes", possibleJar.getCanonicalPath(),"/");
webResourcesRoot.addPostResources(resourceSet);
}
}
}
super.lifecycleEvent(event);
}
};
ctx.addLifecycleListener(ctxCfg);
This is an undocumented solution that works on Tomcat 8.0.23. Considering the complexity and difficulty of this I can't say it is a better solution than adding jars directly to ClassLoaders.

Dynamically add a properties file in classpath in tomcat web application

I am trying to add a properties file to the classpath dynamically as below
try {
File fileToAdd = new File(FILE_PATH);
URL u = fileToAdd.toURL();
ClassLoader sysLoader = ClassLoader.getSystemClassLoader();
if (sysLoader instanceof URLClassLoader) {
sysLoader = (URLClassLoader) sysLoader;
Class<URLClassLoader> sysLoaderClass = URLClassLoader.class;
// use reflection to invoke the private addURL method
Method method = sysLoaderClass.getDeclaredMethod("addURL",
new Class[] { URL.class });
method.setAccessible(true);
method.invoke(sysLoader, new Object[] { u });
}
} catch (Exception e) {
logger.error(e.getMessage());
}
But i cant see this file in my classpath. When i checked it using
System.getProperty("java.class.path")
I cant see my file in this list. Am i missing anything here?
you can't add the URL of the properties file, you have to add the URL of the directory in which the properties file resides in. As in: method.invoke(sysLoader, fileToAdd.getParent().toURL());
then you can use ClassLoader.getResourceAsStream("my.properties"); and the ClassLoader will search the newly added directory for the file.
from URLClassLoader
"This class loader is used to load classes and resources from a search path of URLs referring to both JAR files and directories. Any URL that ends with a '/' is assumed to refer to a directory. Otherwise, the URL is assumed to refer to a JAR file which will be opened as needed."
Perhaps try this code, but changing java.library.path or keep it the way it is if you can live with using the library path instead.
/**
* Allows you to add a path to the library path during runtime
* #param dllLocation The path you would like to add
* #return True if the operation completed successfully, false otherwise
*/
public boolean addDllLocationToPath(final String dllLocation)
{
//our return value
boolean retVal = false;
try
{
System.setProperty("java.library.path", System.getProperty("java.library.path") + ";" + dllLocation);
//get the sys path field
Field fieldSysPath = ClassLoader.class.getDeclaredField("sys_paths");
fieldSysPath.setAccessible(true);
fieldSysPath.set(null, null);
retVal = true;
}
catch (Exception e)
{
System.err.println("Could not modify path");
}
return retVal;
}

Can I dynamically discover xml files in the classpath inside an EJB 3 container?

Background:
One of the components of our project operates using spring. Some SQL code is dynamically generated, based on a given XML spring configuration.
At first it was fine to store all the XML configurations in the same package on the classpath, (and then load it as a resource when the service is called) but over time we ended up with a large number of configurations. It came time to separate the configurations into different namespaces.
The Goal
What I want is, given a starting package on the classpath, to recursively walk the directory structure and discover any spring XML files dynamically. (So that as new configurations / packages are added, the files will still be found by the service).
The Problem
I was able to accomplish my goal fine when running outside an EJB container by using Thread.getContextClassloader().getResource(myBasePackage), then getting a File object and using it to walk the tree on the filesystem. Clunky, I know, but it was still classpath relative and it worked.
However, you cannot do this inside an EJB container (you can't interact with the filesystem at all), so I had to use the rather annoying workaround in which I maintain a list of hardcoded packages to search.
The Question
Is there a way (running inside an EJB container) to dynamically walk the classpath (from a given starting location) searching for arbitrary resources?
Short answer: Not while staying in compliance with the EJB spec. Because the spec envisions containers running in all kinds of non-standard situations, it does not make this possible.
Longer answer: Since you are not creating these resources dynamically, I would write a routine that gives you a list of all of the resources at build time and puts them in a dynamically generated file that your EJB knows how to reference. So you basically create a directory listing of packages and files that you can load in the EJB that are referenced in one master file.
Spring answer: Spring supports finding resources on the classpath, although I have no idea how well this works in the EJB context (and I doubt its EJB compliant, but I haven't checked). Some details here.
DISCLAIMER: As already pointed out, creating resources in the classpath is not recommended and depending on the EJB container explicitly forbidden. This may cause you a lot of problems because containers may explode your resources into another folder or even replicate the resources throughout the cluster (if thats the case). In order to create resources dynamically you have to create a custom classloader. So, I would never do it. It is better to access the filesystem directly than the classpath. It is less ugly and eventually cluster-safe if you use a remote filesystem + file locks.
If even after all I explained you still want to play with the classpath, you can try to do something like: get the classloader via
ClassLoader cld = Thread.currentThread().getContextClassLoader();
Starting from a base package enumerate all occurrences
Enumeration<URL> basePackageUrls = cld.getResources(basePackagePath);
Each URL is generally either a file link (file:///home/scott/.../MyResource.properties) or a jar link (file:///lib.jar!/com/domain/MyResource.properties). You have to check the pattern in the URL. Using that, enumerate the contents of the folder using the normal java API and find the subpackages. Proceed until you have scanned all packages.
See the class below (will be released with an open-source project of mine soon). It implemens a classpath scanner that you can pass in a selector. It works like a visitor. It my work for you, if not, get ideas from it. See the sample annotation selector at the end.
public class ClasspathScanner
{
private static final Log log = LogFactory.getLog(ClasspathScanner.class);
private static final String JAR_FILE_PATTERN = ".jar!";
private ClassSelector selector;
private Set<Class<?>> classes;
// PUBLIC METHODS ------------------------------------------------------------------------------
public synchronized Set<Class<?>> scanPackage(String basePackage, ClassSelector selector)
throws Exception
{
if (selector == null)
{
throw new NullPointerException("Selector cannot be NULL");
}
this.selector = selector;
this.classes = new HashSet<Class<?>>();
Set<Class<?>> aux;
try
{
scanClasses0(basePackage);
aux = this.classes;
}
finally
{
this.selector = null;
this.classes = null;
}
return aux;
}
// HELPER CLASSES ------------------------------------------------------------------------------
private void scanClasses0(String basePackage)
throws IOException, ClassNotFoundException, FileNotFoundException
{
File packageDirectory = null;
ClassLoader cld = getLoader();
String basePackagePath = basePackage.replace('.', '/');
Enumeration<URL> basePackageUrls = cld.getResources(basePackagePath);
if (basePackageUrls == null || !basePackageUrls.hasMoreElements())
{
throw new ClassNotFoundException("Base package path not found: [" + basePackagePath
+ "]");
}
while (basePackageUrls.hasMoreElements())
{
String packagePath = basePackageUrls.nextElement().getFile();
if (packagePath.contains(JAR_FILE_PATTERN))
{
scanJarFile(basePackagePath, packagePath);
}
else
{
packageDirectory = new File(packagePath);
scanDirectory(basePackage, packageDirectory);
}
}
}
private void scanDirectory(String packageName, File packagePath)
throws ClassNotFoundException, FileNotFoundException
{
if (packagePath.exists())
{
File[] packageFiles = packagePath.listFiles();
for (File file : packageFiles)
{
if (file.isFile() && file.getName().endsWith(".class"))
{
String fullFileName = packageName + '.' + file.getName();
checkClass(fullFileName);
}
else if (file.isDirectory())
{
scanDirectory(packageName + "." + file.getName(), file);
}
}
}
else
{
throw new FileNotFoundException(packagePath.getPath());
}
}
private void scanJarFile(String basePackagePath, String jarFileUrl)
throws IOException, ClassNotFoundException
{
String jarFilePath = jarFileUrl.substring("file:".length(), jarFileUrl
.indexOf(JAR_FILE_PATTERN)
+ JAR_FILE_PATTERN.length() - 1);
log.debug("URL JAR file path: [" + jarFilePath + "]");
jarFilePath = URLDecoder.decode(jarFilePath, "UTF-8");
log.debug("Decoded JAR file path: [" + jarFilePath + "]");
JarFile jar = new JarFile(new File(jarFilePath));
for (Enumeration<JarEntry> jarFiles = jar.entries(); jarFiles.hasMoreElements();)
{
JarEntry file = jarFiles.nextElement();
String fileName = file.getName();
if (!file.isDirectory() && fileName.endsWith(".class")
&& fileName.startsWith(basePackagePath))
{
String className = fileName.replace('/', '.');
checkClass(className);
}
}
}
private void checkClass(String fullFilePath) throws ClassNotFoundException
{
String className = fullFilePath.substring(0, fullFilePath.length() - 6);
Class<?> c = getLoader().loadClass(className);
if (selector.select(c))
{
classes.add(c);
}
}
private ClassLoader getLoader()
{
ClassLoader loader = Thread.currentThread().getContextClassLoader();
if (loader == null)
{
loader = getClass().getClassLoader();
}
return loader;
}
// INNER CLASSES -------------------------------------------------------------------------------
public interface ClassSelector
{
boolean select(Class<?> clazz);
}
public static class AnnotatedClassSelector implements ClassSelector
{
private final Class<? extends Annotation>[] annotations;
public AnnotatedClassSelector(Class<? extends Annotation>... annotations)
{
this.annotations = annotations;
}
public boolean select(Class<?> clazz)
{
for (Class<? extends Annotation> ac : annotations)
{
if (clazz.isAnnotationPresent(ac))
{
return true;
}
}
return false;
}
}
}

getSystemResourceAsStream() returns null

Hiii...
I want to get the content of properties file into InputStream class object using getSystemResourceAsStream(). I have built the sample code. It works well using main() method,but when i deploy the project and run on the server, properties file path cannot obtained ... so inputstream object store null value.
Sample code is here..
public class ReadPropertyFromFile {
public static Logger logger = Logger.getLogger(ReadPropertyFromFile.class);
public static String readProperty(String fileName, String propertyName) {
String value = null;
try {
//fileName = "api.properties";
//propertyName = "api_loginid";
System.out.println("11111111...In the read proprty file.....");
// ClassLoader loader = ClassLoader.getSystemClassLoader();
InputStream inStream = ClassLoader.getSystemResourceAsStream(fileName);
System.out.println("In the read proprty file.....");
System.out.println("File Name :" + fileName);
System.out.println("instream = "+inStream);
Properties prop = new Properties();
try {
prop.load(inStream);
value = prop.getProperty(propertyName);
} catch (Exception e) {
logger.warn("Error occured while reading property " + propertyName + " = ", e);
return null;
}
} catch (Exception e) {
System.out.println("Exception = " + e);
}
return value;
}
public static void main(String args[]) {
System.out.println("prop value = " + ReadPropertyFromFile.readProperty("api.properties", "api_loginid"));
}
}
i deploy the project and run on the server,
This sounds like a JSP/Servlet webapplication. In that case, you need to use the ClassLoader which is obtained as follows:
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
This one has access to the all classpath paths tied to the webapplication in question and you're not anymore dependent on which parent classloader (a webapp has more than one!) has loaded your class.
Then, on this classloader, you need to just call getResourceAsStream() to get a classpath resource as stream, not the getSystemResourceAsStream() which is dependent on how the webapplication is started. You don't want to be dependent on that as well since you have no control over it at external hosting:
InputStream input = classLoader.getResourceAsStream("filename.extension");
This is finally more robust than your initial getSystemResourceAsStream() approach and the Class#getResourceAsStream() as suggested by others.
The SystemClassLoader loads resources from java.class.path witch maps to the system variable CLASSPATH. In your local application, you probably have the resource your trying to load configured in java.class.path variable. In the server, it's another story because most probably the server loads your resources from another class loader.
Try using the ClassLoader that loaded class using the correct path:
getClass().getResourceAsStream(fileName);
This article might also be useful.
Try using getResourceAsStream() instead of getSystemResourceAsStream().

Categories

Resources