Home » Software Development » Atlassian » How to Use the Atlassian Cache API

About Joel Patrick Llosa

I graduated from Silliman University in Dumaguete City with a degree in Bachelor of Science in Business Computer Application. I have contributed to many Java related projects at Neural Technologies Ltd., University of Southampton (iSolutions), Predictive Technologies, LLC., Confluence Service, North Concepts, Inc., NEC Telecom Software Philippines, Inc., and NEC Technologies Philippines, Inc. You can also find me in Upwork freelancing as a Java Developer.

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

  1. Atlassian SDK.
  2. Mars Eclipse.
  3. Have created a simple Atlassian Confluence add-on. See Atlassian Confluence Add-on Development Examples if you haven’t done so.
  4. Be familiar with Apache Maven.
  5. 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:

Atlassian Cache API

Top of Both Pages

The bottom of the uncached Spaces Macro should look like this:

Atlassian Cache API

Bottom of Uncached Page

The bottom of the cached Spaces Macro should look like this:

Atlassian Cache API

Bottom of Cached Page

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.

Download
You can download the source code of this example here: atlassian-cache-api.zip.

Do you want to know how to develop your skillset to become a Java Rockstar?

Subscribe to our newsletter to start Rocking right now!

To get you started we give you our best selling eBooks for FREE!

 

1. JPA Mini Book

2. JVM Troubleshooting Guide

3. JUnit Tutorial for Unit Testing

4. Java Annotations Tutorial

5. Java Interview Questions

6. Spring Interview Questions

7. Android UI Design

 

and many more ....

 

Receive Java & Developer job alerts in your Area

 

Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments