1 2 3 4 Previous Next 52 Replies Latest reply on Nov 17, 2007 3:10 PM by pmuir Go to original post
      • 15. Re: ResourceBundle in Database
        zerg-spirit

        Sorry for the triple post, but I just found something strange to me.*
        I actually managed to call the @Create method by calling the bean on the jsf page (I simply put an outputText with a property of my Bean as value).
        Since I've done that, the @Create method is now called, apparently the bean is 'created' when it reaches that jsf tag.
        But then, I've got apparently two different instance of my DBResourceBundle.

        @Create
         public void fillAvailableResourceBundle(){
         try{
         availableResourceBundles = em.createQuery("select rb from CustomResourceBundle rb").getResultList();
         System.out.println("Found "+availableResourceBundles.size()+" custom resource bundles");
         }
         catch(Exception e){
        
         e.printStackTrace();
        
         }
        
        
         }

        When that method is called, it finds my 'CustomResourceBundles in the db.

        But then, when I put messages['something'] in the jsf page, the method handleObject of the same class is called, and the availableResourceBundles is null for it, aswell as my Injected EntityManager, although it was used just before in the @Create method withtout any problem!
        Like if there was 2 different instances of my class.


        • 16. Re: ResourceBundle in Database
          zerg-spirit

          Ok, sorry again for the multi posts, but I think I finally found out what was wrong.

          I think I simply can't retrieve my EntityManager by injection at all.
          Here's what I understood.

          First, seam load the overrided Seam.ResourceBundle:

          @Scope(SESSION)
          @BypassInterceptors
          @Name("org.jboss.seam.core.resourceBundle")
          public class ExtendedResourceBundle extends org.jboss.seam.international.ResourceBundle{
          
           @Override
           protected ResourceBundle loadBundle(String bundleName) {
           try {
           ResourceBundle bundle =
           ResourceBundle.getBundle(bundleName,
           Locale.instance(),
           Thread.currentThread().getContextClassLoader(),new DBControl());
          ...

          When the DBControl is instanciated, the 'newBundle' method is then automatically called in order to load an actual ResourceBundle:

          @Override
           public ResourceBundle newBundle(String baseName, Locale locale, String format, ClassLoader loader, boolean reload)throws IllegalAccessException,
           InstantiationException, IOException {
           if ((baseName == null) || (locale == null) ||
           (format == null) || (loader == null)) {
           throw new NullPointerException();
           }
          
           return new DBResourceBundle(locale);
           }...

          At that point, I instanciate my DBResourceBundle, which is an extended java.util.ResourceBundle.

          So, thanks to that implementation, when any jsf pages reference to the object 'messages' (for exemple: <h:outputText value="#{messages['something']}" />) it references to the object created at that line:
          return new DBResourceBundle(locale);


          So basically, any injected property isn't reachable, cause this is not the Session Bean at all, although the class is referenced as one. I guess when you manually instanciate a new object, even if its class has some annoation declaring it as a Session Bean, it's not. It's simply a Java object.

          So when further in the application I make a call on the @Create method, the method is called without problem and do whatever it has to do (in my case, loading my messages from the DB thanks to the EntityManager), but it is all wrapped in my Session Bean, NOT in my Java Object referenced by 'messages'.

          If I understood it right, I then have to answer one simple question:
          Is it possible to retrieve the EntityManager without using the Seam injection mecanism?
          If not, I'll simply use plain java code to connect to my base, but it'll be quite dirty.

          Finally, I hope I get all that right =)

          • 17. Re: ResourceBundle in Database
            pmuir

            No. If you do it like that, you won't get managed objects.

            • 18. Re: ResourceBundle in Database
              zerg-spirit

              Ok thanks.
              I don't know if I did it the right way though, but it seems to be working, and I tryed to follow peter's steps (page1) as much as possible.

              • 19. Re: ResourceBundle in Database
                zerg-spirit

                Just one question though, I'm trying to retrieve the connection String to the Database (aswell as user and password) used by Hibernate (and set in the persistence-xml) dynamically, so that I just have to change it there later on (since I'll use plain java to access my db with my resource bundle).
                How to do that? I've seen no methods to do that in the EntityManager.

                • 20. Re: ResourceBundle in Database
                  pbrewer_uk

                  You can also retrieve the entity manager by calling:

                  EntityManager em = (EntityManager) Component.getInstance("entityManager", true);
                  


                  Sorry that this posting is delayed, it sounds like you're pretty much there. But here is a more expanded version of the steps I outlined earlier. Hopefully it'll help you and others...

                  1. Create the entity beans/ tables. (see my post earlier for table structure)

                  2. To understand how the new ResourceBundle works in jdk , have a look at these links (in addition those provided by Udo):
                  http://java.sun.com/developer/JDCTechTips/2005/tt1018.html#2
                  http://java.sun.com/javase/6/docs/api/java/util/ResourceBundle.html

                  Step 3. i) Create DBControl.java
                  import java.util.ResourceBundle.Control ;
                  import java.util.ResourceBundle ;
                  ...
                  
                  public class DBControl extends Control {
                  
                   public static final String FORMAT_DATABASE = "database" ;
                  
                   public static final DBControl INSTANCE = new DBControl() ;
                  
                   @Override
                   public List<String> getFormats(String baseName) {
                   return Collections.singletonList(FORMAT_DATABASE);
                   }
                  
                   @Override
                   public ResourceBundle newBundle(String baseName, Locale locale, String format,
                   ClassLoader loader, boolean reload) throws IllegalAccessException,
                   InstantiationException, IOException {
                  
                   if ((baseName == null) || (locale == null) || (format == null) ) {
                   throw new NullPointerException("baseName=" + baseName + ", locale=" + locale + " and format=" + format + " cannot be null.");
                   }
                  
                   ResourceBundle bundle = null;
                   if (format.equals(FORMAT_DATABASE)) {
                   try {
                   bundle = new DBResourceBundle(baseName, locale) ;
                   } catch (MissingBundleException ex) {
                   // bundle must return null
                   }
                   }
                   return bundle;
                   }
                  
                   @Override
                   public boolean needsReload(String baseName,
                   Locale locale,
                   String format,
                   ClassLoader loader,
                   ResourceBundle bundle,
                   long loadTime) {
                   if (bundle == null) {
                   return true ;
                   } else if (format.equals(FORMAT_DATABASE) && bundle instanceof DBResourceBundle) {
                   try {
                   EntityManager em = (EntityManager) Component.getInstance("myEntityManager") ;
                   Long bundleId = ( (DBResourceBundle) bundle).getResourceBundleId() ;
                   uk.co.iblocks.iflow.data.ResourceBundle rb = em.find(uk.co.iblocks.iflow.data.ResourceBundle.class, bundleId) ;
                  
                   return rb.isDirty(new Date(loadTime)) ;
                  
                   } catch (Exception ex) {
                   LOG.warn("Error checking dirty state of resource bundle, requesting reload anyway.", ex) ;
                   return true ;
                   }
                   } else {
                   return false ;
                   }
                   }
                  
                  
                  }
                  


                  Step 3. ii) Create DBResourceBundle.java
                  public class DBResourceBundle extends java.util.ResourceBundle {
                  
                   private Map<String, String> messages = new HashMap<String, String>();
                  
                   private Long resourceBundleId = -1L;
                  
                   public DBResourceBundle (String baseName, Locale locale) throws MissingBundleException, BundleLoadException {
                   try {
                   loadBundle(baseName, locale) ;
                   } catch (RuntimeException ex) {
                   LOG.error("An error occurred loading the resource bundle, baseName=" + baseName + ", locale=" + locale, ex) ;
                   BundleLoadException mbe = new BundleLoadException(baseName, locale) ;
                   mbe.initCause(ex) ;
                   throw mbe ;
                   }
                   }
                  
                  
                   private void loadBundle(String baseName, Locale locale) throws MissingBundleException {
                   // load from db...
                   EntityManager em = (EntityManager) Component.getInstance("entityManager") ;
                  
                   // query the db and load your messages here - you'll need to handle when variant, country and language aren't defined,
                   // but a simple example is:
                   Query bundleQuery = em.createNamedQuery("loadBundleMessages") ;
                   bundleQuery.setParameter("language", locale.getLanguage());
                   bundleQuery.setParameter("country", locale.getLanguage());
                   bundleQuery.setParameter("variant", locale.getLanguage());
                   bundleQuery.setParameter("baseName", baseName) ;
                  
                   List<ResourceBundle> bundles = bundleQuery.getResultList() ;
                   if (!bundles.isEmpty()) {
                   // use the most specific bundle
                   ResourceBundle resourceBundle = bundles.get(bundles.size()-1) ;
                   this.resourceBundleId = resourceBundle.getBundleId() ;
                   for (ResourceMessage msg : resourceBundle.getResourceMessages()) {
                   messages.put(msg.getKey(), msg.getValue()) ;
                   }
                   } else {
                   throw new MissingBundleException(baseName, locale) ;
                   }
                  
                   em.clear() ;
                  
                   }
                  
                   @Override
                   public Enumeration<String> getKeys() {
                  
                   Enumeration<String> keys = new Enumeration<String>() {
                  
                   private Iterator<String> keyIterator = messages.keySet().iterator() ;
                  
                   public boolean hasMoreElements() {
                   return keyIterator.hasNext() ;
                   }
                  
                   public String nextElement() {
                   return keyIterator.next() ;
                   }
                  
                   } ;
                   return keys ;
                   }
                  
                   @Override
                   protected Object handleGetObject(String key) {
                   return messages.get(key) ;
                   }
                  
                  
                   // returns id of the resource bundle stored in the database
                   public Long getResourceBundleId() {
                   return this.resourceBundleId;
                   }
                  
                   public void setResourceBundleId(Long resourceBundleId) {
                   this.resourceBundleId = resourceBundleId;
                   }
                  
                  }
                  


                  Step 4. Extend the standard seam resource bundle, SeamResourceBundle.java:
                  @Name("org.jboss.seam.core.resourceBundle")
                  public class SeamResourceBundle extends org.jboss.seam.core.ResourceBundle implements Serializable {
                  
                   @Logger
                   private Log LOG ;
                  
                   private transient ResourceBundle bundle ;
                  
                   @Override
                   protected ResourceBundle loadBundle(String bundleName) {
                   try {
                   ResourceBundle bundle = ResourceBundle.getBundle(bundleName, Locale.instance(), Thread.currentThread().getContextClassLoader(), DBControl.INSTANCE);
                   return bundle;
                   } catch (MissingResourceException mre) {
                   LOG.debug("resource bundle is missing: #0", bundleName);
                   return null;
                   }
                   }
                  
                   private void createUberBundle() {
                   bundle = new java.util.ResourceBundle() {
                  
                   @Override
                   public java.util.Locale getLocale() {
                   return Locale.instance();
                   }
                  
                   public List<ResourceBundle> getLittleBundles() {
                   List<java.util.ResourceBundle> littleBundles = new ArrayList<ResourceBundle>();
                   if (getBundleNames() != null) {
                   for (String bundleName : getBundleNames()) {
                   java.util.ResourceBundle littleBundle = SeamResourceBundle.this.loadBundle(bundleName);
                   if (littleBundle != null)
                   littleBundles.add(littleBundle);
                   }
                   }
                  
                   java.util.ResourceBundle validatorBundle = SeamResourceBundle.this.loadBundle("ValidatorMessages");
                   if (validatorBundle != null) {
                   littleBundles.add(validatorBundle);
                   }
                   java.util.ResourceBundle validatorDefaultBundle = SeamResourceBundle.this.loadBundle("org/hibernate/validator/resources/DefaultValidatorMessages");
                   if (validatorDefaultBundle != null) {
                   littleBundles.add(validatorDefaultBundle);
                   }
                   return littleBundles ;
                   }
                  
                  
                   @Override
                   public Enumeration<String> getKeys() {
                   List<ResourceBundle> pageBundles = getPageResourceBundles();
                   Enumeration<String>[] enumerations = new Enumeration[getLittleBundles().size() + pageBundles.size()];
                   int i = 0;
                   for (; i < pageBundles.size(); i++) {
                   enumerations[i++] = pageBundles.get(i).getKeys();
                   }
                   for (; i < getLittleBundles().size(); i++) {
                   enumerations = getLittleBundles().get(i).getKeys();
                   }
                   return new EnumerationEnumeration<String>(enumerations);
                   }
                  
                   @Override
                   protected Object handleGetObject(String key) {
                   List<ResourceBundle> pageBundles = getPageResourceBundles();
                   for (ResourceBundle pageBundle : pageBundles) {
                   try {
                   return pageBundle.getObject(key);
                   } catch (MissingResourceException mre) {
                   }
                   }
                  
                   for (ResourceBundle littleBundle : getLittleBundles()) {
                   if (littleBundle != null) {
                   try {
                   return littleBundle.getObject(key);
                   } catch (MissingResourceException mre) {
                   }
                   }
                   }
                  
                   throw new MissingResourceException("Can't find resource in bundles: " + key, getClass().getName(), key);
                   }
                  
                   private List<java.util.ResourceBundle> getPageResourceBundles() {
                   FacesContext facesContext = FacesContext.getCurrentInstance();
                   if (facesContext != null) {
                   UIViewRoot viewRoot = facesContext.getViewRoot();
                   if (viewRoot != null) {
                   return Pages.instance().getResourceBundles(viewRoot.getViewId());
                   }
                   }
                   return Collections.emptyList();
                   }
                  
                   };
                  
                   }
                  
                   @Override
                   public java.util.ResourceBundle getBundle() {
                   if (bundle == null)
                   createUberBundle();
                   return bundle;
                   }
                  
                  
                  
                   }
                  


                  Step 5. Override seam messages to avoid caching. SeamMessages.java:
                  @Name("org.jboss.seam.core.messages")
                  public class SeamMessages extends Messages {
                  
                   private transient BundleMap resourceBundleMap = null ;
                  
                   @Override
                   public Map getMessages() {
                   java.util.ResourceBundle resourceBundle = ResourceBundle.instance() ;
                  
                   if ( getResourceBundleMap() == null || !getResourceBundleMap().getBundle().equals(resourceBundle) ) {
                   setResourceBundleMap( new BundleMap(resourceBundle) );
                   }
                   return getResourceBundleMap() ;
                   }
                  
                   protected BundleMap getResourceBundleMap() {
                   return this.resourceBundleMap;
                   }
                  
                   protected void setResourceBundleMap(BundleMap resourceBundleMap) {
                   this.resourceBundleMap = resourceBundleMap;
                   }
                  
                  }
                  


                  Step 6. optional - use the code in step 5 as a template for extending the ThemeSelector

                  Step 7. Component.xml extract:
                   <component name="org.jboss.seam.core.resourceBundle">
                   <property name="bundleNames">
                   <value>messages</value>
                   </property>
                   </component>
                  


                  • 21. Re: ResourceBundle in Database
                    zerg-spirit

                    Thanks for your message, I couldn't retrieve my EntityManager.

                    Finally, I managed to get it working, and I think what I did if pretty much what you did, except I didn't change that much the component processes, such as the caching which I'm using.
                    But your needsReload method seems very useful so I might implement it!

                    • 22. Re: ResourceBundle in Database
                      pbrewer_uk

                      For the needsReload method, I implemented a public boolean isDirty(Date lastLoaded) on the resource bundle entity. It simply compares the lastLoaded date to the last modified date stored on the entity (which is updated each time a message in the bundle is updated).

                      If you do decide to reload the resource bundle, you will need to override the built-in seam message component, otherwise you'll always get the cached bundle.

                      Cheers, Pete

                      • 23. Re: ResourceBundle in Database
                        zerg-spirit

                        Hey guys!

                        Sorry about the revival, but with the new ResourceBundle implementation in the last Seam CVS, I'm kinda lost.

                        I modified my implementation trying to get it working, but somehow I never manage to get the 'ResourceBundle.instance()' to change.
                        I can easily load whatever language I want, but once it's loaded, I can't get it to change using the localeSelector like I did before.
                        What method/class should I implement to get it to change?

                        At the moment, I have made an implementation of ResourceLoader to override the method loadBundle and basically do something like that:



                        ResourceBundle bundle = ResourceBundle.getBundle(bundleName,
                         Locale.instance(), Thread.currentThread()
                         .getContextClassLoader(), DBControl
                         .getInstance());



                        DBControl being my own controller (which is by the way working fine).
                        Actually everything seems to work find behind, but the locale just doesn't change, with org.jboss.seam.core.ResourceBundle.getInstance() being always the first locale I loaded.

                        • 24. Re: ResourceBundle in Database
                          zerg-spirit

                          Actually, I've worked around the problem, since we just need to override ResourceLoader loadBundle method, but now I have a problem about the caching to reload the messages when I set a locale as 'Dirty'.
                          Before the last changes, I was using an implementation or international.Message, but I don't really know how to make it work now, since the same implementation on the messageFactory class doens't work.

                          • 25. Re: ResourceBundle in Database
                            zerg-spirit

                            Another revival since I still can't find a way to make my 'dirty' thing works.

                            I don't know where to hook to change the code in order to reload the resourceBundle from the database.
                            I tryed some different things using the messageFactory class, but couldn't work out with it.

                            Basically, I'd need to have the org.jboss.seam.core.ResourceBundle.instance() to change when a Locale is referred as dirty, but I can't find where to make it reload.

                            I already overriden the ResourceLoader class to fetch the message from the database, and this works perfectly.

                            • 26. Re: ResourceBundle in Database
                              pmuir

                              Observer org.jboss.seam.localeSelected

                              • 27. Re: ResourceBundle in Database
                                zerg-spirit

                                Yes, I've noticed it and I manage to reload a resource bundle using that, but it doesn't change the actual resourceBundle.instance(), which means if I'm changing my locale again, the resourceBundle will be back to its dirty state.

                                • 28. Re: ResourceBundle in Database
                                  pmuir

                                  No, the resourcebundle should be reloaded due to Messages being reloaded in the current design of this in CVS.

                                  • 29. Re: ResourceBundle in Database
                                    zerg-spirit

                                    Ok, then I must do something wrong, that's for sure.

                                    I have overriden 2 classes:
                                    ResourceLoader and MessageFactory

                                    ResourceLoader:

                                    @Scope(SESSION)
                                    @BypassInterceptors
                                    @Name("org.jboss.seam.core.resourceLoader")
                                    public class ExtendedResourceLoader extends
                                     org.jboss.seam.core.ResourceLoader {
                                     ...
                                    
                                     @Override
                                     @Observer("org.jboss.seam.localeSelected")
                                     public ResourceBundle loadBundle(String bundleName) {
                                    
                                     try {
                                     if (DBControl.getInstance().isDirty(
                                     org.jboss.seam.core.Locale.instance().toString()))
                                     //If the current locale is dirty, we create a new bundle.
                                     ResourceBundle bundle = DBControl.getInstance().newBundle(
                                     bundleName, Locale.instance(), "Db",
                                     Thread.currentThread().getContextClassLoader(), true);
                                     return bundle;
                                     else {
                                     ResourceBundle bundle = ResourceBundle.getBundle(bundleName,
                                     Locale.instance(), Thread.currentThread()
                                     .getContextClassLoader(), DBControl
                                     .getInstance());
                                     return bundle;
                                     }


                                    messageFactory:

                                    @Scope(SESSION)
                                    @BypassInterceptors
                                    @Name("org.jboss.seam.international.messagesFactory")
                                    public class SeamMessages extends Messages {
                                    @Override
                                     public Map<String,String> createMap(){
                                     ResourceBundle resourceBundle = org.jboss.seam.core.ResourceBundle.instance();
                                     //That retrived bundle doesn't match the newly created bundle in ResourceLoader overriden class, so the created map isn't matching the current state of the database.
                                    
                                    ...
                                    public static Map<String, String> instance()
                                     {
                                    
                                     if ( !Contexts.isSessionContextActive() )
                                     {
                                     throw new IllegalStateException("no event context active");
                                     }
                                    
                                     return (Map<String, String>) Component.getInstance("org.jboss.seam.international.messages", true);
                                     }


                                    DBcontrol is a custom class extending Control class to instanciate ResourceBundles that are loading data from the database (this mecanism works since when the app is deployed, the correct messages are loaded, as well as when I'm changing the locale).
                                    So basically, when I changed a message and reload the locale, the change isn't performed on messages map.