2016-01-29 47 views
5

Mamy aplikację JSF na WildFly 8, która używa tradycyjnego mechanizmu z internacjonalizacją tekstu poprzez posiadanie pakietów wiadomości dla języka niemieckiego i angielskiego w folderze WEB-INF\classes WAR i konfigurację w faces-config.xml mapowania nazwy do niej i listy ustawień regionalnych. Aplikacja nie ma połączenia z bazą danych, ale używa usług REST do komunikacji z drugą aplikacją.Jak umieścić pakiet komunikatów JSF poza WAR, aby można go było edytować bez ponownego rozmieszczania?

Teraz musimy mieć możliwość łatwej zmiany tekstu, co oznacza, że ​​nie trzeba tworzyć nowego pliku WAR i wdrażać podczas zmiany tekstu. Potrzebuję więc mechanizmu, by mieć pakiety komunikatów poza WAR, mając jednocześnie możliwość korzystania z niego jak wcześniej na stronach XHTML.

Dwa opcjonalne wymagania to zmiana tekstu i odświeżanie wiadomości w aplikacji bez konieczności ponownego uruchamiania aplikacji (priorytet 2) oraz posiadanie pakietu domyślnego w WAR, który jest nadpisywany przez pakiet zewnętrzny (priorytet 3).

Moja myśl polegała na używaniu konfiguracji podobnej do Apache, aby odczytać plik właściwości w ramach komponentu o zasięgu aplikacji i odsłonić getter pod nazwą EL używaną wcześniej. Ale wydaje się, że trzeba ponownie wdrożyć istniejący mechanizm i że powinno to być w jakiś sposób łatwiejsze, może nawet z rdzeniem Java EE.

Czy ktoś użył tego mechanizmu w taki sposób i może wskazać mi jakiś przykład/opis na szczegółach lub ma lepszy pomysł na wdrożenie wymienionych wymagań?

+0

Czy to pomocne? http://stackoverflow.com/q/4499732 – BalusC

+0

@BalusC Cóż, nie przyjrzeliśmy się szczegółom tego pytania, gdy natknęliśmy się na niego wcześniej, ponieważ odnosi się ono do obsługi przez bazę danych, której nie mam tutaj - ale domyślam się masz na myśli część rozszerzenia 'ResourceBundle'? Więc w części 'getItSomehow' należy ją załadować poprzez operację na plikach? W takim przypadku może to być sposób na poradzenie sobie z tym. Tylko te dwa opcjonalne wymagania nie są tutaj jasne. –

+0

@BalusC Ok, 2) ma sens, 1) może jest źle zrozumiany - nie muszę odzwierciedlać zmian z powrotem do pliku, ale być w stanie zmienić plik, a następnie uruchomić przeładowanie pakietu. - Jeśli chcesz spędzić czas, aby umieścić komentarze w odpowiedzi, z przyjemnością przypiszę nagrodę. –

Odpowiedz

6

Jak umieścić pakiet wiadomości JSF poza WAR?

dwa sposoby:

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

  2. Create a custom ResourceBundle implementation with a Control.


zmienić tekst i odświeżyć wiadomości w aplikacji bez konieczności ponownego uruchomienia aplikacji

zmieniając tekst będzie trywialne. Odświeżanie nie jest jednak trywialne. Mojarra wewnętrznie buforuje to agresywnie. Trzeba to wziąć pod uwagę na wypadek, gdybyś chciał pójść w drogę 1. Arjan Tijms opublikował specjalną sztuczkę Mojarra, aby wyczyścić pamięć podręczną wewnętrznego pakietu zasobów w tym pokrewnym pytaniu: How to reload resource bundle in web application?

Jeśli zmiana tekstu nastąpi w aplikacji webowej samodzielnie, możesz po prostu wykonać czyszczenie pamięci podręcznej w metodzie składowania. Jeśli zmiana tekstu może się jednak zdarzyć na zewnątrz, musisz zarejestrować file system watch service, aby nasłuchiwać zmian (tutorial here), a następnie albo dla sposobu 1 wyczyścić pamięć podręczną pakunku, albo dla sposobu 2 przeładować wewnętrznie w handleGetObject().


mieć domyślny pakiet w wojnie, która jest zastąpione przez wiązki zewnętrznej

Wkładając je ze ścieżki klasy, domyślne zachowanie jest na odwrót (zasoby na wojnie mają wyższy priorytet ładowania klas), więc to zdecydowanie zadrapuje 1 i zostawia nas na drodze 2.

Poniżej znajduje się przykład kickoff z drogi 2. Zakłada się, że używasz koksu zasobów nieruchomości dles o nazwie bazowej text (tj. bez pakietu) i że ścieżka zewnętrzna znajduje się w /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); 
     } 

    } 

} 

Aby uruchomić, należy zarejestrować się poniżej w pozycji faces-config.xml.

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

Jako usal bardzo wszechstronny, dziękuję. –