How to install a JSF messaging package outside of WAR so that it can be edited without redistribution?

We have a JSF application on WildFly 8 that uses a traditional mechanism with internationalization of text, having message packages for German and English in the WEB-INF\classes WAR folder and configuration in faces-config.xml , matching them with a name and a list of locales. The application does not have a database connection, but uses REST services to communicate with the second application.

Now we need to be able to easily change the text, that is, we do not need to create a new WAR file and deploy when the text changes. Thus, I need a mechanism so that message packages are outside the WAR when you can use it, as before, in XHTML pages.

Two optional requirements are to change the text and update messages in the application without restarting the application (priority 2) and have a default pool in WAR, which is overwritten by an external package (priority 3).

My thought was to use something like the Apache commons configuration to read the properties file in a bean application and expose a getter under the EL name that was used earlier. But for some reason, it seems like the need to re-implement the existing mechanism and that it should be somehow simpler, maybe even with the Java EE core.

Someone used this mechanism in this way and can point me to some example / description of the details or is it better to implement the listed requirements?

+5
source share
1 answer

How to install JSF message package outside WAR?

Two ways:


change text and update messages in the application without restarting the application

Changing the text will be trivial. However, the update is not trivial. Mojarra internally caches it aggressively. This must be taken into account if you want to go on the way 1. Arjan Tijms published a special Mojarra trick to clear its internal resource cache in this related question: How to reload a resource in a web application?

If the text change occurs in the web application itself, you can simply clear the cache in the save method. If the text change can occur from outside, you need to register the file system viewer to listen for the changes (the tutorial is here ), and then either clear the beam cache for path 1 or reload 2 inside handleGetObject() again.


has a default pool in WAR that is overwritten by an external package

When loading from the path class, the default behavior is the other way around (resources in WAR have a higher priority when loading), so this will definitely scratch path 1 and leave us with method 2.

The following is an example of launch 2. This assumes that you are using resource resource packages with the base name text (that is, without a package) and that the external path is in /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); } } } 

To run it, register as below in faces-config.xml .

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

Source: https://habr.com/ru/post/1241813/


All Articles