How to Use the Atlassian Cache API
Nowadays, people want fast service. If your web page takes a long time to load, chances are the user will move to another page. To help us speed things up, we will use the Atlassian Cache API. The Atlassian Cache API stores data so that future requests for that data can be served faster.
In this example, the data stored in the cache is a result of an earlier computation. A cache hit occurs when the requested data can be found in the cache, while a cache miss occurs when it cannot. Cache hits are served by reading data from the cache, which is faster than recomputing a result. Thus, more requests can be served from the cache and the system performs faster. Here we go.
1. Requirements
- Atlassian SDK.
- Mars Eclipse.
- Have created a simple Atlassian Confluence add-on. See Atlassian Confluence Add-on Development Examples if you haven’t done so.
- Be familiar with Apache Maven.
- A copy of unit-test-confluence-addon.tar.gz from How to Unit Test Your Confluence Add-on. We will modify this add-on to use the Atlassian Cache API.
2. Edit pom.xml
After extracting the compressed file (unit-test-confluence-addon.tar.gz), add the following lines in the package import of your pom.xml
as highlighted below.
pom.xml
... snipped ... <Import-Package> com.atlassian.confluence.pages, com.atlassian.confluence.spaces, com.atlassian.cache, org.springframework.osgi.*;resolution:="optional", org.eclipse.gemini.blueprint.*;resolution:="optional", * </Import-Package> ... snipped ...
Run atlas-mvn eclipse:eclipse. This should generate the necessary Eclipse files so that we can import the project in Eclipse. Remove the files under the test directory, we wont be needing it.
3. Edit the Plugin Descriptor
We’ll add another Macro that uses the Atlassian Cache API so that we can compare it to the existing Spaces Macro. Let’s add the highlighted lines below in atlassian-plugin.xml.
atlassian-plugin.xml
<atlassian-plugin key="${atlassian.plugin.key}" name="${project.name}" plugins-version="2"> ... snipped... <xhtml-macro name="unit-testing-plugin" class="com.javacodegeeks.example.SpacesMacro" key="my-macro"> <parameters/> </xhtml-macro> <xhtml-macro name="cached-spaces-macro" class="com.javacodegeeks.example.CachedSpacesMacro" key="cached-my-macro"> <parameters/> </xhtml-macro> </atlassian-plugin>
4. Modify the Code
SpacesMacro.java
package com.javacodegeeks.example; import java.util.ArrayList; import java.util.List; import java.util.Map; import org.springframework.beans.factory.annotation.Autowired; import com.atlassian.confluence.content.render.xhtml.ConversionContext; import com.atlassian.confluence.macro.Macro; import com.atlassian.confluence.macro.MacroExecutionException; import com.atlassian.confluence.pages.Page; import com.atlassian.confluence.pages.PageManager; import com.atlassian.confluence.spaces.Space; import com.atlassian.confluence.spaces.SpaceManager; import com.atlassian.plugin.spring.scanner.annotation.component.Scanned; import com.atlassian.plugin.spring.scanner.annotation.imports.ComponentImport; @Scanned public class SpacesMacro implements Macro { private final SpaceManager spaceManager; private final PageManager pageManager; @Autowired public SpacesMacro(@ComponentImport SpaceManager spaceManager, @ComponentImport PageManager pageManager) { this.spaceManager = spaceManager; this.pageManager = pageManager; } @Override public String execute(Map<String, String> map, String body, ConversionContext context) throws MacroExecutionException { long startTime = System.currentTimeMillis(); List<Space> spaces = spaceManager.getAllSpaces(); List<Page> pages = new ArrayList<Page>(); StringBuilder builder = new StringBuilder(); builder.append("<h4>Spaces</h4>"); builder.append("<ul>"); for (Space space : spaces) { builder.append("<li>"); builder.append(space.getName()); pages.addAll(pageManager.getPages(space, false)); builder.append("</li>"); } builder.append("</ul>"); builder.append("<h4>Pages</h4>"); builder.append("<ul>"); for (Page page : pages) { builder.append("<li>"); builder.append(page.getTitle()); builder.append("</li>"); } builder.append("</ul>"); long endTime = System.currentTimeMillis(); builder.append("<p>"); builder.append("Processing time in milliseconds: " + (endTime - startTime)); builder.append("</p>"); return builder.toString(); } @Override public BodyType getBodyType() { return BodyType.NONE; } @Override public OutputType getOutputType() { return OutputType.BLOCK; } }
We have modified the SpacesMacro
to also display the available pages in Confluence as well as the spaces. We also computed the processing time of the execute
method and displayed the result. We are displaying the pages available in Confluence so that the processing would take a little bit more time. This way we can differentiate the processing times of an uncached Macro and a cached Macro.
CachedSpacesMacro.java
package com.javacodegeeks.example; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import org.springframework.beans.factory.annotation.Autowired; import com.atlassian.cache.Cache; import com.atlassian.cache.CacheLoader; import com.atlassian.cache.CacheManager; import com.atlassian.cache.CacheSettingsBuilder; import com.atlassian.confluence.content.render.xhtml.ConversionContext; import com.atlassian.confluence.macro.Macro; import com.atlassian.confluence.macro.MacroExecutionException; import com.atlassian.confluence.pages.Page; import com.atlassian.confluence.pages.PageManager; import com.atlassian.confluence.spaces.Space; import com.atlassian.confluence.spaces.SpaceManager; import com.atlassian.plugin.spring.scanner.annotation.component.Scanned; import com.atlassian.plugin.spring.scanner.annotation.imports.ComponentImport; @Scanned public class CachedSpacesMacro implements Macro { private final SpaceManager spaceManager; private final PageManager pageManager; private final Cache<String, String> cache; @Autowired public CachedSpacesMacro(@ComponentImport SpaceManager spaceManager, @ComponentImport PageManager pageManager, @ComponentImport CacheManager cacheManager) { this.spaceManager = spaceManager; this.pageManager = pageManager; this.cache = cacheManager.getCache(CachedSpacesMacro.class.getName() + ".cache", new CachedSpacesMacroLoader(), new CacheSettingsBuilder().expireAfterAccess(60, TimeUnit.MINUTES).build()); } @Override public String execute(Map<String, String> map, String body, ConversionContext context) throws MacroExecutionException { long startTime = System.currentTimeMillis(); String output = cache.get(CachedSpacesMacro.class.getName() + ".cache"); long endTime = System.currentTimeMillis(); String append = "<p>Processing time in milliseconds: " + (endTime - startTime) + "</p>"; return output + append; } @Override public BodyType getBodyType() { return BodyType.NONE; } @Override public OutputType getOutputType() { return OutputType.BLOCK; } private String renderSpacesMacro() { List<Space> spaces = spaceManager.getAllSpaces(); List<Page> pages = new ArrayList<Page>(); StringBuilder builder = new StringBuilder(); builder.append("<h4>Spaces</h4>"); builder.append("<ul>"); for (Space space : spaces) { builder.append("<li>"); builder.append(space.getName()); pages.addAll(pageManager.getPages(space, false)); builder.append("</li>"); } builder.append("</ul>"); builder.append("<h4>Pages</h4>"); builder.append("<ul>"); for (Page page : pages) { builder.append("<li>"); builder.append(page.getTitle()); builder.append("</li>"); } builder.append("</ul>"); return builder.toString(); } private class CachedSpacesMacroLoader implements CacheLoader<String, String> { @Override public String load(String key) { return renderSpacesMacro(); } } }
This class is the same as SpacesMacro
. It also displays the spaces and pages available in Confluence. It also displays the time it took to process the execute
method. The only difference is that we are caching the result for 60 minutes (highlighted code). Take note of the highlighted code. During a cache miss the CachedSpacesMacroLoader
is used to add a cache entry. We can just do a cache.get()
to retrieve our cached data just like Java’s Map
.
5. Setup Some Data in Confluence
Execute atlas-run in the command prompt. Confluence should be accessible at http://localhost:1990/confluence. Use admin as your username and password. Add a couple of spaces (e.g., Microsoft, Oracle). If you don’t know how to add a space in Confluence, check out How to Unit Test Your Confluence Add-on.
Now that we have a few spaces, let’s create two pages. One page using the uncached Spaces Macro and the other using the cached Spaces Macro. If you don’t know how to create a page and add a Macro, check out How to Build a Macro Add-on for Atlassian Confluence Server. After creating the pages, reload the pages a few times and take note of the processing time.
On both pages, the top should look like this:
The bottom of the uncached Spaces Macro should look like this:
The bottom of the cached Spaces Macro should look like this:
What did you notice about the processing time? Obviously, after a few reloads the processing time of the cached Spaces Macro is faster than the uncached one.
6. How to Use the Atlassian Cache API Summary
How to use the Atlassian Cache API? It’s pretty simple. The Atlassian Cache is very similar to Java’s Map
. All you need to do is implement a loader in case there is a cache miss and set the time on how long the cached data will be valid. The default expiration of cache data is 1 hour. For more information check out Confluence Caching Architecture, Atlassian Cache 2 Overview, and How do I cache data in a plugin?
7. Download The Source Code
This is a How to Use the Atlassian Cache API example.
You can download the source code of this example here: atlassian-cache-api.zip.