4

I'm using the Java money (JSR354) reference implementation found here: http://javamoney.github.io/ri.html

However, I'm using it in an OSGI environment. This is giving me the following exception:

[qtp305372452-33] ERROR org.javamoney.moneta.spi.MonetaryConfig - Error loading javamoney.properties, ignoring bundleresource://7.fwk302155142:3/javamoney.properties
java.lang.IllegalStateException: AmbiguousConfiguration detected for 'load.ECBHistoricRateProvider.resource'.
    at org.javamoney.moneta.spi.MonetaryConfig.updateConfig(MonetaryConfig.java:90)
    at org.javamoney.moneta.spi.MonetaryConfig.<init>(MonetaryConfig.java:53)
    at org.javamoney.moneta.spi.MonetaryConfig.<clinit>(MonetaryConfig.java:39)
    at org.javamoney.moneta.DefaultMonetaryContextFactory.getContext(DefaultMonetaryContextFactory.java:38)
    at org.javamoney.moneta.Money.<clinit>(Money.java:79)
    at org.javamoney.moneta.internal.MoneyAmountBuilder.create(MoneyAmountBuilder.java:42)
    at org.javamoney.moneta.internal.MoneyAmountBuilder.create(MoneyAmountBuilder.java:33)
    at org.javamoney.moneta.spi.AbstractAmountBuilder.create(AbstractAmountBuilder.java:61)
    at com.eijsink.ef.module.pricing.Price.createAmount(Price.java:214)
    at com.eijsink.ef.module.pricing.Price.<init>(Price.java:73)
    at com.eijsink.ef.module.pricing.view.PriceField.storeValue(PriceField.java:210)
    at com.eijsink.ef.module.pricing.view.PriceField.lambda$6(PriceField.java:169)
    at com.eijsink.ef.module.pricing.view.PriceField$$Lambda$163/23040973.valueChange(Unknown Source)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:483)
    at com.vaadin.event.ListenerMethod.receiveEvent(ListenerMethod.java:508)
    at com.vaadin.event.EventRouter.fireEvent(EventRouter.java:198)
    at com.vaadin.event.EventRouter.fireEvent(EventRouter.java:161)
    at com.vaadin.server.AbstractClientConnector.fireEvent(AbstractClientConnector.java:977)
    at com.vaadin.ui.AbstractField.fireValueChange(AbstractField.java:1137)
    at com.vaadin.ui.AbstractField.setValue(AbstractField.java:548)
    at com.vaadin.ui.AbstractSelect.setValue(AbstractSelect.java:709)
    at com.vaadin.ui.ComboBox.changeVariables(ComboBox.java:674)
    at com.vaadin.server.communication.ServerRpcHandler.changeVariables(ServerRpcHandler.java:486)
    at com.vaadin.server.communication.ServerRpcHandler.handleInvocations(ServerRpcHandler.java:305)
    at com.vaadin.server.communication.ServerRpcHandler.handleRpc(ServerRpcHandler.java:184)
    at com.vaadin.server.communication.UidlRequestHandler.synchronizedHandleRequest(UidlRequestHandler.java:92)
    at com.vaadin.server.SynchronizedRequestHandler.handleRequest(SynchronizedRequestHandler.java:41)
    at com.vaadin.server.VaadinService.handleRequest(VaadinService.java:1408)
    at com.vaadin.server.VaadinServlet.service(VaadinServlet.java:350)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:848)
    at org.eclipse.equinox.http.servlet.internal.ServletRegistration.service(ServletRegistration.java:61)
    at org.eclipse.equinox.http.servlet.internal.ProxyServlet.processAlias(ProxyServlet.java:128)
    at org.eclipse.equinox.http.servlet.internal.ProxyServlet.service(ProxyServlet.java:76)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:848)
    at org.eclipse.equinox.http.jetty.internal.HttpServerManager$InternalHttpServiceServlet.service(HttpServerManager.java:386)
    at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:684)
    at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:501)
    at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:229)
    at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:137)
    at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:533)
    at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1088)
    at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:428)
    at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:193)
    at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1020)
    at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:135)
    at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:116)
    at org.eclipse.jetty.server.Server.handle(Server.java:370)
    at org.eclipse.jetty.server.AbstractHttpConnection.handleRequest(AbstractHttpConnection.java:494)
    at org.eclipse.jetty.server.AbstractHttpConnection.content(AbstractHttpConnection.java:982)
    at org.eclipse.jetty.server.AbstractHttpConnection$RequestHandler.content(AbstractHttpConnection.java:1043)
    at org.eclipse.jetty.http.HttpParser.parseNext(HttpParser.java:865)
    at org.eclipse.jetty.http.HttpParser.parseAvailable(HttpParser.java:240)
    at org.eclipse.jetty.server.AsyncHttpConnection.handle(AsyncHttpConnection.java:82)
    at org.eclipse.jetty.io.nio.SelectChannelEndPoint.handle(SelectChannelEndPoint.java:667)
    at org.eclipse.jetty.io.nio.SelectChannelEndPoint$1.run(SelectChannelEndPoint.java:52)
    at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:608)
    at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:543)
    at java.lang.Thread.run(Thread.java:744)

The exception is not thrown when using the Java money classes, but instead it is only logged. Everything works, but this exception pops up all the time in my logs, which is very annoying for me and my colleagues.

I've traced the problem down to this class in the library:

package org.javamoney.moneta.spi;

//imports

