How to Add Active Objects to Your Confluence Add-on
In this article you are going to add Active Objects to you Confluence Add-on. Active Objects is the ORM layer of Atlassian. This will enable your plug-in to persist data.
1. Requirement
We will require the following:
- Atlassian SDK
- Mars Eclipse
- Have read and tried out
How to Add a Servlet Module to Your Confluence Add-on - You will build upon the source code of How to Add a Servlet Module to Your Confluence Add-on article.
Download the source: space-admin-servlet.tar.gz
Upon extracting space-admin-servlet.tar.gz, your space-admin directory will contain the following files:
pom.xml
README
LICENSE
- Under “src/main/resources” –
atlassian-plugin.xml
,space-admin.properties
, css folder,
images folder, js folder, and META-INF folder (all folders contained their default files) - Under “src/main/resources/templates” –
space-admin-action.vm
- Under “src/main/java” –
com.javacodegeeks.example
package and under itMyAction.java
,QuoteServlet.java
,Quote.java
The Quote.java
doesn’t exist yet. You will create it later.
2. Modify the pom.xml
Add the Active Objects library and the Atlassian Components to be imported as highlighted below.
pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <!-- other contents omitted --> <dependencies> <dependency> <groupId>com.atlassian.activeobjects</groupId> <artifactId>activeobjects-plugin</artifactId> <version>1.2.3</version> <scope>provided</scope> </dependency> <!-- other contents omitted --> <instructions> <Atlassian-Plugin-Key>${atlassian.plugin.key}</Atlassian-Plugin-Key> <!-- Add package to export here --> <Export-Package> </Export-Package> <!-- Add package import here --> <Import-Package> com.atlassian.activeobjects.external, com.atlassian.confluence.plugin.descriptor.web.conditions, org.springframework.osgi.*;resolution:="optional", org.eclipse.gemini.blueprint.*;resolution:="optional", * </Import-Package> <!-- Ensure plugin is spring powered --> <Spring-Context>*</Spring-Context> </instructions> <!-- other contents omitted --> </project>
Once done, execute atlas-mvn eclipse:eclipse
to create .classpath and .project so that you can import this Maven project into Eclipse.
3. Add the Active Objects Module in the Add-on Descriptor
Add the Active Objects Module in atlassian-plugin.xml
just before the </atlassian-plugin>
closing tag and after the Servlet Module entry. Your add-on descriptor will look like the one below:
atlassian-plugin.xml
<atlassian-plugin key="${atlassian.plugin.key}" name="${project.name}" plugins-version="2"> <!-- other contents omitted --> </servlet> <ao key="ao-module"> <description>The module configuring the Active Objects service used by this plugin</description> <entity>com.javacodegeeks.example.Quote</entity> </ao> </atlassian-plugin>
You defined your entity here named Quote
.
4. Create the Quote(Entity) Interface
Next up is to create your entity like so:
Quote.java
package com.javacodegeeks.example; import net.java.ao.Entity; import net.java.ao.Preload; @Preload public interface Quote extends Entity { String getQuote(); void setQuote(String quote); }
By default, Active Objects use lazy loading. The @Preload
annotation tells Active Objects to use eager loading.
5. Modify MyAction Class
Modify the MyAction
class like so:
MyAction.java
package com.javacodegeeks.example; import java.util.List; import com.atlassian.activeobjects.external.ActiveObjects; import com.atlassian.confluence.spaces.actions.SpaceAdminAction; import com.atlassian.plugin.spring.scanner.annotation.imports.ComponentImport; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.collect.Lists.newArrayList; public class MyAction extends SpaceAdminAction { private static final long serialVersionUID = 1L; private ActiveObjects ao; @Override public String doDefault() { return INPUT; } public void setActiveObjects(@ComponentImport ActiveObjects ao) { this.ao = checkNotNull(ao); } public List<Quote> getQuotes() { return newArrayList(ao.find(Quote.class)); } }
You are able to inject ActiveObjects
in the setter method because you included this component in the import package in your pom.xml. Do you remember? The getter method will make available a list of quotes in the Velocity template.
6. Modify QuoteServlet Class
Modify the doPost method of your servlet like so:
QuoteServlet.java
package com.javacodegeeks.example; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import com.atlassian.activeobjects.external.ActiveObjects; import com.atlassian.plugin.spring.scanner.annotation.component.Scanned; import com.atlassian.plugin.spring.scanner.annotation.imports.ComponentImport; import com.atlassian.sal.api.transaction.TransactionCallback; import static com.google.common.base.Preconditions.checkNotNull; @Scanned public class QuoteServlet extends HttpServlet { private static final long serialVersionUID = 1L; private static final Logger log = LoggerFactory.getLogger(QuoteServlet.class); private final ActiveObjects ao; @Autowired public QuoteServlet(@ComponentImport ActiveObjects ao) { this.ao = checkNotNull(ao); } @Override protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { } @Override protected void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { final String quote = req.getParameter("quote"); final String spaceKey = req.getParameter("spaceKey"); log.warn("====== \n Quote: {} \n ======", quote); ao.executeInTransaction(new TransactionCallback<Quote>() { @Override public Quote doInTransaction() { final Quote q = ao.create(Quote.class); q.setQuote(quote); q.save(); return q; } }); res.sendRedirect(req.getContextPath() + "/plugins/space-admin/sas.action?key=" + spaceKey); } }
All Active Objects operations must happen in a transaction. Inside the transaction, you created a Quote entity, then set its values and persisted it to the database. You auto wire the ActiveObjects
object in the constructor just like the MyAction
class by using @ComponentImport
.
7. Modify the Velocity Template
Modify the body section of the template like so:
space-admin-action.vm
<html> <head> <title>$action.getText("space.admin.item.label")</title> <meta name="decorator" content="main"/> </head> #applyDecorator("root") #decoratorParam("helper" $action.helper) ## Name of the tab to highlight: space-operations is also valid. #decoratorParam("context" "space-administration") #applyDecorator ("root") ## The .vmd to use - This one displays both in Space Admin and Space Tools. #decoratorParam ("context" "spaceadminpanel") ## Key of the web-item to highlight in Space Tools #decoratorParam ("selectedSpaceToolsWebItem" "space-admin-item") #decoratorParam ("helper" $action.helper) <body> <form class="aui top-label" action="$req.contextPath/plugins/servlet/quotes" method="post"> <div class ="field-group top-label"> <label for="quote">Quote<span class="aui-icon icon-required">required</span></label> <input class="text long-field" id="quote" title="Quote field" type="text" name="quote" placeholder="enter quote here"> <div class="description">Your quote.</div> <input type="hidden" name="spaceKey" value="$helper.spaceKey"> <input class="button submit" type="submit" value="Add"> </div> </form> <table class="aui"> <thead> <tr> <th id="quote-header">Quote</th> </tr> </thead> <tbody> #foreach( $quote in $quotes ) <tr> <td headers="quote-header">$quote.quote</td> </tr> #end </tbody> </table> </body> #end #end </html>
As you can see, you have added some styling to your template. You are using the Atlassion User Interface front-end library to conform to the Atlassian Design Guidelines.
The values of the $quotes
variable are provided by the getQuotes() method of your MyAction
class. Check out Apache Velocity for more details.
8. Run the Plug-in
Execute atlas-run to run the plug-in. Your Confluence Server is accessible at http://localhost:1990/confluence. Your username and password are the same. It’s admin
. Click on Demonstration Space at the lower left corner of the dashboard page. At the lower left corner of the Demonstration Space page, click on Space Tools and then Add-ons. Your plug-in will be displayed in the Your Plug-in tab. Try adding some quotes. The final output looks like this:
9. How to Add Active Objects Summary
In this example, you were able to add Active Objects to your add-on by following these steps:
- Fulfilling the Requirements
- Modifying the pom.xml
- Adding the Active Objects Module to the Add-on Descriptor
- Creating the Quote/Entity Interface
- Modifying the MyAction Class
- Modifying the QuoteServlet Class
- Modifying the Velocity Template
- Running the Plug-in
10. Download the Source Code
This is an example of how to add Active Obejcts to your Confluence Add-on.
You can download the source code of this example here: space-admin-ao.tar.gz.
Joel,
This article is great, I am trying to develop a similar model but my macro should GET and POST to an external database using the REST Apis exposed on my Db. I have tried a few ways but no luck. Can you please help me with some inputs ?
Thank you nani. If your DB has REST support then you can hit it with Spring’s RestTemplate or Apache HttpClient Fluent API (org.apache.http.client.fluent.Request). I’m sure there are also other APIs you can use.
Hi Joel,
I like your tutorial sooooo much!
Now i have a problem, maybe you can help me?
I downloaded this tutorial and run it. But when i call http://localhost:1990/confluence/ i get the message:
HTTP Status 404 – Not Found
Type Status Report
Message /confluence/
Description The origin server did not find a current representation for the target resource or is not willing to disclose that one exists.
I unzipped the files. Executed in the root directory atlas-mvn package and then atlas-run.
What did i wrong?
Hi Jorg,
It’s been a long time since I did Atlassian stuff. The error says it can’t find /confluence resource. Have you tried cleaning (atlas-clean) it then running (atlas-run) it again? Do you have the Atlassian SDK installed?
Hope this helps.
Cheers,
Joel