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

如何将JSF消息包放在WAR之外,以便在不重新部署的情况下对其进行编辑?,jsf,jsf-2,internationalization,external,resourcebundle,Jsf,Jsf 2,Internationalization,External,Resourcebundle,我们在WildFly 8上有一个JSF应用程序,它使用传统的文本国际化机制,在WAR的WEB-INF\classes文件夹中有德语和英语的消息包,在faces config.xml中有一个配置,将一个名称映射到它并列出区域设置。应用程序没有数据库连接,但使用REST服务与第二个应用程序通信 现在我们需要能够更轻松地更改文本,这意味着在更改文本时不必构建新的WAR文件和进行部署。因此,我需要一种机制,使消息包在WAR之外,同时能够像以前一样在XHTML页面中使用它 两个可选要求是更改文本并刷新应用

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

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

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

我的想法是使用类似ApacheCommons配置的东西来读取应用程序范围bean中的属性文件,并在以前使用的EL名称下公开一个getter。但不知何故,它感觉必须重新实现一个现有的机制,而且这应该更容易,甚至可能只使用JavaEE核心

是否有人以这种方式使用了这种机制,并且可以向我指出一些关于细节的示例/描述,或者有更好的想法来实现列出的需求

如何将JSF消息包置于WAR之外

两种方式:


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

    更改文本将是微不足道的。然而,提神并非小事。莫哈拉在内部大量地缓存它。如果你想走第一条路,就必须考虑到这一点。Arjan Tijms发布了一个特定于Mojarra的技巧,以清除其内部资源包缓存

    如果更改文本发生在webapp本身,那么您只需在save方法中执行缓存清理。如果文本的更改可能发生在外部,则需要注册一个侦听更改(),然后对于方式1,清除捆绑缓存,或者对于方式2,在
    handleGetObject()中内部重新加载


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

    当从类路径加载它们时,默认行为是相反的(战争中的资源具有更高的类加载优先级),因此这肯定会划破方法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);
            }
    
        }
    
    }
    

    这有用吗@BalusC好吧,我以前无意中发现了这个问题的细节,因为它指的是通过数据库处理它,我这里没有,但我猜您指的是扩展
    资源包的部分?那么在
    getItSomehow
    部分中,必须通过文件操作加载它?在这种情况下,这可能是一种处理方法。只有两个可选要求在这里不清楚。@BalusC Ok,2)有意义,1)可能被误解-我不需要将更改反映回文件,但可以更改文件,然后触发重新加载捆绑包。-如果您愿意花时间将评论放到答案中,我很乐意指定赏金。使用提供的ResourceBundle.getBundle(…)方法无法实现所需的行为。提供的实现使用内部查找映射(cacheList),因此每个ResourceBundle只加载一次。出于我的目的,我编写了一个国际化标记库(fmt)的替代品,以使用文件并在更改后重新加载这些文件。