2016-01-29 48 views
5

Abbiamo un'applicazione JSF su WildFly 8 che utilizza il tradizionale meccanismo di internazionalizzazione del testo con bundle di messaggi per tedesco e inglese nella cartella WEB-INF\classes di WAR e una configurazione in faces-config.xml mappando un nome e elencando le impostazioni locali. L'applicazione non ha una connessione al database, ma utilizza i servizi REST per comunicare con una seconda applicazione.Come mettere JSF messaggio fascio di fuori di WAR in modo che possa essere modificato senza ridistribuzione?

Ora abbiamo bisogno di essere in grado di cambiare il testo più facilmente, il che significa non dover costruire un nuovo file WAR e fare una distribuzione quando si cambia un testo. Quindi ho bisogno di un meccanismo per avere i bundle di messaggi al di fuori del WAR, pur essendo in grado di usarlo come prima all'interno delle pagine XHTML.

Due requisiti facoltativi sarebbero modificare il testo e aggiornare i messaggi nell'applicazione senza dover riavviare l'applicazione (priorità 2) e disporre di un bundle predefinito all'interno di WAR, che viene sovrascritto dal pacchetto esterno (priorità 3).

Il mio pensiero è stato quello di usare qualcosa di simile configurazione di Apache Commons per leggere un file di proprietà all'interno di un'applicazione ambito di fagioli ed esporre un getter sotto il nome di EL usato prima. Ma in qualche modo ci si sente come dover re-implementare un meccanismo esistente e che questo dovrebbe in qualche modo essere più facile, forse anche con anima in Java EE solo.

ha qualcuno utilizzato questo meccanismo in modo tale e mi può puntare a qualche esempio/descrizione sui dettagli o ha una migliore idea per applicare la norma quotata (s)?

+0

È utile? http://stackoverflow.com/q/4499732 – BalusC

+0

@BalusC Beh, non ho esaminato i dettagli della domanda quando ci siamo imbattuti prima, in quanto si riferisce alla gestione tramite database, che non ho qui - ma credo ti riferisci alla parte di estendere il 'ResourceBundle'? Quindi nel 'getItSomehow'part deve quindi essere caricato tramite l'operazione sui file? In tal caso potrebbe essere un modo per gestirlo. Qui non sono chiari solo i due requisiti opzionali. –

+0

@BalusC Ok, 2) ha senso, 1) è forse frainteso - Non ho bisogno di riflettere le modifiche al file, ma essere in grado di cambiare il file e quindi attivare una ricarica del bundle. - Se ti piace passare il tempo a mettere i commenti in una risposta, sono felice di assegnare la taglia. –

risposta

6

Come mettere il pacchetto di messaggi JSF fuori da WAR?

due modi:

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

  2. Create a custom ResourceBundle implementation with a Control.


modificare il testo e aggiornare i messaggi nell'applicazione senza dover riavviare l'applicazione

Modifica il testo sarà banale. Tuttavia, l'aggiornamento non è banale. Mojarra lo memorizza internamente in modo aggressivo. Questo deve essere preso in considerazione nel caso in cui si desideri andare per la via 1. Arjan Tijms ha pubblicato un trucco specifico di Mojarra per cancellare la sua cache interna del bundle di risorse in questa domanda correlata: How to reload resource bundle in web application?

Se la modifica del testo avviene nella webapp da solo, quindi è possibile semplicemente eseguire la pulizia della cache nel metodo di salvataggio. Se la modifica del testo può tuttavia avvenire esternamente, è necessario registrare un file system watch service per ascoltare le modifiche (tutorial here) e quindi per il modo 1 svuotare la cache del bundle o per il modo 2 ricaricare internamente in handleGetObject().


hanno un fascio predefinita nel WAR, che viene sovrascritto dal fascio esterno

Quando caricandoli da classpath, il comportamento predefinito è viceversa (risorse in WAR avere precedenza più alta sulla classe), quindi questo sicuramente scalfisce il modo 1 e ci lascia con il modo 2.

Di seguito è riportato un esempio di kickoff del modo 2. Ciò presuppone che si stia utilizzando la risorsa di proprietà bun dles con un nome di base text (ad es. nessun pacchetto) e che il percorso esterno si trova 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); 
     } 

    } 

} 

Al fine di farlo funzionare, registrarsi come di seguito in faces-config.xml.

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

Come usalissimo, grazie. –