Eclipse Community Forums
Forum Search:

Search      Help    Register    Login    Home
Home » Eclipse Projects » scout » Trouble with images being cached(How to suppress image caching when using IImageField (or Icons in tables))
icon5.gif  Trouble with images being cached [message #991136] Mon, 17 December 2012 13:10 Go to next message
Urs Beeli is currently offline Urs Beeli
Messages: 335
Registered: October 2012
Location: Bern, Switzerland
Senior Member
I have succesfully tried adding an image to a table page by following the description on Display_images_in_a_table_page howto. This works well for displaying "static" images in a table (I tried this on the company table page of the minicrm tutorial by just using a server side stored image whose name is identical to the company short name).

Next, I extended the CompanyForm to also display this image using an ImageField. This, too, worked nicely.

As a next step I added an image column to the person table page and added an ImageField to the PersonForm. Instead of using a "static" image I added the functionality to let the user choose an arbitrary image file to display (using a button from the company form) and then storing this image in the database (using a blob in a seperate table, accessed using the personNr). This, too, works in principle.

I soon noticed, that this works with the first image being set (either when a user adds an image for the first time or when first displaying an image from the DB after restarting the application). However, if I try to change the image assigned to a user, I started running into problems. The new data was being sent back to the database and stored, but both the ImageField in the form and the column in the table still displayed the old image.

After some debugging, I found out the following:
Both the icon in the table column and the ImageField use SwingIconLocator.getIcon() which in turn calls SwingIconLocator.getImage() to retrieve the image data. getImage() uses m_imagesByNameCache and as far as I could tell, there is no way to invalidate this cache from the client side.

I was using "Person-"+personNr as imageId on the client side (and the image cache described in the Howto linked above explicitely allows for udpating a caches image). However, the Swing client does not take this into account and keeps using the old image, that is cached in m_imagesByNameCache.

One solution would probably be for me to use a has over the image data as imageId to make sure the imageId/imageName changes with new content. However, over time this would make the swing side image cache contain tons of stale objects.

As I'm trying to learn as much about Swing as I can, I tried a different approach. I created a custom control that I named "UncachedImageField". In order to do this, I took the following steps:

In my scout client plugin I added the following files:

  • IUncachedImageField (a copy of IImagegeField)
  • IUncachedImageFieldUIFacade (a copy of IImageFieldUIFacade)
  • UncachedImageFieldEvent (a copy of ImageFieldEvent)
  • UncachedImagedFieldListener (a copy of ImageFieldListener)
  • AbstractUncachedImageField (a copy of AbstractImageField)

These files are 1:1 copies of the corresponding ImageField classes with the exception that they reference each other instead of the originals).

In my scout swing ui plugin I added the following files:

  • ISwingScoutUncachedImagedField (a copy of ISwingScoutImageField)
    This, too is a 1:1 copy of the original with adjusted references)
  • SwingScoutUncachedImageField (a copy of SwingScoutImageField with some modifications)
    - adjusting the names to the corresponding classes with "Uncached"
    - changing setImageFromScout as follows:
      protected void setImageFromScout(String imageId, Object image) {
        if (image == null) {
          if (imageId != null) {
            // try to use uncached image
            image = getUncachedIconLocator().getImage(imageId);
            if (image == null) {
              // as fallback, use cached image
              image = getSwingEnvironment().getImage(imageId);
            }
          }
        }
        getSwingImageViewer().setImage(image);
      }

    - added the following code to the class:
      private SwingUncachedIconLocator m_uncachedIconLocator;
      protected SwingUncachedIconLocator getUncachedIconLocator() {
        if (m_uncachedIconLocator == null) {
          m_uncachedIconLocator = new SwingUncachedIconLocator(getSwingEnvironment().getScoutSession().getIconLocator());
        }
        return m_uncachedIconLocator;
      }

  • SwingUncachedIconLocator (a copy of SwingIconLocator with some modifications)
      public Image getImage(String name) {
        if (name == null || AbstractIcons.Null.equals(name) || name.length() == 0) {
          return null;
        }
        Image img;
        synchronized (m_cacheLock) {
          //img = m_imagesByNameCache.get(name); // not using cache
          //if (img == null && !m_imagesByNameCache.containsKey(name)) { // not using cache
            img = createImageImpl(name);
            if (img == null) {
              img = autoCreateMissingImage(name);
            }
            //m_imagesByNameCache.put(name, img); // not using cache
            if (LOG.isDebugEnabled()) {
              LOG.debug("load image '" + name + "' as " + img);
            }
            if (img == null) {
              warnImageNotFound(name);
            }
          //} // not using cache
        }
        return img;
      }

  • added the following block to plugin.xml
         <formField
               active="true"
               modelClass="com.bsiag.minicrm.client.ui.form.fields.ext.IUncachedImageField"
               name="Uncached Image Field"
               scope="default">
            <uiClass
                  class="com.bsiag.minicrm.ui.swing.form.fields.ext.SwingScoutUncachedImageField">
            </uiClass>
         </formField>



