Loading a ResourceBundle within an OSGi bundle
Asked Answered
C

2

6

I have an application that uses java.util.ResourceBundle to handle display strings. I use the ListResourceBundle as a base type because I want to be able to return an array of strings for some property keys (most of them are the standard name/value pairing).

When I'm trying to load the ResourceBundle in the OSGi environment I get a MissingResourceException. I can't figure out why this would occur. I can resolve the class name and make a new instance of the class in the two statements before I try to fetch the bundle, so I know the class is in the bundle. I'm using a locale (en_AU) but not providing a specific class for that locale, the fallback rules should select my default implementation. Even when I added a class for the locale it didn't resolve.

The process I go through to fetch data is:

The service

public interface NameManager {
   public String getCategoryName(String identifier, Locale locale);
}

The implementation of this

public class OsgiNameManager implements NameManager {
   public String getCategoryName(@Nonnull Identifier category, Locale locale)
   {
      try {
        Collection<ServiceReference> references = (Collection)osgiContext.getServiceReferences(
                 DisplayNameProvider.class, "(namespace=" + category + ")");
        Iterator<ServiceReference> it = references.iterator();
        while ( it.hasNext() ) {
           ServiceReference reference = (ServiceReference)it.next();
           DisplayNameProvider namer = (DisplayNameProvider)osgiContext.getService(reference);
           String name = namer.getCategoryName(category, locale);
           osgiContext.ungetService(reference);

           if (name != null)
              return name;
        }
     }
     catch (InvalidSyntaxException badFormat) {
        LOG.warn("No registered provider for category [" + category + "]" , badFormat);
     }

      return null;
   }
}

This uses a custom service interface, DisplayNameProvider. Modules that wish to register names add this as a service in their activator. The default implementation takes a class for the fully qualified resource bundle name.

public class DefaultDisplayNameProvider implements DisplayNameProvider {
    private Class<?> categoryResolver;
    public String getCategoryName(@Nonnull String category, Locale locale)
    {
       ResourceBundle res = ResourceBundle.getBundle(categoryResolver.toString(), locale);
       try {
          return res.getString(CATEGORY_NAME_KEY);
       }
       catch (MissingResourceException noCols) {
          return null;
       }
    }
 }

(Note, I've left out some fields and static references, but this is the gist of what is done).

I'm getting inside of the DefaultDisplayNameProvider so I know the resource is being found. I know the class is in the bundle (it is stored in the internal bundle classes but is visible to the local class loader).

My question is what do I need to do load my ResourceBundle's up within OSGi? I'd prefer to continue with this approach rather than start using Resource Fragments or hard-coding string names.

Solution

Both of the advice below to help with the ClassLoader was useful but it turns out my problem was much simpler. Don't use toString() to get the class name (which I should've known but must have been lazy that day). The correct method to use is Class.getName().

This was found by noticing that if I used a properties file it loaded correctly. Then if I typed the class name in also worked. It was only when I was trying to reduce text entry error by using the existing class reference to get the correct name.

I'll stick with the String versions of the resource names. This means the JVM doesn't have to load the class till actually requested (not that there is a lot for it to do at this time).

Capablanca answered 28/11, 2012 at 5:21 Comment(0)
B
5

Off the top of my head: You might want to try the ResourceBundle.getBundle(name,locale,classloader) method, as ResourceBundle wouldn't know which classloader to search. You can get the classloader of the DefaultDisplayNameProvider with getClass().getClassLoader().

regards, Frank

Bathsheeb answered 28/11, 2012 at 7:16 Comment(1)
I tried using the class loader from the DefaultDisplayNameProvider and from the specified class. They both use the same loader (as I would expect if they are both in the same bundle) but the ResourceBundle continues to fail to find the resource.Capablanca
A
3

You should always use the getBundle methods that take a ClassLoader parameter. Calling the other versions of this method are pretty much as bad as calling the single-argument Class.forName... i.e., you're forcing the Java runtime to guess which classloader contains the resources.

I really don't understand why ResourceBundle forces us to pass a class name rather than a Class object. It's just another example of poor design in the Java standard libraries, I'm afraid.

Arp answered 28/11, 2012 at 10:56 Comment(1)
I see now that the ResourceBundle uses this.getClass().getClassLoader() when you don't supply one. That is likely a very bad thing to do in an OSGi environment. Given that the resource can manage classes or property files (as the two built in types) I can see why they didn't just want to limit resources to a class.Capablanca

© 2022 - 2024 — McMap. All rights reserved.