7 Replies Latest reply on Oct 4, 2008 9:22 PM by Alexander Schwartz

    Hot redeploy of message bundles

    Vladimir Kovalyuk Apprentice

      SeamResourceBundle does not take into account ResourceBundle.Control capabilities of Java 6.
      Even in Java 5 resource bundles are cached by runtime. I believe it does not make sense to cache them in SeamResourceBundle along with implicit caching SeamResourceBundle in Messages.instance


      I suggest to avoid caching in SeamResourceBundle and provide a way to plug custom ResourceBundle.Control into the process of creating resource bundles.


      With all that implemented I believe it would be possible to re-deploy properties files without restart of application server.

        • 1. Re: Hot redeploy of message bundles
          Vladimir Kovalyuk Apprentice

          It turned out that it is not very difficult to override org.jboss.seam.i18n.Messages and org.jboss.seam.core.ResourceLoader. Overriding components uses custom Control with timeToLive about 15 seconds and they are installed only in debug mode, so they are not intrusive in production mode.


          After that I can play with message files and see the result immediately.
          I can post details if someone is interested in.

          • 2. Re: Hot redeploy of message bundles
            Ingo Jobling Master

            Hello,


            Please post details.


            Thanks!

            • 3. Re: Hot redeploy of message bundles
              Vladimir Kovalyuk Apprentice

              DynamicMessages.java:



              import static org.jboss.seam.annotations.Install.APPLICATION;
              
              import java.util.Map;
              import java.util.MissingResourceException;
              import java.util.ResourceBundle;
              
              import org.jboss.seam.ScopeType;
              import org.jboss.seam.annotations.Install;
              import org.jboss.seam.annotations.Name;
              import org.jboss.seam.annotations.Scope;
              import org.jboss.seam.annotations.intercept.BypassInterceptors;
              import org.jboss.seam.core.SeamResourceBundle;
              import org.jboss.seam.international.Messages;
              
              @Scope(ScopeType.STATELESS)
              @BypassInterceptors
              @Name("org.jboss.seam.international.messagesFactory")
              @Install(precedence = APPLICATION, debug = true)
              public class DynamicMessages extends Messages {
              
                   @Override
                   protected Map createMap() {
              
                        return new FakeMap<String, String>() {
              
                             protected ResourceBundle getSeamBundle() {
                                  ResourceBundle.Control control = DynamicResourceBundleControl.instance();
                                  return ResourceBundle.getBundle(SeamResourceBundle.class.getName(), org.jboss.seam.core.Locale
                                            .instance(), Thread.currentThread().getContextClassLoader(), control);
                             }
              
                             @Override
                             public String get(Object key) {
                                  if (key instanceof String) {
                                       String resourceKey = (String) key;
                                       String resource = null;
                                       if (getSeamBundle() != null) {
                                            try {
                                                 resource = getSeamBundle().getString(resourceKey);
                                            } catch (MissingResourceException mre) {
                                                 // Just swallow
                                            }
                                       }
                                       return resource == null ? resourceKey : resource;
                                  } else {
                                       return null;
                                  }
                             }
              
                        };
                   }
              }
              



              DynamicResourceLoader.java:



              import static org.jboss.seam.annotations.Install.APPLICATION;
              
              import java.util.MissingResourceException;
              import java.util.ResourceBundle;
              
              import org.jboss.seam.ScopeType;
              import org.jboss.seam.annotations.Install;
              import org.jboss.seam.annotations.Name;
              import org.jboss.seam.annotations.Scope;
              import org.jboss.seam.annotations.intercept.BypassInterceptors;
              import org.jboss.seam.core.Locale;
              import org.jboss.seam.core.ResourceLoader;
              import org.jboss.seam.log.LogProvider;
              import org.jboss.seam.log.Logging;
              
              @Scope(ScopeType.STATELESS)
              @BypassInterceptors
              @Install(precedence = APPLICATION, debug = true)
              @Name("org.jboss.seam.core.resourceLoader")
              public class DynamicResourceLoader extends ResourceLoader {
              
                   private static final LogProvider log = Logging.getLogProvider(DynamicResourceLoader.class);
              
                   @Override
                   public ResourceBundle loadBundle(String bundleName) {
                        try {
                             ResourceBundle.Control control = DynamicResourceBundleControl.instance();
                             ResourceBundle bundle = ResourceBundle.getBundle(bundleName, Locale.instance(), Thread.currentThread()
                                       .getContextClassLoader(), control);
                             log.debug("loaded resource bundle: " + bundleName);
                             return bundle;
                        } catch (MissingResourceException mre) {
                             log.debug("resource bundle missing: " + bundleName);
                             return null;
                        }
                   }
              }
              



              DynamicResourceBundleControl.java:



              import java.util.ResourceBundle;
              
              import org.jboss.seam.Component;
              import org.jboss.seam.ScopeType;
              import org.jboss.seam.annotations.AutoCreate;
              import org.jboss.seam.annotations.Name;
              import org.jboss.seam.annotations.Scope;
              import org.jboss.seam.annotations.intercept.BypassInterceptors;
              
              @Name("dynamicResourceBundleControl")
              @BypassInterceptors
              @Scope(ScopeType.SESSION)
              @AutoCreate
              public class DynamicResourceBundleControl extends ResourceBundle.Control {
              
                   public static DynamicResourceBundleControl instance() {
                        return (DynamicResourceBundleControl) Component.getInstance(DynamicResourceBundleControl.class, true);
                   }
              
                   private long timeToLive = ResourceBundle.Control.TTL_NO_EXPIRATION_CONTROL;
              
                   public long getTimeToLive() {
                        return timeToLive;
                   }
              
                   @Override
                   public long getTimeToLive(String baseName, java.util.Locale locale) {
                        return timeToLive;
                   }
              
                   @Override
                   public boolean needsReload(String baseName, java.util.Locale locale, String format, ClassLoader loader,
                             ResourceBundle bundle, long loadTime) {
                        return true;
                   }
              
                   public void setTimeToLive(long timeToLive) {
                        this.timeToLive = timeToLive;
                   }
              
              }
              


              • 4. Re: Hot redeploy of message bundles
                Vladimir Kovalyuk Apprentice

                You may play with timeToLive initializer, by default it is initialized as everlasting value ResourceBundle.Control.TTL_NO_EXPIRATION_CONTROL.
                Or you can add to components.xml the following lines:



                     <component name="dynamicResourceBundleControl">
                          <property name="timeToLive">15000</property>
                     </component>
                



                The second way is more convinient for me since I use an extra components.xml file in dev env.

                • 5. Re: Hot redeploy of message bundles
                  Vladimir Kovalyuk Apprentice

                  Just forgot to mention that FakeMap is a fake implementation of Map interface and looks as follows:


                  public abstract class FakeMap<TKey, TValue> implements Map<TKey, TValue> {
                  
                       @Override
                       public void clear() {
                            throw new UnsupportedOperationException();
                       }
                       ...
                  }
                  



                  You get the idea.

                  • 6. Re: Hot redeploy of message bundles
                    Pete Muir Master

                    Could you write this up as a blog or knowledge base tutorial? If you have any Java 5 recommendations, please put them in JIRA.

                    • 7. Re: Hot redeploy of message bundles
                      Alexander Schwartz Newbie

                      Hi, i tried your code and it works great.


                      I had one little problem with the standard faces messages. It seems that the ResourceBundle loads the standard messages earlier without using a control or timeout as you do.


                      I used this workaround to clear the complete resource bundle cache and then asking seam to load the resources with the proper timeout.


                      I just added the following code as constructor for DynamicResourceLoader:



                        public DynamicResourceLoader() {
                          ResourceBundle.clearCache();
                          new SeamResourceBundle();
                        }