Here is what I would like to achieve. We have an application that is running as a servlet on an IBM Domino server.
The application uses resource bundle to get translated messages and labels according to the browser language.
We want to enable customers to override some of the values.
We cannot modify the bundle_lang.properties files in the .jar at runtime.
So the idea was to provide additional bundleCustom_lang.properties files along with the .jar
This bundle could be loaded at runtime using
private static void addToClassPath(String s) throws Exception {
File file = new File(s);
URLClassLoader cl = (URLClassLoader) ClassLoader.getSystemClassLoader();
java.lang.reflect.Method m = URLClassLoader.class.getDeclaredMethod("addURL", new Class[] { URL.class });
m.setAccessible(true);
m.invoke(cl, new Object[] { file.toURI().toURL() });
}
So far, so good, this works in Eclipse. Here I had the bundleCustom files in a directory outside the workspace ( /volumes/DATA/Temp/ )
Once the addition ResourceBundle is available, We check this bundle for the key first. If it returns a value than this value is being used for the translation. If no value is returned, or the file does not exist, the value from the bundle inside the .jar is used.
My full code is here
public class BundleTest2 {
static final String CUSTOM_BUNDLE_PATH = "/volumes/DATA/Temp/";
static final String CUSTOM_BUNDLE_MODIFIER = "Custom";
public static void main(String[] args) {
try {
addToClassPath(CUSTOM_BUNDLE_PATH);
System.out.println(_getTranslation("LabelBundle", "OutlineUsersAllVIP"));
} catch (Exception e) {
}
}
private static String _getTranslation(String bundle, String translation) {
return _getTranslation0(bundle, new Locale("de"), translation);
}
private static String _getTranslation0(String bundle, Locale locale, String key) {
String s = null;
try {
try {
ResourceBundle custom = ResourceBundle.getBundle(bundle + CUSTOM_BUNDLE_MODIFIER, locale);
if (custom.containsKey(key)) {
s = custom.getString(key);
}
} catch (MissingResourceException re) {
System.out.println("CANNOT FIND CUSTOM RESOURCE BUNDLE: " + bundle + CUSTOM_BUNDLE_MODIFIER);
}
if (null == s || "".equals(s)) {
s = ResourceBundle.getBundle(bundle, locale).getString(key);
}
} catch (Exception e) {
}
return s;
}
private static void addToClassPath(String s) throws Exception {
File file = new File(s);
URLClassLoader cl = (URLClassLoader) ClassLoader.getSystemClassLoader();
java.lang.reflect.Method m = URLClassLoader.class.getDeclaredMethod("addURL", new Class[] { URL.class });
m.setAccessible(true);
m.invoke(cl, new Object[] { file.toURI().toURL() });
}
}
When I try the same from inside the servlet, I get a MissingResourceException.
I also tried to put the .properties files into a customization.jar and provide the full path ( incl. the .jar ) when invoking addToClassPath().
Apparently, the customization.jar is loaded ( it is locked in the file system ), but I still get the MissingResourceException.
We already use the same code in addToClassPath to load a Db2 driver and this is working as expected.
What am I missing?
Why don't you use Database to store the overriden translations? Persisting something crated by client in the local deployment of application is generally not a good idea, what will happen if you redeploy the app, will these resources be deleted? What if you have to run another node of your app, how will you replicate the custom properties file?
Related
We're trying to debug an unreproducible issue with WebStart, where access to resources inside Jars will "randomly" fail. Maybe one every 1000 application run will end with this error, which can happen anywhere where resources are read from a jar.
Searching in Google and the Java Bug database brought nothing similar (or at least, nothing helpful).
We are trying to get more info into what happens on the client by "instrumenting" the application so we track all calls to ClassLoader.getResource(String) (including indirectly over ClassLoader.getResourceAsStream(String)). Without changing the app code, I have created a "launcher" that would run the whole app with a custom classloader.
Unfortunately, it seems my ClassLoader is somehow bypassed. I do not see any of the expected System.out output. Here is what I tried:
private static final class MyClassLoader extends ClassLoader {
private MyClassLoader() {
super(TheClassThatMainIsIn.class.getClassLoader());
}
#Override
public URL getResource(String name) {
System.out.println("getResource("+name+")");
// Snip
return super.getResource(name);
}
#Override
public InputStream getResourceAsStream(String name) {
System.out.println("getResourceAsStream("+name+")");
final URL url = getResource(name);
try {
return url != null ? url.openStream() : null;
} catch (final IOException e) {
return null;
}
}
}
public static void main(String[] args) {
System.out.println("Starting MyRealApp Launcher ...");
final MyClassLoader loader = new MyClassLoader();
try {
Class<?> realAppClasss = loader.loadClass("MyRealAppClass");
Method main = realAppClasss.getMethod("main", String[].class);
main.invoke(null, (Object) args);
} catch (final RuntimeException e) {
throw e;
} catch (final Error e) {
throw e;
} catch (final InvocationTargetException e) {
final Throwable cause = e.getCause();
if (cause instanceof RuntimeException) {
throw (RuntimeException) cause;
}
if (cause instanceof Error) {
throw (Error) cause;
}
throw new UndeclaredThrowableException(cause);
} catch (final Throwable t) {
throw new UndeclaredThrowableException(t);
}
}
What am I doing wrong here?
Yes. This works, in principal.
However, you've to account how the resource loading code get's to the class loader. Since the class don't show up, it looks like they use the parents class loader.
You've to account different scenarios:
Code using context class loader, like:
Thread.currentThread().getContextClassLoader().getResource("via-context");
This is easy to achieve, by setting it before calling into main:
Thread.currentThread().setContextClassLoader(loader);
Method main = realAppClasss.getMethod("main", String[].class);
main.invoke(null, (Object) args);
Next thing you've to account is code which 'takes' class loader from current class, and load it that. When you're class is loaded via the parent class loader, it will also use that class loader to get the resource. Like:
MyRealAppClass.class.getResource("via-class");
MyRealAppClass.class.getClassLoader().getResource("via-class");
objectInfApp.getClass().getClassLoader().getResource("via-class");
To avoid that you've to ensure that the apps classes are actually loaded with your class loader, not the parent. For a simple main, you can extend from the URL class loader, skip any parent and user the original class path for the URL's. Like:
// URL class loader to lookup in jars etc
private static class MyClassLoader extends URLClassLoader
{
public MyClassLoader(URL[] urls) {
// Use the given URLs and skip any parent class loader, directly go to the system loader
super(urls,null);
}
// ...
// Then setup the class path
String[] classPath = System.getProperty("java.class.path").split(";");
URL[] classPathUrls = new URL[classPath.length];
for (int i = 0; i < classPath.length; i++) {
classPathUrls[i] = new File(classPath[i]).toURL();
}
MyClassLoader loader = new MyClassLoader(classPathUrls);
This should cover the most basic cases. When you're actual application itself has more class loader trickery, there might more you need to setup.
I am working on a project which requires dynamic Loading of a class from a file system, I googled and found out that I need to use custom ClassLoader. I have implemented my own class loader which is working fine when I run it on console, the problem I when I try to deploy the application on the server it results in ClassNotFoundException.
The problem is the class which I am trying to load contain some references to another class which is already loaded by the application but it is trying to load the reference from the same location.
public class HandlerClassLoader extends ClassLoader {
static Logger log = Logger.getLogger(HandlerClassLoader.class.getName());
URLClassLoader ucl;
private ProcessDefinition processDefinition = null;
boolean flag = false;
public URL[] loadJars() throws MalformedURLException {
PropertiesFiles propFiles = new PropertiesFiles();
File f = new File(propFiles.getValue("jarslocation"));
log.debug("Loading JARS files from " + f.getAbsolutePath());
List<URL> urls = new ArrayList<URL>();
String filesName[] = f.list();
for (String jars : filesName)
if (jars.endsWith("jar")) {
urls.add(new URL("file:///"
+ propFiles.getValue("jarslocation") + "/" + jars));
}
URL[] array = urls.toArray(new URL[urls.size()]);
return array;
}
public HandlerClassLoader() {
super(Thread.currentThread().getContextClassLoader());
log.debug("Called to the " + this.getClass().getName()
+ " No Parameter Constructor");
try {
ucl = new URLClassLoader(loadJars());
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
public HandlerClassLoader(ClassLoader parent,
ProcessDefinition processDefinition) {
super(parent);
log.debug("Called to the " + this.getClass().getName()
+ " Parameterized Constructor");
try {
ucl = new URLClassLoader(loadJars());
} catch (MalformedURLException e) {
e.printStackTrace();
}
this.processDefinition = processDefinition;
}
public Class<?> findClass(String className) throws ClassNotFoundException {
log.debug("findClass method of " + this.getClass().getName()
+ " is called with className : " + className);
return ucl.loadClass(className);
}
I think the delegation principle is not working or maybe that server must have a different implementation of classloader.
Most likely, you do not delegate to the correct parent. I assume that picking up Thread.currentThread().getContextClassLoader() results in using the wrong class loader on the application server while this is simply the system class loader (class path) when running from a console app.
Therefore, you need to make sure that the parent that you are handing to your custom class loader is capable of seeing the classes you are intending to load by it.
On a final note, implementing your own class loader is a tricky business. For example, you have not accounted for locating ressources or for defining packages. Both might be required by third-party libraries that you are using. If you really only need to load files from disk, consider using a URLClassLoader.
I'm trying to extend my library for integrating Swing and JPA by making JPA config as automatic (and portable) as can be done, and it means programmatically adding <class> elements. (I know it can be done via Hibernate's AnnotationConfiguration or EclipseLInk's ServerSession, but - portability). I'd also like to avoid using Spring just for this single purpose.
I can create a persistence.xml on the fly, and fill it with <class> elements from specified packages (via the Reflections library). The problem starts when I try to feed this persistence.xml to a JPA provider. The only way I can think of is setting up a URLClassLoader, but I can't think of a way what wouldn't make me write the file to the disk somewhere first, for sole ability to obtain a valid URL. Setting up a socket for serving the file via an URL(localhost:xxxx) seems... I don't know, evil?
Does anyone have an idea how I could solve this problem? I know it sounds like a lot of work to avoid using one library, but I'd just like to know if it can be done.
EDIT (a try at being more clear):
Dynamically generated XML is kept in a String object. I don't know how to make it available to a persistence provider. Also, I want to avoid writing the file to disk.
For purpose of my problem, a persistence provider is just a class which scans the classpath for META-INF/persistence.xml. Some implementations can be made to accept dynamic creation of XML, but there is no common interface (especially for a crucial part of the file, the <class> tags).
My idea is to set up a custom ClassLoader - if you have any other I'd be grateful, I'm not set on this one.
The only easily extendable/configurable one I could find was a URLClassLoader. It works on URL objects, and I don't know if I can create one without actually writing XML to disk first.
That's how I'm setting things up, but it's working by writing the persistenceXmlFile = new File("META-INF/persistence.xml") to disk:
Thread.currentThread().setContextClassLoader(
new URLResourceClassLoader(
new URL[] { persistenceXmlFile.toURI().toURL() },
Thread.currentThread().getContextClassLoader()
)
);
URLResourceClassLoader is URLCLassLoader's subclass, which allows for looking up resources as well as classes, by overriding public Enumeration<URL> findResources(String name).
Maybe a bit late (after 4 years), but for others that are looking for a similar solution, you may be able to use the URL factory I created:
public class InMemoryURLFactory {
public static void main(String... args) throws Exception {
URL url = InMemoryURLFactory.getInstance().build("/this/is/a/test.txt", "This is a test!");
byte[] data = IOUtils.toByteArray(url.openConnection().getInputStream());
// Prints out: This is a test!
System.out.println(new String(data));
}
private final Map<URL, byte[]> contents = new WeakHashMap<>();
private final URLStreamHandler handler = new InMemoryStreamHandler();
private static InMemoryURLFactory instance = null;
public static synchronized InMemoryURLFactory getInstance() {
if(instance == null)
instance = new InMemoryURLFactory();
return instance;
}
private InMemoryURLFactory() {
}
public URL build(String path, String data) {
try {
return build(path, data.getBytes("UTF-8"));
} catch (UnsupportedEncodingException ex) {
throw new RuntimeException(ex);
}
}
public URL build(String path, byte[] data) {
try {
URL url = new URL("memory", "", -1, path, handler);
contents.put(url, data);
return url;
} catch (MalformedURLException ex) {
throw new RuntimeException(ex);
}
}
private class InMemoryStreamHandler extends URLStreamHandler {
#Override
protected URLConnection openConnection(URL u) throws IOException {
if(!u.getProtocol().equals("memory")) {
throw new IOException("Cannot handle protocol: " + u.getProtocol());
}
return new URLConnection(u) {
private byte[] data = null;
#Override
public void connect() throws IOException {
initDataIfNeeded();
checkDataAvailability();
// Protected field from superclass
connected = true;
}
#Override
public long getContentLengthLong() {
initDataIfNeeded();
if(data == null)
return 0;
return data.length;
}
#Override
public InputStream getInputStream() throws IOException {
initDataIfNeeded();
checkDataAvailability();
return new ByteArrayInputStream(data);
}
private void initDataIfNeeded() {
if(data == null)
data = contents.get(u);
}
private void checkDataAvailability() throws IOException {
if(data == null)
throw new IOException("In-memory data cannot be found for: " + u.getPath());
}
};
}
}
}
We can use the Jimfs google library for that.
First, we need to add the maven dependency to our project:
<dependency>
<groupId>com.google.jimfs</groupId>
<artifactId>jimfs</artifactId>
<version>1.2</version>
</dependency>
After that, we need to configure our filesystem behavior, and write our String content to the in-memory file, like this:
public static final String INPUT =
"\n"
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<note>\n"
+ " <to>Tove</to>\n"
+ " <from>Jani</from>\n"
+ " <heading>Reminder</heading>\n"
+ " <body>Don't forget me this weekend!</body>\n"
+ "</note>";
#Test
void usingJIMFS() throws IOException {
try (var fs = Jimfs.newFileSystem(Configuration.unix())) {
var path = fs.getPath(UUID.randomUUID().toString());
Files.writeString(path, INPUT);
var url = path.toUri().toURL();
assertThat(url.getProtocol()).isEqualTo("jimfs");
assertThat(Resources.asCharSource(url, UTF_8).read()).isEqualTo(INPUT);
}
}
We can find more examples in the official repository.
If we look inside the jimfs source code we will find the implementation is similar to #NSV answer.
I have the following problem.
A HashMap is used to set properties and the key is a ClassLoader.
The code that sets a property is the following (AxisProperties):
public static void setProperty(String propertyName, String value, boolean isDefault){
if(propertyName != null)
synchronized(propertiesCache)
{
ClassLoader classLoader = getThreadContextClassLoader();
HashMap properties = (HashMap)propertiesCache.get(classLoader);
if(value == null)
{
if(properties != null)
properties.remove(propertyName);
} else
{
if(properties == null)
{
properties = new HashMap();
propertiesCache.put(classLoader, properties);
}
properties.put(propertyName, new Value(value, isDefault));
}
}
}
One of these values is cached somewhere and I need to reset this hashmap but the problem is I don't know how to do this.
I thought to load the class (delegating to axis using a URLClassLoader) but I see that the code does getThreadContextClassLoader(); which is:
public ClassLoader getThreadContextClassLoader()
{
ClassLoader classLoader;
try
{
classLoader = Thread.currentThread().getContextClassLoader();
}
catch(SecurityException e)
{
classLoader = null;
}
return classLoader;
}
So I think it will use the classloader of my current thread not the one that I used to load the class to use (i.e. axis).
So is there a way around this?
Note: I already have loaded axis as part of my application. So the idea would be to reload it via a different classloader
If you know the class loader in question, you can set the context class loader before making the call into axis:
ClassLoader key = ...;
ClassLoader oldCtx = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(key);
// your code here.
}
finally {
Thread.currentThread().setContextClassLoader(oldCtx);
}
You often have to do this in cases when you are outside a servlet container, but the library assumes that you are in one. For instance, you have to do this with CXF in an OSGi container, where the semantics of context class loader are not defined. You can use a template like this to keep things clean:
public abstract class CCLTemplate<R>
{
public R execute( ClassLoader context )
throws Exception
{
ClassLoader oldCtx = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(context);
return inContext();
}
finally {
Thread.currentThread().setContextClassLoader(oldCtx);
}
}
public abstract R inContext() throws Exception;
}
And then do this when interacting with Axis:
ClassLoader context = ...;
new CCLTemplate<Void>() {
public Void inContext() {
// your code here.
return null;
}
}.execute(context);
I'm working with a Java sort-of-application-server (Smartfox) which can run multiple applications ("extensions") but has a very inconvenient classpath setup to go along with it, along with issues when trying to use SLF4J.
To work around that I'd like to wrap my applications in their own classloaders. Such a containing classloader should be much like Tomcat's, in that it
Can load classes from a directory containing JARs.
Prefers classes from its own classpath over those from the parent
Is there a library somewhere that has such a classloader I can just "drag and drop" in my project? If not, would it be hard to create it myself? Any known pitfalls?
OSGi (and other module systems) are designed to handle exactly this kind of problem.
It might look like overkill at first, but I think you'll quickly re-implement significant parts of the things that OSGi alread does for you.
Equinox is the OSGi implementation used by Eclipse, for example.
Since I had trouble embedding an OSGi container and it was indeed a bit overkill, I rolled my own solution. But I'll learn to use OSGi one day, in a situation where I don't need to embed the framework.
If you somehow happen to want to use this code, it's under the "do whatever you want with it" license.
public class SmartfoxExtensionContainer extends AbstractExtension {
private AbstractExtension extension;
private void initRealExtension() {
final String zone = this.getOwnerZone();
System.out.println("[SmartfoxExtensionContainer] ========= Init extension for zone " + zone + " =========");
try {
// load properties
File propFile = new File("wext/" + zone + ".properties");
System.out.println("[SmartfoxExtensionContainer] Load config from " + propFile.getCanonicalPath());
Properties props = new Properties();
final FileInputStream ins = new FileInputStream(propFile);
try {
props.load(new InputStreamReader(ins, "UTF-8"));
} finally {
try {
ins.close();
} catch (IOException e) {}
}
// construct classloader
File jarDir = new File(props.getProperty("classpath", "wext/" + zone));
System.out.println("[SmartfoxExtensionContainer] Load classes from " + jarDir.getCanonicalPath());
if (!jarDir.isDirectory()) throw new RuntimeException("That is not an existing directory");
final File[] fs = jarDir.listFiles();
URL[] urls = new URL[fs.length];
for (int f = 0; f < fs.length; f++) {
System.out.println("[SmartfoxExtensionContainer] " + fs[f].getName());
urls[f] = fs[f].toURI().toURL();
}
SelfishClassLoader cl = new SelfishClassLoader(urls, SmartfoxExtensionContainer.class.getClassLoader());
// get real extension class
String mainClass = props.getProperty("mainClass", "Extension");
System.out.println("[SmartfoxExtensionContainer] Main class: " + mainClass);
#SuppressWarnings("unchecked")
Class<? extends AbstractExtension> extClass = (Class<? extends AbstractExtension>) cl.loadClass(mainClass);
// create extension and copy settings
extension = extClass.newInstance();
extension.setOwner(this.getOwnerZone(), this.getOwnerRoom());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/* ======================= DELEGATES ======================= */
#Override
public void init() {
initRealExtension();
extension.init();
}
#Override
public void destroy() {
extension.destroy();
}
#Override
public void handleRequest(String arg0, ActionscriptObject arg1, User arg2, int arg3) {
extension.handleRequest(arg0, arg1, arg2, arg3);
}
#Override
public void handleRequest(String arg0, String[] arg1, User arg2, int arg3) {
extension.handleRequest(arg0, arg1, arg2, arg3);
}
#Override
public void handleInternalEvent(InternalEventObject arg0) {
extension.handleInternalEvent(arg0);
}
#Override
public Object handleInternalRequest(Object params) {
return extension.handleInternalRequest(params);
}
#Override
public void handleRequest(String cmd, JSONObject jso, User u, int fromRoom) {
extension.handleRequest(cmd, jso, u, fromRoom);
}
/* ======================= CUSTOM CLASSLOADER ======================= */
private static class SelfishClassLoader extends URLClassLoader {
SelfishClassLoader(URL[] urls, ClassLoader parent) {
super(urls, parent);
}
// override default behaviour: find classes in local path first, then parent
#Override protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
// First, check if the class has already been loaded
Class<?> clz = findLoadedClass(name);
if (clz == null) {
try {
clz = findClass(name);
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from current class loader
}
if (clz == null) {
// If still not found, then invoke parent.findClass in order
// to find the class.
clz = getParent().loadClass(name);
}
}
if (resolve) {
resolveClass(clz);
}
return clz;
};
}
}