如何将JSF消息包放在WAR之外,以便可以在不重新部署的情况下进行编辑?

时间:2016-01-29 13:47:05

标签: jsf jsf-2 internationalization external resourcebundle

我们在WildFly 8上有一个JSF应用程序,它通过在WAR的WEB-INF\classes文件夹中包含德语和英语的消息包以及faces-config.xml映射名称的配置,使用传统的文本国际化机制它并列出语言环境。应用程序没有数据库连接,但使用REST服务与第二个应用程序通信。

现在我们需要能够更轻松地更改文本,这意味着无需在更改文本时构建新的WAR文件并进行部署。所以我需要一种机制来将消息包放在WAR之外,同时能够像以前一样在XHTML页面中使用它。

两个可选的要求是更改文本并刷新应用程序中的消息,而不必重新启动应用程序(优先级2),并在WAR中使用默认包,该包将被外部包覆盖(优先级3) )。

我的想法是使用Apache commons配置之类的东西来读取Application scoped bean中的属性文件,并在之前使用的EL名称下公开getter。但不知何故,它感觉不得不重新实现一个现有的机制,并且这应该更容易,甚至可能只使用Java EE核心。

是否有人以这种方式使用此机制并且可以指出我对细节的一些示例/描述或者更好地实现列出的要求?

1 个答案:

答案 0 :(得分:6)

  

如何将JSF消息包放在WAR之外?

两种方式:

  1. Add its path to the runtime classpath of the server

  2. Create a custom ResourceBundle implementation with a Control

  3.   

    更改文本并刷新应用程序中的消息,而无需重新启动应用程序

    更改文字将是微不足道的。然而,令人耳目一新并非易事。 Mojarra在内部广泛地缓解它。如果你想要方便的话,必须考虑到这一点.Arjan Tijms在这个相关问题中发布了一个Mojarra特定技巧来清除其内部资源包缓存:How to reload resource bundle in web application?

    如果在webapp本身中更改文本,则可以在save方法中执行缓存清理。如果更改文本可以在外部进行,那么您需要注册一个file system watch service来监听更改(tutorial here),然后方式1清除包缓存,或者方式2重新加载内部在handleGetObject()

      

    在WAR中有一个默认包,它被外部包覆盖

    从类路径加载它们时,默认行为是反过来的(WAR中的资源具有更高的类加载优先级),所以这肯定会划分方式1并使我们离开方式2.

    以下是方式2的启动示例。这假设您使用的基本名称为text的属性资源包(即没有包),并且外部路径位于/var/webapp/i18n

    public class YourBundle extends ResourceBundle {
    
        protected static final Path EXTERNAL_PATH = Paths.get("/var/webapp/i18n");
        protected static final String BASE_NAME = "text";
        protected static final Control CONTROL = new YourControl();
    
        private static final WatchKey watcher;
    
        static {
            try {
                watcher = EXTERNAL_PATH.register(FileSystems.getDefault().newWatchService(), StandardWatchEventKinds.ENTRY_MODIFY);
            } catch (IOException e) {
                throw new ExceptionInInitializerError(e);
            }
        }
    
        private Path externalResource;
        private Properties properties;
    
        public YourBundle() {
            Locale locale = FacesContext.getCurrentInstance().getViewRoot().getLocale();
            setParent(ResourceBundle.getBundle(BASE_NAME, locale, CONTROL));
        }
    
        private YourBundle(Path externalResource, Properties properties) {
            this.externalResource = externalResource;
            this.properties = properties;
        }
    
        @Override
        protected Object handleGetObject(String key) {
            if (properties != null) {
                if (!watcher.pollEvents().isEmpty()) { // TODO: this is naive, you'd better check resource name if you've multiple files in the folder and keep track of others.
                    synchronized(properties) {
                        try (InputStream input = new FileInputStream(externalResource.toFile())) {
                            properties.load(input);
                        } catch (IOException e) {
                            throw new IllegalStateException(e);
                        }
                    }
                }
    
                return properties.get(key);
            }
    
            return parent.getObject(key);
        }
    
        @Override
        @SuppressWarnings({ "rawtypes", "unchecked" })
        public Enumeration<String> getKeys() {
            if (properties != null) {
                Set keys = properties.keySet();
                return Collections.enumeration(keys);
            }
    
            return parent.getKeys();
        }
    
        protected static class YourControl extends Control {
    
            @Override
            public ResourceBundle newBundle
                (String baseName, Locale locale, String format, ClassLoader loader, boolean reload)
                    throws IllegalAccessException, InstantiationException, IOException
            {
                String resourceName = toResourceName(toBundleName(baseName, locale), "properties");
                Path externalResource = EXTERNAL_PATH.resolve(resourceName);
                Properties properties = new Properties();
    
                try (InputStream input = loader.getResourceAsStream(resourceName)) {
                    properties.load(input); // Default (internal) bundle.
                }
    
                try (InputStream input = new FileInputStream(externalResource.toFile())) {
                    properties.load(input); // External bundle (will overwrite same keys).
                }
    
                return new YourBundle(externalResource, properties);
            }
    
        }
    
    }
    

    要使其运行,请在faces-config.xml中注册如下。

    <application>
        <resource-bundle>
            <base-name>com.example.YourBundle</base-name>
            <var>i18n</var>
        </resource-bundle>
    </application>