public final class MonetaryConfig {

    private static final Logger LOG = Logger
            .getLogger(MonetaryConfig.class.getName());

    private static final MonetaryConfig INSTANCE = new MonetaryConfig();

    private Map<String, String> config = new HashMap<>();
    private Map<String, Integer> priorities = new HashMap<>();

    private MonetaryConfig() {
        try {
            Enumeration<URL> urls = getClass().getClassLoader().getResources(
                    "javamoney.properties");
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                try {
                    Properties props = new Properties();
                    props.load(url.openStream());
                    updateConfig(props);
                } catch (Exception e) {
                    LOG.log(Level.SEVERE,
                            "Error loading javamoney.properties, ignoring "
                                    + url, e);
                }
            }
        } catch (IOException e) {
            LOG.log(Level.SEVERE, "Error loading javamoney.properties.", e);
        }
    }

    private void updateConfig(Properties props) {
        for (Map.Entry<Object, Object> en : props.entrySet()) {
            String key = en.getKey().toString();
            String value = en.getValue().toString();
            int prio = 0;
            if (key.startsWith("{")) {
                int index = key.indexOf('}');
                if (index > 0) {
                    String prioString = key.substring(1, index);
                    try {
                        prio = Integer.parseInt(prioString);
                        key = key.substring(index + 1);
                    } catch (NumberFormatException e) {
                        LOG.warning("Invalid config key in javamoney.properties: " + key);
                    }
                }
            }
            Integer existingPrio = priorities.get(key);
            if (Objects.isNull(existingPrio)) {
                priorities.put(key, prio);
                config.put(key, value);
            } else if (existingPrio < prio) {
                priorities.put(key, prio);
                config.put(key, value);
            } else if (existingPrio == prio) {
                throw new IllegalStateException(
                        "AmbiguousConfiguration detected for '" + key + "'.");
            }
            // else ignore entry with lower prio!
        }
    }

    public static Map<String, String> getConfig() {
        return Collections.unmodifiableMap(INSTANCE.config);
    }

}

The updateConfig() method throws the exception. The MonetaryConfig constructor logs it. The problem occurs because the line

Enumeration<URL> urls = getClass().getClassLoader().getResources(
                "javamoney.properties");

returns 2 urls for the same file, namely: "bundleresource://7.fwk302155142/javamoney.properties" and "bundleresource://7.fwk302155142:3/javamoney.properties".

I checked the .JAR file, there is only 1 properties file and it is definitely being read twice.

The real source of the problem is the way the resource is loaded. In OSGI, this is not the way to load resources (You should get the resource from your bundle instead). However, I do not know how to modify the way this library loads the resource (apart from modifying and recompiling the sources, which I don't want to do).

Is there any OSGI technique I can use when loading this library to make this work properly?

EDIT: My Manifest bundle classpath

Bundle-ClassPath: jars/moneta-1.0-RC3.jar,
 jars/money-api-1.0-RC3.jar,
 jars/javax.annotation-api-1.2.jar
David ten Hove
  • 2,748
  • 18
  • 33
  • 1
    Is it possible you have two versions of the jar file deployed to your OSGi container, hence the two URLs? Might be worth deleting the bundle cache folder to check. – Nick Wilson May 01 '15 at 14:01
  • I'm running Eclipse, we are developing Eclipse PDE plugins. Can you tell me where to find the bundle cache folder? – David ten Hove May 01 '15 at 14:27

2 Answers2

4

From the URLs:

bundleresource://7.fwk302155142/javamoney.properties
bundleresource://7.fwk302155142:3/javamoney.properties

It looks like the resource is in there twice due to the Bundle-Classpath. The :3 should refer an index into the Bundle-Classpath. Make sure that no entry in the Bundle-Classpath contains the properties file.

BJ Hargrave
  • 9,324
  • 1
  • 19
  • 27
  • Thank you for your suggestion. I have edited my question and added the bundle classpath from my manifest file. Please take a look – David ten Hove May 01 '15 at 14:25
  • As BJ said, *you* need to check that no entry in the `Bundle-Classpath` contains the properties file. There is no way for BJ to check that for you with the information you have provided. – Neil Bartlett May 03 '15 at 21:46
  • @NeilBartlett I provided the exact information he needs to my question. It's right there at the bottom. Anyway I checked it myself too and it's not there. – David ten Hove May 04 '15 at 08:03
  • @DavidtenHove No you didn't. That's just the names of some JAR files on your Bundle-Classpath. How on earth is BJ, or anybody else, supposed to tell whether any of them contain "javamoney.properties"? Is it really too much to expect you to check this for yourself?? – Neil Bartlett May 04 '15 at 12:09
  • @NeilBartlett You are right, I'm sorry. I thought he meant the problem is that the properties file itself is on the classpath. I did check the contents of the jar files though, and only one of them contains the properties file, and only 1 time. The properties file itself is not present in the project which has the bundles in its classpath. – David ten Hove May 04 '15 at 12:41
3

This is an old topic, but surprisingly, I got this issue today. What fixed it for me was to change my dependency from:

<dependency>
    <groupId>org.javamoney</groupId>
    <artifactId>moneta</artifactId>
    <version>1.1</version>
</dependency>

to:

<dependency>
    <groupId>org.javamoney.moneta</groupId>
    <artifactId>moneta-core</artifactId>
    <version>1.3</version>
</dependency>

This is for a Java 8 project.

user3179263
  • 170
  • 1
  • 5