I then changed the ImageField on my Form to be an UncachedImageField instead. With these changes the image on the Form is now updated whenever the user selects a different image. However, I have the feeling that there must be an easier solution to this...

Also, the image in the table column still uses the cached version of the icon locator and I haven't found out how to make that use the uncached icon locator.

So, my questions are:


  • what is the recommended way to handle images that can change during the life time of the client? Use constantly changing imageIds (bearing the cost of an overblown cache)? Or is there a more elegant solution I fail to see?
  • Instead of copying more than half a dozen files to emulate ImageField except for not using a cache in the icon locator, is there an easier way of changing the behaviour of a an existing FormField?
  • how would I need to proceed to make the table column icon use the uncached icon locator (if there is no easier solution to my problem)?


Thanks for any light you can shed on this issue.
Re: Trouble with images being cached [message #991220 is a reply to message #991136] Mon, 17 December 2012 20:21 Go to previous messageGo to next message
Beat Schwarzentrub is currently offline Beat Schwarzentrub
Messages: 43
Registered: November 2010
Member
Hi Urs

The tutorial basically seems to read the image data and then put it in the icon cache from which it can be retrieved by the string id. So maybe it was designed to cache the icon and never change it again. Wink

Have you tried reading the image data (= byte[]) by yourself and directly setting it into the ImageField by using the method setImage(Object)? For reading a file, Scout's IOUtility may be handy.

This does not work for images in a table, though, because the ImageField is not used there. For an ICell, you can only specify icons "by ID" and not "by data". IDs are then resolved using the SwingEnvironment's icon locator. So, if you override the method createIconLocator() and provide your own (non-caching) SwingIconLocator, you should be able to achieve the desired behavior. As far as I could see at a quick glance, the ImageField also retrieves the icon locator from the SwingEnvironment. If you try that our, please let us know if it worked.

(Note: All classes that use org.eclipse.scout.rt.ui.swing.Activator to retrieve icons and images cannot be changed, because you cannot replace that SwingIconLocator. This mostly "only" affects UI images, like the splash screen and not application-specific icons.)

Regarding your concern about having to copy so many files the create a custom widget: I admit that I did not try it out ( Smile ), but I think in your case it would have to be sufficient to only register a new UncachedSwingScoutImageField class that extends SwingScoutImageField (for 'IImageField'). If I am correct, the most specific (i.e. greatest distance to IFormField) class "wins". Duplicating the model class (AbstractImageField) should not be necessary as you do not want to alter the model behavior. (Note that this approach would then affect all ImageField!)

Does that already help your for your problem? If you think that a mechanism to invalidate the image cache is missing from Scout, maybe we should discuss it further.
Re: Trouble with images being cached [message #991257 is a reply to message #991220] Tue, 18 December 2012 07:27 Go to previous messageGo to next message
Urs Beeli is currently offline Urs Beeli
Messages: 335
Registered: October 2012
Location: Bern, Switzerland
Senior Member
Beat Schwarzentrub wrote on Mon, 17 December 2012 21:21
The tutorial basically seems to read the image data and then put it in the icon cache from which it can be retrieved by the string id. So maybe it was designed to cache the icon and never change it again. Wink

Yes, I'm sure it was designed for exactly that use case. I wasn't trying to criticise the tutorial. It's just that I'm trying to push a bit further than the tutorials and howto's go and occasionally stumble on a blocking point Wink

Beat Schwarzentrub wrote on Mon, 17 December 2012 21:21
Have you tried reading the image data (= byte[]) by yourself and directly setting it into the ImageField by using the method setImage(Object)?

I didn't even realise that there was a setImage method, I'll certainly give that a try.

Beat Schwarzentrub wrote on Mon, 17 December 2012 21:21
This does not work for images in a table, though, because the ImageField is not used there. For an ICell, you can only specify icons "by ID" and not "by data". IDs are then resolved using the SwingEnvironment's icon locator. So, if you override the method createIconLocator() and provide your own (non-caching) SwingIconLocator, you should be able to achieve the desired behavior. As far as I could see at a quick glance, the ImageField also retrieves the icon locator from the SwingEnvironment. If you try that our, please let us know if it worked.

Thanks, I hadn't thought of that approach and will try it out. If I understand this correctly though, this will mean the non-caching locator would be used for all users of the SwingEnvironment? That may or may not be desirable... Still, it's an approach to look at.

Beat Schwarzentrub wrote on Mon, 17 December 2012 21:21
Regarding your concern about having to copy so many files the create a custom widget: I admit that I did not try it out ( Smile ), but I think in your case it would have to be sufficient to only register a new UncachedSwingScoutImageField class that extends SwingScoutImageField (for 'IImageField'). If I am correct, the most specific (i.e. greatest distance to IFormField) class "wins". Duplicating the model class (AbstractImageField) should not be necessary as you do not want to alter the model behavior.

Thanks. I had first tried that approach but didn't know about this "greatest distance" rule and tried to 'bend' the new class to implement a new interface and step by step ended up adding "just one more" class until I had a full duplicate solution Smile

Beat Schwarzentrub wrote on Mon, 17 December 2012 21:21
(Note that this approach would then affect all ImageField!)

Yes, that is clear. Again, this may or may not be desirable.

Beat Schwarzentrub wrote on Mon, 17 December 2012 21:21
Does that already help your for your problem? If you think that a mechanism to invalidate the image cache is missing from Scout, maybe we should discuss it further.


For my ImageFields I know have several solutions:

  • using my own, full blown custom UncachedImageField implementation (advantage: I can choose which fields are cached and which are uncached)
  • extending SwingScoutImageField to use the non-caching locator (disadvantage: all ImageFields will be uncached)
  • overwriting createIconLocator (disadvantage: all ImageFields and table icons will be uncached)
  • using setImage method (advantage: I can choose which fields are cached and which are uncached)


On the other hand, for the table icons it seems to only solution is:

  • overwriting createIconLocator, with the disadvantage that all ImageFields and all table icons will be uncached)
  • I suppose I could look at "cloning" all classes used for a TableField (or maybe the TablePage?) to allow for selective uncached icons in cells, but that may be a bit much effort even though it would probably teach me a lot about scout Smile


I will play through the various suggestions you made (though it may be next year, I'm off to attend a two day workshop, and then on holidays after that).

Thanks for your help.
Re: Trouble with images being cached [message #991831 is a reply to message #991257] Thu, 20 December 2012 09:47 Go to previous messageGo to next message
Urs Beeli is currently offline Urs Beeli
Messages: 335
Registered: October 2012
Location: Bern, Switzerland
Senior Member
Beat,

I've found some time to try out your various suggestions, here is some feedback:

  • using setImage(byte[]) instead of setImageId(String) in the form solves the caching problem
  • overwriting SwingEnvironment.createIconLocator() succesfully makes all images uncached (form and tables)
  • I then removed all files mirroring the *ImageField classes and only kept my SwingScoutUncachedImageField and SwingUncachedIconLocator classes, I also removed the entry in plugin.xml for the new classes.
    I let SwingScoutUncachedImageField inherit from SwingScoutImageField
    public class SwingScoutUncachedImageField extends SwingScoutImageField {
      private SwingUncachedIconLocator m_uncachedIconLocator;
    
      protected SwingUncachedIconLocator getUncachedIconLocator() {
        if (m_uncachedIconLocator == null) {
          m_uncachedIconLocator = new SwingUncachedIconLocator(getSwingEnvironment().getScoutSession().getIconLocator());
        }
        return m_uncachedIconLocator;
      }
    
      @Override
      protected void setImageFromScout(String imageId, Object image) {
        if (image == null) {
          if (imageId != null) {
            // try to use uncached image
            image = getUncachedIconLocator().getImage(imageId);
            if (image == null) {
              // as fallback, use cached image
              image = getSwingEnvironment().getImage(imageId);
            }
          }
        }
        getSwingImageViewer().setImage(image);
      }

    and added the following entry to the plugin.xml
         <formField
               active="true"
               modelClass="org.eclipse.scout.rt.client.ui.form.fields.imagebox.IImageField"
               name="Uncached Image Field"
               scope="default">
            <uiClass
                  class="com.bsiag.minicrm.ui.swing.form.fields.ext.SwingScoutUncachedImageField">
            </uiClass>
         </formField>


    This should make my new class the "most distant" for this interface but it seems that this does not work, the setImageFromScout() method was always called on SwingScoutImageField and not on my class. Removing the plugin entry doesn't make a difference.
    I don't know if you have any further ideas on this.


I also looked at alternatives to ensure the table icons are updated by using a different imageId. To minimize the number of stale objects in the image cache I didn't want to use a permantently increasing id whenever an image changes. Instead, I am now using an md5 hash over the image contents and store those together with the blob in the DB and return it in the outline service for my icon column. This way, I can check if the image is in the cache of the CustomIconProvider and only need to retrieve it from the blob, if it isn't. If a new icon is chosen the new image gets added to the cache with the MD5 hash of the new image. The old image will stay in the cache, but on the plus side if more than one person uses the same image, that will only be cached once and not multiple times (as it was before, when the personNr was used as imageId).

Overall I think using the MD5 hash as imageId for the table cells and directly using the setImage() method on the form will be the most efficient way to deal with my requirement.

Still, looking at all those alternatives has been an educational exercise Smile
Re: Trouble with images being cached [message #991884 is a reply to message #991831] Thu, 20 December 2012 12:11 Go to previous messageGo to next message
Beat Schwarzentrub is currently offline Beat Schwarzentrub
Messages: 43
Registered: November 2010
Member
Urs Beeli wrote on Thu, 20 December 2012 04:47
This should make my new class the "most distant" for this interface but it seems that this does not work, the setImageFromScout() method was always called on SwingScoutImageField and not on my class. Removing the plugin entry doesn't make a difference.
I don't know if you have any further ideas on this.


I just took a deeper look at the code and found that the distance is calculated for the model class and not the UI (a.k.a. "SwingScout...") class, i.e. between IFormField and IImageField. Because both contributions refer to IImageField, they also get the same distance (as you can see from the logged warning). I was mistaken there. (Eventually, the approach can be made to work, when you set the "scope" of your contribution from "default" to "global". But that does only work because no other "global" contributions are present and does not look very elegant to me.)

The other possibility would be to not delegate the creation of IImageField UI objects to the FormFieldFactory, but build them yourself in your own SwingEnvironment:

  @Override
  public ISwingScoutFormField<?> createFormField(JComponent parent, IFormField field) {
    if (field instanceof IImageField /* && add further conditions here */) {
      SwingScoutUncachedImageField ui = new SwingScoutUncachedImageField();
      ui.createField((IImageField) field, this);
      decorate(field, ui);
      return ui;
    }
    return super.createFormField(parent, field);
  }


Urs Beeli wrote on Thu, 20 December 2012 04:47
Overall I think using the MD5 hash as imageId for the table cells and directly using the setImage() method on the form will be the most efficient way to deal with my requirement.


I like the idea of using the MD5 hash as a cache value. Looks promising! Thanks for sharing.

Beat
Re: Trouble with images being cached [message #994881 is a reply to message #991884] Fri, 28 December 2012 14:40 Go to previous messageGo to next message
Li Hao is currently offline Li Hao
Messages: 28
Registered: August 2011
Junior Member
Hi, I am facing the same issue now. I would like to display image which will constantly change. but the image is cached. I am using rap client and swt client. do I need to modify SwtEnvironment to make it not cache image? If I using MD5 hash as cache value, will the image cache be released? or it will keep building up again and again and eventually hit too many handle in windows or too many open file in linux?
Re: Trouble with images being cached [message #994899 is a reply to message #994881] Fri, 28 December 2012 15:38 Go to previous messageGo to next message
Beat Schwarzentrub is currently offline Beat Schwarzentrub
Messages: 43
Registered: November 2010
Member
Quote:
Hi, I am facing the same issue now. I would like to display image which will constantly change. but the image is cached. I am using rap client and swt client. do I need to modify SwtEnvironment to make it not cache image?


Yes, SwtEnvironment and RwtEnvironment are the equivalents of SwingEnvironment for SWT and RAP/RWT, respectively. Those two UI layers both use the org.eclipse.jface.resource.ImageRegistry to store the images. The JavaDoc of ImageRegistry says:

(...)
Unlike the FontRegistry, it is an error to replace images. As a result
there are no events that fire when values are changed in the registry


So there is also no possibility to alter the image cache. You have to implement your own (Swing|Swt|Rwt)IconLocator to add that behaviour.

Quote:
If I using MD5 hash as cache value, will the image cache be released? or it will keep building up again and again and eventually hit too many handle in windows or too many open file in linux?


Yes, the icons in the default implementation will stay in the cache forever. This may indeed be a problem on SWT and maybe also on RWT, because each image is using a resource handle. (This problem is not adressed by the default implementation because it was not designed to work with dynamic images.)

Overriding the createIconLocator() method in your environment class should easily allow you to change the caching behaviour according to your needs.

Beat
Re: Trouble with images being cached [message #994938 is a reply to message #994899] Fri, 28 December 2012 17:21 Go to previous messageGo to next message
Li Hao is currently offline Li Hao
Messages: 28
Registered: August 2011
Junior Member
I append a time value at the end of image name. I replace the temp file with ByteArray so it won't create new file every time. I did some test and looks like the handle is not building up.
Re: Trouble with images being cached [message #996925 is a reply to message #991884] Thu, 03 January 2013 08:01 Go to previous message
Urs Beeli is currently offline Urs Beeli
Messages: 335
Registered: October 2012
Location: Bern, Switzerland
Senior Member
Beat thanks for further looking at the "most distant" issue.

Beat Schwarzentrub wrote on Thu, 20 December 2012 13:11
The other possibility would be to not delegate the creation of IImageField UI objects to the FormFieldFactory, but build them yourself in your own SwingEnvironment:

  @Override
  public ISwingScoutFormField<?> createFormField(JComponent parent, IFormField field) {
    if (field instanceof IImageField /* && add further conditions here */) {
      SwingScoutUncachedImageField ui = new SwingScoutUncachedImageField();
      ui.createField((IImageField) field, this);
      decorate(field, ui);
      return ui;
    }
    return super.createFormField(parent, field);
  }


That looks like another elegant solution to this, I'll add it to the things to keep in mind. Thanks!

Beat Schwarzentrub wrote on Thu, 20 December 2012 13:11
Urs Beeli wrote on Thu, 20 December 2012 04:47
Overall I think using the MD5 hash as imageId for the table cells and directly using the setImage() method on the form will be the most efficient way to deal with my requirement.


I like the idea of using the MD5 hash as a cache value. Looks promising! Thanks for sharing.


You're welcome, I'm glad I can return something for all the time you spent on this Smile
Previous Topic:P/Users, does not match outer scope rule
Next Topic:Max Fraction Digits stuck at 2
Goto Forum:
  


Current Time: Thu Oct 23 15:27:21 GMT 2014

Powered by FUDForum. Page generated in 0.02415 seconds
.:: Contact :: Home ::.

Powered by: FUDforum 3.0.2.
Copyright ©2001-2010 FUDforum Bulletin Board Software