How to implement JIRA plugin caching?

Victor Cheung December 18, 2017

Hello,

I'm new to JIRA development and I'm following this article on implementing plugin caching:  https://developer.atlassian.com/confdev/development-resources/confluence-developer-faq/how-do-i-cache-data-in-a-plugin

However, after installing my plugin and trying to test it, it does not behave as expected.

Each time I make a request to my plugin, it basically instantiates a new instance of my plugin.  The constructor is called for every request I make, so a new CacheManager is created and each request does the expensive retrieval of data from the database.  In other words, each request is like visiting my plugin for the very first time and effectively there is no caching because of this.

Why does my plugin not live beyond the scope of a single request?  Is this perhaps a plugin scope configuration that I need to configure?  I read the article about JIRA plugin lifecycle: https://developer.atlassian.com/jiradev/jira-platform/building-jira-add-ons/jira-plugins2-overview/jira-plugin-lifecycle#JIRAPluginLifecycle-Componentinitialisation  and also read articles about "JIRA plugin scope" but they're not talking about plugin scope as it relates to development.

Thanks in advance!

 

1 answer

1 accepted

0 votes
Answer accepted
Alexey Matveev
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
December 18, 2017

Hello,

You need to inject CacheManager. In this case CacheManager always stays the same. Also you can make your class as a bean adding Named annotation to the class. It is difficult to say for sure what is wrong with your code because you did not provide it.

Victor Cheung December 18, 2017

Hi Alexey,

Thanks for your help!  I am injecting the CacheManager into my constructor and also I have just tried adding the "Named" annotation, however it still does not work.  Here is a simplified version of my plugin code:

@Named("MyPlugin")
@Scanned
public class MyPlugin extends JiraWebActionSupport
{
private static final Logger log = LoggerFactory.getLogger(MyPlugin.class);

private MyDBService db;

@ComponentImport
private final PermissionManager permissionMgr;

@ComponentImport
private final CacheManager cachMgr;

private final Cache myCache;
private List<MyField> myFields;

@Inject
public MyPlugin(MyDBService mydb, PermissionManager permissionMgr, CacheManager cachMgr) {
this.db = mydb;
this.permissionMgr = permissionMgr;
this.cachMgr = cachMgr;
myCache = cachMgr.getCache(MyPlugin.class.getName() + "-MyCache.cache",
new MyDataLoader(),
new CacheSettingsBuilder().expireAfterAccess(50, TimeUnit.MINUTES).build());
log.debug("MyPlugin Constructor called!");
}

@Override
public String execute() throws Exception {

myFields = (List<MyField>) myCache.get("myData")
return super.execute();
}

private class MyDataLoader implements CacheLoader<String,List<MyField>> {

public List<MyField> load(@Nonnull String s) {

if(s.equals("myData")) {
log.debug("retrieved data from db!");
return db.getAllData();
}

return null;
}
}

}

 

Alexey Matveev
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
December 18, 2017

You put @Named annotation and made atlas-clean and then atlas-run? And then every time you enter your webwork item you fall into the constructor? If so, make you private MyDataLoader class as a bean and pass it to the constructor.

Alexey Matveev
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
December 18, 2017

Also I found this article

https://community.atlassian.com/t5/Answers-Developer-Questions/Cache-2-CacheLoader-is-called-on-every-cache-read/qaq-p/555222

The point is that

myCache = cachMgr.getCache(MyPlugin.class.getName() + "-MyCache.cache",
new MyDataLoader(),
new CacheSettingsBuilder().expireAfterAccess(50, TimeUnit.MINUTES)
.build());

must be called once. So I would make a bean and make the myCache as a property of the bean. Then I would inject it into the constructor of your Webwork. 

But That would be true only if the constructor of your Webwork is called everytime you call the webwork. I need to check it. But I can not check it right now. But I ll try to check it tomorrow.

Victor Cheung December 18, 2017

Hi Alexey, 

Thank you so much for your help.  I did some quick testing and it seems your suggestion will work:

  - create a separate dedicated 'cache bean' with the cache objects as properties (and instantiate the cache properties in this bean's constructor -- which will only be called once)

  - inject this dedicated 'cache bean' into my plugin bean's constructor

Even though my plugin bean constructor is called once per request (intended by design or not), the 'cache bean' constructor will only be invoked once when the bean is initially injected (so the cache objects stay the same and the cached data are reused). 

Thanks again, Alexey!

Best regards,

victor

Victor Cheung December 18, 2017

Oh btw, re: your question about atlas-clean atlas-run..

I was just making my changes, then doing:  atlas-mvn package

Then I'd uninstall and reinstall the newly compiled .jar into the JIRA instance and test it.

(But I also tried doing atlas-clean and then atlas-mvn package and it didn't fix my problem.. it seems since my plugin constructor is called many times, the only solution is to refactor out the instantiation of the cache objects into a separate bean and inject back into my plugin)

Suggest an answer

Log in or Sign up to answer