在 Android 中为 ResourceBundle.getBundle(String path) 放置资源的位置?

Where to put resources in Android for ResourceBundle.getBundle(String path)?

我在我的 android 项目中使用了一个 ee-jars。它使用 ResourceBundle.getBundle(String path) 作为 i18n。由于资源在 jar 中被剥离,而 .apk 不包含资源,我遇到了一个异常:

java.util.MissingResourceException: Can't find resource for bundle 'org/apache/xml/security/resource/xmlsecurity_en_US', key ''
        at java.util.ResourceBundle.missingResourceException(ResourceBundle.java:239)
        at java.util.ResourceBundle.getBundle(ResourceBundle.java:231)
        at org.apache.wss4j.common.crypto.WSS4JResourceBundle.<init>(WSS4JResourceBundle.java:54)
        at org.apache.wss4j.common.crypto.WSProviderConfig.initializeResourceBundles(WSProviderConfig.java:199)
        at org.apache.wss4j.common.crypto.WSProviderConfig.init(WSProviderConfig.java:65)
        at org.apache.wss4j.dom.WSSConfig.init(WSSConfig.java:428)
        at org.apache.wss4j.dom.WSSConfig.getNewInstance(WSSConfig.java:435)
        at org.apache.wss4j.dom.WSSecurityEngine.getWssConfig(WSSecurityEngine.java:148)
        at org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor.handleMessageInternal(WSS4JInInterceptor.java:215)
        at org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor.handleMessage(WSS4JInInterceptor.java:190)
        at org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor.handleMessage(WSS4JInInterceptor.java:96)
        at org.apache.cxf.phase.PhaseInterceptorChain.doIntercept(PhaseInterceptorChain.java:307)
        at org.apache.cxf.transport.ChainInitiationObserver.onMessage(ChainInitiationObserver.java:122)
        at org.apache.cxf.transport.http.AbstractHTTPDestination.invoke(AbstractHTTPDestination.java:243)
        at org.apache.cxf.transport.http_jetty.JettyHTTPDestination.doService(JettyHTTPDestination.java:261)
        at org.apache.cxf.transport.http_jetty.JettyHTTPHandler.handle(JettyHTTPHandler.java:70)
        at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1088)
        at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1024)
        at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:135)
        at org.eclipse.jetty.server.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:255)
        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:696)
        at org.eclipse.jetty.io.nio.SelectChannelEndPoint.run(SelectChannelEndPoint.java:53)
        at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:608)
        at org.eclipse.jetty.util.thread.QueuedThreadPool.run(QueuedThreadPool.java:543)
        at java.lang.Thread.run(Thread.java:841)

我该怎么做才能修复它?我的想法是从 jar 中获取资源并将它们放入 Android 项目的 resources 文件夹中。它的正确位置在哪里? Android 确实支持 ResourceBundle.getBundle(String path) (reference),但没有写出资源的放置位置。

我不得不:

  1. 引入资源加载器接口(WSS4JResourceBundle.ResourceBundleLoader)
  2. 引入静态资源加载器变量加载资源(public static ResourceBundleLoader sharedLoader)
  3. 将使用 ResourceBundle.getBundle 的加载资源替换为静态 var 方法调用 (wss4jSecResourceBundle = sharedLoader.getBundle("messages.wss4j_errors"); f.e.)
  4. 创建默认值 (DefaultResourceBundleLoader) 和 android 实现 (AndroidResourceBundleLoader)
  5. 重新打包资源到android的项目assets文件夹
  6. 在应用程序启动时注入 android 实现而不是默认实现 (WSS4JResourceBundle.sharedLoader = new AndroidResourceBundleLoader(ctx, "wss4j/");)

修改WSS4JResourceBundle:

package org.apache.wss4j.common.crypto;

import java.util.Enumeration;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;

import org.apache.xml.security.utils.Constants;
import org.apache.xml.security.utils.I18n;

/**
 * ResourceBundle for WSS4J
 */
public class WSS4JResourceBundle extends ResourceBundle {

    /**
     * Loader API
     */
    public interface ResourceBundleLoader {
        ResourceBundle getBundle(String path);
        ResourceBundle getBundle(String baseName, Locale locale, ClassLoader loader);
    }

    // default
    public static ResourceBundleLoader sharedLoader = new DefaultResourceBundleLoader();

    /**
     * Default IMPL
     */
    public static class DefaultResourceBundleLoader implements ResourceBundleLoader {
        @Override
        public ResourceBundle getBundle(String path) {
            return ResourceBundle.getBundle(path);
        }

        @Override
        public ResourceBundle getBundle(String baseName, Locale locale, ClassLoader loader) {
            return ResourceBundle.getBundle(baseName, locale, loader);
        }
    }

    private static final org.slf4j.Logger LOG = 
        org.slf4j.LoggerFactory.getLogger(WSS4JResourceBundle.class);

    private final ResourceBundle wss4jSecResourceBundle;
    private final ResourceBundle xmlSecResourceBundle;

    public WSS4JResourceBundle() {
        wss4jSecResourceBundle = sharedLoader.getBundle("messages.wss4j_errors");

        ResourceBundle tmpResourceBundle;
        try {
            tmpResourceBundle = sharedLoader.getBundle(
                        Constants.exceptionMessagesResourceBundleBase,
                        Locale.getDefault(),
                        I18n.class.getClassLoader());
        } catch (MissingResourceException ex) {
            // Using a Locale of which there is no properties file.
            LOG.debug(ex.getMessage());
            // Default to en/US
            tmpResourceBundle =
                    sharedLoader.getBundle(Constants.exceptionMessagesResourceBundleBase,
                                         new Locale("en", "US"), I18n.class.getClassLoader());
        }
        xmlSecResourceBundle = tmpResourceBundle;
    }

    @Override
    protected Object handleGetObject(String key) {
        Object value = null;
        try {
            value = wss4jSecResourceBundle.getObject(key);
        } catch (MissingResourceException e) {
            try {
                value = xmlSecResourceBundle.getObject(key);
            } catch (MissingResourceException ex) { //NOPMD
                //ignore
            }
        }
        return value;
    }

    @Override
    public Enumeration<String> getKeys() {
        throw new UnsupportedOperationException("getKeys not supported");
    }

}

AndroidResourceBundleLoader:

package org.apache.wss4j.common.crypto;

import android.content.Context;
import android.content.res.AssetManager;
import com.splinex.streaming.Log;

import java.io.IOException;
import java.io.InputStream;
import java.util.Locale;
import java.util.PropertyResourceBundle;
import java.util.ResourceBundle;

public class AndroidResourceBundleLoader implements WSS4JResourceBundle.ResourceBundleLoader {

    private static final String TAG = AndroidResourceBundleLoader.class.getSimpleName();

    private String prefix;
    private AssetManager manager;

    public AndroidResourceBundleLoader(Context context, String prefix) {
        manager = context.getAssets();
        this.prefix = prefix;
    }

    @Override
    public ResourceBundle getBundle(String path) {
        try {
            String fullPath = prefix + path.replace(".", "/") + ".properties";
            return new PropertyResourceBundle(manager.open(fullPath));
        } catch (IOException e) {
            android.util.Log.e(TAG, "Failed to load " + path, e);
            throw new RuntimeException(e);
        }
    }

    @Override
    public ResourceBundle getBundle(String path, Locale locale, ClassLoader classLoader) {
        return getBundle(path + "_" + locale.getLanguage());
    }
}

像这样注入 android 实现:

WSS4JResourceBundle.sharedLoader = new AndroidResourceBundleLoader(ctx, "wss4j/");